diff --git a/application/Bootstrap.php b/application/Bootstrap.php new file mode 100644 index 000000000..6ae138cb6 --- /dev/null +++ b/application/Bootstrap.php @@ -0,0 +1,59 @@ +getMessage()." ".$CC_DBC->getUserInfo()."\n"; + exit(1); +} +$CC_DBC->setFetchMode(DB_FETCHMODE_ASSOC); + +class Bootstrap extends Zend_Application_Bootstrap_Bootstrap +{ + protected function _initDoctype() + { + $this->bootstrap('view'); + $view = $this->getResource('view'); + $view->doctype('XHTML1_STRICT'); + } + + protected function _initHeadLink() + { + $view = $this->getResource('view'); + + $view->headLink()->appendStylesheet('/css/excite-bike/excite_bike.css'); + } + + protected function _initHeadScript() + { + $view = $this->getResource('view'); + $view->headScript()->appendFile('/js/libs/jquery-1.4.4.min.js','text/javascript'); + $view->headScript()->appendFile('/js/libs/jquery-ui-1.8.5.min.js','text/javascript'); + } + +} + diff --git a/application/configs/ACL.php b/application/configs/ACL.php new file mode 100644 index 000000000..61f657bae --- /dev/null +++ b/application/configs/ACL.php @@ -0,0 +1,33 @@ +addRole(new Zend_Acl_Role('guest')) + ->addRole(new Zend_Acl_Role('host'), 'guest') + ->addRole(new Zend_Acl_Role('admin'), 'host'); + +$ccAcl->add(new Zend_Acl_Resource('library')) + ->add(new Zend_Acl_Resource('index')) + ->add(new Zend_Acl_Resource('error')) + ->add(new Zend_Acl_Resource('login')) + ->add(new Zend_Acl_Resource('playlist')) + ->add(new Zend_Acl_Resource('plupload')) + ->add(new Zend_Acl_Resource('schedule')) + ->add(new Zend_Acl_Resource('search')); + +/** Creating permissions */ +$ccAcl->allow('guest', 'index') + ->allow('guest', 'login') + ->allow('guest', 'error') + ->allow('guest', 'library') + ->allow('guest', 'search') + ->allow('host', 'plupload') + ->allow('host', 'playlist') + ->allow('host', 'schedule'); + +$aclPlugin = new Zend_Controller_Plugin_Acl($ccAcl); + +$front = Zend_Controller_Front::getInstance(); +$front->registerPlugin($aclPlugin); diff --git a/application/configs/application.ini b/application/configs/application.ini new file mode 100644 index 000000000..88feac347 --- /dev/null +++ b/application/configs/application.ini @@ -0,0 +1,28 @@ +[production] +phpSettings.display_startup_errors = 0 +phpSettings.display_errors = 0 +includePaths.library = APPLICATION_PATH "/../library" +bootstrap.path = APPLICATION_PATH "/Bootstrap.php" +bootstrap.class = "Bootstrap" +appnamespace = "Application" +resources.frontController.controllerDirectory = APPLICATION_PATH "/controllers" +resources.frontController.params.displayExceptions = 0 +resources.layout.layoutPath = APPLICATION_PATH "/layouts/scripts/" +resources.view[] = +resources.db.adapter = "Pdo_Pgsql" +resources.db.params.charset = "utf8" +resources.db.params.host = "localhost" +resources.db.params.username = "campcaster" +resources.db.params.password = "campcaster" +resources.db.params.dbname = "campcaster" + +[staging : production] + +[testing : production] +phpSettings.display_startup_errors = 1 +phpSettings.display_errors = 1 + +[development : production] +phpSettings.display_startup_errors = 1 +phpSettings.display_errors = 1 +resources.frontController.params.displayExceptions = 1 diff --git a/application/configs/classmap-propel-config.php b/application/configs/classmap-propel-config.php new file mode 100644 index 000000000..9e36b34fc --- /dev/null +++ b/application/configs/classmap-propel-config.php @@ -0,0 +1,95 @@ + 'campcaster/map/CcAccessTableMap.php', + 'CcAccessPeer' => 'campcaster/CcAccessPeer.php', + 'CcAccess' => 'campcaster/CcAccess.php', + 'CcAccessQuery' => 'campcaster/CcAccessQuery.php', + 'BaseCcAccessPeer' => 'campcaster/om/BaseCcAccessPeer.php', + 'BaseCcAccess' => 'campcaster/om/BaseCcAccess.php', + 'BaseCcAccessQuery' => 'campcaster/om/BaseCcAccessQuery.php', + 'CcBackupTableMap' => 'campcaster/map/CcBackupTableMap.php', + 'CcBackupPeer' => 'campcaster/CcBackupPeer.php', + 'CcBackup' => 'campcaster/CcBackup.php', + 'CcBackupQuery' => 'campcaster/CcBackupQuery.php', + 'BaseCcBackupPeer' => 'campcaster/om/BaseCcBackupPeer.php', + 'BaseCcBackup' => 'campcaster/om/BaseCcBackup.php', + 'BaseCcBackupQuery' => 'campcaster/om/BaseCcBackupQuery.php', + 'CcFilesTableMap' => 'campcaster/map/CcFilesTableMap.php', + 'CcFilesPeer' => 'campcaster/CcFilesPeer.php', + 'CcFiles' => 'campcaster/CcFiles.php', + 'CcFilesQuery' => 'campcaster/CcFilesQuery.php', + 'BaseCcFilesPeer' => 'campcaster/om/BaseCcFilesPeer.php', + 'BaseCcFiles' => 'campcaster/om/BaseCcFiles.php', + 'BaseCcFilesQuery' => 'campcaster/om/BaseCcFilesQuery.php', + 'CcPermsTableMap' => 'campcaster/map/CcPermsTableMap.php', + 'CcPermsPeer' => 'campcaster/CcPermsPeer.php', + 'CcPerms' => 'campcaster/CcPerms.php', + 'CcPermsQuery' => 'campcaster/CcPermsQuery.php', + 'BaseCcPermsPeer' => 'campcaster/om/BaseCcPermsPeer.php', + 'BaseCcPerms' => 'campcaster/om/BaseCcPerms.php', + 'BaseCcPermsQuery' => 'campcaster/om/BaseCcPermsQuery.php', + 'CcShowTableMap' => 'campcaster/map/CcShowTableMap.php', + 'CcShowPeer' => 'campcaster/CcShowPeer.php', + 'CcShow' => 'campcaster/CcShow.php', + 'CcShowQuery' => 'campcaster/CcShowQuery.php', + 'BaseCcShowPeer' => 'campcaster/om/BaseCcShowPeer.php', + 'BaseCcShow' => 'campcaster/om/BaseCcShow.php', + 'BaseCcShowQuery' => 'campcaster/om/BaseCcShowQuery.php', + 'CcPlaylistTableMap' => 'campcaster/map/CcPlaylistTableMap.php', + 'CcPlaylistPeer' => 'campcaster/CcPlaylistPeer.php', + 'CcPlaylist' => 'campcaster/CcPlaylist.php', + 'CcPlaylistQuery' => 'campcaster/CcPlaylistQuery.php', + 'BaseCcPlaylistPeer' => 'campcaster/om/BaseCcPlaylistPeer.php', + 'BaseCcPlaylist' => 'campcaster/om/BaseCcPlaylist.php', + 'BaseCcPlaylistQuery' => 'campcaster/om/BaseCcPlaylistQuery.php', + 'CcPlaylistcontentsTableMap' => 'campcaster/map/CcPlaylistcontentsTableMap.php', + 'CcPlaylistcontentsPeer' => 'campcaster/CcPlaylistcontentsPeer.php', + 'CcPlaylistcontents' => 'campcaster/CcPlaylistcontents.php', + 'CcPlaylistcontentsQuery' => 'campcaster/CcPlaylistcontentsQuery.php', + 'BaseCcPlaylistcontentsPeer' => 'campcaster/om/BaseCcPlaylistcontentsPeer.php', + 'BaseCcPlaylistcontents' => 'campcaster/om/BaseCcPlaylistcontents.php', + 'BaseCcPlaylistcontentsQuery' => 'campcaster/om/BaseCcPlaylistcontentsQuery.php', + 'CcPrefTableMap' => 'campcaster/map/CcPrefTableMap.php', + 'CcPrefPeer' => 'campcaster/CcPrefPeer.php', + 'CcPref' => 'campcaster/CcPref.php', + 'CcPrefQuery' => 'campcaster/CcPrefQuery.php', + 'BaseCcPrefPeer' => 'campcaster/om/BaseCcPrefPeer.php', + 'BaseCcPref' => 'campcaster/om/BaseCcPref.php', + 'BaseCcPrefQuery' => 'campcaster/om/BaseCcPrefQuery.php', + 'CcScheduleTableMap' => 'campcaster/map/CcScheduleTableMap.php', + 'CcSchedulePeer' => 'campcaster/CcSchedulePeer.php', + 'CcSchedule' => 'campcaster/CcSchedule.php', + 'CcScheduleQuery' => 'campcaster/CcScheduleQuery.php', + 'BaseCcSchedulePeer' => 'campcaster/om/BaseCcSchedulePeer.php', + 'BaseCcSchedule' => 'campcaster/om/BaseCcSchedule.php', + 'BaseCcScheduleQuery' => 'campcaster/om/BaseCcScheduleQuery.php', + 'CcSessTableMap' => 'campcaster/map/CcSessTableMap.php', + 'CcSessPeer' => 'campcaster/CcSessPeer.php', + 'CcSess' => 'campcaster/CcSess.php', + 'CcSessQuery' => 'campcaster/CcSessQuery.php', + 'BaseCcSessPeer' => 'campcaster/om/BaseCcSessPeer.php', + 'BaseCcSess' => 'campcaster/om/BaseCcSess.php', + 'BaseCcSessQuery' => 'campcaster/om/BaseCcSessQuery.php', + 'CcSmembTableMap' => 'campcaster/map/CcSmembTableMap.php', + 'CcSmembPeer' => 'campcaster/CcSmembPeer.php', + 'CcSmemb' => 'campcaster/CcSmemb.php', + 'CcSmembQuery' => 'campcaster/CcSmembQuery.php', + 'BaseCcSmembPeer' => 'campcaster/om/BaseCcSmembPeer.php', + 'BaseCcSmemb' => 'campcaster/om/BaseCcSmemb.php', + 'BaseCcSmembQuery' => 'campcaster/om/BaseCcSmembQuery.php', + 'CcSubjsTableMap' => 'campcaster/map/CcSubjsTableMap.php', + 'CcSubjsPeer' => 'campcaster/CcSubjsPeer.php', + 'CcSubjs' => 'campcaster/CcSubjs.php', + 'CcSubjsQuery' => 'campcaster/CcSubjsQuery.php', + 'BaseCcSubjsPeer' => 'campcaster/om/BaseCcSubjsPeer.php', + 'BaseCcSubjs' => 'campcaster/om/BaseCcSubjs.php', + 'BaseCcSubjsQuery' => 'campcaster/om/BaseCcSubjsQuery.php', + 'CcTransTableMap' => 'campcaster/map/CcTransTableMap.php', + 'CcTransPeer' => 'campcaster/CcTransPeer.php', + 'CcTrans' => 'campcaster/CcTrans.php', + 'CcTransQuery' => 'campcaster/CcTransQuery.php', + 'BaseCcTransPeer' => 'campcaster/om/BaseCcTransPeer.php', + 'BaseCcTrans' => 'campcaster/om/BaseCcTrans.php', + 'BaseCcTransQuery' => 'campcaster/om/BaseCcTransQuery.php', +); \ No newline at end of file diff --git a/application/configs/conf.php b/application/configs/conf.php new file mode 100644 index 000000000..76cfd86ee --- /dev/null +++ b/application/configs/conf.php @@ -0,0 +1,193 @@ + array( + 'username' => 'campcaster', + 'password' => 'campcaster', + 'hostspec' => 'localhost', + 'phptype' => 'pgsql', + 'database' => 'campcaster', + ), + + // Name of the web server user + 'webServerUser' => 'www-data', + + // prefix for table names in the database + 'tblNamePrefix' => 'cc_', + + /* ================================================ storage configuration */ + + 'apiKey' => array('AAA'), + + // main directory for storing binary media files + 'storageDir' => dirname(__FILE__).'/../../stor', + + // directory for temporary files + 'bufferDir' => dirname(__FILE__).'/../../stor/buffer', + + // directory for incomplete transferred files + 'transDir' => dirname(__FILE__).'/trans', + + // directory for symlinks to accessed files + 'accessDir' => dirname(__FILE__).'/access', + 'cronDir' => dirname(__FILE__).'/backend/cron', + + "rootDir" => dirname(__FILE__), + "smartyTemplate" => dirname(__FILE__)."/htmlUI/templates", + "smartyTemplateCompiled" => dirname(__FILE__)."/htmlUI/templates_c", + 'pearPath' => dirname(__FILE__).'/3rd_party/php/pear', + 'zendPath' => dirname(__FILE__).'/3rd_party/php/Zend', + 'phingPath' => dirname(__FILE__).'/3rd_party/php/phing', + 'LogPath' => dirname(__FILE__).'/3rd_party/php/Log', + + // secret token cookie name + 'authCookieName'=> 'campcaster_session_id', + + // name of admin group + //'AdminsGr' => 'Admins', + + // name of station preferences group + 'StationPrefsGr'=> 'StationPrefs', + + // name of 'all users' group + //'AllGr' => 'All', + 'TrashName' => 'trash_', + + // enable/disable validator + 'validate' => TRUE, + + // enable/disable safe delete (move to trash) + 'useTrash' => FALSE, + + /* ==================================================== URL configuration */ + // path-URL-part of storageServer base dir + 'storageUrlPath' => '/campcaster/backend', + + // XMLRPC server script address relative to storageUrlPath + 'storageXMLRPC' => 'xmlrpc/xrLocStor.php', + + // host and port of storageServer + 'storageUrlHost' => 'localhost', + 'storageUrlPort' => 80, + + /* ================================================ remote link configuration */ + // path-URL-part of remote server base dir + 'archiveUrlPath' => '/campcaster/backend', + + // XMLRPC server script address relative to archiveUrlPath + 'archiveXMLRPC' => 'xmlrpc/xrLocStor.php', + + // host and port of archiveServer + 'archiveUrlHost' => 'localhost', +// 'archiveUrlHost' => '192.168.30.166', + 'archiveUrlPort' => 80, + + // account info for login to archive + 'archiveAccountLogin' => 'root', + 'archiveAccountPass' => 'q', + + /* ============================================== scheduler configuration */ + 'schedulerUrlPath' => '', + 'schedulerXMLRPC' => 'RC2', + 'schedulerUrlHost' => 'localhost', + 'schedulerUrlPort' => 3344, + 'schedulerPass' => 'change_me', + + /* ==================================== application-specific configuration */ + 'objtypes' => array( + 'Storage' => array(/*'Folder',*/ 'File' /*, 'Replica'*/), + 'File' => array(), + 'audioclip' => array(), + 'playlist' => array(), +// 'Replica' => array(), + ), + 'allowedActions'=> array( + 'File' => array('editPrivs', 'write', 'read'), + 'audioclip' => array('editPrivs', 'write', 'read'), + 'playlist' => array('editPrivs', 'write', 'read'), +// 'Replica' => array('editPrivs', 'write', 'read'), +// '_class' => array('editPrivs', 'write', 'read'), + ), + 'allActions' => array( + 'editPrivs', 'write', 'read', /*'classes',*/ 'subjects' + ), + + /* ============================================== auxiliary configuration */ + 'tmpRootPass' => 'q', + + /* =================================================== cron configuration */ + 'cronUserName' => 'www-data', +# 'lockfile' => dirname(__FILE__).'/cron/cron.lock', + 'lockfile' => dirname(__FILE__).'/stor/buffer/cron.lock', + 'cronfile' => dirname(__FILE__).'/cron/croncall.php', + 'paramdir' => dirname(__FILE__).'/cron/params', + 'systemPrefId' => "0", // ID for system prefs in prefs table +); + +// Add database table names +$CC_CONFIG['playListTable'] = $CC_CONFIG['tblNamePrefix'].'playlist'; +$CC_CONFIG['playListContentsTable'] = $CC_CONFIG['tblNamePrefix'].'playlistcontents'; +$CC_CONFIG['filesTable'] = $CC_CONFIG['tblNamePrefix'].'files'; +$CC_CONFIG['accessTable'] = $CC_CONFIG['tblNamePrefix'].'access'; +$CC_CONFIG['permTable'] = $CC_CONFIG['tblNamePrefix'].'perms'; +$CC_CONFIG['sessTable'] = $CC_CONFIG['tblNamePrefix'].'sess'; +$CC_CONFIG['subjTable'] = $CC_CONFIG['tblNamePrefix'].'subjs'; +$CC_CONFIG['smembTable'] = $CC_CONFIG['tblNamePrefix'].'smemb'; +$CC_CONFIG['transTable'] = $CC_CONFIG['tblNamePrefix'].'trans'; +$CC_CONFIG['prefTable'] = $CC_CONFIG['tblNamePrefix'].'pref'; +$CC_CONFIG['scheduleTable'] = $CC_CONFIG['tblNamePrefix'].'schedule'; +$CC_CONFIG['backupTable'] = $CC_CONFIG['tblNamePrefix'].'backup'; +$CC_CONFIG['playListTimeView'] = $CC_CONFIG['tblNamePrefix'].'playlisttimes'; + +$CC_CONFIG['playListSequence'] = $CC_CONFIG['playListTable'].'_id'; +$CC_CONFIG['filesSequence'] = $CC_CONFIG['filesTable'].'_id'; +$CC_CONFIG['transSequence'] = $CC_CONFIG['transTable'].'_id'; +$CC_CONFIG['prefSequence'] = $CC_CONFIG['prefTable'].'_id'; +$CC_CONFIG['permSequence'] = $CC_CONFIG['permTable'].'_id'; +$CC_CONFIG['subjSequence'] = $CC_CONFIG['subjTable'].'_id'; +$CC_CONFIG['smembSequence'] = $CC_CONFIG['smembTable'].'_id'; + +// System users/groups - they cannot be deleted +$CC_CONFIG['sysSubjs'] = array( + 'root', /*$CC_CONFIG['AdminsGr'],*/ /*$CC_CONFIG['AllGr'],*/ $CC_CONFIG['StationPrefsGr'] +); + +// Add libs to the PHP path +$old_include_path = get_include_path(); +set_include_path('.'.PATH_SEPARATOR.$CC_CONFIG['pearPath'] + .PATH_SEPARATOR.$CC_CONFIG['zendPath'] + .PATH_SEPARATOR.$old_include_path); + +// Check that all the required directories exist. +//foreach (array('storageDir', 'bufferDir', 'transDir', 'accessDir', 'cronDir') as $d) { +// $test = file_exists($CC_CONFIG[$d]); +// if ( $test === FALSE ) { +// echo " * Error: directory {$CC_CONFIG[$d]} is missing.\n"; +// echo " * Please run the install script again.\n"; +// exit(1); +// } else { +// $rp = realpath($CC_CONFIG[$d]); +// } +// $CC_CONFIG[$d] = $rp; +//} + +// Check that htmlUI/templates_c has the right permissions +//$ss=@stat($CC_CONFIG["smartyTemplateCompiled"]); +//$groupOwner = (function_exists('posix_getgrgid'))?@posix_getgrgid($ss['gid']):''; +//if (!empty($groupOwner) && ($groupOwner["name"] != $CC_CONFIG["webServerUser"])) { +// echo " * Error: Your directory permissions for {$CC_CONFIG['smartyTemplateCompiled']} are not set correctly.
\n"; +// echo " * The group perms need to be set to the web server user, in this case '{$CC_CONFIG['webServerUser']}'.
\n"; +// exit(1); +//} +//$fileperms=@fileperms($CC_CONFIG["smartyTemplateCompiled"]); +//if (!($fileperms & 0x0400)) { +// echo " * Error: Sticky bit not set for {$CC_CONFIG['smartyTemplateCompiled']}.
\n"; +// exit(1); +//} +?> diff --git a/application/configs/constants.php b/application/configs/constants.php new file mode 100644 index 000000000..7dfa420aa --- /dev/null +++ b/application/configs/constants.php @@ -0,0 +1,22 @@ + 'Home', + 'title' => 'Go Home', + 'module' => 'default', + 'controller' => 'index', + 'action' => 'index', + 'order' => -100 // make sure home is the first page + ), + array( + 'label' => 'Playlists', + 'module' => 'default', + 'controller' => 'Playlist', + 'action' => 'index', + 'pages' => array( + array( + 'label' => 'Edit', + 'module' => 'default', + 'controller' => 'Playlist', + 'action' => 'edit', + 'visible' => false + ) + ) + ), + array( + 'label' => 'Media Library', + 'module' => 'default', + 'controller' => 'Library', + 'action' => 'index', + 'pages' => array( + array( + 'label' => 'Add Audio', + 'module' => 'default', + 'controller' => 'Plupload', + 'action' => 'plupload' + ), + array( + 'label' => 'Search', + 'module' => 'default', + 'controller' => 'Search', + 'action' => 'display' + ) + ) + ), + array( + 'label' => 'Schedule', + 'module' => 'default', + 'controller' => 'Schedule', + 'action' => 'index' + ), + array( + 'label' => 'Logout', + 'module' => 'default', + 'controller' => 'Login', + 'action' => 'logout' + ) +); + +// Create container from array +$container = new Zend_Navigation($pages); + +//store it in the registry: +Zend_Registry::set('Zend_Navigation', $container); diff --git a/application/configs/propel-config.php b/application/configs/propel-config.php new file mode 100644 index 000000000..52b446655 --- /dev/null +++ b/application/configs/propel-config.php @@ -0,0 +1,20 @@ + + array ( + 'campcaster' => + array ( + 'adapter' => 'pgsql', + 'connection' => + array ( + 'dsn' => 'pgsql:host=localhost;port=5432;dbname=campcaster;user=campcaster;password=campcaster', + ), + ), + 'default' => 'campcaster', + ), + 'generator_version' => '1.5.2', +); +$conf['classmap'] = include(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'classmap-propel-config.php'); +return $conf; \ No newline at end of file diff --git a/application/controllers/ErrorController.php b/application/controllers/ErrorController.php new file mode 100644 index 000000000..70f73ee37 --- /dev/null +++ b/application/controllers/ErrorController.php @@ -0,0 +1,58 @@ +_getParam('error_handler'); + + switch ($errors->type) { + case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_ROUTE: + case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_CONTROLLER: + case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_ACTION: + + // 404 error -- controller or action not found + $this->getResponse()->setHttpResponseCode(404); + $this->view->message = 'Page not found'; + break; + default: + // application error + $this->getResponse()->setHttpResponseCode(500); + $this->view->message = 'Application error'; + break; + } + + // Log exception, if logger available + if ($log = $this->getLog()) { + $log->crit($this->view->message, $errors->exception); + } + + // conditionally display exceptions + if ($this->getInvokeArg('displayExceptions') == true) { + $this->view->exception = $errors->exception; + } + + $this->view->request = $errors->request; + } + + public function getLog() + { + $bootstrap = $this->getInvokeArg('bootstrap'); + if (!$bootstrap->hasPluginResource('Log')) { + return false; + } + $log = $bootstrap->getResource('Log'); + return $log; + } + + public function deniedAction() + { + // action body + } + + +} + + + diff --git a/application/controllers/IndexController.php b/application/controllers/IndexController.php new file mode 100644 index 000000000..1f5135329 --- /dev/null +++ b/application/controllers/IndexController.php @@ -0,0 +1,39 @@ +_forward('index', 'login'); + } + + public function mainAction() + { + $this->_helper->layout->setLayout('layout'); + } + + public function newfieldAction() + { + // action body + } + + public function displayAction() + { + // action body + } + + +} + + + + + + + diff --git a/application/controllers/LibraryController.php b/application/controllers/LibraryController.php new file mode 100644 index 000000000..a36941194 --- /dev/null +++ b/application/controllers/LibraryController.php @@ -0,0 +1,104 @@ +hasIdentity()) + { + $this->_redirect('login/index'); + } + + $ajaxContext = $this->_helper->getHelper('AjaxContext'); + $ajaxContext->addActionContext('contents', 'html') + ->addActionContext('plupload', 'html') + ->addActionContext('upload', 'json') + ->addActionContext('delete', 'json') + ->initContext(); + + $this->pl_sess = new Zend_Session_Namespace(UI_PLAYLIST_SESSNAME); + } + + public function indexAction() + { + $this->view->headScript()->appendFile('/js/contextmenu/jquery.contextMenu.js','text/javascript'); + $this->view->headScript()->appendFile('/js/campcaster/library/library.js','text/javascript'); + + $this->view->headLink()->appendStylesheet('/css/jquery.contextMenu.css'); + + $this->_helper->actionStack('context-menu', 'library'); + } + + public function contextMenuAction() + { + $pl_sess = $this->pl_sess; + $contextMenu; + + $contextMenu[] = array('action' => '/Library/delete', 'text' => 'Delete'); + + if(isset($pl_sess->id)) + $contextMenu[] = array('action' => '/Playlist/add-item', 'text' => 'Add To Playlist'); + + $this->view->menu = $contextMenu; + + $this->_helper->actionStack('contents', 'library'); + } + + public function deleteAction() + { + $id = $this->_getParam('id'); + + if (!is_null($id)) { + $file = StoredFile::Recall($id); + + if (PEAR::isError($file)) { + die('{"jsonrpc" : "2.0", "error" : {"message": ' + $file->getMessage() + '}}'); + } + else if(is_null($file)) { + die('{"jsonrpc" : "2.0", "error" : {"message": "file doesn\'t exist"}}'); + } + + $res = $file->delete(); + + if (PEAR::isError($res)) { + die('{"jsonrpc" : "2.0", "error" : {"message": ' + $res->getMessage() + '}}'); + } + } + else { + die('{"jsonrpc" : "2.0", "error" : {"message": "file doesn\'t exist"}}'); + } + + die('{"jsonrpc" : "2.0"}'); + } + + public function contentsAction() + { + $query["category"] = $this->_getParam('ob', "dc:creator"); + $query["order"] = $this->_getParam('order', "asc"); + + $this->view->files = StoredFile::getFiles($query); + } + + public function searchAction() + { + // action body + } + + +} + + + + + + + + + + + + + diff --git a/application/controllers/LoginController.php b/application/controllers/LoginController.php new file mode 100644 index 000000000..0a41850b5 --- /dev/null +++ b/application/controllers/LoginController.php @@ -0,0 +1,96 @@ +hasIdentity()) + { + $this->_redirect('library/index'); + } + + //uses separate layout without a navigation. + $this->_helper->layout->setLayout('login'); + + $request = $this->getRequest(); + $form = new Application_Form_Login(); + + $errorMessage = ""; + + if($request->isPost()) + { + if($form->isValid($request->getPost())) + { + + $authAdapter = $this->getAuthAdapter(); + + # get the username and password from the form + $username = $form->getValue('username'); + $password = $form->getValue('password'); + + # pass to the adapter the submitted username and password + $authAdapter->setIdentity($username) + ->setCredential($password); + + $auth = Zend_Auth::getInstance(); + $result = $auth->authenticate($authAdapter); + + # is the user a valid one? + if($result->isValid()) + { + # all info about this user from the login table + # omit only the password, we don't need that + $userInfo = $authAdapter->getResultRowObject(null, 'password'); + + # the default storage is a session with namespace Zend_Auth + $authStorage = $auth->getStorage(); + $authStorage->write($userInfo); + + $this->_redirect('library/index'); + } + else + { + $errorMessage = "Wrong username or password provided. Please try again."; + } + } + } + + $this->view->errorMessage = $errorMessage; + $this->view->form = $form; + + } + + public function logoutAction() + { + Zend_Auth::getInstance()->clearIdentity(); + $this->_redirect('login/index'); + } + + /** + * Gets the adapter for authentication against a database table + * + * @return object + */ + protected function getAuthAdapter() + { + $dbAdapter = Zend_Db_Table::getDefaultAdapter(); + $authAdapter = new Zend_Auth_Adapter_DbTable($dbAdapter); + + $authAdapter->setTableName('cc_subjs') + ->setIdentityColumn('login') + ->setCredentialColumn('pass') + ->setCredentialTreatment('MD5(?)'); + + return $authAdapter; + } + +} + + + diff --git a/application/controllers/PlaylistController.php b/application/controllers/PlaylistController.php new file mode 100644 index 000000000..b4768b71f --- /dev/null +++ b/application/controllers/PlaylistController.php @@ -0,0 +1,255 @@ +hasIdentity()) + { + $this->_redirect('login/index'); + } + + $ajaxContext = $this->_helper->getHelper('AjaxContext'); + $ajaxContext->addActionContext('add-item', 'json') + ->addActionContext('delete-item', 'html') + ->addActionContext('set-fade', 'json') + ->addActionContext('set-cue', 'json') + ->addActionContext('move-item', 'html') + ->initContext(); + + $this->pl_sess = new Zend_Session_Namespace(UI_PLAYLIST_SESSNAME); + } + + public function indexAction() + { + + } + + public function newAction() + { + $pl_sess = $this->pl_sess; + $userInfo = Zend_Auth::getInstance()->getStorage()->read(); + + $pl = new Playlist(); + $pl_id = $pl->create("Test Zend Auth"); + $pl->setPLMetaData('dc:creator', $userInfo->login); + $pl->lock($userInfo->id); + + //set this playlist as active id. + $pl_sess->id = $pl_id; + + $this->_helper->redirector('metadata'); + } + + public function metadataAction() + { + $pl_sess = $this->pl_sess; + + $request = $this->getRequest(); + $form = new Application_Form_PlaylistMetadata(); + + if ($this->getRequest()->isPost()) { + if ($form->isValid($request->getPost())) { + + $formdata = $form->getValues(); + + $pl = Playlist::Recall($pl_sess->id); + $pl->setPLMetaData(UI_MDATA_KEY_TITLE, $formdata["title"]); + + if(isset($formdata["description"])) + $pl->setPLMetaData(UI_MDATA_KEY_DESCRIPTION, $formdata["description"]); + + $this->_helper->redirector('edit'); + } + } + + $this->view->form = $form; + } + + public function editAction() + { + $this->view->headScript()->appendFile('/js/campcaster/playlist/playlist.js','text/javascript'); + + $pl_sess = $this->pl_sess; + + if(isset($pl_sess->id)) { + + $pl = Playlist::Recall($pl_sess->id); + + $this->view->playlistcontents = $pl->getContents(); + return; + } + + $this->_helper->redirector('index'); + } + + public function addItemAction() + { + $pl_sess = $this->pl_sess; + $id = $this->_getParam('id'); + + if (!is_null($id)) { + if(isset($pl_sess->id)) { + $pl = Playlist::Recall($pl_sess->id); + $res = $pl->addAudioClip($id); + + if (PEAR::isError($res)) { + die('{"jsonrpc" : "2.0", "error" : {"message": ' + $res->getMessage() + '}}'); + } + + die('{"jsonrpc" : "2.0"}'); + } + die('{"jsonrpc" : "2.0", "error" : {"message": "no open playlist"}}'); + } + + die('{"jsonrpc" : "2.0", "error" : {"message": "a file is not chosen"}}'); + } + + public function moveItemAction() + { + $pl_sess = $this->pl_sess; + + if(isset($pl_sess->id)) { + + $oldPos = $this->_getParam('oldPos'); + $newPos = $this->_getParam('newPos'); + + $pl = Playlist::Recall($pl_sess->id); + + $pl->moveAudioClip($oldPos, $newPos); + + $this->view->playlistcontents = $pl->getContents(); + return; + } + + $this->_helper->redirector('index'); + } + + public function deleteItemAction() + { + $pl_sess = $this->pl_sess; + + if(isset($pl_sess->id)) { + + $positions = $this->_getParam('pos', array()); + + $pl = Playlist::Recall($pl_sess->id); + + if (!is_array($positions)) + $positions = array($positions); + + //so the automatic updating of playlist positioning doesn't affect removal. + sort($positions); + $positions = array_reverse($positions); + + foreach ($positions as $pos) { + $pl->delAudioClip($pos); + } + + $this->view->playlistcontents = $pl->getContents(); + return; + } + + $this->_helper->redirector('index'); + } + + public function setCueAction() + { + $pl_sess = $this->pl_sess; + + if(isset($pl_sess->id)) { + + $pos = $this->_getParam('pos'); + $cueIn = $this->_getParam('cueIn', null); + $cueOut = $this->_getParam('cueOut', null); + + $pl = Playlist::Recall($pl_sess->id); + + $response = $pl->changeClipLength($pos, $cueIn, $cueOut); + + die(json_encode($response)); + } + + $this->_helper->redirector('index'); + } + + public function setFadeAction() + { + $pl_sess = $this->pl_sess; + + if(isset($pl_sess->id)) { + + $pos = $this->_getParam('pos'); + $fadeIn = $this->_getParam('fadeIn', null); + $fadeOut = $this->_getParam('fadeOut', null); + + $pl = Playlist::Recall($pl_sess->id); + + $response = $pl->changeFadeInfo($pos, $fadeIn, $fadeOut); + + die(json_encode($response)); + } + + $this->_helper->redirector('index'); + } + + public function deleteAction() + { + $id = $this->_getParam('id', null); + + if (!is_null($id)) { + + $this->closePlaylist(); + Playlist::Delete($id); + } + } + + public function deleteActiveAction() + { + $pl_sess = $this->pl_sess; + + if(isset($pl_sess->id)) { + + $pl = Playlist::Recall($pl_sess->id); + $this->closePlaylist($pl); + + Playlist::Delete($pl_sess->id); + + unset($pl_sess->id); + } + + $this->_helper->redirector('index'); + } + + public function closePlaylist($pl) + { + $userInfo = Zend_Auth::getInstance()->getStorage()->read(); + $res = $pl->unlock($userInfo->id); + return $res; + } + +} + + + + + + + + + + + + + + + + + + + + + diff --git a/application/controllers/PluploadController.php b/application/controllers/PluploadController.php new file mode 100644 index 000000000..ffaff33e8 --- /dev/null +++ b/application/controllers/PluploadController.php @@ -0,0 +1,179 @@ +hasIdentity()) + { + $this->_redirect('login/index'); + } + + $ajaxContext = $this->_helper->getHelper('AjaxContext'); + $ajaxContext->addActionContext('upload', 'json') + ->initContext(); + } + + public function indexAction() + { + // action body + } + + public function uploadAction() + { + // HTTP headers for no cache etc + header('Content-type: text/plain; charset=UTF-8'); + header("Expires: Mon, 26 Jul 1997 05:00:00 GMT"); + header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT"); + header("Cache-Control: no-store, no-cache, must-revalidate"); + header("Cache-Control: post-check=0, pre-check=0", false); + header("Pragma: no-cache"); + + // Settings + $targetDir = ini_get("upload_tmp_dir") . DIRECTORY_SEPARATOR . "plupload"; + $cleanupTargetDir = false; // Remove old files + $maxFileAge = 60 * 60; // Temp file age in seconds + + // 5 minutes execution time + @set_time_limit(5 * 60); + // usleep(5000); + + // Get parameters + $chunk = isset($_REQUEST["chunk"]) ? $_REQUEST["chunk"] : 0; + $chunks = isset($_REQUEST["chunks"]) ? $_REQUEST["chunks"] : 0; + $fileName = isset($_REQUEST["name"]) ? $_REQUEST["name"] : ''; + + // Clean the fileName for security reasons + //$fileName = preg_replace('/[^\w\._]+/', '', $fileName); + + // Create target dir + if (!file_exists($targetDir)) + @mkdir($targetDir); + + // Remove old temp files + if (is_dir($targetDir) && ($dir = opendir($targetDir))) { + while (($file = readdir($dir)) !== false) { + $filePath = $targetDir . DIRECTORY_SEPARATOR . $file; + + // Remove temp files if they are older than the max age + if (preg_match('/\.tmp$/', $file) && (filemtime($filePath) < time() - $maxFileAge)) + @unlink($filePath); + } + + closedir($dir); + } else + die('{"jsonrpc" : "2.0", "error" : {"code": 100, "message": "Failed to open temp directory."}, "id" : "id"}'); + + // Look for the content type header + if (isset($_SERVER["HTTP_CONTENT_TYPE"])) + $contentType = $_SERVER["HTTP_CONTENT_TYPE"]; + + if (isset($_SERVER["CONTENT_TYPE"])) + $contentType = $_SERVER["CONTENT_TYPE"]; + + if (strpos($contentType, "multipart") !== false) { + if (isset($_FILES['file']['tmp_name']) && is_uploaded_file($_FILES['file']['tmp_name'])) { + // Open temp file + $out = fopen($targetDir . DIRECTORY_SEPARATOR . $fileName, $chunk == 0 ? "wb" : "ab"); + if ($out) { + // Read binary input stream and append it to temp file + $in = fopen($_FILES['file']['tmp_name'], "rb"); + + if ($in) { + while ($buff = fread($in, 4096)) + fwrite($out, $buff); + } else + die('{"jsonrpc" : "2.0", "error" : {"code": 101, "message": "Failed to open input stream."}, "id" : "id"}'); + + fclose($out); + unlink($_FILES['file']['tmp_name']); + } else + die('{"jsonrpc" : "2.0", "error" : {"code": 102, "message": "Failed to open output stream."}, "id" : "id"}'); + } else + die('{"jsonrpc" : "2.0", "error" : {"code": 103, "message": "Failed to move uploaded file."}, "id" : "id"}'); + } else { + // Open temp file + $out = fopen($targetDir . DIRECTORY_SEPARATOR . $fileName, $chunk == 0 ? "wb" : "ab"); + if ($out) { + // Read binary input stream and append it to temp file + $in = fopen("php://input", "rb"); + + if ($in) { + while ($buff = fread($in, 4096)) + fwrite($out, $buff); + } else + die('{"jsonrpc" : "2.0", "error" : {"code": 101, "message": "Failed to open input stream."}, "id" : "id"}'); + + fclose($out); + } else + die('{"jsonrpc" : "2.0", "error" : {"code": 102, "message": "Failed to open output stream."}, "id" : "id"}'); + } + + $audio_file = $targetDir . DIRECTORY_SEPARATOR . $fileName; + + $md5 = md5_file($audio_file); + $duplicate = StoredFile::RecallByMd5($md5); + if ($duplicate) { + if (PEAR::isError($duplicate)) { + die('{"jsonrpc" : "2.0", "error" : {"code": 101, "message": ' . $duplicate->getMessage() .'}}'); + } + else { + $duplicateName = $duplicate->getMetadataValue(UI_MDATA_KEY_TITLE); + die('{"jsonrpc" : "2.0", "error" : {"code": 101, "message": "An identical audioclip named ' . $duplicateName . ' already exists in the storage server."}}'); + } + } + + $metadata = camp_get_audio_metadata($audio_file); + + if (PEAR::isError($metadata)) { + die('{"jsonrpc" : "2.0", "error" : {"code": 101, "message": ' + $metadata->getMessage() + '}}'); + } + + // #2196 no id tag -> use the original filename + if (basename($audio_file) == $metadata[UI_MDATA_KEY_TITLE]) { + $metadata[UI_MDATA_KEY_TITLE] = basename($audio_file); + $metadata[UI_MDATA_KEY_FILENAME] = basename($audio_file); + } + + // setMetadataBatch doesnt like these values + unset($metadata['audio']); + unset($metadata['playtime_seconds']); + + $values = array( + "filename" => basename($audio_file), + "filepath" => $audio_file, + "filetype" => "audioclip", + "mime" => $metadata[UI_MDATA_KEY_FORMAT], + "md5" => $md5 + ); + $storedFile = StoredFile::Insert($values); + + if (PEAR::isError($storedFile)) { + die('{"jsonrpc" : "2.0", "error" : {"code": 101, "message": ' + $storedFile->getMessage() + '}}'); + } + + $storedFile->setMetadataBatch($metadata); + + // Return JSON-RPC response + die('{"jsonrpc" : "2.0", "id" : '.$storedFile->getId().' }'); + } + + public function pluploadAction() + { + $view = $this->view; + + $view->headScript()->appendFile('/js/plupload/plupload.full.min.js','text/javascript'); + $view->headScript()->appendFile('/js/plupload/jquery.plupload.queue.min.js','text/javascript'); + $view->headScript()->appendFile('/js/campcaster/library/plupload.js','text/javascript'); + + $view->headLink()->appendStylesheet('/css/plupload.queue.css'); + } + + +} + + + + + diff --git a/application/controllers/ScheduleController.php b/application/controllers/ScheduleController.php new file mode 100644 index 000000000..e9730cb52 --- /dev/null +++ b/application/controllers/ScheduleController.php @@ -0,0 +1,53 @@ +hasIdentity()) + { + $this->_redirect('login/index'); + } + + $ajaxContext = $this->_helper->getHelper('AjaxContext'); + $ajaxContext->addActionContext('event-feed', 'json') + ->addActionContext('add-show-dialog', 'json') + ->initContext(); + } + + public function indexAction() + { + $this->view->headScript()->appendFile('/js/fullcalendar/fullcalendar.min.js','text/javascript'); + $this->view->headScript()->appendFile('/js/campcaster/schedule/schedule.js','text/javascript'); + + $this->view->headLink()->appendStylesheet('/css/fullcalendar.css'); + $this->view->headLink()->appendStylesheet('/css/schedule.css'); + } + + public function eventFeedAction() + { + $start = $this->_getParam('start', null); + $end = $this->_getParam('end', null); + $weekday = $this->_getParam('weekday', null); + + $userInfo = Zend_Auth::getInstance()->getStorage()->read(); + + $show = new Show($userInfo->type); + $this->view->events = $show->getFullCalendarEvents($start, $end, $weekday); + } + + public function addShowDialogAction() + { + $user = new User(); + + $this->view->hosts = $user->getHosts(); + } + + +} + + + + + diff --git a/application/controllers/SearchController.php b/application/controllers/SearchController.php new file mode 100644 index 000000000..4309d4c5b --- /dev/null +++ b/application/controllers/SearchController.php @@ -0,0 +1,77 @@ +hasIdentity()) + { + $this->_redirect('login/index'); + } + + $ajaxContext = $this->_helper->getHelper('AjaxContext'); + $ajaxContext->addActionContext('newfield', 'html') + ->initContext(); + + $this->form = new Application_Form_AdvancedSearch(); + } + + public function indexAction() + { + // action body + } + + public function displayAction() + { + $this->view->headScript()->appendFile('/js/campcaster/library/advancedsearch.js','text/javascript'); + $this->view->headLink()->appendStylesheet('/css/library_search.css'); + + $this->form = new Application_Form_AdvancedSearch(); + $form = $this->form; + + // Form has not been submitted - pass to view and return + if (!$this->getRequest()->isPost()) { + $sub = new Application_Form_AdvancedSearchRow(1); + $form->addSubForm($sub, 'row_1'); + $form->getSubForm('row_1')->removeDecorator('DtDdWrapper'); + + $this->view->form = $form; + return; + } + + // Form has been submitted - run data through preValidation() + $form->preValidation($_POST); + + if (!$form->isValid($_POST)) { + $this->view->form = $form; + return; + } + + // Form is valid + $this->view->form = $form; + + $info = $form->getValues(); + $this->view->files = StoredFile::searchFiles($info); + } + + public function newfieldAction() + { + $id = $this->_getParam('id', 1); + + $this->form->addSubForm(new Application_Form_AdvancedSearchRow($id), 'row_'.$id, $id); + + $this->form->getSubForm('row_'.$id)->removeDecorator('DtDdWrapper'); + $e = $this->form->getSubForm('row_'.$id); + + $this->view->field = $e->__toString(); + } + +} + + + + + diff --git a/application/controllers/plugins/Acl_plugin.php b/application/controllers/plugins/Acl_plugin.php new file mode 100644 index 000000000..2d7a5dc7d --- /dev/null +++ b/application/controllers/plugins/Acl_plugin.php @@ -0,0 +1,147 @@ +_errorPage = array('module' => 'default', + 'controller' => 'error', + 'action' => 'denied'); + + $this->_roleName = $roleName; + + if (null !== $aclData) { + $this->setAcl($aclData); + } + } + + /** + * Sets the ACL object + * + * @param mixed $aclData + * @return void + **/ + public function setAcl(Zend_Acl $aclData) + { + $this->_acl = $aclData; + } + + /** + * Returns the ACL object + * + * @return Zend_Acl + **/ + public function getAcl() + { + return $this->_acl; + } + + /** + * Returns the ACL role used + * + * @return string + * @author + **/ + public function getRoleName() + { + return $this->_roleName; + } + + public function setRoleName($type) + { + $roles = array("A" => "admin", "H" => "host", "G" => "guest"); + $this->_roleName = $roles[$type]; + } + + /** + * Sets the error page + * + * @param string $action + * @param string $controller + * @param string $module + * @return void + **/ + public function setErrorPage($action, $controller = 'error', $module = null) + { + $this->_errorPage = array('module' => $module, + 'controller' => $controller, + 'action' => $action); + } + + /** + * Returns the error page + * + * @return array + **/ + public function getErrorPage() + { + return $this->_errorPage; + } + + /** + * Predispatch + * Checks if the current user identified by roleName has rights to the requested url (module/controller/action) + * If not, it will call denyAccess to be redirected to errorPage + * + * @return void + **/ + public function preDispatch(Zend_Controller_Request_Abstract $request) + { + if (Zend_Auth::getInstance()->hasIdentity()){ + $userInfo = Zend_Auth::getInstance()->getStorage()->read(); + $this->setRoleName($userInfo->type); + } + else { + $this->_roleName = "guest"; + } + + $resourceName = ''; + + if ($request->getModuleName() != 'default') { + $resourceName .= strtolower($request->getModuleName()) . ':'; + } + + $resourceName .= strtolower($request->getControllerName()); + + /** Check if the controller/action can be accessed by the current user */ + if (!$this->getAcl()->isAllowed($this->_roleName, $resourceName, $request->getActionName())) { + /** Redirect to access denied page */ + $this->denyAccess(); + } + } + + /** + * Deny Access Function + * Redirects to errorPage, this can be called from an action using the action helper + * + * @return void + **/ + public function denyAccess() + { + $this->_request->setModuleName($this->_errorPage['module']); + $this->_request->setControllerName($this->_errorPage['controller']); + $this->_request->setActionName($this->_errorPage['action']); + } +} diff --git a/application/forms/AdvancedSearch.php b/application/forms/AdvancedSearch.php new file mode 100644 index 000000000..4c915c38e --- /dev/null +++ b/application/forms/AdvancedSearch.php @@ -0,0 +1,51 @@ +addElement('hidden', 'search_next_id', array( + 'value' => 2 + )); + $this->getElement('search_next_id')->removeDecorator('Label')->removeDecorator('HtmlTag'); + } + + public function preValidation(array $data) { + + function findId($name) { + $t = explode("_", $name); + return $t[1]; + } + + // array_filter callback + function findFields($field) { + return strpos($field, 'row') !== false; + } + + $fields = array_filter(array_keys($data), 'findFields'); + + foreach ($fields as $field) { + // use id to set new order + $id = findId($field); + $this->addNewField($data, $id); + } + } + + public function addNewField($data, $id) { + + $sub = new Application_Form_AdvancedSearchRow($id); + + $values = array("metadata_".$id => $data["row_".$id]["metadata_".$id], + "match_".$id => $data["row_".$id]["match_".$id], + "search_".$id => $data["row_".$id]["search_".$id]); + + $sub->setDefaults($values); + + $this->addSubForm($sub, 'row_'.$id, $id); + $this->getSubForm('row_'.$id)->removeDecorator('DtDdWrapper'); + } + + +} + diff --git a/application/forms/AdvancedSearchRow.php b/application/forms/AdvancedSearchRow.php new file mode 100644 index 000000000..a647e7656 --- /dev/null +++ b/application/forms/AdvancedSearchRow.php @@ -0,0 +1,81 @@ +_rowid = $id; + parent::__construct(); + } + + public function init() + { + $id = $this->_rowid; + + $this->addElement( + 'select', + 'metadata_'.$id, + array( + 'required' => true, + 'multiOptions' => array( + "dc:title" => "Title", + "dc:format" => "Format", + "dc:creator" => "Artist/Creator", + "dc:source" => "Album", + "ls:bitrate" => "Bitrate", + "ls:samplerate" => "Samplerate", + "dcterms:extent" => "Length", + "dc:description" => "Comments", + "dc:type" => "Genre", + "ls:channels" => "channels", + "ls:year" => "Year", + "ls:track_num" => "track_number", + "ls:mood" => "mood", + "ls:bpm" => "BPM", + "ls:rating" => "rating", + "ls:encoded_by" => "encoded_by", + "dc:publisher" => "label", + "ls:composer" => "Composer", + "ls:encoder" => "Encoder", + "ls:lyrics" => "lyrics", + "ls:orchestra" => "orchestra", + "ls:conductor" => "conductor", + "ls:lyricist" => "lyricist", + "ls:originallyricist" => "original_lyricist", + "ls:isrcnumber" => "isrc_number", + "dc:language" => "Language", + ), + ) + ); + $this->getElement('metadata_'.$id)->removeDecorator('Label')->removeDecorator('HtmlTag'); + + $this->addElement( + 'select', + 'match_'.$id, + array( + 'required' => true, + 'multiOptions' => array( + "0" => "partial", + "1" => "=", + "2" => "<", + "3" => "<=", + "4" => ">", + "5" => ">=", + "6" => "!=", + ), + ) + ); + $this->getElement('match_'.$id)->removeDecorator('Label')->removeDecorator('HtmlTag'); + + $this->addElement('text', 'search_'.$id, array( + 'required' => true, + )); + $this->getElement('search_'.$id)->removeDecorator('Label')->removeDecorator('HtmlTag'); + } + + +} + diff --git a/application/forms/EditAudioMD.php b/application/forms/EditAudioMD.php new file mode 100644 index 000000000..4f9323a56 --- /dev/null +++ b/application/forms/EditAudioMD.php @@ -0,0 +1,13 @@ +setMethod('post'); + + // Add username element + $this->addElement('text', 'username', array( + 'label' => 'Username:', + 'required' => true, + 'filters' => array('StringTrim'), + 'validators' => array( + 'NotEmpty', + ) + )); + + // Add password element + $this->addElement('password', 'password', array( + 'label' => 'Password:', + 'required' => true, + 'filters' => array('StringTrim'), + 'validators' => array( + 'NotEmpty', + ) + )); + + // Add the submit button + $this->addElement('submit', 'submit', array( + 'ignore' => true, + 'label' => 'Login', + )); + + } + + +} + diff --git a/application/forms/PlaylistMetadata.php b/application/forms/PlaylistMetadata.php new file mode 100644 index 000000000..fb15ee544 --- /dev/null +++ b/application/forms/PlaylistMetadata.php @@ -0,0 +1,36 @@ +setMethod('post'); + + // Add username element + $this->addElement('text', 'title', array( + 'label' => 'Title:', + 'required' => true, + 'filters' => array('StringTrim'), + 'validators' => array( + 'NotEmpty', + ) + )); + + // Add the comment element + $this->addElement('textarea', 'description', array( + 'label' => 'Description:', + 'required' => false, + )); + + // Add the submit button + $this->addElement('submit', 'submit', array( + 'ignore' => true, + 'label' => 'Submit', + )); + } + + +} + diff --git a/application/layouts/scripts/layout.phtml b/application/layouts/scripts/layout.phtml new file mode 100644 index 000000000..b85faa6b4 --- /dev/null +++ b/application/layouts/scripts/layout.phtml @@ -0,0 +1,17 @@ + +doctype() ?> + + + + Campcaster + headScript() ?> + headLink() ?> + + + + + +
layout()->content ?>
+ + + diff --git a/application/layouts/scripts/login.phtml b/application/layouts/scripts/login.phtml new file mode 100644 index 000000000..ceee1db32 --- /dev/null +++ b/application/layouts/scripts/login.phtml @@ -0,0 +1,15 @@ + +doctype() ?> + + + + Campcaster + headScript() ?> + headLink() ?> + + + +
layout()->content ?>
+ + + diff --git a/application/models/AccessRecur.php b/application/models/AccessRecur.php new file mode 100644 index 000000000..6e0fd7931 --- /dev/null +++ b/application/models/AccessRecur.php @@ -0,0 +1,184 @@ +ls =& $ls; + $this->sessid = $sessid; + } + + + public static function accessPlaylist(&$ls, $sessid, $plid, $parent='0') + { + $ppa = new AccessRecur($ls, $sessid); + $r = $ls->accessPlaylist($sessid, $plid, FALSE, $parent); + if (PEAR::isError($r)) { + return $r; + } + $plRes = $r; + $r = StoredFile::RecallByGunid($plid); + if (is_null($r) || PEAR::isError($r)) { + return $r; + } + $ac = $r; + $r = $ac->md->genPhpArray(); + if (PEAR::isError($r)) { + return $r; + } + $pla = $r; + $r = $ppa->processPlaylist($pla, $plRes['token']); + if (PEAR::isError($r)) { + return $r; + } + $plRes['content'] = $r; + return $plRes; + } + + + public static function releasePlaylist(&$ls, $sessid, $token) + { + global $CC_CONFIG, $CC_DBC; + $ppa = new AccessRecur($ls, $sessid); + $r = $CC_DBC->getAll(" + SELECT to_hex(token)as token2, to_hex(gunid)as gunid + FROM ".$CC_CONFIG['accessTable']." + WHERE parent=x'{$token}'::bigint + "); + if (PEAR::isError($r)) { + return $r; + } + $arr = $r; + foreach ($arr as $i => $item) { + extract($item); // token2, gunid + $r = BasicStor::GetType($gunid); + if (PEAR::isError($r)) { + return $r; + } + $ftype = $r; + # echo "$ftype/$token2\n"; + switch (strtolower($ftype)) { + case "audioclip": + $r = $ppa->ls->releaseRawAudioData($ppa->sessid, $token2); + if (PEAR::isError($r)) { + return $r; + } + # var_dump($r); + break; + case "playlist": + $r = $ppa->releasePlaylist($ppa->ls, $ppa->sessid, $token2); + if (PEAR::isError($r)) { + return $r; + } + # var_dump($r); + break; + default: + } + } + $r = $ppa->ls->releasePlaylist($ppa->sessid, $token, FALSE); + if (PEAR::isError($r)) { + return $r; + } + return $r; + } + + + private function processPlaylist($pla, $parent) + { + $res = array(); + foreach ($pla['children'] as $ple) { + switch ($ple['elementname']) { + case "playlistElement": + $r = $this->processPlaylistElement($ple, $parent); + if (PEAR::isError($r)) { + return $r; + } + // $res = array_merge($res, $r); + $res[] = $r; + break; + default: + } + } + return $res; + } + + + private function processAudioClip($gunid, $parent) + { + $r = $this->ls->accessRawAudioData($this->sessid, $gunid, $parent); + if (PEAR::isError($r)) { + return $r; + } + return $r; + } + + + private function processPlaylistElement($ple, $parent='0') + { + foreach ($ple['children'] as $ac) { + switch ($ac['elementname']) { + case "audioClip": + $r = $this->processAudioClip($ac['attrs']['id'], $parent); + if (PEAR::isError($r)) { + return $r; + } + return $r; + case "playlist": +// if(empty($ac['children'])){ + $r = $this->accessPlaylist($this->ls, $this->sessid, + $ac['attrs']['id'], $parent); + if (PEAR::isError($r)) { + if ($r->getCode() != GBERR_NOTF) { + return $r; + } else { + $r = $this->processPlaylist($ac, $parent); + if (PEAR::isError($r)) { + return $r; + } + $r = array( + 'content' => $r, + 'url' => NULL, + 'token' => NULL, + 'chsum' => NULL, + 'size' => NULL, + 'warning' => 'inline playlist?', + ); + } + } + return $r; +/* + }else{ + $r = $this->processPlaylist($ac, $parent); + if(PEAR::isError($r)) return $r; + $res = array( + 'content' => $r, + 'url' => NULL, + 'token' => NULL, + 'chsum' => NULL, + 'size' => NULL, + 'warning' => 'inline playlist', + ); + return $res; + } +*/ + break; + default: + } + } + return array(); + } + +} // class AccessRecur +?> \ No newline at end of file diff --git a/application/models/Alib.php b/application/models/Alib.php new file mode 100644 index 000000000..8fa7c51a9 --- /dev/null +++ b/application/models/Alib.php @@ -0,0 +1,622 @@ +query($sql); + if (PEAR::isError($r)) { + return $r; + } + Subjects::SetTimeStamp($login, FALSE); + return $sessid; + } // fn login + + + /** + * Logout and destroy session + * + * @param string $sessid + * @return true|PEAR_Error + */ + public static function Logout($sessid) + { + global $CC_CONFIG, $CC_DBC; + $ct = Alib::CheckAuthToken($sessid); + if ($ct === FALSE) { + return PEAR::raiseError("Alib::logout: not logged ($sessid)", + ALIBERR_NOTLOGGED, PEAR_ERROR_RETURN); + } elseif (PEAR::isError($ct)) { + return $ct; + } else { + $sql = "DELETE FROM ".$CC_CONFIG['sessTable'] + ." WHERE sessid='$sessid'"; + $r = $CC_DBC->query($sql); + if (PEAR::isError($r)) { + return $r; + } + return TRUE; + } + } // fn logout + + + /** + * Return true if the token is valid + * + * @param string $sessid + * @return boolean|PEAR_Error + */ + private static function CheckAuthToken($sessid) + { + global $CC_CONFIG, $CC_DBC; + $sql = "SELECT count(*) as cnt FROM ".$CC_CONFIG['sessTable'] + ." WHERE sessid='$sessid'"; + $c = $CC_DBC->getOne($sql); + return ($c == 1 ? TRUE : (PEAR::isError($c) ? $c : FALSE )); + } //fn checkAuthToken + + + /** + * Set valid token in alib object + * + * @param string $sessid + * @return TRUE|PEAR_Error + */ +// public function setAuthToken($sessid) +// { +// $r = $this->checkAuthToken($sessid); +// if (PEAR::isError($r)) { +// return $r; +// } +// if (!$r) { +// return PEAR::raiseError("ALib::setAuthToken: invalid token ($sessid)"); +// } +// //$this->sessid = $sessid; +// return TRUE; +// } // fn setAuthToken + + + /* -------------------------------------------------------- authorization */ + /** + * Insert permission record + * + * @param int $sid + * local user/group id + * @param string $action + * @param int $oid + * local object id + * @param string $type + * 'A'|'D' (allow/deny) + * @return int + * local permission id + */ + public static function AddPerm($sid, $action, $oid, $type='A') + { + global $CC_CONFIG, $CC_DBC; + $permid = $CC_DBC->nextId($CC_CONFIG['permSequence']); + $sql = "INSERT INTO ".$CC_CONFIG['permTable']." (permid, subj, action, obj, type)" + ." VALUES ($permid, $sid, '$action', $oid, '$type')"; + $r = $CC_DBC->query($sql); + if (PEAR::isError($r)) { + return($r); + } + return $permid; + } // fn addPerm + + + /** + * Remove permission record + * + * @param int $permid + * local permission id + * @param int $subj + * local user/group id + * @param int $obj + * local object id + * @return boolean|PEAR_Error + */ + public static function RemovePerm($permid=NULL, $subj=NULL, $obj=NULL) + { + global $CC_CONFIG, $CC_DBC; + $ca = array(); + if ($permid) { + $ca[] = "permid=$permid"; + } + if ($subj) { + $ca[] = "subj=$subj"; + } + if ($obj) { + $ca[] = "obj=$obj"; + } + $cond = join(" AND ", $ca); + if (!$cond) { + return TRUE; + } + $sql = "DELETE FROM ".$CC_CONFIG['permTable']." WHERE $cond"; + return $CC_DBC->query($sql); + } // fn removePerm + + + /** + * Return object related with permission record + * + * @param int $permid + * local permission id + * @return int + * local object id + */ + public static function GetPermOid($permid) + { + global $CC_CONFIG, $CC_DBC; + $sql = "SELECT obj FROM ".$CC_CONFIG['permTable']." WHERE permid=$permid"; + $res = $CC_DBC->getOne($sql); + return $res; + } // fn GetPermOid + + + /** + * Check if specified subject have permission to specified action + * on specified object + * + * Look for sequence of corresponding permissions and order it by + * relevence, then test the most relevant for result. + * High relevence have direct permission (directly for specified subject + * and object. Relevance order is done by level distance in the object + * tree, level distance in subjects (user/group system). + * Similar way is used for permissions related to object classes. + * But class-related permissions have lower priority then + * object-tree-related. + * Support for object classes can be disabled by USE_ALIB_CLASSES const. + * + * @param int $sid + * subject id (user or group id) + * @param string $action + * from set defined in config + * @param int $oid + * object id (default: root node) + * @return boolean|PEAR_Error + */ + public static function CheckPerm($sid, $action, $oid=NULL) + { + return TRUE; +// global $CC_DBC; +// global $CC_CONFIG; +// if (!is_numeric($sid)) { +// return FALSE; +// } +//// if (is_null($oid) or $oid=='') { +//// $oid = M2tree::GetRootNode(); +//// } +//// if (PEAR::isError($oid)) { +//// return $oid; +//// } +// if (!is_numeric($oid)) { +// return FALSE; +// } +// // query construction +// // shortcuts: +// // p: permTable, +// // s: subjTable, m smembTable, +// // t: treeTable ts: structTable, +// // c: classTable, cm: cmembTable +// // main query elements: +// $q_flds = "m.level , p.subj, s.login, action, p.type, p.obj"; +// $q_from = $CC_CONFIG['permTable']." p "; +// // joins for solving users/groups: +// $q_join = "LEFT JOIN ".$CC_CONFIG['subjTable']." s ON s.id=p.subj "; +// $q_join .= "LEFT JOIN ".$CC_CONFIG['smembTable']." m ON m.gid=p.subj "; +// $q_cond = "p.action in('_all', '$action') AND +// (s.id=$sid OR m.uid=$sid) "; +// // coalesce -1 for higher priority of nongroup rows: +// // action DESC order for lower priority of '_all': +// $q_ordb = "ORDER BY coalesce(m.level,-1), action DESC, p.type DESC"; +// $q_flds0 = $q_flds; +// $q_from0 = $q_from; +// $q_join0 = $q_join; +// $q_cond0 = $q_cond; +// $q_ordb0 = $q_ordb; +// // joins for solving object tree: +// $q_flds .= ", t.name, ts.level as tlevel"; +// //$q_join .= "LEFT JOIN ".$CC_CONFIG['treeTable']." t ON t.id=p.obj "; +// //$q_join .= "LEFT JOIN ".$CC_CONFIG['structTable']." ts ON ts.parid=p.obj "; +// //$q_cond .= " AND (t.id=$oid OR ts.objid=$oid)"; +// // action DESC order is hack for lower priority of '_all': +// $q_ordb = "ORDER BY coalesce(ts.level,0), m.level, action DESC, p.type DESC"; +// // query by tree: +// $query1 = "SELECT $q_flds FROM $q_from $q_join WHERE $q_cond $q_ordb"; +// $r1 = $CC_DBC->getAll($query1); +// if (PEAR::isError($r1)) { +// return($r1); +// } +// // if there is row with type='A' on the top => permit +// //$AllowedByTree = +// // (is_array($r1) && count($r1)>0 && $r1[0]['type']=='A'); +// //$DeniedByTree = +// // (is_array($r1) && count($r1)>0 && $r1[0]['type']=='D'); +// +// if (!USE_ALIB_CLASSES) { +// return $AllowedbyTree; +// } +// +// // joins for solving object classes: +// $q_flds = $q_flds0.", c.cname "; +// $q_join = $q_join0."LEFT JOIN ".$CC_CONFIG['classTable']." c ON c.id=p.obj "; +// $q_join .= "LEFT JOIN ".$CC_CONFIG['cmembTable']." cm ON cm.cid=p.obj "; +// $q_cond = $q_cond0." AND (c.id=$oid OR cm.objid=$oid)"; +// $q_ordb = $q_ordb0; +// // query by class: +// $query2 = "SELECT $q_flds FROM $q_from $q_join WHERE $q_cond $q_ordb"; +// $r2 = $CC_DBC->getAll($query2); +// if (PEAR::isError($r2)) { +// return $r2; +// } +// $AllowedByClass = +// (is_array($r2) && count($r2)>0 && $r2[0]['type']=='A'); +// // not used now: +// // $DeniedByClass = +// // (is_array($r2) && count($r2)>0 && $r2[0]['type']=='D'); +// $res = ($AllowedByTree || (!$DeniedByTree && $AllowedByClass)); +// return $res; + } // fn CheckPerm + + + /* ---------------------------------------------------------- object tree */ + + /** + * Remove all permissions on object and then remove object itself + * + * @param int $id + * @return void|PEAR_Error + */ + public static function RemoveObj($id) + { + $r = Alib::RemovePerm(NULL, NULL, $id); + return $r; + } // fn removeObj + + /* --------------------------------------------------------- users/groups */ + + /** + * Remove all permissions of subject and then remove subject itself + * + * @param string $login + * @return void|PEAR_Error + */ + public static function RemoveSubj($login) + { + global $CC_CONFIG, $CC_DBC; + $uid = Subjects::GetSubjId($login); + if (PEAR::isError($uid)) { + return $uid; + } + if (is_null($uid)){ + return $CC_DBC->raiseError("Alib::removeSubj: Subj not found ($login)", + ALIBERR_NOTEXISTS, PEAR_ERROR_RETURN); + } + $r = Alib::RemovePerm(NULL, $uid); + if (PEAR::isError($r)) { + return $r; + } + return Subjects::RemoveSubj($login, $uid); + } // fn RemoveSubj + + + /* ------------------------------------------------------------- sessions */ + /** + * Get login from session id (token) + * + * @param string $sessid + * @return string|PEAR_Error + */ + public static function GetSessLogin($sessid) + { + global $CC_CONFIG, $CC_DBC; + $sql = "SELECT login FROM ".$CC_CONFIG['sessTable']." WHERE sessid='$sessid'"; + $r = $CC_DBC->getOne($sql); + if (PEAR::isError($r)) { + return $r; + } + if (is_null($r)){ + return PEAR::raiseError("Alib::GetSessLogin:". + " invalid session id ($sessid)", + ALIBERR_NOTEXISTS, PEAR_ERROR_RETURN); + } + return $r; + } // fn GetSessLogin + + + /** + * Get user id from session id. + * + * @param string $p_sessid + * @return int|PEAR_Error + */ + public static function GetSessUserId($p_sessid) + { + global $CC_CONFIG, $CC_DBC; + $sql = "SELECT userid FROM ".$CC_CONFIG['sessTable']." WHERE sessid='$p_sessid'"; + $r = $CC_DBC->getOne($sql); + if (PEAR::isError($r)) { + return $r; + } + if (is_null($r)) { + return PEAR::raiseError("Alib::getSessUserId:". + " invalid session id ($p_sessid)", + ALIBERR_NOTEXISTS, PEAR_ERROR_RETURN); + } + return $r; + } // fn getSessUserId + + + /* --------------------------------------------------------- info methods */ + /** + * Get all permissions on object. + * + * @param int $id + * @return array|null|PEAR_Error + */ + public static function GetObjPerms($id) + { + global $CC_CONFIG, $CC_DBC; + $sql = "SELECT s.login, p.* FROM ".$CC_CONFIG['permTable']." p, ".$CC_CONFIG['subjTable']." s" + ." WHERE s.id=p.subj AND p.obj=$id"; + return $CC_DBC->getAll($sql); + } // fn GetObjPerms + + + /** + * Get all permissions of subject. + * + * @param int $sid + * @return array + */ + public static function GetSubjPerms($sid) + { + global $CC_CONFIG, $CC_DBC; + $sql = "SELECT *" + ." FROM ".$CC_CONFIG['permTable'] + ." WHERE p.subj=$sid"; +// $sql = "SELECT t.name, t.type as otype , p.*" +// ." FROM ".$CC_CONFIG['permTable']." p, ".$CC_CONFIG['treeTable']." t" +// ." WHERE t.id=p.obj AND p.subj=$sid"; + $a1 = $CC_DBC->getAll($sql); + return $a1; + } // fn GetSubjPerms + + + /* ------------------------ info methods related to application structure */ + /* (this part should be added/rewritten to allow defining/modifying/using + * application structure) + * (only very simple structure definition - in $CC_CONFIG - supported now) + */ + + /** + * Get all actions + * + * @return array + */ + public static function GetAllActions() + { + global $CC_CONFIG; + return $CC_CONFIG['allActions']; + } // fn GetAllActions + + + /** + * Get all allowed actions on specified object type. + * + * @param string $type + * @return array + */ + public static function GetAllowedActions($type) + { + global $CC_CONFIG; + return $CC_CONFIG['allowedActions'][$type]; + } // fn GetAllowedActions + + + /* ====================================================== private methods */ + + /** + * Create new session id. Return the new session ID. + * + * @return string + */ + private static function _createSessid() + { + global $CC_CONFIG, $CC_DBC; + for ($c = 1; $c > 0; ){ + $sessid = md5(uniqid(rand())); + $sql = "SELECT count(*) FROM ".$CC_CONFIG['sessTable'] + ." WHERE sessid='$sessid'"; + $c = $CC_DBC->getOne($sql); + if (PEAR::isError($c)) { + return $c; + } + } + return $sessid; + } // fn _createSessid + + + /* =============================================== test and debug methods */ + + /** + * Dump all permissions for debug + * + * @param string $indstr + * indentation string + * @param string $ind + * actual indentation + * @return string + */ + public static function DumpPerms($indstr=' ', $ind='') + { + global $CC_CONFIG, $CC_DBC; + $sql = "SELECT s.login, p.action, p.type" + ." FROM ".$CC_CONFIG['permTable']." p, ".$CC_CONFIG['subjTable']." s" + ." WHERE s.id=p.subj" + ." ORDER BY p.permid"; + $arr = $CC_DBC->getAll($sql); + if (PEAR::isError($arr)) { + return $arr; + } + $r = $ind.join(', ', array_map(create_function('$v', + 'return "{$v[\'login\']}/{$v[\'action\']}/{$v[\'type\']}";' + ), + $arr + ))."\n"; + return $r; + } // fn dumpPerms + + + /** + * Delete everything form the permission table and session table. + * + * @return void + */ + public static function DeleteData() + { + global $CC_CONFIG, $CC_DBC; + $CC_DBC->query("DELETE FROM ".$CC_CONFIG['permTable']); + $CC_DBC->query("DELETE FROM ".$CC_CONFIG['sessTable']); + Subjects::DeleteData(); + } // fn deleteData + + + /** + * Insert test permissions + * + * @return array + */ + public static function TestData() + { + global $CC_CONFIG, $CC_DBC; + $tdata = Subjects::TestData(); + $t =& $tdata['tree']; + $c =& $tdata['classes']; + $s =& $tdata['subjects']; + $CC_DBC->setErrorHandling(PEAR_ERROR_PRINT); + $perms = array( + array($s['root'], '_all', $t['root'], 'A'), + array($s['test1'], '_all', $t['pa'], 'A'), + array($s['test1'], 'read', $t['s2b'], 'D'), + array($s['test2'], 'addChilds', $t['pa'], 'D'), + array($s['test2'], 'read', $t['i2'], 'A'), + array($s['test2'], 'edit', $t['s1a'], 'A'), + array($s['test1'], 'addChilds', $t['s2a'], 'D'), + array($s['test1'], 'addChilds', $t['s2c'], 'D'), + array($s['gr2'], 'addChilds', $t['i2'], 'A'), + array($s['test3'], '_all', $t['t1'], 'D'), + ); + if (USE_ALIB_CLASSES){ + $perms[] = array($s['test3'], 'read', $c['cl_sa'], 'D'); + $perms[] = array($s['test4'], 'editPerms', $c['cl2'], 'A'); + } + foreach ($perms as $p){ + $o[] = $r = Alib::AddPerm($p[0], $p[1], $p[2], $p[3]); + if (PEAR::isError($r)) { + return $r; + } + } + $tdata['perms'] = $o; + return $tdata; + } // fn testData + + + /** + * Make basic test + * + * @return boolean|PEAR_Error + */ + public static function Test() + { + $p = Subjects::test(); + if (PEAR::isError($p)) { + return $p; + } + Alib::DeleteData(); + $tdata = Alib::TestData(); + if (PEAR::isError($tdata)) { + return $tdata; + } + $test_correct = "root/_all/A, test1/_all/A, test1/read/D,". + " test2/addChilds/D, test2/read/A, test2/edit/A,". + " test1/addChilds/D, test1/addChilds/D, gr2/addChilds/A,". + " test3/_all/D"; + if (USE_ALIB_CLASSES){ + $test_correct .= ", test3/read/D, test4/editPerms/A"; + } + $test_correct .= "\nno, yes\n"; + $r = Alib::DumpPerms(); + if (PEAR::isError($r)) { + return $r; + } + $test_dump = $r. + (Alib::CheckPerm( + $tdata['subjects']['test1'], 'read', + $tdata['tree']['t1'] + )? 'yes':'no').", ". + (Alib::CheckPerm( + $tdata['subjects']['test1'], 'addChilds', + $tdata['tree']['i2'] + )? 'yes':'no')."\n" + ; + Alib::RemovePerm($tdata['perms'][1]); + Alib::RemovePerm($tdata['perms'][3]); + $test_correct .= "root/_all/A, test1/read/D,". + " test2/read/A, test2/edit/A,". + " test1/addChilds/D, test1/addChilds/D, gr2/addChilds/A,". + " test3/_all/D"; + if (USE_ALIB_CLASSES) { + $test_correct .= ", test3/read/D, test4/editPerms/A"; + } + $test_correct .= "\n"; + $test_dump .= Alib::DumpPerms(); + Alib::DeleteData(); + if ($test_dump == $test_correct) { + $test_log .= "alib: OK\n"; + return TRUE; + } else { + return PEAR::raiseError('Alib::test', 1, PEAR_ERROR_DIE, '%s'. + "
\ncorrect:\n{$test_correct}\n".
+                "dump:\n{$test_dump}\n
\n"); + } + } // fn test + +} // class Alib +?> \ No newline at end of file diff --git a/application/models/Backup.php b/application/models/Backup.php new file mode 100755 index 000000000..211faa362 --- /dev/null +++ b/application/models/Backup.php @@ -0,0 +1,487 @@ +gb =& $gb; + $this->token = null; + $this->logFile = $CC_CONFIG['bufferDir'].'/'.ACCESS_TYPE.'.log'; + $this->addLogItem("-I- ".date("Ymd-H:i:s")." construct\n"); + } + + + /** + * Open a backup + * Create a backup file (tarball) + * + * @param string $sessid + * @param array $criteria + * struct - see search criteria + * @return array + * hasharray with field: + * token string: backup token + */ + public function openBackup($sessid, $criteria='') + { + if ($this->loglevel=='debug') { + $this->addLogItem("-I- ".date("Ymd-H:i:s")." openBackup - sessid:$sessid\n"); + } + $this->sessid = $sessid; + $this->criteria = $criteria; + + // get ids (and real filenames) which files match with criteria + $srch = $this->gb->localSearch($this->criteria,$this->sessid); + if (PEAR::isError($srch)) { + return $srch; + } + $this->setIDs($srch); + + // get real filenames + if (is_array($this->ids)) { + $this->setFilenames(); + + $this->setEnviroment(true); + + // write a status file + file_put_contents($this->statusFile, 'working'); + + // save the metafile to tmpdir + $hostname = trim(`hostname`); + $ctime = time(); + $ctime_f = date("Ymd-H:i:s"); + file_put_contents("{$this->tmpDirMeta}/storage.xml", + "\n". + "\n" + ); + + // copy all file to tmpdir + $this->copyAllFiles(); + + // do everything + $this->doIt(); + + return array('token'=>$this->token); + } else { + return false; + } + } + + + /** + * Check the status of backup. + * + * @param unknown $token + * @return array + * status : string - susccess | working | fault + * faultString: string - description of fault + * token : stirng - backup token + * url : string - access url + * tmpfile : string - access filename + */ + public function checkBackup($token) + { + global $CC_CONFIG; + if ($this->loglevel=='debug') { + $this->addLogItem("-I- ".date("Ymd-H:i:s")." checkBackup - token:$token\n"); + } + $this->token = $token; + $this->setEnviroment(); + $status = file_get_contents($this->statusFile); + if (strpos($status,'fault')!==false) { + list($status,$faultString) = explode('|',$status); + } + switch ($status) { + case 'success': + $r['url'] = BasicStor::GetUrlPart()."access/$token.".BACKUP_EXT; + $r['tmpfile'] = $CC_CONFIG['accessDir']."/$token.".BACKUP_EXT; + case 'working': + case 'fault': + $r['status'] = $status; + $r['faultString'] = $faultString; + $r['token'] = $token; + break; + } + return $r; + } + + + /** + * Close a backup + * + * @param unknown $token + * @return boolean + */ + public function closeBackup($token) + { + if ($this->loglevel=='debug') { + $this->addLogItem("-I- ".date("Ymd-H:i:s")." closeBackup - token:$token\n"); + } + # post procedures + $this->token = $token; + $this->setEnviroment(); + BasicStor::bsRelease($token, ACCESS_TYPE); + Backup::rRmDir($this->tmpDir); + unlink($this->statusFile); + unlink($this->tmpFile); + if (is_file($this->tmpName)) { + unlink($this->tmpName); + } + return !is_file($this->tmpFile); + } + + + /** + * list of unclosed backups + * + * @param string $stat + * if this parameter is not set, then return with all unclosed backups + * @return array of hasharray with field: + * status : string - susccess | working | fault + * token : stirng - backup token + * url : string - access url + */ + public function listBackups($stat='') + { + if ($this->loglevel=='debug') { + $this->addLogItem("-I- ".date("Ymd-H:i:s")." listBackups - stat:$stat\n"); + } + // open temporary dir + $tokens = BasicStor::GetTokensByType(ACCESS_TYPE); + // echo 'tokens:'; print_r($tokens); echo ''; + foreach ($tokens as $token) { + $st = $this->checkBackup($token); + if ($stat=='' || $st['status']==$stat) { + $r[] = $st; + } + } + return $r; + } + + + /** + * Set the ids from searchResult + * + * @param array $searchResult : array of gunids + */ + private function setIDs($searchResult) + { + if ($this->loglevel=='debug') { + $this->addLogItem("-I- ".date("Ymd-H:i:s")." setIDs\n"); + } + if (is_array($searchResult['results'])) { + $this->ids = $searchResult['results']; + } else { + $this->addLogItem("-E- ".date("Ymd-H:i:s")." setIDs - the parameter is not array!\n"); + return PEAR::raiseError('The IDs variable isn\'t array.'); + } + } + + + /** + * Set the filenames from ids. + * + */ + private function setFilenames() + { +// if ($this->loglevel=='debug') { +// $this->addLogItem("-I- ".date("Ymd-H:i:s")." setFilenames\n"); +// } +// if (is_array($this->ids)) { +// foreach ($this->ids as $i => $item) { +// $gunid = $item['gunid']; +// // get a stored file object of this gunid +// $sf = StoredFile::RecallByGunid($gunid); +// if (is_null($sf) || PEAR::isError($sf)) { +// return $sf; +// } +// $lid = BasicStor::IdFromGunid($gunid); +// if (($res = BasicStor::Authorize('read', $lid, $this->sessid)) !== TRUE) { +// $this->addLogItem("-E- ".date("Ymd-H:i:s")." setFilenames - authorize gunid:$gunid\n"); +// return PEAR::raiseError('Backup::setFilenames : Authorize ... error.'); +// } +// // if the file is a playlist then it has only a meta file +// if (strtolower($sf->md->format) != 'playlist') { +// $this->filenames[] = array( +// 'filename' => $sf->getRealFileName(), +// 'format' => $sf->md->format +// ); +// } +// $this->filenames[] = array( +// 'filename' => $sf->getRealMetadataFileName(), +// 'format' => $sf->md->format +// ); +// if ($this->loglevel=='debug') { +// $this->addLogItem("-I- ".date("Ymd-H:i:s")." setFilenames - add file: {$sf->md->format}|".$sf->getRealMetadataFileName()."\n"); +// } +// } +// return $this->filenames; +// } else { +// $this->addLogItem("-E- ".date("Ymd-H:i:s")." setFilenames - The IDs variable isn't array.\n"); +// return PEAR::raiseError('Backup::setFilenames : The IDs variable isn\'t array.'); +// } + } + + + /** + * Create the tarball - call the shell script + * + */ + private function doIt() + { + if ($this->loglevel=='debug') { + $this->addLogItem("-I- ".date("Ymd-H:i:s")." doIt\n"); + } + $command = dirname(__FILe__)."/../bin/backup.sh" + ." {$this->tmpDir}" + ." {$this->tmpFile}" + ." {$this->statusFile}" + ." >> {$this->logFile} &"; + $res = system("$command"); + sleep(2); + if ($this->loglevel=='debug') { + $this->addLogItem("-I- ".date("Ymd-H:i:s")." doIt - command:$command\n"); + } + } + + + /** + * Copy the real files into the tmp dirs to tar they. + * + */ + private function copyAllFiles() + { + if ($this->loglevel=='debug') { + $this->addLogItem("-I- ".date("Ymd-H:i:s")." copyAllFiles\n"); + } + //echo 'this->filenames:'; print_r($this->filenames); echo ''; + if (is_array($this->filenames)) { + foreach ($this->filenames as $v) { + # get the filename from full path + $fn = substr($v['filename'],strrpos($v['filename'],'/')); + switch (strtolower($v['format'])) { + case 'playlist': + # if playlist then copy to the playlist dir + copy($v['filename'],$this->tmpDirPlaylist.$fn); + break; + case 'audioclip': + # if audioclip then copy to the audioclip dir + copy($v['filename'],$this->tmpDirClip.$fn); + break; + } + } + } + } + + + /** + * Figure out the enviroment to the backup + * + */ + private function setEnviroment($createDir=false) + { + global $CC_CONFIG; + if ($this->loglevel=='debug') { + $this->addLogItem("-I- ".date("Ymd-H:i:s")." setEnviroment - createDirs:$createDir\n"); + } + // create temporary directories + if (is_null($this->token) && $createDir) { + $this->tmpName = tempnam($CC_CONFIG['bufferDir'], ACCESS_TYPE.'_'); + $this->tmpFile = $this->tmpName.'.'.BACKUP_EXT; + $this->tmpDir = $this->tmpName.'.dir'; + $this->tmpDirPlaylist = $this->tmpDir. '/playlist'; + $this->tmpDirClip = $this->tmpDir. '/audioClip'; + $this->tmpDirMeta = $this->tmpDir. '/meta-inf'; + touch($this->tmpFile); + mkdir($this->tmpDir); + mkdir($this->tmpDirPlaylist); + mkdir($this->tmpDirClip); + mkdir($this->tmpDirMeta); + $this->genToken(); + } else { + $symlink = $CC_CONFIG['accessDir'].'/'.$this->token.'.'.BACKUP_EXT; + if (is_link($symlink) && is_file(readlink($symlink))) { + $this->tmpName = str_replace('.tar','',readlink($symlink)); + $this->tmpFile = $this->tmpName.'.'.BACKUP_EXT; + $this->tmpDir = $this->tmpName.'.dir'; + $this->tmpDirPlaylist = $this->tmpDir. '/playlist'; + $this->tmpDirClip = $this->tmpDir. '/audioClip'; + $this->tmpDirMeta = $this->tmpDir. '/meta-inf'; + } else { + $this->addLogItem("-E- ".date("Ymd-H:i:s")." setEnviroment - Corrupt symbolic link.\n"); + return false; + } + } + $this->statusFile = $CC_CONFIG['accessDir'].'/'.$this->token.'.'.BACKUP_EXT.'.status'; + if ($this->loglevel=='debug') { + $this->addLogItem("this->tmpName: $this->tmpName\n"); + $this->addLogItem("this->tmpFile: $this->tmpFile\n"); + $this->addLogItem("this->tmpDir: $this->tmpDir\n"); + $this->addLogItem("this->tmpDirPlaylist: $this->tmpDirPlaylist\n"); + $this->addLogItem("this->tmpDirClip: $this->tmpDirClip\n"); + $this->addLogItem("this->tmpDirMeta: $this->tmpDirMeta\n"); + $this->addLogItem("this->token: $this->token\n"); + $this->addLogItem("this->statusFile: $this->statusFile\n"); + } + } + + + /** + * Generate a new token. + * @return void + */ + private function genToken() + { + $acc = BasicStor::bsAccess($this->tmpFile, BACKUP_EXT, null, ACCESS_TYPE); + if (PEAR::isError($acc)) { + return $acc; + } + $this->token = $acc['token']; + } + + + /** + * Add a line to the logfile. + * + * @param string $item + * the new row of log file + */ + private function addLogItem($item) + { + $f = fopen($this->logFile,'a'); + fwrite($f,$item); + fclose($f); + } + + + /** + * Delete a directory recursive + * + * @param string $dirname + * path of dir. + */ + private static function rRmDir($dirname) + { + if (is_dir($dirname)) { + $dir_handle = opendir($dirname); + } + while ($file = readdir($dir_handle)) { + if ( ($file != ".") && ($file != "..") ) { + if (!is_dir($dirname."/".$file)) { + unlink ($dirname."/".$file); + } else { + Backup::rRmDir($dirname."/".$file); + } + } + } + closedir($dir_handle); + rmdir($dirname); + return true; + } + +} // classs Backup +?> diff --git a/application/models/BasicStor.php b/application/models/BasicStor.php new file mode 100644 index 000000000..22cd01bbf --- /dev/null +++ b/application/models/BasicStor.php @@ -0,0 +1,2186 @@ + + * + *

+ * Format of search/browse results: hash, with following structure:
+ *

+ * + */ +define('GBERR_DENY', 40); +define('GBERR_FILEIO', 41); +define('GBERR_FILENEX', 42); +define('GBERR_FOBJNEX', 43); +define('GBERR_WRTYPE', 44); +define('GBERR_NONE', 45); +define('GBERR_AOBJNEX', 46); +define('GBERR_NOTF', 47); +define('GBERR_SESS', 48); +define('GBERR_PREF', 49); +define('GBERR_TOKEN', 50); +define('GBERR_PUT', 51); +define('GBERR_LOCK', 52); +define('GBERR_GUNID', 53); +define('GBERR_BGERR', 54); +define('GBERR_NOTIMPL', 69); + +require_once(dirname(__FILE__)."/Alib.php"); +require_once(dirname(__FILE__)."/StoredFile.php"); +require_once(dirname(__FILE__)."/Transport.php"); +require_once(dirname(__FILE__)."/Playlist.php"); + +//$g_metadata_xml_to_db_mapping = array( +// "dc:format" => "format", +// "ls:bitrate" => "bit_rate", +// "ls:samplerate" => "sample_rate", +// "dcterms:extent" => "length", +// "dc:title" => "track_title", +// "dc:description" => "comments", +// "dc:type" => "genre", +// "dc:creator" => "artist_name", +// "dc:source" => "album_title", +// "ls:channels" => "channels", +// "ls:filename" => "name", +// "ls:year" => "year", +// "ls:url" => "url", +// "ls:track_num" => "track_number", +// "ls:mood" => "mood", +// "ls:bpm" => "bpm", +// "ls:disc_num" => "disc_number", +// "ls:rating" => "rating", +// "ls:encoded_by" => "encoded_by", +// "dc:publisher" => "label", +// "ls:composer" => "composer", +// "ls:encoder" => "encoder", +// "ls:crc" => "checksum", +// "ls:lyrics" => "lyrics", +// "ls:orchestra" => "orchestra", +// "ls:conductor" => "conductor", +// "ls:lyricist" => "lyricist", +// "ls:originallyricist" => "original_lyricist", +// "ls:radiostationname" => "radio_station_name", +// "ls:audiofileinfourl" => "info_url", +// "ls:artisturl" => "artist_url", +// "ls:audiosourceurl" => "audio_source_url", +// "ls:radiostationurl" => "radio_station_url", +// "ls:buycdurl" => "buy_this_url", +// "ls:isrcnumber" => "isrc_number", +// "ls:catalognumber" => "catalog_number", +// "ls:originalartist" => "original_artist", +// "dc:rights" => "copyright", +// "dcterms:temporal" => "report_datetime", +// "dcterms:spatial" => "report_location", +// "dcterms:entity" => "report_organization", +// "dc:subject" => "subject", +// "dc:contributor" => "contributor", +// "dc:language" => "language"); + +/** + * Core of Campcaster file storage module + * + * @package Campcaster + * @subpackage StorageServer + * @copyright 2010 Sourcefabric O.P.S. + * @license http://www.gnu.org/licenses/gpl.txt + * @see Alib + */ +class BasicStor { + public $storId; + private $fileTypes; + + public function __construct() + { + $this->filetypes = array( + 'all'=>NULL, + 'audioclip'=>'audioclip', + 'webstream'=>'webstream', + 'playlist'=>'playlist', + ); + } + + + /** + * Store new file in the storage + * + * @param array $p_values + * See StoredFile::Insert() for details. + * @param boolean $copyMedia + * copy the media file if true, make symlink if false + * @return StoredFile|PEAR_Error + * The StoredFile that was created. + */ +// public function bsPutFile($p_values, $p_copyMedia=TRUE) +// { +// $storedFile = StoredFile::Insert($p_values, $p_copyMedia); +// return $storedFile; +// } + + + /** + * Rename file + * + * @param int $id + * Virtual file's local id + * @param string $newName + * @return boolean|PEAR_Error + */ +// public function bsRenameFile($id, $newName) +// { +// switch (BasicStor::GetObjType($id)) { +// case "audioclip": +// case "playlist": +// case "webstream": +// $storedFile = StoredFile::Recall($id); +// if (is_null($storedFile) || PEAR::isError($storedFile)) { +// // catch nonerror exception: +// //if($storedFile->getCode() != GBERR_FOBJNEX) +// return $storedFile; +// } +// $res = $storedFile->setName($newName); +// if (PEAR::isError($res)) { +// return $res; +// } +// break; +// case "File": +// default: +// } +// return TRUE; +// } + + + /** + * Replace file. Doesn't change filetype! + * + * @param int $id + * Virtual file's local id + * @param string $localFilePath + * Local path of media file + * @param string $metadataFilePath + * Local path of metadata file + * @param string $mdataLoc + * 'file'|'string' + * @return true|PEAR_Error + * @exception PEAR::error + */ +// public function bsReplaceFile($id, $localFilePath, $metadataFilePath, $mdataLoc='file') +// { +// $storedFile = StoredFile::Recall($id); +// if (is_null($storedFile) || PEAR::isError($storedFile)) { +// return $storedFile; +// } +// if (!empty($metadataFilePath) && +// ($mdataLoc!='file' || file_exists($metadataFilePath))) { +// $r = $storedFile->setMetadata($metadataFilePath, $mdataLoc); +// if (PEAR::isError($r)) { +// return $r; +// } +// } +// if (!empty($localFilePath) && file_exists($localFilePath)) { +// $r = $storedFile->setRawMediaData($localFilePath); +// if (PEAR::isError($r)) { +// return $r; +// } +// } +// return TRUE; +// } + + + /** + * Delete file + * + * @param int $id + * Virtual file's local id + * @param boolean $forced + * If true don't use trash + * @return true|PEAR_Error + */ +// public function bsDeleteFile($id, $forced=FALSE) +// { +// global $CC_CONFIG; +// // full delete: +// if (!$CC_CONFIG['useTrash'] || $forced) { +// $res = BasicStor::RemoveObj($id, $forced); +// return $res; +// } +// +// $storedFile = StoredFile::Recall($id); +// +// if (is_null($storedFile) || PEAR::isError($storedFile)) { +// return $storedFile; +// } +// if ($storedFile->isAccessed()) { +// return PEAR::raiseError( +// 'Cannot delete an object that is currently accessed.' +// ); +// } +// // move to trash: +// switch (BasicStor::GetObjType($id)) { +// +// case "audioclip": +// $playLists = $storedFile->getPlaylists(); +// $item_gunid = $storedFile->getGunid(); +// if( $playLists != NULL) { +// +// foreach($playLists as $key=>$val) { +// $playList_id = BasicStor::IdFromGunidBigInt($val["gunid"]); +// $playList_titles[] = BasicStor::bsGetMetadataValue($playList_id, "dc:title"); +// } +// return PEAR::raiseError( +// 'Please remove this song from all playlists: ' . join(",", $playList_titles) +// ); +// } +// break; +// +// case "playlist": +// if($storedFile->isScheduled()) { +// return PEAR::raiseError( +// 'Cannot delete an object that is scheduled to play.' +// ); +// } +// break; +// +// case "webstream": +// +// break; +// default: +// } +// +// $res = $storedFile->setState('deleted'); +// if (PEAR::isError($res)) { +// return $res; +// } +// +// return TRUE; +// } + + + /* ----------------------------------------------------- put, access etc. */ + /** + * Check validity of access/put token + * + * @param string $token + * Access/put token + * @param string $type + * 'put'|'access'|'download' + * @return boolean + */ + public static function bsCheckToken($token, $type='put') + { + global $CC_CONFIG, $CC_DBC; + $cnt = $CC_DBC->getOne(" + SELECT count(token) FROM ".$CC_CONFIG['accessTable']." + WHERE token=x'{$token}'::bigint AND type='$type' + "); + if (PEAR::isError($cnt)) { + return FALSE; + } + return ($cnt == 1); + } + + + /** + * Create and return access link to real file + * + * @param string $realFname + * Local filepath to accessed file + * (NULL for only increase access counter, no symlink) + * @param string $ext + * Useful filename extension for accessed file + * @param int $gunid + * Global unique id + * (NULL for special files such exported playlists) + * @param string $type + * 'access'|'download' + * @param int $parent + * parent token (recursive access/release) + * @param int $owner + * Local user id - owner of token + * @return array + * array with: seekable filehandle, access token + */ + public static function bsAccess($realFname, $ext, $gunid, $type='access', + $parent='0', $owner=NULL) + { + global $CC_CONFIG, $CC_DBC; + if (!is_null($gunid)) { + $gunid = StoredFile::NormalizeGunid($gunid); + } + $token = StoredFile::CreateGunid(); + if (!is_null($realFname)) { + $linkFname = $CC_CONFIG['accessDir']."/$token.$ext"; + //broken links are ignored by the player, do not worry about it here +/* if (!is_file($realFname) && !is_link($realFname)) { + return PEAR::raiseError( + "BasicStor::bsAccess: real file not found ($realFname)", + GBERR_FILEIO); + } +*/ + if (! @symlink($realFname, $linkFname)) { + return PEAR::raiseError( + "BasicStor::bsAccess: symlink create failed ($linkFname)", + GBERR_FILEIO); + } + } else { + $linkFname = NULL; + } + $escapedExt = pg_escape_string($ext); + $escapedType = pg_escape_string($type); + $CC_DBC->query("BEGIN"); + $gunidSql = (is_null($gunid) ? "NULL" : "x'{$gunid}'::bigint" ); + $ownerSql = (is_null($owner) ? "NULL" : "$owner" ); + $res = $CC_DBC->query(" + INSERT INTO ".$CC_CONFIG['accessTable']." + (gunid, token, ext, type, parent, owner, ts) + VALUES + ($gunidSql, x'$token'::bigint, + '$escapedExt', '$escapedType', x'{$parent}'::bigint, $ownerSql, now()) + "); + if (PEAR::isError($res)) { + $CC_DBC->query("ROLLBACK"); + return $res; + } + if (!is_null($gunid)) { + $res = $CC_DBC->query(" + UPDATE ".$CC_CONFIG['filesTable']." + SET currentlyAccessing=currentlyAccessing+1, mtime=now() + WHERE gunid=x'{$gunid}'::bigint + "); + } + if (PEAR::isError($res)) { + $CC_DBC->query("ROLLBACK"); + return $res; + } + $res = $CC_DBC->query("COMMIT"); + if (PEAR::isError($res)) { + return $res; + } + return array('fname'=>$linkFname, 'token'=>$token); + } + + + /** + * Release access link to real file + * + * @param string $token + * Access token + * @param string $type + * 'access'|'download' + * @return array + * gunid: string, global unique ID or real pathname of special file + * owner: int, local subject id of token owner + * realFname: string, real local pathname of accessed file + */ + public static function bsRelease($token, $type='access') + { + global $CC_CONFIG, $CC_DBC; + if (!BasicStor::bsCheckToken($token, $type)) { + return PEAR::raiseError( + "BasicStor::bsRelease: invalid token ($token)" + ); + } + $acc = $CC_DBC->getRow(" + SELECT to_hex(gunid)as gunid, ext, owner FROM ".$CC_CONFIG['accessTable']." + WHERE token=x'{$token}'::bigint AND type='$type' + "); + if (PEAR::isError($acc)) { + return $acc; + } + $ext = $acc['ext']; + $owner = $acc['owner']; + $linkFname = $CC_CONFIG['accessDir']."/$token.$ext"; + $realFname = readlink($linkFname); + if (file_exists($linkFname)) { + if(! @unlink($linkFname)){ + return PEAR::raiseError( + "BasicStor::bsRelease: unlink failed ($linkFname)", + GBERR_FILEIO); + } + } + $CC_DBC->query("BEGIN"); + if (!is_null($acc['gunid'])) { + $gunid = StoredFile::NormalizeGunid($acc['gunid']); + $res = $CC_DBC->query(" + UPDATE ".$CC_CONFIG['filesTable']." + SET currentlyAccessing=currentlyAccessing-1, mtime=now() + WHERE gunid=x'{$gunid}'::bigint AND currentlyAccessing>0 + "); + if (PEAR::isError($res)) { + $CC_DBC->query("ROLLBACK"); + return $res; + } + } + $res = $CC_DBC->query(" + DELETE FROM ".$CC_CONFIG['accessTable']." WHERE token=x'$token'::bigint + "); + if (PEAR::isError($res)) { + $CC_DBC->query("ROLLBACK"); + return $res; + } + $res = $CC_DBC->query("COMMIT"); + if (PEAR::isError($res)) { + return $res; + } + $res = array( + 'gunid' => (isset($gunid) ? $gunid : NULL ), + 'realFname' => $realFname, + 'owner' => $owner, + ); + return $res; + } + + + /** + * Create and return downloadable URL for file + * + * @param int $id + * Virtual file's local id + * @param string $part + * 'media'|'metadata' + * @param int $parent + * parent token (recursive access/release) + * @return array + * array with strings: + * downloadable URL, download token, chsum, size, filename + */ +// public function bsOpenDownload($id, $part='media') +// { +// $storedFile = StoredFile::Recall($id); +// if (is_null($storedFile) || PEAR::isError($storedFile)) { +// return $storedFile; +// } +// $gunid = $storedFile->gunid; +// switch ($part) { +// case "media": +// $realfile = $storedFile->getRealFileName(); +// $ext = $storedFile->getFileExtension(); +// $filename = $storedFile->getName(); +// break; +// case "metadata": +// $realfile = $storedFile->getRealMetadataFileName(); +// $ext = "xml"; +// $filename = $storedFile->getName(); +// break; +// default: +// return PEAR::raiseError( +// "BasicStor::bsOpenDownload: unknown part ($part)" +// ); +// } +// $acc = BasicStor::bsAccess($realfile, $ext, $gunid, 'download'); +// if (PEAR::isError($acc)) { +// return $acc; +// } +// $url = BasicStor::GetUrlPart()."access/".basename($acc['fname']); +// $chsum = md5_file($realfile); +// $size = filesize($realfile); +// return array( +// 'url'=>$url, 'token'=>$acc['token'], +// 'chsum'=>$chsum, 'size'=>$size, +// 'filename'=>$filename +// ); +// } + + + /** + * Discard downloadable URL + * + * @param string $token + * Download token + * @param string $part + * 'media'|'metadata' + * @return string + * gunid + */ +// public function bsCloseDownload($token, $part='media') +// { +// if (!BasicStor::bsCheckToken($token, 'download')) { +// return PEAR::raiseError( +// "BasicStor::bsCloseDownload: invalid token ($token)" +// ); +// } +// $r = BasicStor::bsRelease($token, 'download'); +// if (PEAR::isError($r)){ +// return $r; +// } +// return (is_null($r['gunid']) ? $r['realFname'] : $r['gunid']); +// } + + + /** + * Create writable URL for HTTP PUT method file insert + * + * @param string $chsum + * md5sum of the file having been put + * @param string $gunid + * global unique id + * (NULL for special files such imported playlists) + * @param int $owner + * local user id - owner of token + * @return array + * array with: + * url string: writable URL + * fname string: writable local filename + * token string: PUT token + */ +// public function bsOpenPut($chsum, $gunid, $owner=NULL) +// { +// global $CC_CONFIG, $CC_DBC; +// if (!is_null($gunid)) { +// $gunid = StoredFile::NormalizeGunid($gunid); +// } +// $escapedChsum = pg_escape_string($chsum); +// $token = StoredFile::CreateGunid(); +// $res = $CC_DBC->query("DELETE FROM ".$CC_CONFIG['accessTable'] +// ." WHERE token=x'$token'::bigint"); +// if (PEAR::isError($res)) { +// return $res; +// } +// $gunidSql = (is_null($gunid) ? "NULL" : "x'{$gunid}'::bigint" ); +// $ownerSql = (is_null($owner) ? "NULL" : "$owner" ); +// $res = $CC_DBC->query(" +// INSERT INTO ".$CC_CONFIG['accessTable']." +// (gunid, token, ext, chsum, type, owner, ts) +// VALUES +// ($gunidSql, x'$token'::bigint, +// '', '$escapedChsum', 'put', $ownerSql, now())"); +// if (PEAR::isError($res)) { +// return $res; +// } +// $fname = $CC_CONFIG['accessDir']."/$token"; +// touch($fname); // is it needed? +// $url = BasicStor::GetUrlPart()."xmlrpc/put.php?token=$token"; +// return array('url'=>$url, 'fname'=>$fname, 'token'=>$token); +// } + + + /** + * Get file from writable URL and return local filename. + * Caller should move or unlink this file. + * + * @param string $token + * PUT token + * @return array + * hash with fields: + * fname string, local path of the file having been put + * owner int, local subject id - owner of token + */ +// public function bsClosePut($token) +// { +// global $CC_CONFIG, $CC_DBC; +// $token = StoredFile::NormalizeGunid($token); +// +// if (!BasicStor::bsCheckToken($token, 'put')) { +// return PEAR::raiseError( +// "BasicStor::bsClosePut: invalid token ($token)", +// GBERR_TOKEN); +// } +// $row = $CC_DBC->getRow( +// "SELECT chsum, owner FROM ".$CC_CONFIG['accessTable'] +// ." WHERE token=x'{$token}'::bigint"); +// if (PEAR::isError($row)) { +// return $row; +// } +// $fname = $CC_CONFIG['accessDir']."/$token"; +// $md5sum = md5_file($fname); +// +// $chsum = $row['chsum']; +// $owner = $row['owner']; +// $error = null; +// if ( (trim($chsum) != '') && ($chsum != $md5sum) ) { +// // Delete the file if the checksums do not match. +// if (file_exists($fname)) { +// @unlink($fname); +// } +// $error = new PEAR_Error( +// "BasicStor::bsClosePut: md5sum does not match (token=$token)". +// " [$chsum/$md5sum]", +// GBERR_PUT); +// } else { +// // Remember the MD5 sum +// $storedFile = StoredFile::RecallByToken($token); +// if (!is_null($storedFile) && !PEAR::isError($storedFile)) { +// $storedFile->setMd5($md5sum); +// } else { +//# $error = $storedFile; +// } +// } +// +// // Delete entry from access table. +// $res = $CC_DBC->query("DELETE FROM ".$CC_CONFIG['accessTable'] +// ." WHERE token=x'$token'::bigint"); +// if (PEAR::isError($error)) { +// return $error; +// } elseif (PEAR::isError($res)) { +// return $res; +// } +// +// return array('fname'=>$fname, 'owner'=>$owner); +// } + + + /** + * Check uploaded file + * + * @param string $token + * "Put" token + * @return array + * hash, ( + * status: boolean, + * size: int - filesize + * expectedsum: string - expected checksum + * realsum: string - checksum of uploaded file + * ) + */ +// public function bsCheckPut($token) +// { +// global $CC_CONFIG, $CC_DBC; +// if (!BasicStor::bsCheckToken($token, 'put')) { +// return PEAR::raiseError( +// "BasicStor::bsCheckPut: invalid token ($token)" +// ); +// } +// $chsum = $CC_DBC->getOne(" +// SELECT chsum FROM ".$CC_CONFIG['accessTable']." +// WHERE token=x'{$token}'::bigint +// "); +// if (PEAR::isError($chsum)) { +// return $chsum; +// } +// $fname = $CC_CONFIG['accessDir']."/$token"; +// $md5sum = md5_file($fname); +// $size = filesize($fname); +// $status = ($chsum == $md5sum); +// return array( +// 'status'=>$status, 'size'=>$size, +// 'expectedsum'=>$chsum, +// 'realsum'=>$md5sum, +// ); +// } + + + /** + * Return starting part of storageServer URL + * + * @return string + * URL + */ +// public static function GetUrlPart() +// { +// global $CC_CONFIG; +// $host = $CC_CONFIG['storageUrlHost']; +// $port = $CC_CONFIG['storageUrlPort']; +// $path = $CC_CONFIG['storageUrlPath']; +// return "http://$host:$port$path/"; +// } + + + /** + * Get tokens by type + * + * @param string $type + * access|put|render etc. + * @return array + * array of tokens + */ +// public static function GetTokensByType($type) +// { +// global $CC_CONFIG, $CC_DBC; +// $res = $CC_DBC->query( +// "SELECT TO_HEX(token) AS token FROM ".$CC_CONFIG['accessTable']." WHERE type=?", +// array($type)); +// while ($row = $res->fetchRow()) { +// $r[] = $row['token']; +// } +// return $r; +// } + + + /* ----------------------------------------------------- metadata methods */ + + /** + * Replace metadata with new XML file or string + * + * @param int $id + * Virtual file's local id + * @param string $mdata + * Local path of metadata XML file + * @param string $mdataLoc + * 'file'|'string' + * @return boolean|PEAR_Error + */ +// public function bsReplaceMetadata($id, $mdata, $mdataLoc='file') +// { +// $storedFile = StoredFile::Recall($id); +// if (is_null($storedFile) || PEAR::isError($storedFile)) { +// return $storedFile; +// } +// return $storedFile->setMetadata($mdata, $mdataLoc); +// } + + + /** + * Get metadata as XML string + * + * @param int $id + * Virtual file's local id + * @return string|PEAR_Error + */ +// public function bsGetMetadata($id) +// { +// $storedFile = StoredFile::Recall($id); +// if (is_null($storedFile) || PEAR::isError($storedFile)) { +// return $storedFile; +// } +// return $storedFile->getMetadata(); +// } + + + /** + * Get dc:title (if exists) + * + * @param int $id + * Virtual file's local id + * @param string $gunid + * Virtual file's gunid, optional, used only if not + * null, id is then ignored + * @return string|PEAR_Error + */ +// public function bsGetTitle($id, $gunid=NULL) +// { +// if (is_null($gunid)) { +// $storedFile = StoredFile::Recall($id); +// } else { +// $storedFile = StoredFile::RecallByGunid($gunid); +// } +// if (is_null($storedFile) || PEAR::isError($storedFile)) { +// return $storedFile; +// } +// $r = $storedFile->md["title"]; +// $title = (empty($r) ? 'unknown' : $r); +// return $title; +// } + + + /** + * Get metadata element value + * + * @param int $id + * Virtual file's local id + * @param string|array|null $category + * metadata element name, or array of metadata element names, + * if null is passed, all metadata values for the given ID will + * be fetched. + * @return string|array + * If a string is passed in for $category, a string is returned, + * if an array is passed, an array is returned. + * @see Metadata::getMetadataValue + */ +// public function bsGetMetadataValue($id, $category = null) +// { +// if (!is_numeric($id)) { +// return null; +// } +// $storedFile = StoredFile::Recall($id); +// if (is_null($storedFile) || PEAR::isError($storedFile)) { +// return $storedFile; +// } +// if (is_null($category)) { +// return $storedFile->md; +// } elseif (is_array($category)) { +// $values = array(); +// foreach ($category as $tmpCat) { +// $values[$tmpCat] = $storedFile->md[$tmpCat]; +// } +// return $values; +// } else { +// return $storedFile->md[$category]; +// } +// } + + + /** + * Convert XML name to database column name. Used for backwards compatibility + * with old code. + * + * @param string $p_category + * @return string|null + */ +// public static function xmlCategoryToDbColumn($p_category) +// { +// global $g_metadata_xml_to_db_mapping; +// if (array_key_exists($p_category, $g_metadata_xml_to_db_mapping)) { +// return $g_metadata_xml_to_db_mapping[$p_category]; +// } +// return null; +// } + + + /** + * Convert database column name to XML name. + * + * @param string $p_dbColumn + * @return string|null + */ +// public static function dbColumnToXmlCatagory($p_dbColumn) +// { +// global $g_metadata_xml_to_db_mapping; +// $str = array_search($p_dbColumn, $g_metadata_xml_to_db_mapping); +// // make return value consistent with xmlCategoryToDbColumn() +// if ($str === FALSE) { +// $str = null; +// } +// return $str; +// } + + /** + * Set metadata element value + * + * @param int|StoredFile $id + * Database ID of file + * @param string $category + * Metadata element identification (e.g. dc:title) + * @param string $value + * value to store, if NULL then delete record + * @return boolean + */ +// public static function bsSetMetadataValue($p_id, $p_category, $p_value) +// { +// global $CC_CONFIG, $CC_DBC; +// if (!is_string($p_category) || is_array($p_value)) { +// return FALSE; +// } +// if (is_a($p_id, "StoredFile")) { +// $p_id = $p_id->getId(); +// } +// if ($p_category == 'dcterms:extent') { +// $p_value = BasicStor::NormalizeExtent($p_value); +// } +// $columnName = BasicStor::xmlCategoryToDbColumn($p_category); // Get column name +// +// if (!is_null($columnName)) { +// $escapedValue = pg_escape_string($p_value); +// $sql = "UPDATE ".$CC_CONFIG["filesTable"] +// ." SET $columnName='$escapedValue'" +// ." WHERE id=$p_id"; +// //var_dump($sql); +// $res = $CC_DBC->query($sql); +// if (PEAR::isError($res)) { +// return $res; +// } +// } +// return TRUE; +// } + + + /** + * Normalize time value to hh:mm:ss:dddddd format + * + * @param mixed $v + * value to normalize + * @return string + */ +// private static function NormalizeExtent($v) +// { +// if (!preg_match("|^\d{2}:\d{2}:\d{2}.\d{6}$|", $v)) { +// $s = Playlist::playlistTimeToSeconds($v); +// $t = Playlist::secondsToPlaylistTime($s); +// return $t; +// } +// return $v; +// } + + + /** + * Set metadata values in 'batch' mode + * + * @param int|StoredFile $id + * Database ID of file or StoredFile object + * @param array $values + * array of key/value pairs + * (e.g. 'dc:title'=>'New title') + * @return boolean + */ +// public static function bsSetMetadataBatch($id, $values) +// { +// global $CC_CONFIG, $CC_DBC; +// if (!is_array($values)) { +// $values = array($values); +// } +// if (count($values) == 0) { +// return true; +// } +// if (is_a($id, "StoredFile")) { +// $storedFile =& $id; +// } else { +// $storedFile = StoredFile::Recall($id); +// if (is_null($storedFile) || PEAR::isError($storedFile)) { +// return $storedFile; +// } +// } +// foreach ($values as $category => $oneValue) { +// $columnName = BasicStor::xmlCategoryToDbColumn($category); +// if (!is_null($columnName)) { +// if ($category == 'dcterms:extent') { +// $oneValue = BasicStor::NormalizeExtent($oneValue); +// } +// // Since track_number is an integer, you cannot set +// // it to be the empty string, so we NULL it instead. +// if ($columnName == 'track_number' && empty($oneValue)) { +// $sqlPart = "$columnName = NULL"; +// } elseif (($columnName == 'length') && (strlen($oneValue) > 8)) { +// // Postgres doesnt like it if you try to store really large hour +// // values. TODO: We need to fix the underlying problem of getting the +// // right values. +// $parts = explode(':', $oneValue); +// $hour = intval($parts[0]); +// if ($hour > 24) { +// continue; +// } else { +// $sqlPart = "$columnName = '$oneValue'"; +// } +// } else { +// $escapedValue = pg_escape_string($oneValue); +// $sqlPart = "$columnName = '$escapedValue'"; +// } +// $sqlValues[] = $sqlPart; +// } +// } +// if (count($sqlValues)==0) { +// return TRUE; +// } +// $sql = "UPDATE ".$CC_CONFIG["filesTable"] +// ." SET ".join(",", $sqlValues) +// ." WHERE id=$id"; +// $CC_DBC->query($sql); +// return TRUE; +// } + + /** + * Method returning array with where-parts of sql queries + * + * @param array $conditions + * See 'conditions' field in search criteria format + * definition in class documentation + * @return array + * array of strings - WHERE-parts of SQL queries + */ + private function _makeWhereArr($conditions) + { + $ops = array('full'=>"='%s'", 'partial'=>"ILIKE '%%%s%%'", + 'prefix'=>"ILIKE '%s%%'", '<'=>"< '%s'", '='=>"= '%s'", + '>'=>"> '%s'", '<='=>"<= '%s'", '>='=>">= '%s'" + ); + $whereArr = array(); + if (is_array($conditions)) { + foreach ($conditions as $cond) { + $columnName = StoredFile::xmlCategoryToDbColumn($cond['cat']); + $op = strtolower($cond['op']); + $value = $cond['val']; + if (!empty($value)) { + $splittedQn = XML_Util::splitQualifiedName($catQn); + $catNs = $splittedQn['namespace']; + $cat = $splittedQn['localPart']; + $opVal = sprintf($ops[$op], pg_escape_string($value)); + // retype for timestamp value + if ($cat == 'mtime') { + switch ($op) { + case 'partial': + case 'prefix': + break; + default: + $retype = "::timestamp with time zone"; + $opVal = "$retype $opVal$retype"; + } + } + $sqlCond = " {$columnName} {$opVal}\n"; + $whereArr[] = $sqlCond; + } + } + } + return $whereArr; + } + + /** + * Search in local metadata database. + * + * @param array $criteria + * has the following structure:
+ * + * @param int $limit + * limit for result arrays (0 means unlimited) + * @param int $offset + * starting point (0 means without offset) + * @return array + * array of hashes, fields: + * cnt : integer - number of matching gunids + * of files have been found + * results : array of hashes: + * gunid: string + * type: string - audioclip | playlist | webstream + * title: string - dc:title from metadata + * creator: string - dc:creator from metadata + * source: string - dc:source from metadata + * length: string - dcterms:extent in extent format + */ + public function bsLocalSearch($criteria, $limit=0, $offset=0) + { + global $CC_CONFIG, $CC_DBC; + + // Input values + $filetype = (isset($criteria['filetype']) ? $criteria['filetype'] : 'all'); + $filetype = strtolower($filetype); + if (!array_key_exists($filetype, $this->filetypes)) { + return PEAR::raiseError(__FILE__.":".__LINE__.': unknown filetype in search criteria'); + } + $filetype = $this->filetypes[$filetype]; + $operator = (isset($criteria['operator']) ? $criteria['operator'] : 'and'); + $operator = strtolower($operator); + $conditions = (isset($criteria['conditions']) ? $criteria['conditions'] : array()); + + // Create the WHERE clause - this is the actual search part + $whereArr = $this->_makeWhereArr($conditions); + + // Metadata values to fetch + $metadataNames = array('dc:creator', 'dc:source', 'ls:track_num', 'dc:title', 'dcterms:extent'); + + // Order by clause + $orderby = TRUE; + $orderByAllowedValues = array('dc:creator', 'dc:source', 'dc:title', 'dcterms:extent', "ls:track_num"); + $orderByDefaults = array('dc:creator', 'dc:source', 'dc:title'); + if ((!isset($criteria['orderby'])) + || (is_array($criteria['orderby']) && (count($criteria['orderby'])==0))) { + // default ORDER BY + // PaulB: track number removed because it doesnt work yet because + // if track_num is not an integer (e.g. bad metadata like "1/20", + // or if the field is blank) the SQL statement gives an error. + //$orderbyQns = array('dc:creator', 'dc:source', 'ls:track_num', 'dc:title'); + $orderbyQns = $orderByDefaults; + } else { + // ORDER BY clause is given in the parameters. + + // Convert the parameter to an array if it isnt already. + $orderbyQns = $criteria['orderby']; + if (!is_array($orderbyQns)) { + $orderbyQns = array($orderbyQns); + } + + // Check that it has valid ORDER BY values, if not, revert + // to the default ORDER BY values. + foreach ($orderbyQns as $metadataTag) { + if (!in_array($metadataTag, $orderByAllowedValues)) { + $orderbyQns = $orderByDefaults; + break; + } + } + } + + $descA = (isset($criteria['desc']) ? $criteria['desc'] : NULL); + if (!is_array($descA)) { + $descA = array($descA); + } + + $orderBySql = array(); + // $dataName contains the names of the metadata columns we want to + // fetch. It is indexed numerically starting from 1, and the value + // in the array is the qualified name with ":" replaced with "_". + // e.g. "dc:creator" becomes "dc_creator". + foreach ($orderbyQns as $xmlTag) { + $columnName = StoredFile::xmlCategoryToDbColumn($xmlTag); + $orderBySql[] = $columnName; + } + + // Build WHERE clause + $whereClause = ""; + if (!is_null($filetype)) { + $whereClause .= "WHERE (ftype='$filetype')"; + } + else { + $whereClause .= "WHERE (ftype is NOT NULL)"; + } + if (count($whereArr) != 0) { + if ($operator == 'and') { + $whereClause .= " AND ((".join(") AND (", $whereArr)."))"; + } else { + $whereClause .= " AND ((".join(") OR (", $whereArr)."))"; + } + } + + // Final query + + //"dcterms:extent" => "length", + //"dc:title" => "track_title", + //"dc:creator" => "artist_name", + //dc:description + + global $g_metadata_xml_to_db_mapping; + $plSelect = "SELECT "; + $fileSelect = "SELECT "; + $_SESSION["br"] = ""; + foreach ($g_metadata_xml_to_db_mapping as $key => $val){ + $_SESSION["br"] .= "key: ".$key." value:".$val.", "; + if($key === "dc:title"){ + $plSelect .= "name AS ".$val.", "; + $fileSelect .= $val.", "; + } + else if ($key === "dc:creator"){ + $plSelect .= "creator AS ".$val.", "; + $fileSelect .= $val.", "; + } + else if ($key === "dcterms:extent"){ + $plSelect .= "length, "; + $fileSelect .= "length, "; + } + else if ($key === "dc:description"){ + $plSelect .= "text(description) AS ".$val.", "; + $fileSelect .= $val.", "; + } + else { + $plSelect .= "NULL AS ".$val.", "; + $fileSelect .= $val.", "; + } + } + + $sql = "SELECT * FROM ((".$plSelect."PL.id, 'playlist' AS ftype + FROM ".$CC_CONFIG["playListTable"]." AS PL + LEFT JOIN ".$CC_CONFIG['playListTimeView']." PLT ON PL.id = PLT.id) + + UNION + + (".$fileSelect."id, ftype FROM ".$CC_CONFIG["filesTable"]." AS FILES)) AS RESULTS "; + + $sql .= $whereClause; + + if ($orderby) { + $sql .= " ORDER BY ".join(",", $orderBySql); + } + + $_SESSION["debugsql"] = $sql; + + $res = $CC_DBC->getAll($sql); + if (PEAR::isError($res)) { + return $res; + } + if (!is_array($res)) { + $res = array(); + } + + $count = count($res); + $_SESSION["br"] .= " COUNT: ".$count; + + $res = array_slice($res, $offset != 0 ? $offset : 0, $limit != 0 ? $limit : 10); + + $eres = array(); + foreach ($res as $it) { + $eres[] = array( + 'id' => $it['id'], + 'type' => strtolower($it['ftype']), + 'title' => $it['track_title'], + 'creator' => $it['artist_name'], + 'duration' => $it['length'], + 'source' => $it['album_title'], + 'track_num' => $it['track_number'], + ); + } + return array('results'=>$eres, 'cnt'=>$count); + } + + + /** + * Return values of specified metadata category + * + * @param string $category + * metadata category name with or without namespace prefix (dc:title, author) + * @param int $limit + * limit for result arrays (0 means unlimited) + * @param int $offset + * starting point (0 means without offset) + * @param array $criteria + * see bsLocalSearch method + * @return array + * hash, fields: + * results : array with found values + * cnt : integer - number of matching values + */ + public function bsBrowseCategory($category, $limit=0, $offset=0, $criteria=NULL) + { + global $CC_CONFIG, $CC_DBC; + + $pl_cat = array( + "dcterms:extent" => "length", + "dc:title" => "name", + "dc:creator" => "creator", + "dc:description" => "description" + ); + + $category = strtolower($category); + $columnName = StoredFile::xmlCategoryToDbColumn($category); + if (is_null($columnName)) { + return new PEAR_Error(__FILE__.":".__LINE__." -- could not map XML category to DB column."); + } + $sql = "SELECT DISTINCT $columnName FROM ".$CC_CONFIG["filesTable"]; + $limitPart = ($limit != 0 ? " LIMIT $limit" : '' ). + ($offset != 0 ? " OFFSET $offset" : '' ); + $countRowsSql = "SELECT COUNT(DISTINCT $columnName) FROM ".$CC_CONFIG["filesTable"]; + + //$_SESSION["br"] = "in Browse Category: ".$category; + $cnt = $CC_DBC->GetOne($countRowsSql); + if (PEAR::isError($cnt)) { + return $cnt; + } + $res = $CC_DBC->getCol($sql.$limitPart); + if (PEAR::isError($res)) { + return $res; + } + if (!is_array($res)) { + $res = array(); + } + + if (array_key_exists($category, $pl_cat) && $category !== "dcterms:extent") { + $columnName = $pl_cat[$category]; + + $sql = "SELECT DISTINCT $columnName FROM ".$CC_CONFIG["playListTable"]; + $limitPart = ($limit != 0 ? " LIMIT $limit" : '' ). + ($offset != 0 ? " OFFSET $offset" : '' ); + $countRowsSql = "SELECT COUNT(DISTINCT $columnName) FROM ".$CC_CONFIG["playListTable"]; + + $pl_cnt = $CC_DBC->GetOne($countRowsSql); + if (PEAR::isError($cnt)) { + return $cnt; + } + $pl_res = $CC_DBC->getCol($sql.$limitPart); + if (PEAR::isError($res)) { + return $pl_res; + } + if (!is_array($pl_res)) { + $pl_res = array(); + } + + $res = array_merge($res, $pl_res); + $res = array_slice($res, 0, $limit); + $cnt = $cnt + $pl_cnt; + } + else if ($category === "dcterms:extent") { + $columnName = $pl_cat[$category]; + + $limitPart = ($limit != 0 ? " LIMIT $limit" : '' ). + ($offset != 0 ? " OFFSET $offset" : '' ); + + $sql = "SELECT DISTINCT length AS $columnName FROM ".$CC_CONFIG["playListTimeView"]; + + $countRowsSql = "SELECT COUNT(DISTINCT length) FROM ".$CC_CONFIG["playListTimeView"]; + + $pl_cnt = $CC_DBC->GetOne($countRowsSql); + if (PEAR::isError($cnt)) { + return $cnt; + } + $pl_res = $CC_DBC->getCol($sql.$limitPart); + if (PEAR::isError($res)) { + return $pl_res; + } + if (!is_array($pl_res)) { + $pl_res = array(); + } + + $res = array_merge($res, $pl_res); + $res = array_slice($res, 0, $limit); + $cnt = $cnt + $pl_cnt; + } + + return array('results'=>$res, 'cnt'=>$cnt); + } + + + /* ---------------------------------------------------- methods4playlists */ + + /** + * Create a tarfile with playlist export - playlist and all matching + * sub-playlists and media files (if desired) + * + * @param array $plids + * Array of strings, playlist global unique IDs (one gunid is accepted too) + * @param string $type + * Playlist format, possible values: lspl | smil | m3u + * @param boolean $withContent + * if true, export related files too + * @return array + * hasharray with fields: + * fname string: readable fname, + * token string: access token + */ +// public function bsExportPlaylistOpen($plids, $type='lspl', $withContent=TRUE) +// { +// global $CC_CONFIG; +// if (!is_array($plids)) { +// $plids = array($plids); +// } +// $gunids = array(); +// foreach ($plids as $plid) { +// $pl = StoredFile::RecallByGunid($plid); +// if (is_null($pl) || PEAR::isError($pl)) { +// return $pl; +// } +// if ($withContent) { +// $gunidsX = $pl->export(); +// if (PEAR::isError($gunidsX)) { +// return $gunidsX; +// } +// } else { +// $gunidsX = array(array('gunid'=>$plid, 'type'=>'playlist')); +// } +// $gunids = array_merge($gunids, $gunidsX); +// } +// $plExts = array('lspl'=>"lspl", 'smil'=>"smil", 'm3u'=>"m3u"); +// $plExt = (isset($plExts[$type]) ? $plExts[$type] : "xml" ); +// $res = array(); +// $tmpn = tempnam($CC_CONFIG['bufferDir'], 'plExport_'); +// $tmpf = "$tmpn.tar"; +// $tmpd = "$tmpn.dir"; +// mkdir($tmpd); +// $tmpdp = "$tmpn.dir/playlist"; +// mkdir($tmpdp); +// if ($withContent) { +// $tmpdc = "$tmpn.dir/audioClip"; +// mkdir($tmpdc); +// } +// foreach ($gunids as $i => $it) { +// $storedFile = StoredFile::RecallByGunid($it['gunid']); +// if (is_null($storedFile) || PEAR::isError($storedFile)) { +// return $storedFile; +// } +//// $MDfname = $storedFile->md->getFileName(); +// $MDfname = $storedFile->md["name"]; +// if (PEAR::isError($MDfname)) { +// return $MDfname; +// } +// if (file_exists($MDfname)) { +// switch ($it['type']) { +// case "playlist": +// $storedFile = $r = StoredFile::RecallByGunid($it['gunid']); +// switch ($type) { +// case "smil": +// $string = $r = $storedFile->outputToSmil(); +// break; +// case "m3u": +// $string = $r = $storedFile->outputToM3u(); +// break; +// default: +//// $string = $r = $storedFile->md->genXmlDoc(); +// } +// if (PEAR::isError($r)) { +// return $r; +// } +// $r = BasicStor::WriteStringToFile($string, "$tmpdp/{$it['gunid']}.$plExt"); +// if (PEAR::isError($r)) { +// return $r; +// } +// break; +// default: +// copy($MDfname, "$tmpdc/{$it['gunid']}.xml"); break; +// } // switch +// } // if file_exists() +// $RADfname = $storedFile->getRealFileName(); +// if (PEAR::isError($RADfname)) { +// return $RADfname; +// } +// $RADext = $storedFile->getFileExtension(); +// if (PEAR::isError($RADext)) { +// return $RADext; +// } +// if (file_exists($RADfname)) { +// copy($RADfname, "$tmpdc/{$it['gunid']}.$RADext"); +// } +// } +// if (count($plids)==1) { +// copy("$tmpdp/$plid.$plExt", "$tmpd/exportedPlaylist.$plExt"); +// } +// $res = `cd $tmpd; tar cf $tmpf * --remove-files`; +// @rmdir($tmpdc); +// @rmdir($tmpdp); +// @rmdir($tmpd); +// unlink($tmpn); +// $acc = BasicStor::bsAccess($tmpf, 'tar', NULL/*gunid*/, 'access'); +// if (PEAR::isError($acc)) { +// return $acc; +// } +// return $acc; +// } + + + /** + * Close playlist export previously opened by the bsExportPlaylistOpen + * method + * + * @param string $token + * Access token obtained from bsExportPlaylistOpen method call. + * @return true/PEAR_Error + */ +// public function bsExportPlaylistClose($token) +// { +// $r = BasicStor::bsRelease($token, 'access'); +// if (PEAR::isError($r)) { +// return $r; +// } +// $file = $r['realFname']; +// if (file_exists($file)) { +// if(! @unlink($file)){ +// return PEAR::raiseError( +// "BasicStor::bsExportPlaylistClose: unlink failed ($file)", +// GBERR_FILEIO); +// } +// } +// return TRUE; +// } + + + /** + * Import playlist in LS Archive format + * + * @param string $plid + * Playlist gunid + * @param string $aPath + * Absolute path part of imported file (e.g. /home/user/campcaster) + * @param string $rPath + * Relative path/filename part of imported file (e.g. playlists/playlist_1.smil) + * @param string $ext + * Playlist extension (determines type of import) + * @param array $gunids + * Hash relation from filenames to gunids + * @param int $subjid + * Local subject (user) id (id of user doing the import) + * @return int + * Result file local id (or error object) + */ +// public function bsImportPlaylistRaw($plid, $aPath, $rPath, $ext, &$gunids, $subjid) +// { +// $id = BasicStor::IdFromGunid($plid); +// if (!is_null($id)) { +// return $id; +// } +// $path = realpath("$aPath/$rPath"); +// if (FALSE === $path) { +// return PEAR::raiseError( +// "BasicStor::bsImportPlaylistRaw: file doesn't exist ($aPath/$rPath)" +// ); +// } +// switch ($ext) { +// case "xml": +// case "lspl": +// $fname = $plid; +// $values = array( +// "filename" => $fname, +// "metadata" => $path, +// "gunid" => $plid, +// "filetype" => "playlist" +// ); +// $storedFile = StoredFile::Insert($values); +// $res = $storedFile->getId(); +// break; +// case "smil": +// require_once("SmilPlaylist.php"); +// $res = SmilPlaylist::import($this, $aPath, $rPath, $gunids, $plid, $subjid); +// if (PEAR::isError($res)) { +// break; +// } +// $res = $res->getId(); +// break; +// case "m3u": +// require_once("M3uPlaylist.php"); +// $res = M3uPlaylist::import($this, $aPath, $rPath, $gunids, $plid, $subjid); +// if (PEAR::isError($res)) { +// break; +// } +// $res = $res->getId(); +// break; +// default: +// $res = PEAR::raiseError( +// "BasicStor::importPlaylistRaw: unknown playlist format". +// " (gunid:$plid, format:$ext)" +// ); +// break; +// } +// if (!PEAR::isError($res)) { +// $gunids[basename($rPath)] = $plid; +// } +// return $res; +// } + + + /** + * Import playlist in LS Archive format + * + * @param string $fpath + * Imported file pathname + * @param int $subjid + * Local subject (user) id (id of user doing the import) + * @return int + * Result file local id (or error object) + */ +// public function bsImportPlaylist($fpath, $subjid) +// { +// global $CC_CONFIG; +// // untar: +// $tmpn = tempnam($CC_CONFIG['bufferDir'], 'plImport_'); +// $tmpd = "$tmpn.dir"; +// $tmpdc = "$tmpd/audioClip"; +// $tmpdp = "$tmpd/playlist"; +// mkdir($tmpd); +// $res = `cd $tmpd; tar xf $fpath`; +// // clips: +// $d = @dir($tmpdc); +// $entries = array(); +// $gunids = array(); +// if ($d !== false) { +// while (false !== ($entry = $d->read())) { +// if (preg_match("|^([0-9a-fA-F]{16})\.(.*)$|", $entry, $va)) { +// list(,$gunid, $ext) = $va; +// switch ($ext) { +// case"xml": +// $entries[$gunid]['metadata'] = $entry; +// break; +// default: +// $entries[$gunid]['rawMedia'] = $entry; +// $entries[$gunid]['rawMediaExt'] = $ext; +// $gunids[$entry] = $gunid; +// break; +// } +// } +// } +// $d->close(); +// } +// $res = TRUE; +// foreach ($entries as $gunid => $it) { +// $rawMedia = "$tmpdc/{$it['rawMedia']}"; +// if (!file_exists($rawMedia)) { +// $rawMedia = NULL; +// } +// $metadata = "$tmpdc/{$it['metadata']}"; +// if (!file_exists($metadata)) { +// $metadata = NULL; +// } +// $f = StoredFile::RecallByGunid($gunid); +// if (!PEAR::isError($f)) { +// $exists = $f->existsFile(); +// if ( $exists ) { +// $res = $f->delete(); +// } +// } +// if (!PEAR::isError($res) ) { +// $values = array( +// "filename" => $gunid, +// "filepath" => $rawMedia, +// "metadata" => $metadata, +// "gunid" => $gunid, +// "filetype" => "audioclip" +// ); +// $storedFile = StoredFile::Insert($values); +// $res = $storedFile->getId(); +// } +// @unlink("$tmpdc/{$it['rawMedia']}"); +// @unlink("$tmpdc/{$it['metadata']}"); +// if (PEAR::isError($res)) { +// break; +// } +// } +// // playlists: +// $d = @dir($tmpdp); +// if ($d !== false) { +// while ((!PEAR::isError($res)) && false !== ($entry = $d->read())) { +// if (preg_match("|^([0-9a-fA-F]{16})\.(.*)$|", $entry, $va)) { +// list(,$gunid, $ext) = $va; +// $res = $this->bsImportPlaylistRaw($gunid, +// $tmpdp, $entry, $ext, $gunids, $subjid); +// unlink("$tmpdp/$entry"); +// if (PEAR::isError($res)) { +// break; +// } +// } +// } +// $d->close(); +// } +// //@rmdir($tmpdc); @rmdir($tmpdp); @rmdir($tmpd); +// @system("rm -rf $tmpdc"); +// @system("rm -rf $tmpdp"); +// @system("rm -rf $tmpd"); +// @unlink($tmpn); +// return $res; +// } + + + /* --------------------------------------------------------- info methods */ + + /** + * Analyze media file for internal metadata information + * + * @param int $id + * Virtual file's local id + * @return array + */ +// public function bsAnalyzeFile($id) +// { +// $storedFile = StoredFile::Recall($id); +// if (is_null($storedFile) || PEAR::isError($storedFile)) { +// return $storedFile; +// } +// $ia = $storedFile->analyzeFile(); +// return $ia; +// } + + + /** + * Check if file exists in the storage + * + * @param int $id + * Local id + * @param string $ftype + * Internal file type + * @param boolean $byGunid + * select file by gunid (id is then ignored) + * @return boolean + */ +// public function bsExistsFile($id, $ftype=NULL, $byGunid=FALSE) +// { +// if ($byGunid) { +// $storedFile = StoredFile::RecallByGunid($id); +// } else { +// $storedFile = StoredFile::Recall($id); +// } +// if (is_null($storedFile)) { +// return $storedFile; +// } +// if (PEAR::isError($storedFile)) { +// // catch some exceptions +// switch ($storedFile->getCode()) { +// case GBERR_FILENEX: +// case GBERR_FOBJNEX: +// return FALSE; +// break; +// default: +// return $storedFile; +// } +// } +// $realFtype = BasicStor::GetType($storedFile->gunid); +// if (!is_null($ftype) && ( +// (strtolower($realFtype) != strtolower($ftype)) +// // webstreams are subset of audioclips +// && !($realFtype == 'webstream' && $ftype == 'audioclip') +// )) { +// return FALSE; +// } +// return TRUE; +// } + + + /* ---------------------------------------------------- redefined methods */ + /** + * Get object type by id. + * + * @param int $oid + * Local object id + * @return string|PEAR_Error + */ +// public static function GetObjType($p_id) +// { +// $type = "unknown"; +// $f = StoredFile::Recall($p_id); +// return $f->getType(); + +// $gunid = BasicStor::GunidFromId($oid); +// if (PEAR::isError($gunid)) { +// return $gunid; +// } +// $ftype = BasicStor::GetType($gunid); +// if (PEAR::isError($ftype)) { +// return $ftype; +// } +// if (!is_null($ftype)) { +// $type = $ftype; +// } +// return $type; +// } + + + /** + * Add new user + * + * @param string $login + * @param string $pass + * @param string $realname + * @return int|PEAR_Error + */ + public static function addSubj($login, $pass=NULL, $realname='') + { + global $CC_CONFIG; + $uid = Subjects::AddSubj($login, $pass, $realname); + if (PEAR::isError($uid)) { + return $uid; + } + if (Subjects::IsGroup($uid) === FALSE) { + $res = Alib::AddPerm($uid, '_all', '0', 'A'); + if (PEAR::isError($res)) { + return $res; + } + $res = Subjects::AddSubjectToGroup($login, $CC_CONFIG['StationPrefsGr']); + if (PEAR::isError($res)) { + return $res; + } +// $res = Subjects::AddSubjectToGroup($login, $CC_CONFIG['AllGr']); +// if (PEAR::isError($res)) { +// return $res; +// } + } + return $uid; + } + + + /** + * Remove user by login + * + * @param string $login + * @return boolean|PEAR_Error + */ + public function removeSubj($login) + { + global $CC_CONFIG, $CC_DBC; + if (FALSE !== array_search($login, $CC_CONFIG['sysSubjs'])) { + return $CC_DBC->raiseError( + "BasicStor::removeSubj: cannot remove system user/group"); + } + $uid = Subjects::GetSubjId($login); + if (PEAR::isError($uid)) { + return $uid; + } + $res = $CC_DBC->query(" + DELETE FROM ".$CC_CONFIG['accessTable']." WHERE owner=$uid + "); + if (PEAR::isError($res)) { + return $res; + } + $res = Alib::RemoveSubj($login); + if (PEAR::isError($res)) { + return $res; + } + return TRUE; + } + + + /** + * Authenticate and create session + * + * @param string $login + * @param string $pass + * @return boolean|sessionId|PEAR_Error + */ + function login($login, $pass) + { + $r = Alib::Login($login, $pass); + return $r; + } + + + /* ================================================== "protected" methods */ + /** + * Check authorization - auxiliary method + * + * @param array $acts + * Array of actions + * @param array $pars + * Array of parameters - e.g. ids + * @param string $sessid + * Session id + * @return true|PEAR_Error + */ + public static function Authorize($acts, $pars, $sessid='') + { + $userid = Alib::GetSessUserId($sessid); + if (PEAR::isError($userid)) { + return $userid; + } + if (is_null($userid)) { + return PEAR::raiseError( + "BasicStor::Authorize: invalid session", GBERR_DENY); + } + if (!is_array($pars)) { + $pars = array($pars); + } + if (!is_array($acts)) { + $acts = array($acts); + } + $perm = true; +// foreach ($acts as $i => $action) { +// $res = Alib::CheckPerm($userid, $action, $pars[$i]); +// if (PEAR::isError($res)) { +// return $res; +// } +// $perm = $perm && $res; +// } + if ($perm) { + return TRUE; + } + $adesc = "[".join(',',$acts)."]"; + return PEAR::raiseError( + "BasicStor::$adesc: access denied", GBERR_DENY); + } + + + /** + * Get local id from global id (in hex). + * + * @param string $p_gunid + * Global id + * @return int + * Local id + */ +// public static function IdFromGunid($p_gunid) +// { +// global $CC_DBC; +// global $CC_CONFIG; +// return $CC_DBC->getOne("SELECT id FROM ".$CC_CONFIG['filesTable']." WHERE gunid=x'$p_gunid'::bigint"); +// } + + /** + * Get local id from global id (big int). + * + * @param string $p_gunid + * Global id + * @return int + * Local id + */ +// public static function IdFromGunidBigInt($p_gunid) +// { +// global $CC_DBC; +// global $CC_CONFIG; +// return $CC_DBC->getOne("SELECT id FROM ".$CC_CONFIG['filesTable']." WHERE gunid='$p_gunid'"); +// } + + + /** + * Get global id from local id + * + * @param int $p_id + * Local id + * @return string + * Global id + */ +// public static function GunidFromId($p_id) +// { +// global $CC_CONFIG; +// global $CC_DBC; +// if (!is_numeric($p_id)) { +// return NULL; +// } +// $gunid = $CC_DBC->getOne(" +// SELECT to_hex(gunid)as gunid FROM ".$CC_CONFIG['filesTable']." +// WHERE id='$p_id' +// "); +// if (PEAR::isError($gunid)) { +// return $gunid; +// } +// if (is_null($gunid)) { +// return NULL; +// } +// return StoredFile::NormalizeGunid($gunid); +// } + + + /** + * Get storage-internal file type + * + * @param string $p_gunid + * Global unique id of file + * @return string + */ +// public static function GetType($p_gunid) +// { +// global $CC_CONFIG; +// global $CC_DBC; +// $ftype = $CC_DBC->getOne(" +// SELECT ftype FROM ".$CC_CONFIG['filesTable']." +// WHERE gunid=x'$p_gunid'::bigint +// "); +// return $ftype; +// } + + + /** + * Check gunid format + * + * @param string $p_gunid + * Global unique ID + * @return boolean + */ +// protected static function CheckGunid($p_gunid) +// { +// $res = preg_match("|^([0-9a-fA-F]{16})?$|", $p_gunid); +// return $res; +// } + + /** + * Set playlist edit flag + * + * @param string $p_playlistId + * Playlist unique ID + * @param boolean $p_val + * Set/clear of edit flag + * @param string $p_sessid + * Session id + * @param int $p_subjid + * Subject id (if sessid is not specified) + * @return boolean + * previous state + */ + public function setEditFlag($p_playlistId, $p_val=TRUE, $p_sessid=NULL, $p_subjid=NULL) + { + if (!is_null($p_sessid)) { + $p_subjid = Alib::GetSessUserId($p_sessid); + if (PEAR::isError($p_subjid)) { + return $p_subjid; + } + } + $pl = Playlist::Recall($p_playlistId); + if (is_null($pl) || PEAR::isError($pl)) { + return $pl; + } + $state = $pl->getState(); + if ($p_val) { + $r = $pl->setState('edited', $p_subjid); + } else { + $r = $pl->setState('ready', 'NULL'); + } + if (PEAR::isError($r)) { + return $r; + } + return ($state == 'edited'); + } + + + /** + * Check if playlist is marked as edited + * + * @param string $p_playlistId + * Playlist global unique ID + * @return FALSE|int + * ID of user editing it + */ + public function isEdited($p_playlistId) + { + $pl = Playlist::Recall($p_playlistId); + if (is_null($pl) || PEAR::isError($pl)) { + return $pl; + } + if (!$pl->isEdited($p_playlistId)) { + return FALSE; + } + return $pl->isEditedBy($p_playlistId); + } + + + /* ---------------------------------------- redefined "protected" methods */ + /** + * Copy virtual file. + * Redefined from parent class. + * + * @return int + * New object local id + */ +// protected static function CopyObj($id, $newParid, $after=NULL) +// { +// switch (BasicStor::GetObjType($id)) { +// case "audioclip": +// case "playlist": +// case "webstream": +// $storedFile = StoredFile::Recall($id); +// if (is_null($storedFile) || PEAR::isError($storedFile)) { +// return $storedFile; +// } +// $ac2 = StoredFile::CopyOf($storedFile, $nid); +// //$ac2->setName(M2tree::GetObjName($nid)); +// break; +// case "File": +// default: +// } +// return $nid; +// } + + + /** + * Remove virtual file.
+ * Redefined from parent class. + * + * @param int $id + * Local id of removed object + * @param boolean $forced + * Unconditional delete + * @return true|PEAR_Error + */ +// public static function RemoveObj($id, $forced=FALSE) +// { +// $ot = BasicStor::GetObjType($id); +// if (PEAR::isError($ot)) { +// return $ot; +// } +// switch ($ot) { +// case "audioclip": +// case "playlist": +// case "webstream": +// $storedFile = StoredFile::Recall($id); +// if (is_null($storedFile)) { +// return TRUE; +// } +// if (PEAR::isError($storedFile)) { +// return $storedFile; +// } +// if ($storedFile->isEdited() && !$forced) { +// return PEAR::raiseError( +// 'BasicStor::RemoveObj(): is edited' +// ); +// } +// if ($storedFile->isAccessed() && !$forced) { +// return PEAR::raiseError( +// 'BasicStor::RemoveObj(): is accessed' +// ); +// } +// $storedFile->delete(); +// break; +// case "File": +//// case "Folder": +//// case "Replica": +// break; +// default: +// return PEAR::raiseError( +// "BasicStor::bsDeleteFile: unknown obj type ($ot)" +// ); +// } +// $res = Alib::RemoveObj($id); +// if (PEAR::isError($res)) { +// return $res; +// } +// return TRUE; +// } + + + /* ========================================================= misc methods */ + /** + * Write string to file + * + * @param string $str + * string to be written to file + * @param string $fname + * pathname to file + * @return TRUE|raiseError + */ + private static function WriteStringToFile($p_str, $p_fname) + { + $fp = @fopen($p_fname, "w"); + if ($fp === FALSE) { + return PEAR::raiseError( + "BasicStor::WriteStringToFile: cannot open file ($p_fname)" + ); + } + fwrite($fp, $p_str); + fclose($fp); + return TRUE; + } + + + /* =============================================== test and debug methods */ + + /** + * + * + */ + public function debug($va) + { + echo"
\n";
+        print_r($va);
+    }
+
+
+    /**
+     * deleteFiles
+     *
+     * @return void
+     */
+//    private function deleteFiles()
+//    {
+//        global $CC_CONFIG, $CC_DBC;
+//        $ids = $CC_DBC->getAll("SELECT id FROM ".$CC_CONFIG['filesTable']);
+//        if (is_array($ids)) {
+//            foreach ($ids as $i => $item) {
+//              $f = StoredFile::Recall($item['id']);
+//              $f->delete();
+//            }
+//        }
+//    }
+
+
+    /**
+     * Aux logging for debug
+     *
+     * @param string $msg - log message
+     */
+    public function debugLog($msg)
+    {
+        global $CC_CONFIG, $CC_DBC;
+        $fp = fopen($CC_CONFIG['storageDir']."/log", "a") or die("Can't write to log\n");
+        fputs($fp, date("H:i:s").">$msg<\n");
+        fclose($fp);
+    }
+
+} // class BasicStor
+?>
\ No newline at end of file
diff --git a/application/models/GreenBox.php b/application/models/GreenBox.php
new file mode 100644
index 000000000..a46ffa752
--- /dev/null
+++ b/application/models/GreenBox.php
@@ -0,0 +1,1726 @@
+";
+}
+require_once("BasicStor.php");
+if (isset($WHITE_SCREEN_OF_DEATH) && $WHITE_SCREEN_OF_DEATH) {
+    echo __FILE__.':line '.__LINE__.": Loaded BasicStor
"; +} +require_once("Playlist.php"); +require_once("Renderer.php"); +require_once('Prefs.php'); +require_once("Backup.php"); +require_once('Restore.php'); +require_once("Transport.php"); + +/** + * GreenBox class + * + * File storage module. + * + * @package Campcaster + * @subpackage StorageServer + * @copyright 2010 Sourcefabric O.P.S. + * @license http://www.gnu.org/licenses/gpl.txt + */ +class GreenBox extends BasicStor { + + /* ====================================================== storage methods */ + + /** + * Store new file in the storage + * + * @param string $fileName + * The name for the new file. + * @param string $mediaFileLP + * Local path of the media file + * @param string $mdataFileLP + * Local path of the metadata file + * @param string $sessid + * Session id + * @param string $gunid + * Global unique id + * @param string $ftype + * Internal file type + * @return int + * ID of the StoredFile that was created. + */ + public function putFile($p_values, $p_sessionId='') + { + if (($res = BasicStor::Authorize('write', null, $p_sessionId)) !== TRUE) { + return $res; + } + $storedFile = StoredFile::Insert($p_values); + return $storedFile; + } // fn putFile + + + /** + * Store new webstream + * + * @param string $fileName + * Name for new file + * @param string $mdataFileLP + * Local path of metadata file + * @param string $sessid + * Session id + * @param string $gunid + * Global unique id + * @param string $url + * Webstream url + * @return int + * @exception PEAR::error + */ + public function storeWebstream($fileName, $mdataFileLP, $sessid='', + $gunid=NULL, $url) + { + if (($res = BasicStor::Authorize('write', null, $sessid)) !== TRUE) { + return $res; + } + if (!file_exists($mdataFileLP)) { + $mdataFileLP = dirname(__FILE__).'/emptyWebstream.xml'; + } + $values = array( + "filename" => $fileName, + "metadata" => $mdataFileLP, + "gunid" => $gunid, + "filetype" => "webstream" + ); + $storedFile = StoredFile::Insert($values); + if (PEAR::isError($storedFile)) { + return $storedFile; + } + $r = $storedFile->setMetadataValue('ls:url', $url); + if (PEAR::isError($r)) { + return $r; + } + return $oid; + } // fn storeWebstream + + + /** + * Access stored file - increase access counter + * + * @param int $id + * virt.file's local id + * @param string $sessid + * session id + * @return string access token + */ +// function accessFile($id, $sessid='') +// { +// if (($res = BasicStor::Authorize('read', $id, $sessid)) !== TRUE) { +// return $res; +// } +// $gunid = BasicStor::GunidFromId($id); +// $r = BasicStor::bsAccess(NULL, '', $gunid, 'access'); +// if (PEAR::isError($r)) { +// return $r; +// } +// $token = $r['token']; +// return $token; +// } // fn accessFile + + + /** + * Release stored file - decrease access counter + * + * @param string $token + * access token + * @param string $sessid + * session id + * @return boolean + */ +// function releaseFile($token, $sessid='') +// { +// $r = BasicStor::bsRelease($token, 'access'); +// if (PEAR::isError($r)) { +// return $r; +// } +// return FALSE; +// } // fn releaseFile + + + /** + * Analyze media file for internal metadata information + * + * @param int $id + * Virtual file's local id + * @param string $sessid + * Session id + * @return array + */ +// public function analyzeFile($id, $sessid='') +// { +// if (($res = BasicStor::Authorize('read', $id, $sessid)) !== TRUE) { +// return $res; +// } +// return $this->bsAnalyzeFile($id); +// } + + + /** + * Rename file + * + * @param int $id + * Virtual file's local id + * @param string $newName + * @param string $sessid + * Session id + * @return boolean|PEAR_Error + */ +// public function renameFile($id, $newName, $sessid='') +// { +// if (($res = BasicStor::Authorize('write', $id, $sessid)) !== TRUE) { +// return $res; +// } +// return $this->bsRenameFile($id, $newName); +// } + + + /** + * Replace file. Doesn't change filetype! + * + * @param int $id + * virt.file's local id + * @param string $mediaFileLP + * local path of media file + * @param string $mdataFileLP + * local path of metadata file + * @param string $sessid + * session id + * @return TRUE|PEAR_Error + */ +// public function replaceFile($id, $mediaFileLP, $mdataFileLP, $sessid='') +// { +// if (($res = BasicStor::Authorize('write', $id, $sessid)) !== TRUE) { +// return $res; +// } +// return $this->bsReplaceFile($id, $mediaFileLP, $mdataFileLP); +// } + + + /** + * Delete file + * + * @param int $id + * local id + * @param int $sessid + * @param boolean $forced + * if true don't use trash -- now ignored + * @return true|PEAR_Error + */ + public function deleteFile($id, $sessid='', $forced=FALSE) + { + if (($res = BasicStor::Authorize('write', $id, $sessid)) !== TRUE) { + return $res; + } + $f = StoredFile::Recall($id); + return $f->delete(true); + } + + + /* ------------------------------------------------------------- metadata */ + + /** + * Replace metadata with new XML file or string + * + * @param int $id + * Virtual file's local id + * @param string $mdata + * XML string or local path of metadata XML file + * @param string $mdataLoc + * metadata location: 'file'|'string' + * @param string $sessid + * session id + * @return boolean|PEAR_Error + */ +// public function replaceMetadata($id, $mdata, $mdataLoc='file', $sessid='') +// { +// if (($res = BasicStor::Authorize('write', $id, $sessid)) !== TRUE) { +// return $res; +// } +// return $this->bsReplaceMetadata($id, $mdata, $mdataLoc); +// } // fn replaceMetadata + + + /** + * Get metadata XML tree as string + * + * @param int $id + * Virtual file's local id + * @param string $sessid + * session id + * @return string|PEAR_Error + * @todo rename this function to "getMetadata" + */ + public function getMetadata($id, $sessid='') + { + if (($res = BasicStor::Authorize('read', $id, $sessid)) !== TRUE) { + return $res; + } + $f = StoredFile::Recall($id); + return $f->getMetadata(); + } + + + /** + * Return metadata as hierarchical PHP hash-array + * + * If xml:lang attribute is specified in metadata category, + * array of metadata values indexed by xml:lang values + * is presented instead of one plain metadata value. + * + * @param int $id + * local object id + * @param string $sessid + * session ID + * @return array + */ + public function getMetadataArray($id, $sessid) + { + if (($res = BasicStor::Authorize('read', $id, $sessid)) !== TRUE) { + return $res; + } + $storedFile = StoredFile::Recall($id); + if (is_null($storedFile) || PEAR::isError($storedFile)) { + return $storedFile; + } + return $storedFile->md; + +// $arr = $storedFile->md->genPhpArray(); + $md = FALSE; + foreach ($arr['children'] as $i=>$a) { + if ($a['elementname'] == 'metadata'){ + $md = $arr['children'][$i]; + break; + } + } + if ($md === FALSE) { + return PEAR::raiseError( + "GreenBox::getMetadataArray: no metadata container found" + ); + } + $res = array(); + foreach ($md['children'] as $el) { + $lang = isset($el['attrs']['xml:lang']) ? $el['attrs']['xml:lang'] : ""; + $category = $el['elementname']; + if ($lang == "") { + $res[$category] = $el['content']; + } else { + $res[$category][$lang] = $el['content']; + } + } + return $res; + } + + + /** + * Get metadata element value + * + * @param int $id + * Virtual file's local id + * @param string $category + * metadata element name + * @param string $sessid + * session id + * @param string $lang + * xml:lang value for select language version + * @param string $deflang + * xml:lang for default language + * @return array of matching records as hash with fields: + * + */ + public function getMetadataValue($id, $category, $sessid='', + $lang=NULL, $deflang=NULL) + { + if (!is_numeric($id)) { + return null; + } + if (($res = BasicStor::Authorize('read', $id, $sessid)) !== TRUE) { + return $res; + } + $f = StoredFile::Recall($id); + return $f->getMetadataValue($category); + } + + + /** + * Set metadata element value + * + * @param int $id + * Virtual file's local id + * @param string $category + * Metadata element identification (e.g. dc:title) + * @param string $sessid + * Session id + * @param string $value + * The value to store, if NULL then delete record + * @return boolean + */ + public function setMetadataValue($id, $category, $sessid, $value) + { + if (($res = BasicStor::Authorize('write', $id, $sessid)) !== TRUE) { + return $res; + } + $f = StoredFile::Recall($id); + return $f->setMetadataValue($category, $value); + } // fn setMetadataValue + + + /** + * Search in local metadata database. + * + * @param array $criteria + * with following structure:
+ * + * @param string $sessid + * session id + * @return array of hashes, fields: + * + * @see BasicStor::bsLocalSearch + */ + public function localSearch($criteria, $sessid='') + { + $limit = intval(isset($criteria['limit']) ? $criteria['limit'] : 0); + $offset = intval(isset($criteria['offset']) ? $criteria['offset'] : 0); + return $this->bsLocalSearch($criteria, $limit, $offset); + } // fn localSearch + + + /** + * Return values of specified metadata category + * + * @param string $category + * metadata category name + * with or without namespace prefix (dc:title, author) + * @param array $criteria + * see localSearch method + * @param string $sessid + * @return array, fields: + * results : array with found values + * cnt : integer - number of matching values + * @see BasicStor::bsBrowseCategory + */ + public function browseCategory($category, $criteria = null, $sessid = '') + { + $limit = 0; + $offset = 0; + if (!is_null($criteria)) { + $limit = intval(isset($criteria['limit']) ? $criteria['limit'] : 0); + $offset = intval(isset($criteria['offset']) ? $criteria['offset'] : 0); + } + $res = $this->bsBrowseCategory($category, $limit, $offset, $criteria); + return $res; + } // fn browseCategory + + + /*====================================================== playlist methods */ + /** + * Create a new empty playlist. + * + * @param string $fname + * human readable menmonic file name + * @param string $sessid + * session ID + * @return int + * local id of created playlist + */ + public function createPlaylist($fname, $sessid='') + { + $pl = new Playlist(); + $pl = $pl->create($fname); + + return $pl; + } // fn createPlaylist + + public function setPLMetadataValue($id, $category, $value, $lang=NULL, $mid=NULL) + { + $pl = Playlist::Recall($id); + + if($pl === FALSE) + return FALSE; + + $res = $pl->setPLMetaData($category, $value, $lang); + + return $res; + } + + public function getPLMetadataValue($id, $category, $langId=NULL) + { + $pl = Playlist::Recall($id); + + if($pl === FALSE) + return FALSE; + + $res = $pl->getPLMetaData($category); + + return $res; + } + + /** + * Return playlist as XML string + * + * @param int $id + * local object id + * @param string $sessid + * session ID + * @return string + * XML + */ +// function getPlaylistXml($id, $sessid) +// { +// return $this->getMetadata($id, $sessid); +// } // fn getPlaylistXml + + + /** + * Return playlist as hierarchical PHP hash-array + * + * @param int $id + * local object id + * @param string $sessid + * session ID + * @return array + */ + public function getPlaylistArray($id) + { + $pl = Playlist::Recall($id); + if ($pl === FALSE) { + return FALSE; + } + + $res = $pl->getContents(); + + if(is_null($res)) + return array(); + + return $res; + } // fn getPlaylistArray + + + /** + * Mark playlist as edited and return edit token + * + * @param int $id + * local object id + * @param string $sessid + * session ID + * @return string + * playlist access token + */ + public function lockPlaylistForEdit($id, $sessid) { + $pl = Playlist::Recall($id); + + if($pl === FALSE) + return; + + $res = $pl->lock($sessid); + + return $res; + } + + + /** + * clear edit flag. + * + * @param string $sessid + * session ID + * @return string gunid + */ + public function releaseLockedPlaylist($id, $sessid) { + $pl = Playlist::Recall($id); + + if($pl === FALSE) + return FALSE; + + $res = $pl->unlock($sessid); + return $res; + } + + + /** + * Add audioclip specified by local id to the playlist + * + * @param string $token + * playlist access token + * @param string $acId + * local ID of added file + * @param string $sessid + * session ID + * @param string $fadeIn + * in time format hh:mm:ss.ssssss + * @param string $fadeOut + * in time format hh:mm:ss.ssssss + * @param string $length + * length in extent format - + * for webstream (or for overrule length of audioclip) + * @param string $clipstart + * optional clipstart time format hh:mm:ss.ssssss - relative to begin + * @param string $clipend + * optional $clipend time format hh:mm:ss.ssssss - relative to begin + * @return boolean, true if added. + */ + public function addAudioClipToPlaylist($id, $acId, $pos=NULL, $fadeIn=NULL, $fadeOut=NULL, $cliplength=NULL, $cueIn=NULL, $cueOut=NULL) + { + $pl = Playlist::Recall($id); + if ($pl === FALSE) { + return FALSE; + } + + $res = $pl->addAudioClip($acId, $pos, $fadeIn, $fadeOut, $cliplength, $cueIn, $cueOut); + + return $res; + } // fn addAudioClipToPlaylist + + + /** + * Remove audioclip from playlist + * + * @param string $id + * playlist id + * @param int $pos + * position of element in playlist to delete. + * @return boolean, true if deleted. + * @todo rename this function to "deleteAudioClipFromPlaylist" + */ + public function delAudioClipFromPlaylist($id, $pos) + { + $pl = Playlist::Recall($id); + if ($pl === FALSE) { + return FALSE; + } + + $res = $pl->delAudioClip($pos); + if($res === FALSE) + return FALSE; + + return TRUE; + } + + /** + * Move audioClip to the new position in the playlist. + * + * This method may change id attributes of playlistElements and/or + * fadeInfo. + * + * @param string $id + * playlist id + * @param id $oldPos + * old position in playlist + * @param int $newPos + * new position in playlist + * @return boolean + */ + public function moveAudioClipInPlaylist($id, $oldPos, $newPos) + { + $pl = Playlist::Recall($id); + if ($pl === FALSE) { + return FALSE; + } + + $res = $pl->moveAudioClip($oldPos, $newPos); + + return $res; + } + + /** + * Change fadeInfo values + * + * @param string $id + * playlist id + * @param string $fadeIn + * in time format hh:mm:ss.ssssss + * @param string $fadeOut + * in time format hh:mm:ss.ssssss + * @return boolean + */ + public function changeFadeInfo($id, $pos, $fadeIn, $fadeOut) + { + $pl = Playlist::Recall($id); + if ($pl === FALSE) { + return FALSE; + } + + $res = $pl->changeFadeInfo($pos, $fadeIn, $fadeOut); + + return $res; + } + + /** + * Change cueIn/cueOut values for playlist element + * + * @param string $id + * playlist id + * @param string $cueIn + * in time format hh:mm:ss.ssssss + * @param string $cueOut + * in time format hh:mm:ss.ssssss + * relative to begin + * @param sessid $string + * session ID + * @return boolean or pear error object + */ + public function changeClipLength($id, $pos, $cueIn, $cueOut) + { + $pl = Playlist::Recall($id); + if ($pl === FALSE) { + return FALSE; + } + + $res = $pl->changeClipLength($pos, $cueIn, $cueOut); + + return $res; + } + + /** + * Delete a Playlist metafile. + * + * @param int $id + * local id + * @param string $sessid + * session ID + * @return boolean + */ + public function deletePlaylist($id) + { + return Playlist::Delete($id); + + } + + /** + * Find info about clip at specified offset in playlist. + * + * @param string $sessid + * session id + * @param string $plid + * playlist global unique id + * @param string $offset + * current playtime (hh:mm:ss.ssssss) + * @param int $distance + * 0=current clip; 1=next clip ... + * @return array of matching clip info: + * + */ + public function displayPlaylistClipAtOffset($sessid, $plid, $offset, $distance=0) + { + $pl = Playlist::Recall($plid); + if (is_null($pl) || PEAR::isError($pl)) { + return $pl; + } + $res = $pl->displayPlaylistClipAtOffset($offset, $distance); + if (PEAR::isError($res)) { + return $res; + } + $res['title'] = NULL; + $f = StoredFile::RecallByGunid($res['gunid']); + if (PEAR::isError($f)) { + return $f; + } + $res['title'] = $f->getMetadataValue("dc:title"); + $res['playlist_title'] = NULL; + $pl = Playlist::Recall($plid); + $res['playlist'] = $pl->getName(); + + return $res; + } + + + /** + * Create a tarfile with playlist export - playlist and all matching + * sub-playlists and media files (if desired) + * + * @param string $sessid + * string, session ID + * @param array $plids + * array of strings, playlist global unique IDs + * (one gunid is accepted too) + * @param string $type + * playlist format, values: lspl | smil | m3u + * @param boolean $standalone + * if only playlist should be exported or + * with all related files + * @return hasharray with fields: + * fname string: readable fname, + * token string: access token + */ + public function exportPlaylistOpen($sessid, $plids, $type='lspl', $standalone=FALSE) + { + return $this->bsExportPlaylistOpen($plids, $type, !$standalone); + } // fn exportPlaylistOpen + + + /** + * Close playlist export previously opened by the exportPlaylistOpen method + * + * @param string $token + * access token obtained from exportPlaylistOpen + * method call + * @return TRUE|PEAR_Error + */ + public function exportPlaylistClose($token) + { + return $this->bsExportPlaylistClose($token); + } // fn exportPlaylistClose + + + /** + * Open writable handle for import playlist in LS Archive format + * + * @param string $sessid + * session id + * @param string $chsum + * md5 checksum of imported file + * @return hasharray with: + * fname string: writable local filename + * token string: put token + */ + public function importPlaylistOpen($sessid, $chsum='') + { + $userid = GreenBox::GetSessUserId($sessid); + if (PEAR::isError($userid)) { + return $userid; + } + $r = $this->bsOpenPut($chsum, NULL, $userid); + if (PEAR::isError($r)) { + return $r; + } + return $r; + } // fn importPlaylistOpen + + + /** + * Close import-handle and import playlist + * + * @param string $token + * import token obtained by importPlaylistOpen method + * @return int + * result file local id (or error object) + */ + public function importPlaylistClose($token) + { + $arr = $this->bsClosePut($token); + if (PEAR::isError($arr)) { + return $arr; + } + $fname = $arr['fname']; + $owner = $arr['owner']; + $res = $this->bsImportPlaylist($fname, $owner); + if (file_exists($fname)) { + @unlink($fname); + } + return $res; + } // fn importPlaylistClose + + + /** + * Check whether a Playlist metafile with the given playlist ID exists. + * + * @param int $id + * local id + * @param string $sessid + * session ID + * @return boolean + */ + public function existsPlaylist($id) + { + $pl = Playlist::Recall($id); + if ($pl === FALSE) { + return FALSE; + } + + return TRUE; + } // fn existsPlaylist + + + /** + * Check whether a Playlist metafile with the given playlist ID + * is available for editing, i.e., exists and is not marked as + * beeing edited. + * + * @param int $id + * local id + * @param string $sessid + * session ID + * @return TRUE|int + * id of user editing it + */ + public function playlistIsAvailable($id, $sessid) + { + $pl = Playlist::Recall($id); + if ($pl === FALSE) { + return FALSE; + } + + $res = $pl->isEdited(); + + if($res !== FALSE) + return $res; + + return TRUE; + } // fn playlistIsAvailable + + + /* ---------------------------------------------- time conversion methods */ + /** + * Convert playlist time value to float seconds + * + * @param string $plt + * playlist time value (HH:mm:ss.dddddd) + * @return int + * seconds + */ + public function playlistTimeToSeconds($plt) + { + return Playlist::playlistTimeToSeconds($plt); + } + + + /** + * Convert float seconds value to playlist time format + * + * @param int $s0 + * seconds + * @return string + * time in playlist time format (HH:mm:ss.dddddd) + */ + public static function secondsToPlaylistTime($s0) + { + return Playlist::secondsToPlaylistTime($s0); + } // fn secondsToPlaylistTime + + + /* ------------------------------------------------------- render methods */ + /** + * Render playlist to ogg file (open handle) + * + * @param string $sessid + * session id + * @param string $plid + * playlist gunid + * @return string $token + * render token + */ + public function renderPlaylistToFileOpen($sessid, $plid) + { + $r = Renderer::rnRender2FileOpen($this, $plid); + if (PEAR::isError($r)) { + return $r; + } + return $r; + } // fn renderPlaylistToFileOpen + + + /** + * Render playlist to ogg file (check results) + * + * @param string $token + * render token + * @return hasharray: + * status : string - susccess | working | fault + * tmpfile : string - filepath to result temporary file + */ + public function renderPlaylistToFileCheck($token) + { + $r = Renderer::rnRender2FileCheck($this, $token); + if (PEAR::isError($r)) { + return $r; + } + return array('status'=>$r['status'], 'tmpfile'=>$r['tmpfile']); + } // fn renderPlaylistToFileCheck + + + /** + * Render playlist to ogg file (list results) + * + * @param string $status + * success | working | fault + * if this parameter is not set, then return with all unclosed + * @return array of hasharray: + * status : string - susccess | working | fault + * tmpfile : string - filepath to result temporary file + */ + public function renderPlaylistToFileList($status='') + { + return Renderer::rnRender2FileList($this, $status); + } // fn renderPlaylistToFileList + + + /** + * Render playlist to ogg file (close handle) + * + * @param string $token + * render token + * @return boolean + * status + */ + public function renderPlaylistToFileClose($token) + { + $r = Renderer::rnRender2FileClose($this, $token); + if (PEAR::isError($r)) { + return $r; + } + return array(TRUE); + } // fn renderPlaylistToFileClose + + + /** + * Render playlist to storage media clip (open handle) + * + * @param string $sessid + * session id + * @param string $plid + * playlist gunid + * @return string + * render token + */ + public function renderPlaylistToStorageOpen($sessid, $plid) + { + $owner = GreenBox::getSessUserId($sessid); + if (PEAR::isError($owner)) { + return $owner; + } + $r = Renderer::rnRender2FileOpen($this, $plid, $owner); + if (PEAR::isError($r)) { + return $r; + } + return $r; + } // fn renderPlaylistToStorageOpen + + + /** + * Render playlist to storage media clip (check results) + * + * @param string $token + * render token + * @return hasharray: + * status : string - susccess | working | fault + * gunid : string - gunid of result file + */ + public function renderPlaylistToStorageCheck($token) + { + $r = Renderer::rnRender2StorageCheck($this, $token); + if (PEAR::isError($r)) { + return $r; + } + return $r; + } // fn renderPlaylistToStorageCheck + + + /** + * Render playlist to RSS file (open handle) + * + * @param string $sessid + * session id + * @param string $plid + * playlist gunid + * @return string + * render token + */ + public function renderPlaylistToRSSOpen($sessid, $plid) + { + $token = '123456789abcdeff'; + $fakeFile = $CC_CONFIG['accessDir']."/$token.rss"; + file_put_contents($fakeFile, "fake rendered file"); + return array('token'=>$token); + } // fn renderPlaylistToRSSOpen + + + /** + * Render playlist to RSS file (check results) + * + * @param string $token + * render token + * @return hasharray: + * status : string - susccess | working | fault + * tmpfile : string - filepath to result temporary file + */ + public function renderPlaylistToRSSCheck($token) + { + $fakeFile = $CC_CONFIG['accessDir']."/$token.rss"; + if ($token != '123456789abcdeff' || !file_exists($fakeFile)){ + return PEAR::raiseError( + "renderPlaylistToRSSCheck: invalid token ($token)" + ); + } + return array( + 'status'=> 'success', + 'tmpfile' => $fakeFile, + ); + } // fn renderPlaylistToRSSCheck + + + /** + * Render playlist to RSS file (list results) + * + * @param string $status + * success | working | fault + * @return array of hasharray: + * status : string - susccess | working | fault + * tmpfile : string - filepath to result temporary file + */ +// function renderPlaylistToRSSList($status='') +// { +// $dummytokens = array ('123456789abcdeff'); +// foreach ($dummytokens as $token) { +// $r[] = $this->renderPlaylistToRSSCheck($token); +// } +// return $r; +// } // fn renderPlaylistToRSSList + + + /** + * Render playlist to RSS file (close handle) + * + * @param string $token + * render token + * @return boolean + * status + */ + public function renderPlaylistToRSSClose($token) + { + if ($token != '123456789abcdeff'){ + return PEAR::raiseError( + "GreenBox::renderPlaylistToRSSClose: invalid token" + ); + } + $fakeFile = $CC_CONFIG['accessDir']."/$token.rss"; + unlink($fakeFile); + return TRUE; + } // fn renderPlaylistToRSSClose + + + /*================================================= storage admin methods */ + /* ------------------------------------------------------- backup methods */ + /** + * Create backup of storage (open handle) + * + * @param string $sessid + * session id + * @param struct $criteria + * see search criteria + * @return array + * token : string - backup token + */ + public function createBackupOpen($sessid, $criteria='') + { + $bu = new Backup($this); + if (PEAR::isError($bu)) { + return $bu; + } + return $bu->openBackup($sessid,$criteria); + } // fn createBackupOpen + + + /** + * Create backup of storage (check results) + * + * @param string $token + * backup token + * @return hasharray with field: + * status : string - susccess | working | fault + * faultString: string - description of fault + * token : stirng - backup token + * url : string - access url + */ + public function createBackupCheck($token) + { + $bu = new Backup($this); + if (PEAR::isError($bu)) { + return $bu; + } + return $bu->checkBackup($token); + } // fn createBackupCheck + + + /** + * Create backup of storage (list results) + * + * @param string $sessid + * session id + * @param string $stat + * if this parameter is not set, then return with all unclosed backups + * @return array of hasharray with field: + * status : string - susccess | working | fault + * token : stirng - backup token + * url : string - access url + */ + public function createBackupList($sessid, $stat='') + { + $bu = new Backup($this); + if (PEAR::isError($bu)) { + return $bu; + } + return $bu->listBackups($stat); + } // fn createBackupList + + + /** + * Create backup of storage (close handle) + * + * @param string $token + * backup token + * @return boolean + * status + */ + public function createBackupClose($token) + { + $bu = new Backup($this); + if (PEAR::isError($bu)) { + return $bu; + } + return $bu->closeBackup($token); + } // fn createBackupClose + + + /* ===================================================== restore funcitons*/ + /** + * Restore a backup file + * + * @param string $sessid + * session id + * @param string $filename + * backup file path + * @return string + * restore token + */ + public function backupRestoreOpen($sessid, $filename) + { + $rs = new Restore($this); + if (PEAR::isError($rs)) { + return $rs; + } + return $rs->openRestore($sessid,$filename); + } // fn backupRestoreOpen + + + /** + * Check status of backup restore + * + * @param string $token + * restore token + * @return hasharray + * fields: + * token: string - restore token + * status: string - working | fault | success + * faultString: string - description of fault + */ + public function backupRestoreCheck($token) + { + $rs = new Restore($this); + if (PEAR::isError($rs)) { + return $rs; + } + return $rs->checkRestore($token); + } // fn backupRestoreCheck + + + /** + * Close a restore procedure + * + * @param string $token + * restore token + * @return boolean + * is success + */ + public function backupRestoreClose($token) { + $rs = new Restore($this); + if (PEAR::isError($rs)) { + return $rs; + } + return $rs->closeRestore($token); + } // fn backupRestoreClose + + /* ============================================== methods for preferences */ + + /** + * Read preference record by session id + * + * @param string $sessid + * session id + * @param string $key + * preference key + * @return string + * preference value + */ + public function loadPref($sessid, $key) + { + $pr = new Prefs($this); + $res = $pr->loadPref($sessid, $key); + return $res; + } // fn loadPref + + + /** + * Save preference record by session id + * + * @param string $sessid + * session id + * @param string $key + * preference key + * @param string $value + * preference value + * @return boolean + */ + public function savePref($sessid, $key, $value) + { + $pr = new Prefs($this); + $res = $pr->savePref($sessid, $key, $value); + return $res; + } // fn savePref + + + /** + * Delete preference record by session id + * + * @param string $sessid + * session id + * @param string $key + * preference key + * @return boolean + */ + public function delPref($sessid, $key) + { + $pr = new Prefs($this); + $res = $pr->delPref($sessid, $key); + return $res; + } // fn delPref + + + /** + * Read group preference record + * + * @param string $sessid + * session id + * @param string $group + * group name + * @param string $key + * preference key + * @return string + * preference value + */ + public function loadGroupPref($group, $key) + { + $pr = new Prefs($this); + $res = $pr->loadGroupPref($group, $key); + return $res; + } // fn loadGroupPref + + + /** + * Save group preference record + * + * @param string $sessid + * session id + * @param string $group + * group name + * @param string $key + * preference key + * @param string $value + * preference value + * @return boolean + */ + public function saveGroupPref($sessid, $group, $key, $value) + { + $pr = new Prefs($this); + $res = $pr->saveGroupPref($sessid, $group, $key, $value); + return $res; + } // fn saveGroupPref + + + /** + * Delete group preference record + * + * @param string $sessid + * session id + * @param string $group + * group name + * @param string $key + * preference key + * @return boolean + */ + public function delGroupPref($sessid, $group, $key) + { + $pr = new Prefs($this); + $res = $pr->delGroupPref($sessid, $group, $key); + return $res; + } // fn delGroupPref + + + /* =================================================== networking methods */ + /* ------------------------------------------------------- common methods */ + /** + * Common "check" method for transports + * + * @param string $trtok + * transport token + * @return array with fields: + * trtype: string - audioclip | playlist | search | file + * state: string - transport state + * direction: string - up | down + * expectedsize: int - file size in bytes + * realsize: int - currently transported bytes + * expectedchsum: string - orginal file checksum + * realchsum: string - transported file checksum + * ... ? + */ + public function getTransportInfo($trtok) + { + $tr = new Transport($this); + return $tr->getTransportInfo($trtok); + } // fn getTransportInfo + + + /** + * Turn transports on/off, optionaly return current state. + * + * @param string $sessid + * session id + * @param boolean $onOff + * optional (if not used, current state is returned) + * @return boolean + * previous state + */ + public function turnOnOffTransports($sessid, $onOff=NULL) + { + $tr = new Transport($this); + return $tr->turnOnOffTransports($sessid, $onOff); + } // fn turnOnOffTransports + + + /** + * Pause, resume or cancel transport + * + * @param string $trtok + * transport token + * @param string $action + * pause | resume | cancel + * @return string + * resulting transport state + */ + public function doTransportAction($trtok, $action) + { + $tr = new Transport($this); + $res = $tr->doTransportAction($trtok, $action); + return $res; + } // fn doTransportAction + + + /* ------------------------ methods for ls-archive-format file transports */ + /** + * Open async file transfer from local storageServer to network hub, + * file should be ls-archive-format file. + * + * @param string $filePath + * local path to uploaded file + * @return string + * transport token + */ + public function uploadFile2Hub($filePath) + { + $tr = new Transport($this); + return $tr->uploadFile2Hub($filePath); + } // fn uploadFile2Hub + + + /** + * Get list of prepared transfers initiated by hub + * + * @return array of structs/hasharrays with fields: + * trtok: string transport token + * ... ? + */ + public function getHubInitiatedTransfers() + { + $tr = new Transport($this); + return $tr->getHubInitiatedTransfers(); + } // fn getHubInitiatedTransfers + + + /** + * Start of download initiated by hub + * + * @param string $trtok + * transport token obtained from + * the getHubInitiatedTransfers method + * @return string + * transport token + */ + public function startHubInitiatedTransfer($trtok) + { + $tr = new Transport($this); + return $tr->startHubInitiatedTransfer($trtok); + } // fn startHubInitiatedTransfer + + + /* ------------- special methods for audioClip/webstream object transport */ + + /** + * Start upload of audioClip/webstream/playlist from local storageServer + * to hub + * + * @param string $gunid + * global unique id of object being transported + * @param boolean $withContent + * if true, transport playlist content too + * @return string + * transport token + * @todo rename this function "uploadToHub" + */ + public function upload2Hub($gunid, $withContent=FALSE) + { + $tr = new Transport($this); + return $tr->upload2Hub($gunid, $withContent); + } // fn upload2Hub + + + /** + * Start download of audioClip/webstream/playlist from hub to local + * storageServer + * + * @param string $sessid + * session id + * @param string $gunid + * global unique id of playlist being transported + * @param boolean $withContent + * if true, transport playlist content too + * @return string + * transport token + */ + public function downloadFromHub($sessid, $gunid, $withContent=TRUE){ + $uid = GreenBox::getSessUserId($sessid); + if (PEAR::isError($uid)) { + return $uid; + } + $tr = new Transport($this); + return $tr->downloadFromHub($uid, $gunid, $withContent); + } // fn downloadFromHub + + + /* ------------------------------------------------ global-search methods */ + /** + * Start search job on network hub + * + * @param array $criteria + * criteria format (see localSearch) + * @return string + * transport token + */ + public function globalSearch($criteria) + { + $tr = new Transport($this); + //return $tr->globalSearch($criteria); + return $tr->remoteSearch($criteria); + } + + + /** + * Get results from search job on network hub + * + * @param string $trtok + * transport token + * @param boolean $andClose + * if TRUE, close transport token + * @return array + * search result format (see localSearch) + */ +// public function getSearchResults($trtok, $andClose=TRUE) +// { +// $tr = new Transport($this); +// return $tr->getSearchResults($trtok, $andClose); +// } // fn getSearchResults + + + /* ========================================================= info methods */ + /** + * Check if file gunid exists in the storage and + * user have permission to read it + * + * @param string $sessid + * session id + * @param string $gunid + * @param string $ftype + * internal file type + * @return string|PEAR_Error + */ + public function existsFile($sessid, $gunid, $ftype=NULL) + { + if (($res = BasicStor::Authorize('read', $id, $sessid)) !== TRUE) { + return $res; + } + $f = StoredFile::RecallByGunid($gunid); + return $f->existsFile(); + } // fn existsFile + + + /* ==================================================== redefined methods */ + /** + * Get user id from session id + * + * This redefinition only simulate old (bad) behaviour - returns NULL + * for wrong sessid (code ALIBERR_NOTEXISTS). + * HtmlUI depends on it. + * + * @param string $sessid + * @return int|null|PEAR_Error + */ + public static function GetSessUserId($sessid) + { + $r = Alib::GetSessUserId($sessid); + if (PEAR::isError($r)) { + if ($r->getCode() == ALIBERR_NOTEXISTS) { + return NULL; + } else { + return $r; + } + } + return $r; + } // fn getSessUserId + + + /** + * Change user password. + * + * ('superuser mode'= superuser is changing some password without + * knowledge of the old password) + * + * @param string $login + * @param string $oldpass + * old password + * (should be null or empty for 'superuser mode') + * @param string $pass + * @param string $sessid + * session id, required for 'superuser mode' + * @return boolean/err + */ + public function passwd($login, $oldpass=null, $pass='', $sessid='') + { + if (is_null($oldpass) || ($oldpass == '') ) { + if (($res = BasicStor::Authorize('subjects', $this->rootId, $sessid)) !== TRUE) { + sleep(2); + return $res; + } else { + $oldpass = null; + } + } else { + if (FALSE === Subjects::Authenticate($login, $oldpass)) { + sleep(2); + return PEAR::raiseError( + "GreenBox::passwd: access denied (oldpass)", GBERR_DENY); + } + } + $res = Subjects::Passwd($login, $oldpass, $pass); + if (PEAR::isError($res)) { + return $res; + } + return TRUE; + } // fn passwd + + + /** + * Insert permission record + * + * @param int $sid + * local user/group id + * @param string $action + * @param int $oid + * local object id + * @param char $type + * 'A'|'D' (allow/deny) + * @param string $sessid + * session id + * @return int + * local permission id + */ + public function addPerm($sid, $action, $oid, $type='A', $sessid='') + { + if (($res = BasicStor::Authorize('editPerms', $oid, $sessid)) !== TRUE) { + return $res; + } + return Alib::AddPerm($sid, $action, $oid, $type); + } // fn addPerm + + + /** + * Remove permission record + * + * @param int $permid + * local permission id + * @param int $subj + * local user/group id + * @param int $obj + * local object id + * @param string $sessid + * session id + * @return boolean/error + */ + public function removePerm($permid=NULL, $subj=NULL, $obj=NULL, $sessid='') + { + if (!is_null($permid)) { + $oid = Alib::GetPermOid($permid); + if (PEAR::isError($oid)) { + return $oid; + } + if (!is_null($oid)) { + if (($res = BasicStor::Authorize('editPerms', $oid, $sessid)) !== TRUE) { + return $res; + } + } + } + $res = Alib::RemovePerm($permid, $subj, $obj); + return $res; + } // fn removePerm + +} // class GreenBox +?> \ No newline at end of file diff --git a/application/models/LocStor.php b/application/models/LocStor.php new file mode 100644 index 000000000..a5762b6ab --- /dev/null +++ b/application/models/LocStor.php @@ -0,0 +1,1749 @@ +"; +} +require_once("BasicStor.php"); +if (isset($WHITE_SCREEN_OF_DEATH) && $WHITE_SCREEN_OF_DEATH) { + echo __FILE__.':line '.__LINE__.": Loaded BasicStor
"; +} +require_once("Transport.php"); +if (isset($WHITE_SCREEN_OF_DEATH) && $WHITE_SCREEN_OF_DEATH) { + echo __FILE__.':line '.__LINE__.": Loaded Transport
"; +} + +/** + * LocStor class + * + * Local storage interface + * + * @package Campcaster + * @subpackage StorageServer + * @copyright 2010 Sourcefabric O.P.S. + * @license http://www.gnu.org/licenses/gpl.txt + */ +class LocStor extends BasicStor { + + /* ---------------------------------------------------------------- store */ + + /** + * Store or replace existing audio clip. + * + * Sending a file to the storage server is a 3 step process: + * 1) Call storeAudioClipOpen + * 2) Upload the file to the URL specified + * 3) Call storeAudioClipClose + * + * @param string $sessid + * session id + * @param string $gunid + * global unique id + * @param string $metadata + * metadata XML string + * @param string $fname + * human readable menmonic file name + * with extension corresponding to filetype + * @param string $chsum + * md5 checksum of media file + * @param string $ftype + * audioclip | playlist | webstream + * @return array + * {url:writable URL for HTTP PUT, token:access token} + */ + protected function storeAudioClipOpen($sessid, $gunid, $metadata, + $fname, $chsum, $ftype='audioclip') + { + // Check the gunid format + if (!BasicStor::CheckGunid($gunid)) { + return PEAR::raiseError( + "LocStor::storeAudioClipOpen: Wrong gunid ($gunid)" + ); + } + + // Check if we already have this file. + $duplicate = StoredFile::RecallByMd5($chsum); + if (!empty($chsum) && $duplicate) { + return PEAR::raiseError( + "LocStor::storeAudioClipOpen: Duplicate file" + ." - Matched MD5 ($chsum) against '".$duplicate->getName()."'", + 888); + } + + // Check if specified gunid exists. + $storedFile =& StoredFile::RecallByGunid($gunid); + if (!is_null($storedFile) && !PEAR::isError($storedFile)) { + // gunid exists - do replace + $oid = $storedFile->getId(); + if (($res = BasicStor::Authorize('write', $oid, $sessid)) !== TRUE) { + return $res; + } + if ($storedFile->isAccessed()) { + return PEAR::raiseError( + 'LocStor::storeAudioClipOpen: is accessed' + ); + } + $res = $storedFile->replace($oid, $storedFile->getName(), '', $metadata, 'string'); + if (PEAR::isError($res)) { + return $res; + } + } else { + // gunid doesn't exist - do insert: + $tmpFname = uniqid(); + if (($res = BasicStor::Authorize('write', null, $sessid)) !== TRUE) { + return $res; + } + $values = array( + "metadata" => $metadata, + "gunid" => $gunid, + "filetype" => $ftype); + $storedFile =& StoredFile::Insert($values); + if (PEAR::isError($storedFile)) { + return $storedFile; + } + if (PEAR::isError($res)) { + return $res; + } + } + $res = $storedFile->setState('incomplete'); + if (PEAR::isError($res)) { + return $res; + } + if ($fname == '') { + $fname = "newFile"; + } + $storedFile->setName($fname); + return $this->bsOpenPut($chsum, $storedFile->gunid); + } + + + /** + * Store or replace existing audio clip + * + * @param string $sessid + * @param string $token + * @return string gunid|PEAR_Error + */ + protected function storeAudioClipClose($sessid, $token) + { + $storedFile =& StoredFile::RecallByToken($token); + if (is_null($storedFile) || PEAR::isError($storedFile)) { + return $storedFile; + } + $arr = $this->bsClosePut($token); + if (PEAR::isError($arr)) { + $storedFile->delete(); + return $arr; + } + $fname = $arr['fname']; + $res = $storedFile->setRawMediaData($fname); + if (PEAR::isError($res)) { + return $res; + } + if (file_exists($fname)) { + @unlink($fname); + } + $res = $storedFile->setState('ready'); + if (PEAR::isError($res)) { + return $res; + } + return $storedFile->gunid; + } + + + /** + * Check uploaded file + * + * @param string $token + * "put" token + * @return array + * hash, (status: boolean, size: int - filesize) + */ + protected function uploadCheck($token) + { + return $this->bsCheckPut($token); + } + + + /** + * Store webstream + * + * @param string $sessid + * session id + * @param string $gunid + * global unique id + * @param string $metadata + * metadata XML string + * @param string $fname + * human readable menmonic file name with extension corresponding to filetype + * @param string $url + * webstream url + * @return string + * gunid + */ + protected function storeWebstream($sessid, $gunid, $metadata, $fname, $url) + { + $a = $this->storeAudioClipOpen( + $sessid, $gunid, $metadata, $fname, md5(''), 'webstream'); + if (PEAR::isError($a)) { + return $a; + } + $gunid = $this->storeAudioClipClose($sessid, $a['token']); + if (PEAR::isError($gunid)) { + return $gunid; + } + $storedFile =& StoredFile::RecallByGunid($gunid); + if (is_null($storedFile) || PEAR::isError($storedFile)) { + return $storedFile; + } + $r = $storedFile->setMetadataValue('ls:url', $url); + if (PEAR::isError($r)) { + return $r; + } + return $gunid; + } + + + /* --------------------------------------------------------------- access */ + /** + * Make access to audio clip + * + * @param string $sessid + * @param string $gunid + * @param int $parent + * parent token + * @return array + * with: seekable filehandle, access token + */ + public function accessRawAudioData($sessid, $gunid, $parent='0') + { + $storedFile =& StoredFile::RecallByGunid($gunid); + if (is_null($storedFile) || PEAR::isError($storedFile)) { + return $storedFile; + } + if (($res = BasicStor::Authorize('read', $storedFile->getId(), $sessid)) !== TRUE) { + return $res; + } + return $storedFile->accessRawMediaData($parent); + } + + + /** + * Release access to audio clip + * + * @param string $sessid + * @param string $token + * access token + * @return boolean|PEAR_Error + */ + public function releaseRawAudioData($sessid, $token) + { + $storedFile =& StoredFile::RecallByToken($token); + if (is_null($storedFile) || PEAR::isError($storedFile)) { + return $storedFile; + } + return $storedFile->releaseRawMediaData($token); + } + + + /* ------------------------------------------------------------- download */ + /** + * Create and return downloadable URL for audio file + * + * @param string $sessid + * session id + * @param string $gunid + * global unique id + * @return array + * array with strings: + * downloadable URL, download token, chsum, size, filename + */ + protected function downloadRawAudioDataOpen($sessid, $gunid) + { + $ex = $this->existsAudioClip($sessid, $gunid); + if (PEAR::isError($ex)) { + return $ex; + } + $media = StoredFile::RecallByGunid($gunid); + $id = $media->getId(); + if (is_null($id) || !$ex) { + return PEAR::raiseError( + "LocStor::downloadRawAudioDataOpen: gunid not found ($gunid)", + GBERR_NOTF + ); + } + if (($res = BasicStor::Authorize('read', $id, $sessid)) !== TRUE) { + return $res; + } + return $this->bsOpenDownload($id); + } + + + /** + * Discard downloadable URL for audio file + * + * @param string $token + * download token + * @return string + * gunid + */ + protected function downloadRawAudioDataClose($token) + { + return $this->bsCloseDownload($token); + } + + + /** + * Create and return downloadable URL for metadata + * + * @param string $sessid + * session id + * @param string $gunid + * global unique id + * @return array + * array with strings: + * downloadable URL, download token, chsum, filename + */ + protected function downloadMetadataOpen($sessid, $gunid) + { +// $res = $this->existsAudioClip($sessid, $gunid); +// if(PEAR::isError($res)) return $res; + $media = StoredFile::RecallByGunid($gunid) + $id = $media->getGunid(); + if (is_null($id)) { + return PEAR::raiseError( + "LocStor::downloadMetadataOpen: gunid not found ($gunid)" + ); + } + if (($res = BasicStor::Authorize('read', $id, $sessid)) !== TRUE) { + return $res; + } + $res = $this->bsOpenDownload($id, 'metadata'); + #unset($res['filename']); + return $res; + } + + + /** + * Discard downloadable URL for metadata + * + * @param string $token + * download token + * @return string + * gunid + */ + protected function downloadMetadataClose($token) + { + return $this->bsCloseDownload($token, 'metadata'); + } + + + /** + * Return metadata as XML + * + * @param string $sessid + * @param string $gunid + * @return string|PEAR_Error + */ + protected function getAudioClip($sessid, $gunid) + { + $storedFile =& StoredFile::RecallByGunid($gunid); + if (is_null($storedFile) || PEAR::isError($storedFile)) { + return $storedFile; + } + if (($res = BasicStor::Authorize('read', $storedFile->getId(), $sessid)) !== TRUE) { + return $res; + } + $md = $storedFile->getMetadata(); + if (PEAR::isError($md)) { + return $md; + } + return $md; + } + + + /* ------------------------------------------------------- search, browse */ + + /** + * Search in metadata database + * + * @param string $sessid + * @param array $criteria + * with following structure:
+ * + * @return array of hashes, fields: + * + * @see BasicStor::localSearch + */ + public function searchMetadata($sessid, $criteria) + { + if (($res = BasicStor::Authorize('read', $this->storId, $sessid)) !== TRUE) { + return $res; + } + $criteria['resultMode'] = 'xmlrpc'; + $res = $this->localSearch($criteria, $sessid); + return $res; + } + + + /** + * @param array $criteria + * @param mixed $sessid + * This variable isnt used. + * @return unknown + */ + public function localSearch($criteria, $sessid='') + { + $limit = intval(isset($criteria['limit']) ? $criteria['limit'] : 0); + $offset = intval(isset($criteria['offset']) ? $criteria['offset'] : 0); + $res = $this->bsLocalSearch($criteria, $limit, $offset); + return $res; + } + + + /** + * Return values of specified metadata category + * + * @param string $category + * metadata category name + * with or without namespace prefix (dc:title, author) + * @param hash $criteria + * see searchMetadata method + * @param string $sessid + * @return array + * hash, fields: + * results : array with found values + * cnt : integer - number of matching values + * @see BasicStor::bsBrowseCategory + */ + protected function browseCategory($category, $criteria=NULL, $sessid='') + { + $limit = intval(isset($criteria['limit']) ? $criteria['limit'] : 0); + $offset = intval(isset($criteria['offset']) ? $criteria['offset'] : 0); + $res = $this->bsBrowseCategory($category, $limit, $offset, $criteria); + return $res; + } + + + /* ----------------------------------------------------------------- etc. */ + /** + * Check if audio clip exists + * + * @param string $sessid + * @param string $gunid + * @return boolean + */ + protected function existsAudioClip($sessid, $gunid) + { + $ex = $this->existsFile($sessid, $gunid, 'audioclip'); + // webstreams are subset of audioclips - moved to BasicStor + // if($ex === FALSE ){ + // $ex = $this->existsFile($sessid, $gunid, 'webstream'); + // } + if ($ex === FALSE ) { + return FALSE; + } + if (PEAR::isError($ex)) { + return $ex; + } + $storedFile =& StoredFile::RecallByGunid($gunid); + if (is_null($storedFile) || PEAR::isError($storedFile)) { + return $storedFile; + } + return $storedFile->exists(); + } + + + /** + * Check if file exists in the storage + * + * @param string $sessid + * @param string $gunid + * @param string $ftype + * internal file type + * @return boolean + */ + protected function existsFile($sessid, $gunid, $ftype=NULL) + { + if (($res = BasicStor::Authorize('read', $id, $sessid)) !== TRUE) { + return $res; + } + $f = StoredFile::RecallByGunid($gunid); + if (PEAR::isError($f)) { + return FALSE; + } + return $f->existsFile(); + } + + + /** + * Delete existing audio clip + * + * @param string $sessid + * @param string $gunid + * @param boolean $forced + * if true, don't use trash + * @return boolean|PEAR_Error + */ + protected function deleteAudioClip($sessid, $gunid, $forced=FALSE) + { + $storedFile =& StoredFile::RecallByGunid($gunid); + if (is_null($storedFile)) { + return TRUE; + } + if (PEAR::isError($storedFile)) { + if ($storedFile->getCode()==GBERR_FOBJNEX && $forced) { + return TRUE; + } + return $storedFile; + } + if (($res = BasicStor::Authorize('write', $storedFile->getId(), $sessid)) !== TRUE) { + return $res; + } + $res = $storedFile->delete(); + if (PEAR::isError($res)) { + return $res; + } + return TRUE; + } + + + /** + * Update existing audio clip metadata + * + * @param string $sessid + * @param string $gunid + * @param string $metadata + * metadata XML string + * @return boolean|PEAR_Error + */ + protected function updateAudioClipMetadata($sessid, $gunid, $metadata) + { + $storedFile =& StoredFile::RecallByGunid($gunid); + if (is_null($storedFile) || PEAR::isError($storedFile)) { + return $storedFile; + } + if (($res = BasicStor::Authorize('write', $storedFile->getId(), $sessid)) !== TRUE) { + return $res; + } + return $storedFile->setMetadata($metadata, 'string'); + } + + + /*====================================================== playlist methods */ + /** + * Create a new empty playlist. + * + * @param string $sessid + * session ID + * @param string $playlistId + * playlist global unique ID + * @param string $fname + * human readable mnemonic file name + * @return string + * playlist global unique ID + */ + public function createPlaylist($sessid, $playlistId, $fname) + { + $ex = $this->existsPlaylist($sessid, $playlistId); + if (PEAR::isError($ex)) { + return $ex; + } + if ($ex) { + return PEAR::raiseError( + 'LocStor::createPlaylist: already exists' + ); + } + $tmpFname = uniqid(''); + if (($res = BasicStor::Authorize('write', null, $sessid)) !== TRUE) { + return $res; + } + $values = array( + "metadata" => dirname(__FILE__).'/emptyPlaylist.xml', + "gunid" => $playlistId, + "filetype" => "playlist"); + // This is all wrong now. + $storedFile = StoredFile::Insert($values); + if ($fname == '') { + $fname = "newFile.xml"; + } + $storedFile->setName($fname); + $storedFile->setState('ready'); + $storedFile->setMime('application/smil'); + return $storedFile->gunid; + } + + + /** + * Open a Playlist metafile for editing. + * Open readable URL and mark file as beeing edited. + * + * @param string $sessid + * session ID + * @param string $playlistId + * playlist global unique ID + * @return struct + * {url:readable URL for HTTP GET, token:access token, chsum:checksum} + */ + public function editPlaylist($sessid, $playlistId) + { + $ex = $this->existsPlaylist($sessid, $playlistId); + if (PEAR::isError($ex)) { + return $ex; + } + if (!$ex) { + return PEAR::raiseError( + 'LocStor::editPlaylist: playlist not exists' + ); + } + if ($this->isEdited($playlistId) !== FALSE) { + return PEAR::raiseError( + 'LocStor::editPlaylist: playlist already edited' + ); + } + $storedFile =& StoredFile::RecallByGunid($playlistId); + if (is_null($storedFile) || PEAR::isError($storedFile)) { + return $storedFile; + } + $id = $storedFile->getId(); + if (($res = BasicStor::Authorize('write', $id, $sessid)) !== TRUE) { + return $res; + } + $res = $this->bsOpenDownload($id, 'metadata'); + if (PEAR::isError($res)) { + return $res; + } + $r = $this->setEditFlag($playlistId, TRUE, $sessid); + if (PEAR::isError($r)) { + return $r; + } + unset($res['filename']); + return $res; + } + + + /** + * Store a new Playlist metafile in place of the old one. + * + * @param string $sessid + * session ID + * @param string $playlistToken + * playlist access token + * @param string $newPlaylist + * new playlist as XML string + * @return string + * playlistId + */ + protected function savePlaylist($sessid, $playlistToken, $newPlaylist) + { + $playlistId = $this->bsCloseDownload($playlistToken, 'metadata'); + if (PEAR::isError($playlistId)) { + return $playlistId; + } + $storedFile =& StoredFile::RecallByGunid($playlistId); + if (is_null($storedFile) || PEAR::isError($storedFile)) { + return $storedFile; + } + $res = $storedFile->setMetadata($newPlaylist, 'string', 'playlist'); + if (PEAR::isError($res)) { + return $res; + } + $r = $this->setEditFlag($playlistId, FALSE, $sessid); + if (PEAR::isError($r)) { + return $r; + } + return $playlistId; + } + + + /** + * RollBack playlist changes to the locked state + * + * @param string $playlistToken + * playlist access token + * @param string $sessid + * session ID + * @return string + * gunid of playlist + */ + public function revertEditedPlaylist($playlistToken, $sessid='') + { + $gunid = $this->bsCloseDownload($playlistToken, 'metadata'); + if (PEAR::isError($gunid)) { + return $gunid; + } + $storedFile =& StoredFile::RecallByGunid($gunid); + if (is_null($storedFile) || PEAR::isError($storedFile)) { + return $storedFile; + } + $id = $storedFile->getId(); + $mdata = $storedFile->getMetadata(); + if (PEAR::isError($mdata)) { + return $mdata; + } + $res = $storedFile->setMetadata($mdata, 'string'); + if (PEAR::isError($res)) { + return $res; + } + $this->setEditFlag($gunid, FALSE, $sessid); + return $gunid; + } + + + /** + * Delete a Playlist metafile. + * + * @param string $sessid + * session ID + * @param string $playlistId + * playlist global unique ID + * @param boolean $forced + * if true don't use trash + * @return boolean + */ + public function deletePlaylist($sessid, $playlistId, $forced=FALSE) + { + $ex = $this->existsPlaylist($sessid, $playlistId); + if (PEAR::isError($ex)) { + return $ex; + } + if (!$ex) { + if ($forced) { + return TRUE; + } + return PEAR::raiseError( + 'LocStor::deletePlaylist: playlist not exists', + GBERR_FILENEX + ); + } + $storedFile =& StoredFile::RecallByGunid($playlistId); + if (is_null($storedFile) || PEAR::isError($storedFile)) { + return $storedFile; + } + if (($res = BasicStor::Authorize('write', $storedFile->getId(), $sessid)) !== TRUE) { + return $res; + } + $res = $storedFile->delete(); + if (PEAR::isError($res)) { + return $res; + } + return TRUE; + } + + + /** + * Access (read) a Playlist metafile. + * + * @param string $sessid + * session ID + * @param string $playlistId + * playlist global unique ID + * @param boolean $recursive + * flag for recursive access content inside playlist + * @param int $parent + * parent token + * @return struct { + * url: readable URL for HTTP GET, + * token: access token, + * chsum: checksum, + * content: array of structs - recursive access (optional) + * filename: string mnemonic filename + * } + */ + public function accessPlaylist($sessid, $playlistId, $recursive=FALSE, $parent='0') +// { +// if ($recursive) { +// require_once("AccessRecur.php"); +// $r = AccessRecur::accessPlaylist($this, $sessid, $playlistId); +// if (PEAR::isError($r)) { +// return $r; +// } +// return $r; +// } +// $ex = $this->existsPlaylist($sessid, $playlistId); +// if (PEAR::isError($ex)) { +// return $ex; +// } +// if (!$ex) { +// return PEAR::raiseError( +// "LocStor::accessPlaylist: playlist not found ($playlistId)", +// GBERR_NOTF +// ); +// } +// $id = BasicStor::IdFromGunid($playlistId); +// if (($res = BasicStor::Authorize('read', $id, $sessid)) !== TRUE) { +// return $res; +// } +// $res = $this->bsOpenDownload($id, 'metadata', $parent); +// #unset($res['filename']); +// return $res; + } + + + /** + * Release the resources obtained earlier by accessPlaylist(). + * + * @param string $sessid + * session ID + * @param string $playlistToken + * playlist access token + * @param boolean $recursive + * flag for recursive access content inside playlist + * @return string + * playlist ID + */ + public function releasePlaylist($sessid, $playlistToken, $recursive=FALSE) + { + if ($recursive) { + require_once"AccessRecur.php"; + $r = AccessRecur::releasePlaylist($this, $sessid, $playlistToken); + if (PEAR::isError($r)) { + return $r; + } + return $r; + } + return $this->bsCloseDownload($playlistToken, 'metadata'); + } + + + /** + * Create a tarfile with playlist export - playlist and all matching + * sub-playlists and media files (if desired) + * + * @param string $sessid + * session ID + * @param array $plids + * array of strings, playlist global unique IDs (one gunid is accepted too) + * @param string $type + * playlist format, values: lspl | smil | m3u + * @param boolean $standalone + * if only playlist should be exported or with all related files + * @return hasharray with fields: + * url string: readable url, + * token string: access token + * chsum string: md5 checksum, + */ + protected function exportPlaylistOpen($sessid, $plids, $type='lspl', $standalone=FALSE) + { + $res = $this->bsExportPlaylistOpen($plids, $type, !$standalone); + if (PEAR::isError($res)) { + return $res; + } + $url = BasicStor::GetUrlPart()."access/".basename($res['fname']); + $chsum = md5_file($res['fname']); + $size = filesize($res['fname']); + return array( + 'url' => $url, + 'token' => $res['token'], + 'chsum' => $chsum, + ); + } + + + /** + * Close playlist export previously opened by the exportPlaylistOpen method + * + * @param string $token + * access token obtained from exportPlaylistOpen method call + * @return boolean|PEAR_Error + */ + protected function exportPlaylistClose($token) + { + return $this->bsExportPlaylistClose($token); + } + + + /** + * Open writable handle for import playlist in LS Archive format + * + * @param string $sessid + * session id + * @param string $chsum + * md5 checksum of imported file + * @return hasharray with: + * url string: writable URL + * token string: PUT token + */ + protected function importPlaylistOpen($sessid, $chsum) + { + $userid = Alib::GetSessUserId($sessid); + if (PEAR::isError($userid)) { + return $userid; + } + $r = $this->bsOpenPut($chsum, NULL, $userid); + if (PEAR::isError($r)) { + return $r; + } + return $r; + } + + + /** + * Close import-handle and import playlist + * + * @param string $token + * import token obtained by importPlaylistOpen method + * @return string + * result file global id (or error object) + */ + protected function importPlaylistClose($token) + { + $arr = $this->bsClosePut($token); + if (PEAR::isError($arr)) { + return $arr; + } + $fname = $arr['fname']; + $owner = $arr['owner']; + $res = $this->bsImportPlaylist($fname); + if (file_exists($fname)) { + @unlink($fname); + } + if (PEAR::isError($res)) { + return $res; + } + $media = StoredFile::Recall($id); + return $media->getGunId(); + } + + + /** + * Check whether a Playlist metafile with the given playlist ID exists. + * + * @param string $sessid + * session ID + * @param string $playlistId + * playlist global unique ID + * @return boolean + */ + public function existsPlaylist($sessid, $playlistId) + { + return $this->existsFile($sessid, $playlistId, 'playlist'); + } + + + /** + * Check whether a Playlist metafile with the given playlist ID + * is available for editing, i.e., exists and is not marked as + * being edited. + * + * @param string $sessid + * session ID + * @param string $playlistId + * playlist global unique ID + * @param boolean $getUid + * flag for returning editedby uid + * @return boolean + */ + public function playlistIsAvailable($sessid, $playlistId, $getUid=FALSE) + { + $ex = $this->existsPlaylist($sessid, $playlistId); + if (PEAR::isError($ex)) { + return $ex; + } + if (!$ex) { + return PEAR::raiseError( + 'LocStor::playlistIsAvailable: playlist not exists' + ); + } + $ie = $this->isEdited($playlistId); + if ($ie === FALSE) { + return TRUE; + } + if ($getUid) { + return $ie; + } + return FALSE; + } + + + /* ------------------------------------------------------- render methods */ + /** + * Render playlist to ogg file (open handle) + * + * @param string $sessid + * session id + * @param string $plid + * playlist gunid + * @return hasharray + * token: string - render token + */ + protected function renderPlaylistToFileOpen($sessid, $plid) + { + require_once("Renderer.php"); + $r = Renderer::rnRender2FileOpen($this, $plid); + if (PEAR::isError($r)) { + return $r; + } + return $r; + } + + + /** + * Render playlist to ogg file (check results) + * + * @param string $token + * render token + * @return hasharray: + * status : string - success | working | fault + * url : string - readable url + */ + protected function renderPlaylistToFileCheck($token) + { + require_once("Renderer.php"); + $r = Renderer::rnRender2FileCheck($this, $token); + if (PEAR::isError($r)) { + return $r; + } + return array('status'=>$r['status'], 'url'=>$r['url']); + } + + + /** + * Render playlist to ogg file (close handle) + * + * @param string $token + * render token + * @return boolean status + */ + protected function renderPlaylistToFileClose($token) + { + require_once("Renderer.php"); + $r = Renderer::rnRender2FileClose($this, $token); + if (PEAR::isError($r)) { + return $r; + } + return array(TRUE); + } + + + /** + * Render playlist to storage media clip (open handle) + * + * @param string $sessid + * session id + * @param string $plid + * playlist gunid + * @return string + * render token + */ + protected function renderPlaylistToStorageOpen($sessid, $plid) + { + require_once("Renderer.php"); + $owner = Alib::GetSessUserId($sessid); + if (PEAR::isError($owner)) { + return $owner; + } + $r = Renderer::rnRender2FileOpen($this, $plid, $owner); + if (PEAR::isError($r)) { + return $r; + } + return $r; + } + + + /** + * Render playlist to storage media clip (check results) + * + * @param string $token + * render token + * @return hasharray: + * status : string - success | working | fault + * gunid : string - gunid of result file + */ + protected function renderPlaylistToStorageCheck($token) + { + require_once("Renderer.php"); + $r = Renderer::rnRender2StorageCheck($this, $token); + if (PEAR::isError($r)) { + return $r; + } + return $r; + } + + + /** + * Render playlist to RSS file (open handle) + * + * @param string $sessid + * session id + * @param string $plid + * playlist gunid + * @return string + * render token + */ + protected function renderPlaylistToRSSOpen($sessid, $plid) + { + global $CC_CONFIG; + $token = '123456789abcdeff'; + $fakeFile = $CC_CONFIG['accessDir']."/$token.rss"; + file_put_contents($fakeFile, "fake rendered file"); + return array('token'=>$token); + } + + + /** + * Render playlist to RSS file (check results) + * + * @param string $token + * render token + * @return hasharray : + * status : string - success | working | fault + * url : string - readable url + */ + protected function renderPlaylistToRSSCheck($token) + { + $fakeFile = $CC_CONFIG['accessDir']."/$token.rss"; + if ($token != '123456789abcdeff' || !file_exists($fakeFile)) { + return PEAR::raiseError( + "LocStor::renderPlaylistToRSSCheck: invalid token ($token)" + ); + } + $fakeFUrl = BasicStor::GetUrlPart()."access/$token.rss"; + return array( + 'status'=> 'success', + 'url' => $fakeFUrl, + ); + } + + + /** + * Render playlist to RSS file (close handle) + * + * @param string $token + * render token + * @return boolean + * status + */ + protected function renderPlaylistToRSSClose($token) + { + if ($token != '123456789abcdeff') { + return PEAR::raiseError( + "LocStor::renderPlaylistToRSSClose: invalid token" + ); + } + $fakeFile = $CC_CONFIG['accessDir']."/$token.rss"; + unlink($fakeFile); + return TRUE; + } + + + /*================================================= storage admin methods */ + + /* ------------------------------------------------------- backup methods */ + + /** + * Create backup of storage (open handle) + * + * @param string $sessid + * session id + * @param array $criteria + * see search criteria + * @return array + * token : string - backup token + */ + protected function createBackupOpen($sessid, $criteria='') + { + require_once("Backup.php"); + $bu = new Backup($this); + if (PEAR::isError($bu)) { + return $bu; + } + $r = $bu->openBackup($sessid,$criteria); + if ($r === FALSE) { + return PEAR::raiseError( + "LocStor::createBackupOpen: false returned from Backup" + ); + } + return $r; + } + + + /** + * Create backup of storage (check results) + * + * @param string $token + * backup token + * @return hasharray + * with field: + * status : string - susccess | working | fault + * faultString: string - description of fault + * token : stirng - backup token + * url : string - access url + */ + protected function createBackupCheck($token) + { + require_once("Backup.php"); + $bu = new Backup($this); + if (PEAR::isError($bu)) { + return $bu; + } + return $bu->checkBackup($token); + } + + + /** + * Create backup of storage (list results) + * + * @param string $sessid + * session id + * @param status $stat + * if this parameter is not set, then return with all unclosed backups + * @return array + * array of hasharray with field: + * status : string - susccess | working | fault + * token : stirng - backup token + * url : string - access url + */ + protected function createBackupList($sessid, $stat='') + { + require_once("Backup.php"); + $bu = new Backup($this); + if (PEAR::isError($bu)) { + return $bu; + } + return $bu->listBackups($stat); + } + + + /** + * Create backup of storage (close handle) + * + * @param string $token + * backup token + * @return boolean + * status + */ + protected function createBackupClose($token) + { + require_once("Backup.php"); + $bu = new Backup($this); + if (PEAR::isError($bu)) { + return $bu; + } + return $bu->closeBackup($token); + } + + + /* ------------------------------------------------------ restore methods */ + + /** + * Restore a backup file (open handle) + * + * @param string $sessid + * session id + * @param string $chsum + * md5 checksum of imported file + * @return array + * array with: + * url string: writable URL + * fname string: writable local filename + * token string: PUT token + */ + protected function restoreBackupOpen($sessid, $chsum) + { + $userid = Alib::getSessUserId($sessid); + if (PEAR::isError($userid)) { + return $userid; + } + $r = $this->bsOpenPut($chsum, NULL, $userid); + if (PEAR::isError($r)) { + return $r; + } + return $r; + } + + + /** + * Restore a backup file (close put handle) + * + * @param string $sessid + * session id + * @param string $token + * "put" token + * @return string $token + * restore token + */ + protected function restoreBackupClosePut($sessid, $token) { + $arr = $this->bsClosePut($token); + if (PEAR::isError($arr)) { + return $arr; + } + $fname = $arr['fname']; + require_once('Restore.php'); + $rs = new Restore($this); + if (PEAR::isError($rs)) { + return $rs; + } + return $rs->openRestore($sessid, $fname); + } + + + /** + * Restore a backup file (check state) + * + * @param string $token + * restore token + * @return array + * status - fields: + * token: string - restore token + * status: string - working | fault | success + * faultString: string - description of fault + */ + protected function restoreBackupCheck($token) + { + require_once('Restore.php'); + $rs = new Restore($this); + if (PEAR::isError($rs)) { + return $rs; + } + return $rs->checkRestore($token); + } + + + /** + * Restore a backup file (close handle) + * + * @param string $token + * restore token + * @return array + * status - fields: + * token: string - restore token + * status: string - working | fault | success + */ + protected function restoreBackupClose($token) { + require_once('Restore.php'); + $rs = new Restore($this); + if (PEAR::isError($rs)) { + return $rs; + } + return $rs->closeRestore($token); + } + + + /*===================================================== auxiliary methods */ + /** + * Dummy method - only returns Campcaster version + * + * @return string + */ + public static function getVersion() + { + return CAMPCASTER_VERSION; + } + + /** + * Open upload transport (from station to hub) + * + * @param string $sessid + * session id + * @param string $chsum + * checksum + * @return array + * hasharray with: + * url string: writable URL + * token string: PUT token + */ + function uploadOpen($sessid, $chsum) + { + $owner = Alib::GetSessUserId($sessid); + if (PEAR::isError($owner)) { + return $owner; + } + $res = $this->bsOpenPut($chsum, NULL, $owner); + if (PEAR::isError($res)) { + return $res; + } + return array('url'=>$res['url'], 'token'=>$res['token']); + } + + + /** + * Close upload transport + * + * @param string $token + * transport token + * @param string $trtype + * transport type + * @param array $pars + * transport parameters + * @return mixed + */ + function uploadClose($token, $trtype, $pars=array()) + { + $res = $this->bsClosePut($token); + if (PEAR::isError($res)) { + return $res; + } + extract($res); // fname, owner + switch ($trtype) { + case "audioclip": + $mdtoken = $pars['mdpdtoken']; + $res = $this->bsClosePut($mdtoken); + if (PEAR::isError($res)) { + return $res; + } + $mdfname = $res['fname']; + if ($gunid == '') { + $gunid = NULL; + } + $values = array( + "filename" => $pars['name'], + "filepath" => $fname, + "metadata" => $mdfname, + "gunid" => $pars['gunid'], + "filetype" => "audioclip" + ); + $storedFile = StoredFile::Insert($values); + if (PEAR::isError($storedFile)) { + return $storedFile; + } + $res = $storedFile->getId(); + @unlink($fname); + @unlink($mdfname); + break; + case "playlist": + if ($gunid == '') { + $gunid = NULL; + } + $values = array( + "filename" => $pars['name'], + "metadata" => $fname, + "gunid" => $pars['gunid'], + "filetype" => "playlist" + ); + $storedFile = StoredFile::Insert($values); + if (PEAR::isError($storedFile)) { + return $storedFile; + } + $res = $storedFile->getId(); + @unlink($fname); + break; + case "playlistPkg": + $chsum = md5_file($fname); + // importPlaylistOpen: + $res = $this->bsOpenPut($chsum, NULL, $owner); + if (PEAR::isError($res)) { + return $res; + } + $dest = $res['fname']; + $token = $res['token']; + copy($fname, $dest); + $r = $this->importPlaylistClose($token); + if (PEAR::isError($r)) { + return $r; + } + @unlink($fname); + return $r; + break; + case "searchjob": + $crits = file_get_contents($fname); + $criteria = unserialize($crits); + @unlink($fname); + $results = $this->localSearch($criteria); + if (PEAR::isError($results)) { + return $results; + } + $realfile = tempnam($this->accessDir, 'searchjob_'); + @chmod($realfile, 0660); + $len = file_put_contents($realfile, serialize($results)); + $acc = BasicStor::bsAccess($realfile, '', NULL, 'download'); + if (PEAR::isError($acc)) { + return $acc; + } + $url = BasicStor::GetUrlPart()."access/".basename($acc['fname']); + $chsum = md5_file($realfile); + $size = filesize($realfile); + $res = array( + 'url'=>$url, 'token'=>$acc['token'], + 'chsum'=>$chsum, 'size'=>$size, + 'filename'=>$filename + ); + return $res; + break; + case "metadata": + break; + default: + } + return $res; + } + + + /** + * Open download transport + * + * @param string $sessid + * session id + * @param string $trtype + * transport type + * @param array $pars + * transport parameters + * @return hasharray with: + * url string: writable URL + * token string: PUT token + */ + function downloadOpen($sessid, $trtype, $pars=array()) + { +// global $CC_CONFIG; +// switch ($trtype) { +// case "unknown": +// case "audioclip": +// case "metadata": +// case "playlist": +// case "playlistPkg": +// if (!isset($pars['gunid'])) { +// return PEAR::raiseError("Archive::downloadOpen: gunid not set"); +// } +// break; +// } +// $gunid = $pars['gunid']; +// // resolve trtype by object type: +// if ( ($trtype == 'unknown') || ($trtype == 'playlistPkg') ) { +// $media = StoredFile::RecallByGunid($gunid); +// $trtype2 = $media->getType(); +// if (PEAR::isError($trtype2)) { +// return $trtype2; +// } +// // required with content: +// $trtype = ( ($trtype2 == 'playlist') && ($trtype == 'playlistPkg') ? +// 'playlistPkg' : $trtype2); +// //return PEAR::raiseError("Archive::downloadOpen: TT=$trtype TT2=$trtype2 G=$gunid"); +// } +// switch ($trtype) { +// case "audioclip": +// $res = $this->downloadRawAudioDataOpen($sessid, $gunid); +// break; +// case "metadata": +// $res = $this->downloadMetadataOpen($sessid, $gunid); +// break; +// case "playlist": +// $res = $this->accessPlaylist($sessid, $gunid); +// break; +// case "playlistPkg": +// $res = $this->bsExportPlaylistOpen($gunid); +// if (PEAR::isError($res)) { +// return $res; +// } +// $tmpn = tempnam($CC_CONFIG['transDir'], 'plExport_'); +// $plfpath = "$tmpn.lspl"; +// copy($res['fname'], $plfpath); +// $res = $this->bsExportPlaylistClose($res['token']); +// if (PEAR::isError($res)) { +// return $res; +// } +// $fname = "transported_playlist.lspl"; +// $id = BasicStor::IdFromGunid($gunid); +// $acc = BasicStor::bsAccess($plfpath, 'lspl', NULL, 'download'); +// if (PEAR::isError($acc)) { +// return $acc; +// } +// $url = BasicStor::GetUrlPart()."access/".basename($acc['fname']); +// $chsum = md5_file($plfpath); +// $size = filesize($plfpath); +// $res = array( +// 'url'=>$url, 'token'=>$acc['token'], +// 'chsum'=>$chsum, 'size'=>$size, +// 'filename'=>$fname +// ); +// break; +// case "searchjob": +// $res = $pars; +// break; +// case "file": +// $res = array(); +// break; +// default: +// return PEAR::raiseError("Archive::downloadOpen: NotImpl ($trtype)"); +// } +// if (PEAR::isError($res)) { +// return $res; +// } +// switch ($trtype) { +// case "audioclip": +// case "metadata": +// case "playlist": +// case "playlistPkg": +// $f = StoredFile::RecallByGunid($gunid); +// $title = $f->getTitle(); +// break; +// case "searchjob": +// $title = 'searchjob'; +// break; +// case "file": +// $title = 'regular file'; +// break; +// default: +// } +// $res['title'] = $title; +// $res['trtype'] = $trtype; +// return $res; + } + + + /** + * Close download transport + * + * @param string $token + * transport token + * @param string $trtype + * transport type + * @return array + * hasharray with: + * url string: writable URL + * token string: PUT token + */ + function downloadClose($token, $trtype) + { + switch ($trtype) { + case "audioclip": + $res = $this->downloadRawAudioDataClose($token); + if (PEAR::isError($res)) { + return $res; + } + return $res; + case "metadata": + $res = $this->downloadMetadataClose($token); + return $res; + case "playlist": + $res = $this->releasePlaylist(NULL/*$sessid*/, $token); + return $res; + case "playlistPkg": + $res = BasicStor::bsRelease($token, 'download'); + if (PEAR::isError($res)) { + return $res; + } + $realFname = $r['realFname']; + @unlink($realFname); + if (preg_match("|(plExport_[^\.]+)\.lspl$|", $realFname, $va)) { + list(,$tmpn) = $va; + $tmpn = $CC_CONFIG['transDir']."/$tmpn"; + if (file_exists($tmpn)) { + @unlink($tmpn); + } + } + return $res; + case "searchjob": + $res = BasicStor::bsRelease($token, 'download'); + return $res; + case "file": + return array(); + default: + return PEAR::raiseError("Archive::downloadClose: NotImpl ($trtype)"); + } + } + + + /** + * Prepare hub initiated transport + * + * @param string $target + * hostname of transport target + * @param string $trtype + * transport type + * @param string $direction + * 'up' | 'down' + * @param array $pars + * transport parameters + * @return mixed + */ + function prepareHubInitiatedTransfer( + $target, $trtype='file', $direction='up',$pars=array()) + { + $tr = new Transport($this); + $trec = TransportRecord::create($tr, $trtype, $direction, + array_merge($pars, array('target'=>$target))); + if (PEAR::isError($trec)) { + return $trec; + } + return TRUE; + } + + + /** + * List hub initiated transports + * + * @param string $target + * hostname of transport target + * @param string $direction + * 'up' | 'down' + * @param string $trtok + * transport token + * @return mixed + */ + function listHubInitiatedTransfers( + $target=NULL, $direction=NULL, $trtok=NULL) + { + $tr = new Transport($this); + $res = $tr->getTransports($direction, $target, $trtok); + return $res; + } + + + /** + * Set state of hub initiated transport + * + * @param string $target + * hostname of transport target + * @param string $trtok + * transport token + * @param string $state + * transport state + * @return TransportRecord|PEAR_Error + */ + function setHubInitiatedTransfer($target, $trtok, $state) + { + $tr = new Transport($this); + $trec = TransportRecord::recall($tr, $trtok); + if (PEAR::isError($trec)) { + return $trec; + } + $r = $trec->setState($state); + if (PEAR::isError($r)) { + return $r; + } + return $trec; + } + + /* ==================================================== auxiliary methods */ + +} // class LocStor +?> \ No newline at end of file diff --git a/application/models/M3uPlaylist.php b/application/models/M3uPlaylist.php new file mode 100644 index 000000000..388c24aa5 --- /dev/null +++ b/application/models/M3uPlaylist.php @@ -0,0 +1,352 @@ +lock($gb, $subjid); +// if (PEAR::isError($r)) { +// return $r; +// } +// foreach ($arr as $i => $it) { +// list($md, $uri) = preg_split("|\n|", $it); +// list($length, $title) = preg_split("|, *|", $md); +// // $gunid = StoredFile::CreateGunid(); +// $gunid = ( isset($gunids[basename($uri)]) ? $gunids[basename($uri)] : NULL); +// $acId = BasicStor::IdFromGunid($gunid); +// if (PEAR::isError($acId)) { +// return $acId; +// } +// $length = Playlist::secondsToPlaylistTime($length); +// $offset = '???'; +// if (preg_match("|\.([a-zA-Z0-9]+)$|", $uri, $va)) { +// switch (strtolower($ext = $va[1])) { +// case "lspl": +// case "xml": +// case "smil": +// case "m3u": +// $acId = $gb->bsImportPlaylistRaw($gunid, +// $aPath, $uri, $ext, $gunids, $subjid); +// if (PEAR::isError($acId)) { +// break; +// } +// //no break! +// default: +// if (is_null($gunid)) { +// return PEAR::raiseError( +// "M3uPlaylist::import: no gunid"); +// } +// $r = $pl->addAudioClip($acId); +// if (PEAR::isError($r)) { +// return $r; +// } +// } +// } +// } +// $r = $pl->unlock($gb); +// if (PEAR::isError($r)) { +// return $r; +// } +// return $pl; + } + + /** + * Import M3U file to storage + * + * @param GreenBox $gb + * @param string $data + * local path to M3U file + * @return string + * XML playlist in Campcaster playlist format + */ + function convert2lspl(&$gb, $data) + { + $arr = M3uPlaylist::parse($data); + if (PEAR::isError($arr)) { + return $arr; + } + $ind = ''; + $ind2 = $ind.INDCH; + $ind3 = $ind2.INDCH; + $res = ''; + foreach ($arr as $i => $it) { + list($md, $uri) = preg_split("|\n|", $it); + list($length, $title) = preg_split("|, *|", $md); + $gunid = StoredFile::CreateGunid(); + $gunid2 = StoredFile::CreateGunid(); + $length = Playlist::secondsToPlaylistTime($length); + $offset = '???'; + $clipStart = '???'; + $clipEnd = '???'; + $clipLength = '???'; + $uri_h = preg_replace("|--|", "d;d;", htmlspecialchars("$uri")); + if (preg_match("|\.([a-zA-Z0-9]+)$|", $uri, $va)) { + switch (strtolower($ext = $va[1])) { + case "lspl": + case "xml": + case "smil": + case "m3u": + $acOrPl = "$ind3 ". + "\n"; + break; + default: + $acOrPl = "$ind3 ". + "\n"; + break; + } + } + $res .= "$ind2\n". + $acOrPl. + "$ind2\n"; + } + $res = "$ind\n". + "$ind\n". + "$ind2\n". + "$res". + "$ind\n"; + return $res; + } +} // class M3uPlaylist + + +/** + * @package Campcaster + * @subpackage StorageServer + * @copyright 2010 Sourcefabric O.P.S. + * @license http://www.gnu.org/licenses/gpl.txt + */ +class M3uPlaylistBodyElement { + function convert2lspl(&$tree, $ind='') + { + $ind2 = $ind.INDCH; + if ($tree->name != 'body') { + return PEAR::raiseError("M3uPlaylist::parse: body tag expected"); + } + if (isset($tree->children[1])) { + return PEAR::raiseError(sprintf( + "M3uPlaylist::parse: unexpected tag %s in tag body", + $tree->children[1]->name + )); + } + $res = M3uPlaylistParElement::convert2lspl($tree->children[0], $ind2); + if (PEAR::isError($res)) { + return $res; + } + $gunid = StoredFile::CreateGunid(); + $playlength = '???'; // *** + $res = "$ind\n". + "$ind\n". + "$ind2\n". + "$res". + "$ind\n"; + return $res; + } +} + + +/** + * @package Campcaster + * @subpackage StorageServer + * @copyright 2010 Sourcefabric O.P.S. + * @license http://www.gnu.org/licenses/gpl.txt + */ +class M3uPlaylistParElement { + function convert2lspl(&$tree, $ind='') + { + if ($tree->name != 'par') { + return PEAR::raiseError("M3uPlaylist::parse: par tag expected"); + } + $res = ''; + foreach ($tree->children as $i => $ch) { + $ch =& $tree->children[$i]; + $r = M3uPlaylistAudioElement::convert2lspl($ch, $ind.INDCH); + if (PEAR::isError($r)) { + return $r; + } + $res .= $r; + } + return $res; + } +} + + +/** + * @package Campcaster + * @subpackage StorageServer + * @copyright 2010 Sourcefabric O.P.S. + * @license http://www.gnu.org/licenses/gpl.txt + */ +class M3uPlaylistAudioElement { + function convert2lspl(&$tree, $ind='') + { + $ind2 = $ind.INDCH; + if ($tree->name != 'audio') { + return PEAR::raiseError("M3uPlaylist::parse: audio tag expected"); + } + if (isset($tree->children[2])) { + return PEAR::raiseError(sprintf( + "M3uPlaylist::parse: unexpected tag %s in tag audio", + $tree->children[2]->name + )); + } + $res = ''; $fadeIn = 0; $fadeOut = 0; + foreach ($tree->children as $i => $ch) { + $ch =& $tree->children[$i]; + $r = M3uPlaylistAnimateElement::convert2lspl($ch, $ind2); + if (PEAR::isError($r)) { + return $r; + } + switch ($r['type']) { + case "fadeIn": + $fadeIn = $r['val']; + break; + case "fadeOut": + $fadeOut = $r['val']; + break; + } + } + if ($fadeIn > 0 || $fadeOut > 0) { + $fadeIn = Playlist::secondsToPlaylistTime($fadeIn); + $fadeOut = Playlist::secondsToPlaylistTime($fadeOut); + $fInfo = "$ind2\n"; + } else { + $fInfo = ''; + } + $plElGunid = StoredFile::CreateGunid(); + $aGunid = StoredFile::CreateGunid(); + $title = basename($tree->attrs['src']->val); + $offset = Playlist::secondsToPlaylistTime($tree->attrs['begin']->val); + $playlength = '???'; # *** + $res = "$ind\n". + "$ind2\n". + $fInfo. + "$ind\n"; + return $res; + } +} // class M3uPlaylistAudioElement + + +/** + * @package Campcaster + * @subpackage StorageServer + * @copyright 2010 Sourcefabric O.P.S. + * @license http://www.gnu.org/licenses/gpl.txt + */ +class M3uPlaylistAnimateElement { + function convert2lspl(&$tree, $ind='') { + if ($tree->name != 'animate') { + return PEAR::raiseError("M3uPlaylist::parse: animate tag expected"); + } + if ($tree->attrs['attributeName']->val == 'soundLevel' && + $tree->attrs['from']->val == '0%' && + $tree->attrs['to']->val == '100%' && + $tree->attrs['calcMode']->val == 'linear' && + $tree->attrs['fill']->val == 'freeze' && + $tree->attrs['begin']->val == '0s' && + preg_match("|^([0-9.]+)s$|", $tree->attrs['end']->val, $va) + ) { + return array('type'=>'fadeIn', 'val'=>$va[1]); + } + if ($tree->attrs['attributeName']->val == 'soundLevel' && + $tree->attrs['from']->val == '100%' && + $tree->attrs['to']->val == '0%' && + $tree->attrs['calcMode']->val == 'linear' && + $tree->attrs['fill']->val == 'freeze' && + preg_match("|^([0-9.]+)s$|", $tree->attrs['begin']->val, $vaBegin) && + preg_match("|^([0-9.]+)s$|", $tree->attrs['end']->val, $vaEnd) + ) { + return array('type'=>'fadeOut', 'val'=>($vaEnd[1] - $vaBegin[1])); + } + return PEAR::raiseError( + "M3uPlaylistAnimateElement::convert2lspl: animate parameters too general" + ); + } +} + +?> \ No newline at end of file diff --git a/application/models/Playlist.php b/application/models/Playlist.php new file mode 100644 index 000000000..a0ec8385d --- /dev/null +++ b/application/models/Playlist.php @@ -0,0 +1,1422 @@ + "DbName", "dc:creator" => "DbCreator", "dc:description" => "DbDescription", "dcterms:extent" => "length"); + + + public function __construct() + { + + } + + public static function Insert($p_values) + { + // Create the StoredPlaylist object + $storedPlaylist = new Playlist(); + $storedPlaylist->name = isset($p_values['filename']) ? $p_values['filename'] : date("H:i:s"); + $storedPlaylist->mtime = new DateTime("now"); + + $pl = new CcPlaylist(); + $pl->setDbName($storedPlaylist->name); + $pl->setDbState("incomplete"); + $pl->setDbMtime($storedPlaylist->mtime); + $pl->save(); + + $storedPlaylist->id = $pl->getDbId(); + $storedPlaylist->setState('ready'); + + return $storedPlaylist->id; + + } + + public static function Delete($id) { + $pl = CcPlaylistQuery::create()->findPK($id); + if($pl === NULL) + return FALSE; + + $pl->delete(); + return TRUE; + } + + + /** + * Delete the file from all playlists. + * @param string $p_fileId + */ + public static function DeleteFileFromAllPlaylists($p_fileId) + { + CcPlaylistcontentsQuery::create()->filterByDbFileId($p_fileId)->delete(); + } + + + /** + * Fetch instance of Playlist object.
+ * + * @param string $id + * DB id of file + * @return Playlist|FALSE + * Return FALSE if the object doesnt exist in the DB. + */ + public static function Recall($id) { + + $pl = CcPlaylistQuery::create()->findPK($id); + if($pl === NULL) + return FALSE; + + $storedPlaylist = new Playlist(); + $storedPlaylist->id = $pl->getDbId(); + $storedPlaylist->name = $pl->getDbName(); + $storedPlaylist->state = $pl->getDbState(); + $storedPlaylist->currentlyaccessing = $pl->getDbCurrentlyaccessing(); + $storedPlaylist->editedby = $pl->getDbEditedby(); + $storedPlaylist->mtime = $pl->getDbMtime(); + + return $storedPlaylist; + } + + /** + * Rename stored virtual playlist + * + * @param string $p_newname + * @return TRUE|PEAR_Error + */ + public function setName($p_newname) + { + $pl = CcPlaylistQuery::create()->findPK($this->id); + + if($pl === NULL) + return FALSE; + + $pl->setDbName($p_newname); + $pl->setDbMtime(new DateTime("now")); + $pl->save(); + + $this->name = $p_newname; + return TRUE; + } + + /** + * Get mnemonic playlist name + * + * @param string $p_gunid + * global unique id of playlist + * @return string + */ + public function getName($id=NULL) + { + if (is_null($id)) { + return $this->name; + } + $pl = CcPlaylistQuery::create()->findPK($id); + if($pl === NULL) + return FALSE; + + return $pl->getDbName(); + } + + /** + * Set state of virtual playlist + * + * @param string $p_state + * 'empty'|'incomplete'|'ready'|'edited' + * @param int $p_editedby + * user id | 'NULL' for clear editedBy field + * @return TRUE|PEAR_Error + */ + public function setState($p_state, $p_editedby=NULL) + { + $pl = CcPlaylistQuery::create()->findPK($this->id); + + if($pl === NULL) + return FALSE; + + $pl->setDbState($p_state); + $pl->setDbMtime(new DateTime("now")); + + $eb = (!is_null($p_editedby) ? $p_editedby : NULL); + $pl->setDbEditedby($eb); + + $pl->save(); + + $this->state = $p_state; + $this->editedby = $p_editedby; + return TRUE; + } + + /** + * Get storage-internal file state + * + * @param string $p_gunid + * global unique id of file + * @return string + * see install() + */ + public function getState($id=NULL) + { + if (is_null($id)) { + return $this->state; + } + + $pl = CcPlaylistQuery::create()->findPK($id); + if($pl === NULL) + return FALSE; + + return $pl->getDbState(); + } + + /** + * Returns true if virtual file is currently in use.
+ * Static or dynamic call is possible. + * + * @param string $p_gunid + * optional (for static call), global unique id + * @return boolean|PEAR_Error + */ + public function isAccessed($id=NULL) + { + if (is_null($id)) { + return ($this->currentlyaccessing > 0); + } + + $pl = CcPlaylistQuery::create()->findPK($id); + if (is_null($pl)) { + return PEAR::raiseError( + "StoredPlaylist::isAccessed: invalid id ($id)", + GBERR_FOBJNEX + ); + } + + return ($pl->getDbCurrentlyaccessing() > 0); + } + + /** + * Returns id of user editing playlist + * + * @param string $p_playlistId + * playlist global unique ID + * @return int id of user editing playlist + */ + public function isEdited() { + + if($this->state === 'edited') { + return $this->editedby; + } + return FALSE; + } + +/** + * Set playlist edit flag + * + * @param string $p_playlistId + * Playlist unique ID + * @param boolean $p_val + * Set/clear of edit flag + * @param string $p_sessid + * Session id + * @param int $p_subjid + * Subject id (if sessid is not specified) + * @return boolean + * TRUE on success. + */ + + public function setEditFlag($p_subjid, $p_val=TRUE) { + + if ($p_val) { + $r = $this->setState('edited', $p_subjid); + } else { + $r = $this->setState('ready'); + } + if ($r === FALSE) { + return FALSE; + } + return TRUE; + } + + /** + * Return local ID of virtual file. + * + * @return int + */ + public function getId() { + return $this->id; + } + + private function getNextPos() { + + $res = CcPlaylistQuery::create() + ->findPK($this->id) + ->computeLastPosition(); + + if(is_null($res)) + return 0; + + return $res + 1; + } + + /** + * Get the entire playlist as a two dimentional array, sorted in order of play. + * @return array + */ + public function getContents() { + $files = array(); + $rows = CcPlaylistcontentsQuery::create() + ->joinWith('CcFiles') + ->orderByDbPosition() + ->filterByDbPlaylistId($this->id) + ->find(); + + foreach ($rows as $row) { + $files[] = $row->toArray(BasePeer::TYPE_FIELDNAME, true, true); + } + + return $files; + } + + public function getLength() { + $res = CcPlaylistQuery::create() + ->findPK($this->id) + ->computeLength(); + + if(is_null($res)) + return '00:00:00.000000'; + + return $res; + } + + /** + * Create instance of Playlist object and insert empty file + * + * @param string $fname + * name of new file + * @return instance of Playlist object + */ + public function create($fname=NULL) + { + $values = array("filename" => $fname); + $pl_id = Playlist::Insert($values); + $this->id = $pl_id; + return $this->id; + } + + /** + * Lock playlist for edit + * + * @param string $sessid + * session id + * @param int $subjid + * local subject (user) id + * @param boolean $val + * if false do unlock + * @return boolean + * previous state or error object + */ + public function lock($subjid, $val=TRUE) + { + if ($val && $this->isEdited() !== FALSE) { + return PEAR::raiseError( + 'Playlist::lock: playlist already locked' + ); + } + $r = $this->setEditFlag($subjid, $val); + return $r; + } + + + /** + * Unlock playlist + * + * @param sessId + * reference to GreenBox object + * @return boolean + * previous state or error object + */ + public function unlock($subjid) + { + $r = $this->lock($subjid, FALSE); + return $r; + } + + + /** + * Add audio clip to the playlist + * + * @param string $p_id + * local ID of added file + * @param string $p_position + * optional, Which position in the playlist to insert the audio clip + * @param string $p_fadeIn + * optional, in time format hh:mm:ss.ssssss - total duration + * @param string $p_fadeOut + * optional, in time format hh:mm:ss.ssssss - total duration + * @param string $p_clipLength + * optional length in in time format hh:mm:ss.ssssss - + * for webstream (or for overrule length of audioclip) + * @return true|PEAR_Error + * TRUE on success + */ + public function addAudioClip($p_mediaId, $p_position=NULL, $p_fadeIn=NULL, $p_fadeOut=NULL, $p_clipLength=NULL, $p_cuein=NULL, $p_cueout=NULL) + { + //get audio clip. + $media = StoredFile::Recall($p_mediaId); + if (is_null($media) || PEAR::isError($media)) { + return $media; + } + + $metadata = $media->getMetadata(); + $length = $metadata["length"]; + + if (!is_null($p_clipLength)) { + $length = $p_clipLength; + } + + // insert at end of playlist. + if (is_null($p_position)) + $p_position = $this->getNextPos(); + + // insert default values if parameter was empty + $p_cuein = !is_null($p_cuein) ? $p_cuein : '00:00:00.000000'; + $p_cueout = !is_null($p_cueout) ? $p_cueout : $length; + + $con = Propel::getConnection("campcaster"); + $sql = "SELECT INTERVAL '{$p_cueout}' - INTERVAL '{$p_cuein}'"; + $r = $con->query($sql); + $p_cliplength = $r->fetchColumn(0); + + $res = $this->insertPlaylistElement($this->id, $p_mediaId, $p_position, $p_cliplength, $p_cuein, $p_cueout, $p_fadeIn, $p_fadeOut); + + return TRUE; + } + + + /** + * Remove audioClip from playlist + * + * @param int $position + * position of audioclip in the playlist. + * @return boolean + */ + public function delAudioClip($pos) + { + if($pos < 0 || $pos >= $this->getNextPos()) + return FALSE; + + $row = CcPlaylistcontentsQuery::create() + ->filterByDbPlaylistId($this->id) + ->filterByDbPosition($pos) + ->findOne(); + + if(is_null($row)) + return FALSE; + + $row->delete(); + return $row; + } + + /** + * Move audioClip to the new position in the playlist + * + * @param int $oldPos + * old positioin in playlist + * @param int $newPos + * new position in playlist + * @return mixed + */ + public function moveAudioClip($oldPos, $newPos) + { + if($newPos < 0 || $newPos >= $this->getNextPos() || $oldPos < 0 || $oldPos >= $this->getNextPos()) + return FALSE; + + $row = $this->delAudioClip($oldPos); + if($row === FALSE) + return FALSE; + + $res = $this->addAudioClip($row->getDbFileId(), $newPos, $row->getDbFadein(), $row->getDbFadeout(), $row->getDbCliplength(), $row->getDbCuein(), $row->getDbCueout()); + if($res !== TRUE) + return FALSE; + + return TRUE; + } + + + /** + * Change fadeIn and fadeOut values for playlist Element + * + * @param int $pos + * position of audioclip in playlist + * @param string $fadeIn + * new value in ss.ssssss or extent format + * @param string $fadeOut + * new value in ss.ssssss or extent format + * @return boolean + */ + public function changeFadeInfo($pos, $fadeIn, $fadeOut) + { + $errArray= array(); + $con = Propel::getConnection("campcaster"); + + if(is_null($pos) || $pos < 0 || $pos >= $this->getNextPos()) { + $errArray["error"]="Invalid position."; + return $errArray; + } + + $row = CcPlaylistcontentsQuery::create() + ->filterByDbPlaylistId($this->id) + ->filterByDbPosition($pos) + ->findOne(); + + $clipLength = $row->getDbCliplength(); + + if(!is_null($fadeIn)) { + + $sql = "SELECT INTERVAL '{$fadeIn}' > INTERVAL '{$clipLength}'"; + $r = $con->query($sql); + if($r->fetchColumn(0)) { + $errArray["error"]="Fade In can't be larger than overall playlength."; + return $errArray; + } + + $row->setDbFadein($fadeIn); + } + if(!is_null($fadeOut)){ + + $sql = "SELECT INTERVAL '{$fadeOut}' > INTERVAL '{$clipLength}'"; + $r = $con->query($sql); + if($r->fetchColumn(0)) { + $errArray["error"]="Fade Out can't be larger than overall playlength."; + return $errArray; + } + + $row->setDbFadeout($fadeOut); + } + + $row->save(); + + return array("fadeIn"=>$fadeIn, "fadeOut"=>$fadeOut); + } + + /** + * Change cueIn/cueOut values for playlist element + * + * @param int $pos + * position of audioclip in playlist + * @param string $cueIn + * new value in ss.ssssss or extent format + * @param string $cueOut + * new value in ss.ssssss or extent format + * @return boolean or pear error object + */ + public function changeClipLength($pos, $cueIn, $cueOut) + { + $errArray= array(); + $con = Propel::getConnection("campcaster"); + + if(is_null($cueIn) && is_null($cueOut)) { + $errArray["error"]="Cue in and cue out are null."; + return $errArray; + } + + if(is_null($pos) || $pos < 0 || $pos >= $this->getNextPos()) { + $errArray["error"]="Invalid position."; + return $errArray; + } + + $row = CcPlaylistcontentsQuery::create() + ->joinWith(CcFilesPeer::OM_CLASS) + ->filterByDbPlaylistId($this->id) + ->filterByDbPosition($pos) + ->findOne(); + + $oldCueIn = $row->getDBCuein(); + $oldCueOut = $row->getDbCueout(); + $fadeIn = $row->getDbFadein(); + $fadeOut = $row->getDbFadeout(); + + $file = $row->getCcFiles(); + $origLength = $file->getDbLength(); + + + if(!is_null($cueIn) && !is_null($cueOut)){ + + if($cueOut === ""){ + $cueOut = $origLength; + } + + $sql = "SELECT INTERVAL '{$cueIn}' > INTERVAL '{$cueOut}'"; + $r = $con->query($sql); + if($r->fetchColumn(0)) { + $errArray["error"]= "Can't set cue in to be larger than cue out."; + return $errArray; + } + + $sql = "SELECT INTERVAL '{$cueOut}' > INTERVAL '{$origLength}'"; + $r = $con->query($sql); + if($r->fetchColumn(0)){ + $errArray["error"] = "Can't set cue out to be greater than file length."; + return $errArray; + } + + $sql = "SELECT INTERVAL '{$cueOut}' - INTERVAL '{$cueIn}'"; + $r = $con->query($sql); + $cliplength = $r->fetchColumn(0); + + $row->setDbCuein($cueIn); + $row->setDbCueout($cueOut); + $row->setDBCliplength($cliplength); + + } + else if(!is_null($cueIn)) { + + $sql = "SELECT INTERVAL '{$cueIn}' > INTERVAL '{$oldCueOut}'"; + $r = $con->query($sql); + if($r->fetchColumn(0)) { + $errArray["error"] = "Can't set cue in to be larger than cue out."; + return $errArray; + } + + $sql = "SELECT INTERVAL '{$oldCueOut}' - INTERVAL '{$cueIn}'"; + $r = $con->query($sql); + $cliplength = $r->fetchColumn(0); + + $row->setDbCuein($cueIn); + $row->setDBCliplength($cliplength); + } + else if(!is_null($cueOut)) { + + if($cueOut === ""){ + $cueOut = $origLength; + } + + $sql = "SELECT INTERVAL '{$cueOut}' < INTERVAL '{$oldCueIn}'"; + $r = $con->query($sql); + if($r->fetchColumn(0)) { + $errArray["error"] ="Can't set cue out to be smaller than cue in."; + return $errArray; + } + + $sql = "SELECT INTERVAL '{$cueOut}' > INTERVAL '{$origLength}'"; + $r = $con->query($sql); + if($r->fetchColumn(0)){ + $errArray["error"] ="Can't set cue out to be greater than file length."; + return $errArray; + } + + $sql = "SELECT INTERVAL '{$cueOut}' - INTERVAL '{$oldCueIn}'"; + $r = $con->query($sql); + $cliplength = $r->fetchColumn(0); + + $row->setDbCueout($cueOut); + $row->setDBCliplength($cliplength); + } + + $cliplength = $row->getDbCliplength(); + + $sql = "SELECT INTERVAL '{$fadeIn}' > INTERVAL '{$cliplength}'"; + $r = $con->query($sql); + if($r->fetchColumn(0)){ + $fadeIn = $cliplength; + + $row->setDbFadein($fadeIn); + } + + $sql = "SELECT INTERVAL '{$fadeOut}' > INTERVAL '{$cliplength}'"; + $r = $con->query($sql); + if($r->fetchColumn(0)){ + $fadeOut = $cliplength; + + $row->setDbFadein($fadeOut); + } + + $row->save(); + + return array("cliplength"=>$cliplength, "cueIn"=>$cueIn, "cueOut"=>$cueOut, "length"=>$this->getLength(), + "fadeIn"=>$fadeIn, "fadeOut"=>$fadeOut); + } + + /** + * Find info about clip at specified offset in playlist. + * + * @param string $offset + * current playtime (hh:mm:ss.ssssss) + * @param int $distance + * 0=current clip; 1=next clip ... + * @return array of matching clip info: + *
    + *
  • gunid string, global unique id of clip
  • + *
  • elapsed string, already played time of clip
  • + *
  • remaining string, remaining time of clip
  • + *
  • duration string, total playlength of clip
  • + *
+ */ + public function getPlaylistClipAtPosition($pos) + { + + } + + public function getPLMetaData($category) + { + $cat = $this->categories[$category]; + + if($cat === 'length') { + return $this->getLength(); + } + + $row = CcPlaylistQuery::create()->findPK($this->id); + $method = 'get' . $cat; + return $row->$method(); + } + + public function setPLMetaData($category, $value) + { + $cat = $this->categories[$category]; + + $row = CcPlaylistQuery::create()->findPK($this->id); + $method = 'set' . $cat; + $row->$method($value); + $row->save(); + + return TRUE; + } + + /** + * Return array with gunids of all sub-playlists and clips used in + * the playlist + * + * @return array with hash elements: + * gunid - global id + * type - playlist | audioClip + */ + public function export() + { + + } + + + /** + * Convert playlist time value to float seconds + * + * @param string $plt + * playlist time value (HH:mm:ss.dddddd) + * @return int + * seconds + */ + public static function playlistTimeToSeconds($plt) + { + $arr = preg_split('/:/', $plt); + if (isset($arr[2])) { + return (intval($arr[0])*60 + intval($arr[1]))*60 + floatval($arr[2]); + } + if (isset($arr[1])) { + return intval($arr[0])*60 + floatval($arr[1]); + } + return floatval($arr[0]); + } + + + /** + * Convert float seconds value to playlist time format + * + * @param float $seconds + * @return string + * time in playlist time format (HH:mm:ss.dddddd) + */ + public static function secondsToPlaylistTime($p_seconds) + { + $seconds = $p_seconds; + $milliseconds = intval(($seconds - intval($seconds)) * 1000); + $milliStr = str_pad($milliseconds, 6, '0'); + $hours = floor($seconds / 3600); + $seconds -= $hours * 3600; + $minutes = floor($seconds / 60); + $seconds -= $minutes * 60; + + $res = sprintf("%02d:%02d:%02d.%s", $hours, $minutes, $seconds, $milliStr); + + return $res; + } + + /** + * Export playlist as simplified SMIL XML file. + * + * @param boolean $toString + * if false don't real export, + * return misc info about playlist only + * @return string + * XML string or hasharray with misc info + */ + public function outputToSmil($toString=TRUE) + { + + } + + + /** + * Export playlist as M3U file. + * + * @param boolean $toString + * if false don't real export, + * return misc info about playlist only + * @return string|array + * M3U string or hasharray with misc info + */ + public function outputToM3u($toString=TRUE) + { + + } + + + /** + * Export playlist as RSS XML file + * + * @param boolean $toString + * if false don't really export, + * return misc info about playlist only + * @return mixed + * XML string or hasharray with misc info + */ + public function outputToRss($toString=TRUE) + { + + } + + + /** + * Insert a new playlist element. + * + * @param int $plId + * id of Playlist + * @param int $fileId + * id of File + * @param string $offset + * relative offset in extent format + * @param string $clipstart + * audioClip clipstart in extent format + * @param string $clipEnd + * audioClip clipEnd in extent format + * @param string $clipLength + * audioClip playlength in extent format (?) + * @param string $acGunid + * audioClip gunid + * @param string $acLen + * audioClip length in extent format + * @param string $acTit + * audioClip title + * @param string $fadeIn + * fadeIn value in ss.ssssss or extent format + * @param string $fadeOut + * fadeOut value in ss.ssssss or extent format + + * @return array with fields: + *
    + *
  • plElId int - record id of playlistElement
  • + *
  • plElGunid string - gl.unique id of playlistElement
  • + *
  • fadeInId int - record id
  • + *
  • fadeOutId int - record id
  • + *
+ */ + private function insertPlaylistElement($plId, $fileId, $pos, $clipLength, $cuein, $cueout, $fadeIn=NULL, $fadeOut=NULL) + { + if(is_null($fadeIn)) + $fadeIn = '00:00:00.000'; + if(is_null($fadeOut)) + $fadeOut = '00:00:00.000'; + + $row = new CcPlaylistcontents(); + $row->setDbPlaylistId($plId); + $row->setDbFileId($fileId); + $row->setDbPosition($pos); + $row->save(); + + $row->setDbCliplength($clipLength); + $row->setDbCuein($cuein); + $row->setDbCueout($cueout); + $row->setDbFadein($fadeIn); + $row->setDbFadeout($fadeOut); + + + return TRUE; + } + + /** + * Set playlist length - dcterm:extent + * + * @param string $newPlLen + * new length in extent format + * @param int $parid + * playlist container record id + * @param int $metaParid + * metadata container record id + * @return boolean + */ + private function setPlaylistLength($newPlLen, $parid, $metaParid) + { + $mid = $this->_getMidOrInsert('playlength', $parid, $newPlLen, 'A'); + if (PEAR::isError($mid)) { + return $mid; + } + $r = $this->_setValueOrInsert( + $mid, $newPlLen, $parid, 'playlength', 'A'); + if (PEAR::isError($r)) { + return $r; + } + $mid = $this->_getMidOrInsert('dcterms:extent', $metaParid, $newPlLen); + if (PEAR::isError($mid)) { + return $mid; + } + $r = $this->_setValueOrInsert( + $mid, $newPlLen, $metaParid, 'dcterms:extent'); + if (PEAR::isError($r)) { + return $r; + } + return TRUE; + } + +} // class Playlist + + +/** + * Auxiliary class for GB playlist editing methods + * + * @copyright 2010 Sourcefabric O.P.S. + * @license http://www.gnu.org/licenses/gpl.txt + */ +class PlaylistElement { + private $pl = NULL; + private $plEl = NULL; + + public function PlaylistElement($pl, $plEl) + { + $this->pl = $pl; + $this->plEl = $plEl; + } + + + public function analyze() + { + $plInfo = array( + 'acLen' => '00:00:00.000000', + 'acLenS' => 0, + 'fadeIn' => '00:00:00.000000', + 'fadeInS' => 0, + 'fadeOut' => '00:00:00.000000', + 'fadeOutS' => 0, + 'clipStart' => '00:00:00.000000', + 'clipStartS' => 0, + 'clipEnd' => '00:00:00.000000', + 'clipEndS' => 0 + ); + $plInfo['elOffset'] = $this->plEl['attrs']['relativeOffset']; + $plInfo['elOffsetS'] = Playlist::playlistTimeToSeconds($plInfo['elOffset']); + // cycle over tags inside playlistElement + foreach ($this->plEl['children'] as $j => $acFi) { + switch ($acFi['elementname']) { + case "playlist": + $plInfo['type'] = 'playlist'; + break; + case "audioClip": + $plInfo['type'] = 'audioClip'; + break; + } + switch ($acFi['elementname']) { + case "playlist": + case "audioClip": + $plInfo['acLen'] = $acFi['attrs']['playlength']; + $plInfo['acLenS'] = Playlist::playlistTimeToSeconds($plInfo['acLen']); + $plInfo['acGunid'] = $acFi['attrs']['id']; + break; + case "fadeInfo": + $plInfo['fadeIn'] = $acFi['attrs']['fadeIn']; + $plInfo['fadeInS'] = Playlist::playlistTimeToSeconds($plInfo['fadeIn']); + $plInfo['fadeOut'] = $acFi['attrs']['fadeOut']; + $plInfo['fadeOutS'] = Playlist::playlistTimeToSeconds($plInfo['fadeOut']); + break; + } + $plInfo['clipStart'] = $this->plEl['attrs']['clipStart']; + $plInfo['clipStartS'] = Playlist::playlistTimeToSeconds($this->plEl['attrs']['clipStart']); + $plInfo['clipEnd'] = $this->plEl['attrs']['clipEnd']; + $plInfo['clipEndS'] = Playlist::playlistTimeToSeconds($this->plEl['attrs']['clipEnd']); + } + return $plInfo; + } +} // class PlaylistElement + + +/** + * @package Campcaster + * @subpackage StorageServer + * @copyright 2010 Sourcefabric O.P.S. + * @license http://www.gnu.org/licenses/gpl.txt + * @todo Rename this class PlaylistTag + */ +class PlaylistTagExport +{ + public static function OutputToSmil(&$pl, $plt, $ind='') + { + $ind2 = $ind.INDCH; + $ind3 = $ind2.INDCH; + $ind4 = $ind3.INDCH; + $res = ""; + foreach ($plt['children'] as $ple) { + switch ($ple['elementname']) { + case "playlistElement": + $r = PlaylistElementExport::OutputToSmil($pl, $ple, $ind4); + if (PEAR::isError($r)) { + return $r; + } + if (!is_null($r)) { + $res .= $r; + } + break; + case "metadata": + $r = PlaylistMetadataExport::OutputToSmil($pl, $ple, $ind4); + if (PEAR::isError($r)) { + return $r; + } + if (!is_null($r)) { + $res .= $r; + } + break; + default: + } + } + $res = "$ind\n". + "$ind\n". + "$ind2\n". + "$ind3\n". + "$res". + "$ind3\n". + "$ind2\n". + "$ind\n"; + return $res; + } + + + public static function OutputToM3u(&$pl, $plt, $ind='') + { + $res = ""; + foreach ($plt['children'] as $ple) { + switch ($ple['elementname']) { + case"playlistElement": + $r = PlaylistElementExport::OutputToM3u($pl, $ple); + if (PEAR::isError($r)) { + return $r; + } + if (!is_null($r)) { + $res .= $r; + } + break; + } + } + $res = "#EXTM3U\n$res"; + return $res; + } + + + public static function OutputToRss(&$pl, $plt, $ind='') + { + $ind2 = $ind.INDCH; + $ind3 = $ind2.INDCH; + $res = ""; + foreach ($plt['children'] as $ple) { + switch ($ple['elementname']) { + case "playlistElement": + $r = PlaylistElementExport::OutputToRss($pl, $ple, $ind3); + if (PEAR::isError($r)) { + return $r; + } + if (!is_null($r)) { + $res .= $r; + } + break; + case "metadata": + $r = PlaylistMetadataExport::OutputToRss($pl, $ple, $ind3); + if (PEAR::isError($r)) { + return $r; + } + if (!is_null($r)) { + $res .= $r; + } + break; + default: + } + } + $res = "$ind\n". + "$ind\n". + "$ind2\n". + "$res". + "$ind2\n". + "$ind\n"; + return $res; + } +} + + +/** + * @package Campcaster + * @subpackage StorageServer + * @copyright 2010 Sourcefabric O.P.S. + * @license http://www.gnu.org/licenses/gpl.txt + * @todo Rename this class "PlaylistElement" + */ +class PlaylistElementExport { + + public static function OutputToSmil(&$pl, $ple, $ind='') + { + $acOrPl = NULL; + $finfo = array('fi'=>0, 'fo'=>0); + $ind2 = $ind.INDCH; + $ind3 = $ind2.INDCH; + $anim = ''; + foreach ($ple['children'] as $ac) { + switch ($ac['elementname']) { + case "audioClip": + $r = PlaylistAudioClipExport::OutputToSmil($pl, $ac, $ind2); + if (PEAR::isError($r)) { + return $r; + } + if (!is_null($r)) { + $acOrPl = $r; + } + break; + case "playlist": + $gunid = $ac['attrs']['id']; + $pl2 = StoredFile::RecallByGunid($gunid); + if (is_null($pl2) || PEAR::isError($pl2)) { + return $pl2; + } + $r = $pl2->outputToSmil(FALSE); + if (PEAR::isError($r)) { + return $r; + } + if (!is_null($r)) { + $acOrPl = $r; + } + break; + case "fadeInfo": + $r = PlaylistFadeInfoExport::OutputToSmil($pl, $ac, $ind2); + if (PEAR::isError($r)) { + return $r; + } + if (!is_null($r)) { + $finfo = $r; + } + break; + default: + return PEAR::raiseError( + "PlaylistElementExport::OutputToSmil:". + " unknown tag {$ac['elementname']}" + ); + } + } + $beginS = Playlist::playlistTimeToSeconds($ple['attrs']['relativeOffset']); + $playlengthS = Playlist::playlistTimeToSeconds($acOrPl['playlength']); + $fadeOutS = Playlist::playlistTimeToSeconds($finfo['fo']); + $fiBeginS = 0; + $fiEndS = Playlist::playlistTimeToSeconds($finfo['fi']); + $foBeginS = ($playlengthS - $fadeOutS); + $foEndS = Playlist::playlistTimeToSeconds($acOrPl['playlength']); + foreach (array('fi','fo') as $ff) { + if (${$ff."EndS"} - ${$ff."BeginS"} > 0) { + $anim .= "{$ind2}\n" + ; + } + } + $src = $acOrPl['src']; + $str = "$ind" : " />"). + " ". + "\n"; + return $str; + } + + + public static function OutputToM3u(&$pl, $ple, $ind='') + { + $acOrPl = NULL; + foreach ($ple['children'] as $ac) { + switch ($ac['elementname']) { + case "audioClip": + $r = PlaylistAudioClipExport::OutputToM3u($pl, $ac); + if (PEAR::isError($r)) { + return $r; + } + if (!is_null($r)) { + $acOrPl = $r; + } + break; + case "playlist": + $gunid = $ac['attrs']['id']; + $pl2 = StoredFile::RecallByGunid($gunid); + if (is_null($pl2) || PEAR::isError($pl2)) { + return $pl2; + } + $r = $pl2->outputToM3u(FALSE); + if (PEAR::isError($r)) { + return $r; + } + if (!is_null($r)) { + $acOrPl = $r; + } + break; + } + } + if (is_null($acOrPl)) { + return ''; + } + $playlength = ceil(Playlist::playlistTimeToSeconds($acOrPl['playlength'])); + $title = $acOrPl['title']; + $uri = (isset($acOrPl['uri']) ? $acOrPl['uri'] : '???' ); + $res = "#EXTINF: $playlength, $title\n"; + $res .= "$uri\n"; + return $res; + } + + + public static function OutputToRss(&$pl, $ple, $ind='') + { + $acOrPl = NULL; + $ind2 = $ind.INDCH; + $anim = ''; + foreach ($ple['children'] as $ac) { + switch ($ac['elementname']) { + case "audioClip": + $r = PlaylistAudioClipExport::OutputToRss($pl, $ac, $ind2); + if (PEAR::isError($r)) { + return $r; + } + if (!is_null($r)) { + $acOrPl = $r; + } + break; + case "playlist": + $gunid = $ac['attrs']['id']; + $pl2 = StoredFile::RecallByGunid($gunid); + if (is_null($pl2) || PEAR::isError($pl2)) { + return $pl2; + } + $r = $pl2->outputToRss(FALSE); + if (PEAR::isError($r)) { + return $r; + } + if (!is_null($r)) { + $acOrPl = $r; + } + break; + case "fadeInfo": + break; + default: + return PEAR::raiseError( + "PlaylistElementExport::OutputToRss:". + " unknown tag {$ac['elementname']}" + ); + } + } + $title = (isset($acOrPl['title']) ? htmlspecialchars($acOrPl['title']) : '' ); + $desc = (isset($acOrPl['desc']) ? htmlspecialchars($acOrPl['desc']) : '' ); + $link = htmlspecialchars($acOrPl['src']); + $desc = ''; + $str = "$ind\n". + "$ind2$title\n". + "$ind2$desc\n". + "$ind2$link\n". + "$ind\n"; + return $str; + } +} + + +/** + * @package Campcaster + * @subpackage StorageServer + * @copyright 2010 Sourcefabric O.P.S. + * @license http://www.gnu.org/licenses/gpl.txt + * @todo Rename this class to PlaylistAudioClip (notice the caps) + */ +class PlaylistAudioClipExport +{ + + public static function OutputToSmil(&$pl, $plac, $ind='') + { + $gunid = $plac['attrs']['id']; + $ac = StoredFile::RecallByGunid($gunid); + if (is_null($ac) || PEAR::isError($ac)) { + return $ac; + } + $RADext = $ac->getFileExtension(); + if (PEAR::isError($RADext)) { + return $RADext; + } + return array( + 'type' => 'audioclip', + 'gunid' => $gunid, + 'src' => AC_URL_RELPATH."$gunid.$RADext", + 'playlength' => $plac['attrs']['playlength'], + ); + } + + + public static function OutputToM3u(&$pl, $plac, $ind='') + { + $gunid = $plac['attrs']['id']; + $ac = StoredFile::RecallByGunid($gunid); + if (is_null($ac) || PEAR::isError($ac)) { + return $ac; + } + $RADext = $ac->getFileExtension(); + if (PEAR::isError($RADext)) { + return $RADext; + } + return array( + 'playlength' => $plac['attrs']['playlength'], + 'title' => $plac['attrs']['title'], + 'uri' => AC_URL_RELPATH."$gunid.$RADext", + ); + } + + + public static function OutputToRss(&$pl, $plac, $ind='') + { + $id = $plac['attrs']['id']; + $playlist = Playlist::Recall($id); + if (is_null($playlist) || PEAR::isError($playlist)) { + return $playlist; + } + $RADext = $playlist->getFileExtension(); + if (PEAR::isError($RADext)) { + return $RADext; + } + $title = $playlist->getName(); + $desc = $playlist->getPLMetaData("dc:description"); + return array( + 'type' => 'audioclip', + 'gunid' => $id, + 'src' => "http://XXX/YY/$id.$RADext", + 'playlength' => $plac['attrs']['playlength'], + 'title' => $title, + 'desc' => $desc, + ); + } +} + + +/** + * @package Campcaster + * @subpackage StorageServer + * @copyright 2010 Sourcefabric O.P.S. + * @license http://www.gnu.org/licenses/gpl.txt + * @todo Rename this class "PlaylistFadeInfo" (notive the caps) + */ +class PlaylistFadeInfoExport +{ + + public static function OutputToSmil(&$pl, $plfi, $ind='') + { + $r = array( + 'fi'=>$plfi['attrs']['fadeIn'], + 'fo'=>$plfi['attrs']['fadeOut'], + ); + return $r; + } + + + public static function OutputToM3u(&$pl, $plfa, $ind='') + { + return ''; + } + + + public static function OutputToRss(&$pl, $plfa, $ind='') + { + return ''; + } + +} + + +/** + * @package Campcaster + * @subpackage StorageServer + * @copyright 2010 Sourcefabric O.P.S. + * @license http://www.gnu.org/licenses/gpl.txt + * @todo Rename this class to PlaylistMetadata (notice the caps) + */ +class PlaylistMetadataExport +{ + public static function OutputToSmil(&$pl, $md, $ind='') + { + return NULL; + } + + + public static function OutputToM3u(&$pl, $md, $ind='') + { + return NULL; + } + + + public static function OutputToRss(&$pl, $md, $ind='') + { + return NULL; + } +} + +?> diff --git a/application/models/Prefs.php b/application/models/Prefs.php new file mode 100644 index 000000000..49f0dc6c9 --- /dev/null +++ b/application/models/Prefs.php @@ -0,0 +1,445 @@ +gb =& $gb; + } + + + /* ======================================================= public methods */ + /* ----------------------------------------------------- user preferences */ + /** + * Read preference record by session id + * + * @param string $sessid + * session id + * @param string $key + * preference key + * @return string + * preference value + */ + function loadPref($sessid, $key) + { + $subjid = GreenBox::GetSessUserId($sessid); + if (PEAR::isError($subjid)) { + return $subjid; + } + if (is_null($subjid)) { + return PEAR::raiseError("Prefs::loadPref: invalid session id", + GBERR_SESS); + } + $val = $this->readVal($subjid, $key); + if (PEAR::isError($val)) { + return $val; + } + if ($val === FALSE) { + return PEAR::raiseError("Prefs::loadPref: invalid preference key", + GBERR_PREF); + } + return $val; + } + + + /** + * Save preference record by session id + * + * @param string $sessid + * session id + * @param string $key + * preference key + * @param string $value + * preference value + * @return boolean + */ + function savePref($sessid, $key, $value) + { + $subjid = GreenBox::GetSessUserId($sessid); + if (PEAR::isError($subjid)) { + return $subjid; + } + if (is_null($subjid)) { + return PEAR::raiseError("Prefs::savePref: invalid session id", + GBERR_SESS); + } + $r = $this->update($subjid, $key, $value); + if (PEAR::isError($r)) { + return $r; + } + if ($r === FALSE) { + $r = $this->insert($subjid, $key, $value); + if (PEAR::isError($r)) { + return $r; + } + } + return TRUE; + } + + + /** + * Delete preference record by session id + * + * @param string $sessid + * session id + * @param string $key + * preference key + * @return boolean + */ + function delPref($sessid, $key) + { + $subjid = GreenBox::GetSessUserId($sessid); + if (PEAR::isError($subjid)) { + return $subjid; + } + if (is_null($subjid)) { + return PEAR::raiseError("Prefs::delPref: invalid session id", + GBERR_SESS); + } + $r = $this->delete($subjid, $key); + if (PEAR::isError($r)) { + return $r; + } + if ($r === FALSE) { + return PEAR::raiseError("Prefs::delPref: invalid preference key", + GBERR_PREF); + } + return TRUE; + } + + + /* ---------------------------------------------------- group preferences */ + /** + * Read group preference record + * + * @param string $group + * group name + * @param string $key + * preference key + * @param boolean $returnErrorIfKeyNotExists + * If set to true and the key doesnt exist, return a PEAR error. + * @return string + * preference value + */ + function loadGroupPref($group, $key, $returnErrorIfKeyNotExists = true) + { + // if sessid is would be used here fix Transport::cronCallMethod ! + $subjid = Subjects::GetSubjId($group); + if (PEAR::isError($subjid)) { + return $subjid; + } + if (is_null($subjid)) { + return PEAR::raiseError( + "Prefs::loadGroupPref: invalid group name", ALIBERR_NOTGR); + } + $val = $this->readVal($subjid, $key); + if (PEAR::isError($val)) { + return $val; + } + if ($val === FALSE) { + if ($returnErrorIfKeyNotExists) { + return PEAR::raiseError( + "Prefs::loadGroupPref: invalid preference key", GBERR_PREF); + } else { + return ''; + } + } + return $val; + } + + + /** + * Save group preference record + * + * @param string $sessid + * session id + * @param string $group + * group name + * @param string $key + * preference key + * @param string $value + * preference value + * @return boolean + */ + function saveGroupPref($sessid, $group, $key, $value) + { + $uid = GreenBox::GetSessUserId($sessid); + if (PEAR::isError($uid)) { + return $uid; + } + if (is_null($uid)) { + return PEAR::raiseError( + "Prefs::saveGroupPref: invalid session id", GBERR_SESS); + } + $gid = Subjects::GetSubjId($group); + if (PEAR::isError($gid)) { + return $gid; + } + if (is_null($gid)) { + return PEAR::raiseError( + "Prefs::saveGroupPref: invalid group name", GBERR_SESS); + } + $memb = Subjects::IsMemberOf($uid, $gid); + if (PEAR::isError($memb)) { + return $memb; + } + if (!$memb) { + return PEAR::raiseError( + "Prefs::saveGroupPref: access denied", GBERR_DENY); + } + $r = $this->update($gid, $key, $value); + if (PEAR::isError($r)) { + return $r; + } + if ($r === FALSE) { + $r = $this->insert($gid, $key, $value); + if (PEAR::isError($r)) { + return $r; + } + } + return TRUE; + } + + /** + * Delete group preference record + * + * @param string $sessid + * session id + * @param string $group + * group name + * @param string $key + * preference key + * @return boolean + */ + function delGroupPref($sessid, $group, $key) + { + $uid = GreenBox::GetSessUserId($sessid); + if (PEAR::isError($uid)) { + return $uid; + } + if (is_null($uid)) { + return PEAR::raiseError( + "Prefs::delGroupPref: invalid session id", GBERR_SESS); + } + $gid = Subjects::GetSubjId($group); + if (PEAR::isError($gid)) { + return $gid; + } + if (is_null($gid)) { + return PEAR::raiseError( + "Prefs::delGroupPref: invalid group name", GBERR_SESS); + } + $memb = Subjects::IsMemberOf($uid, $gid); + if (PEAR::isError($memb)) { + return $memb; + } + if (!$memb) { + return PEAR::raiseError( + "Prefs::delGroupPref: access denied", GBERR_DENY); + } + $r = $this->delete($gid, $key); + if (PEAR::isError($r)) { + return $r; + } + if ($r === FALSE) { + return PEAR::raiseError( + "Prefs::delGroupPref: invalid preference key", GBERR_PREF); + } + return TRUE; + } + + + /* ==================================================== low level methods */ + /** + * Insert of new preference record + * + * @param int $subjid + * local user/group id + * @param string $keystr + * preference key + * @param string $valstr + * preference value + * @return int + * local user id + */ + public static function Insert($subjid, $keystr, $valstr='') + { + global $CC_CONFIG, $CC_DBC; + $id = $CC_DBC->nextId($CC_CONFIG['prefSequence']); + if (PEAR::isError($id)) { + return $id; + } + $r = $CC_DBC->query(" + INSERT INTO ".$CC_CONFIG['prefTable']." + (id, subjid, keystr, valstr) + VALUES + ($id, $subjid, '$keystr', '$valstr') + "); + if (PEAR::isError($r)) { + return $r; + } + return $id; + } + + + /** + * Read value of preference record + * + * @param int $subjid + * local user/group id + * @param string $keystr + * preference key + * @return string + * preference value + */ + function readVal($subjid, $keystr) + { + global $CC_CONFIG, $CC_DBC; + $val = $CC_DBC->getOne(" + SELECT valstr FROM ".$CC_CONFIG['prefTable']." + WHERE subjid=$subjid AND keystr='$keystr' + "); + if (PEAR::isError($val)) { + return $val; + } + if (is_null($val)) { + return FALSE; + } + return $val; + } + + + /** + * Read all keys of subject's preferences + * + * @param int $subjid + * local user/group id + * @return array + * preference keys + */ + function readKeys($subjid) + { + global $CC_CONFIG, $CC_DBC; + $res = $CC_DBC->getAll(" + SELECT keystr FROM ".$CC_CONFIG['prefTable']." + WHERE subjid=$subjid + "); + if (PEAR::isError($res)) { + return $res; + } + if (is_null($res)) { + return FALSE; + } + return $res; + } + + + /** + * Update value of preference record + * + * @param int $subjid + * local user/group id + * @param string $keystr + * preference key + * @param string $newvalstr + * new preference value + * @return boolean + */ + function update($subjid, $keystr, $newvalstr='') + { + global $CC_CONFIG, $CC_DBC; + $r = $CC_DBC->query(" + UPDATE ".$CC_CONFIG['prefTable']." SET + valstr='$newvalstr' + WHERE subjid=$subjid AND keystr='$keystr' + "); + if (PEAR::isError($r)) { + return $r; + } + if ($CC_DBC->affectedRows() < 1) { + return FALSE; + } + return TRUE; + } + + + /** + * Delete preference record + * + * @param int $subjid + * local user/group id + * @param string $keystr + * preference key + * @return boolean + */ + function delete($subjid, $keystr) + { + global $CC_CONFIG, $CC_DBC; + $r = $CC_DBC->query(" + DELETE FROM ".$CC_CONFIG['prefTable']." + WHERE subjid=$subjid AND keystr='$keystr' + "); + if (PEAR::isError($r)) { + return $r; + } + if ($CC_DBC->affectedRows() < 1) { + return FALSE; + } + return TRUE; + } + + + /* ==================================================== auxiliary methods */ + /** + * Test method + * + */ + function test() + { + global $CC_CONFIG; + $sessid = Alib::Login('root', $CC_CONFIG['tmpRootPass']); + $testkey = 'testKey'; + $testVal = 'abcDef 0123 ěšÄřžýáíé ĚŠČŘŽÃÃÃÉ'; + $r = savePref($sessid, $testKey, $testVal); + if (PEAR::isError($r)) { + return $r; + } + $val = loadPref($sessid, $testKey); + if ($val != $testVal) { + echo "ERROR: preference storage test failed.\n ($testVal / $val)\n"; + return FALSE; + } + $r = savePref($sessid, $testKey, ''); + if (PEAR::isError($r)) { + return $r; + } + $val = loadPref($sessid, $testKey); + if ($val != $testVal) { + echo "ERROR: preference storage test failed.\n ('' / '$val')\n"; + return FALSE; + } + return TRUE; + } + +} // class Prefs +?> \ No newline at end of file diff --git a/application/models/Renderer.php b/application/models/Renderer.php new file mode 100644 index 000000000..720d71397 --- /dev/null +++ b/application/models/Renderer.php @@ -0,0 +1,290 @@ +outputToSmil(); + if (PEAR::isError($smil)) { + return $smil; + } + // temporary file for smil: + $tmpn = tempnam($CC_CONFIG['bufferDir'], 'plRender_'); + $smilf = "$tmpn.smil"; + file_put_contents($smilf, $smil); + $url = "file://$smilf"; + // output file: + $outf = "$tmpn.".RENDER_EXT; + touch($outf); + // logging: + $logf = $CC_CONFIG['bufferDir']."/renderer.log"; + file_put_contents($logf, "--- ".date("Ymd-H:i:s")."\n", FILE_APPEND); + // open access to output file: /*gunid*/ /*parent*/ + $acc = BasicStor::bsAccess($outf, RENDER_EXT, $plid, 'render', 0, $owner); + if (PEAR::isError($acc)) { + return $acc; + } + extract($acc); + $statf = Renderer::getStatusFile($gb, $token); + file_put_contents($statf, "working"); + // command: + $stServDir = dirname(__FILE__)."/.."; + $renderExe = "$stServDir/bin/renderer.sh"; + $command = "$renderExe -p $url -o $outf -s $statf >> $logf &"; + file_put_contents($logf, "$command\n", FILE_APPEND); + $res = system($command); + if ($res === FALSE) { + return PEAR::raiseError( + 'Renderer::rnRender2File: Error running renderer' + ); + } + return array('token'=>$token); + } + + + /** + * Render playlist to ogg file (check results) + * + * @param GreenBox $gb + * GreenBox object reference + * @param string $token + * render token + * @return array + * status : string - success | working | fault + * url : string - readable url + */ + function rnRender2FileCheck(&$gb, $token) + { + $statf = Renderer::getStatusFile($gb, $token); + if (!file_exists($statf)) { + return PEAR::raiseError( + 'Renderer::rnRender2FileCheck: Invalid token' + ); + } + $status = trim(file_get_contents($statf)); + $url = Renderer::getUrl($gb, $token); + $tmpfile= Renderer::getLocalFile($gb, $token); + return array('status'=>$status, 'url'=>$url, 'tmpfile'=>$tmpfile); + } + + + /** + * Render playlist to ogg file (list results) + * + * @param GreenBox $gb + * greenbox object reference + * @param string $stat + * status (optional) if this parameter is not set, then return with all unclosed backups + * @return array + * array of hasharray: + * status : string - success | working | fault + * url : string - readable url + */ + function rnRender2FileList(&$gb,$stat='') { + // open temporary dir + $tokens = BasicStor::GetTokensByType('render'); + foreach ($tokens as $token) { + $st = Renderer::rnRender2FileCheck($gb, $token); + if ( ($stat=='') || ($st['status']==$stat) ) { + $r[] = $st; + } + } + return $r; + } + + + /** + * Render playlist to ogg file (close handle) + * + * @param GreenBox $gb + * greenbox object reference + * @param string $token + * render token + * @return mixed + * TRUE or PEAR_Error + */ + function rnRender2FileClose(&$gb, $token) + { + global $CC_CONFIG; + $r = BasicStor::bsRelease($token, 'render'); + if (PEAR::isError($r)) { + return $r; + } + $realOgg = $r['realFname']; + $tmpn = $CC_CONFIG['bufferDir']."/".basename($realOgg, '.'.RENDER_EXT); + $smilf = "$tmpn.smil"; + $statf = Renderer::getStatusFile($gb, $token); + @unlink($statf); + @unlink($realOgg); + @unlink($smilf); + @unlink($tmpn); + return TRUE; + } + + + /** + * Render playlist to storage as audioClip (check results) + * + * @param GreenBox $gb + * greenbox object reference + * @param string $token + * render token + * @return array + * status : string - success | working | fault + * gunid: string - global unique id of result file + */ + function rnRender2StorageCheck(&$gb, $token) + { + $r = Renderer::rnRender2FileCheck($gb, $token); + if (PEAR::isError($r)) { + return $r; + } + $status = $r['status']; + $res = array('status' => $status, 'gunid'=>'NULL'); + switch ($status) { + case "fault": + $res['faultString'] = "Error runing renderer"; + break; + case "success": + $r = Renderer::rnRender2StorageCore($gb, $token); + if (PEAR::isError($r)) { + return $r; + } + $res['gunid'] = $r['gunid']; + break; + default: + break; + } + return $res; + } + + + /** + * Render playlist to storage as audioClip (core method) + * + * @param GreenBox $gb + * greenbox object reference + * @param string $token + * render token + * @return array: + * gunid: string - global unique id of result file + */ + function rnRender2StorageCore(&$gb, $token) + { +// $r = BasicStor::bsRelease($token, 'render'); +// if (PEAR::isError($r)) { +// return $r; +// } +// $realOgg = $r['realFname']; +// $owner = $r['owner']; +// $gunid = $r['gunid']; +// $fileName = 'rendered_playlist'; +// $id = BasicStor::IdFromGunid($gunid); +// if (PEAR::isError($id)) { +// return $id; +// } +// $mdata = ''; +// foreach (array('dc:title', 'dcterms:extent', 'dc:creator', 'dc:description') as $item) { +// $val = $gb->bsGetMetadataValue($id, $item); +// $mdata .= " <$item>$val\n"; +// } +// $mdata = "\n \n$mdata \n\n"; +// //$mdata = "\n \n$mdata0\n\n\n"; +// $values = array( +// "filename" => $fileName, +// "filepath" => $realOgg, +// "metadata" => $mdata, +// "filetype" => "audioclip" +// ); +// $storedFile = $gb->bsPutFile($values); +// if (PEAR::isError($storedFile)) { +// return $storedFile; +// } +// return array('gunid' => $storedFile->getGunid()); + } + + + /** + * Return local filepath of rendered file + * + * @param Greenbox $gb + * greenbox object reference + * @param string $token + * render token + * @return array + */ + function getLocalFile(&$gb, $token) + { + global $CC_CONFIG; + $token = StoredFile::NormalizeGunid($token); + return $CC_CONFIG['accessDir']."/$token.".RENDER_EXT; + } + + + /** + * Return filepath of render status file + * + * @param GreenBox $gb + * greenbox object reference + * @param string $token + * render token + * @return array + */ + function getStatusFile(&$gb, $token) + { + return Renderer::getLocalFile($gb, $token).".status"; + } + + + /** + * Return remote accessible URL for rendered file + * + * @param GreenBox $gb + * greenbox object reference + * @param string $token + * render token + * @return array + */ + function getUrl(&$gb, $token) + { + $token = StoredFile::NormalizeGunid($token); + return BasicStor::GetUrlPart()."access/$token.".RENDER_EXT; + } + +} // class Renderer + +?> \ No newline at end of file diff --git a/application/models/Restore.php b/application/models/Restore.php new file mode 100644 index 000000000..afb7c01f3 --- /dev/null +++ b/application/models/Restore.php @@ -0,0 +1,436 @@ +gb =& $gb; + $this->token = null; + $this->logFile = $CC_CONFIG['bufferDir'].'/'.$this->ACCESS_TYPE.'.log'; + if ($this->loglevel == 'debug') { + $this->addLogItem("-I- ".date("Ymd-H:i:s")." construct\n"); + } + } + + + /** + * Call asyncronously the restore procedure. Restore from backup. + * + * @param string $sessid + * session id + * @param string $backup_file + * path of the backup file + * @return array + * hasharray with field: + * token string: backup token + */ + function openRestore($sessid, $backup_file) + { + if ($this->loglevel=='debug') { + $this->addLogItem("-I-".date("Ymd-H:i:s")." doRestore - sessid:$sessid\n"); + } + $this->sessid = $sessid; + + // generate token + $this->token = StoredFile::CreateGunid(); + + // status file -> working + $this->setEnviroment(); + file_put_contents($this->statusFile, 'working'); + + //call the restore script in background + $command = dirname(__FILE__).'/../bin/restore.php'; + $runLog = "/dev/null"; + $params = "{$backup_file} {$this->statusFile} {$this->token} {$sessid}>> $runLog &"; + $ret = system("$command $params", $st); + if ($this->loglevel=='debug') { + $this->addLogItem("-I-".date("Ymd-H:i:s")." restore.php call: $st/$ret\n"); + } + + return array('token'=>$this->token); + } + + + /** + * Check the status of restore + * + * @param string $token + * @return array + * hasharray with field: + * status : string - susccess | working | fault + * faultString : string - description of fault + * token : stirng - backup token + * url : string - access url + * tmpfile : string - access filename + */ + function checkRestore($token) + { + if ($this->loglevel == 'debug') { + $this->addLogItem("-I- ".date("Ymd-H:i:s")." checkBackup - token:$token\n"); + } + $this->token = $token; + $this->setEnviroment(); + if (is_file($this->statusFile)) { + $r = array(); + $stat = file_get_contents($this->statusFile); + if (strpos($stat,'fault|') !== false) { + list($stat,$message) = explode('|',$stat); + } + $r['status'] = $stat; + if ($stat=='fault') { + $r['faultString'] = $message; + } else { + $r['faultString'] = ''; + } + return $r; + } else { + return PEAR::raiseError('Restore::checkRestore: invalid token!'); + } + } + + + /** + * Check the status of restore. + * + * @param string $token + * @return array + * hasharray with field: + * status : boolean - is success + */ + function closeRestore($token) + { + if ($this->loglevel=='debug') { + $this->addLogItem("-I- ".date("Ymd-H:i:s")." checkBackup - token:$token\n"); + } + $this->token = $token; + $this->setEnviroment(); + $this->rRmDir($this->tmpDir); + unlink($this->statusFile); + return array("status" => !is_file($this->statusFile)); + } + + + /** + * Do restore in background + * + * this function is called from the asyncron commandline script + * ../bin/restore.php + * + * @param string $backupfile + * path of backupfile + * @param string $token + * restore token + * @param string $sessid + * session id + */ + function startRestore($backupfile, $token, $sessid) + { + if ($this->loglevel=='debug') { + $this->addLogItem("-I- ".date("Ymd-H:i:s")." startRestore - bufile:$backupfile | token:$token\n"); + } + $this->token = $token; + $this->sessid = $sessid; + $this->setEnviroment(); + + // extract tarball + $command = 'tar -xf '.$backupfile .' --directory '.$this->tmpDir; + $res = system($command); + //$this->addLogItem('command: '.$command."\n"); + //$this->addLogItem('res: '.$res."\n"); + + //simple check of archive format + if (is_dir($this->tmpDir.'audioClip/') && + is_dir($this->tmpDir.'meta-inf/') && + is_dir($this->tmpDir.'playlist/')) { + //search metafiles + $this->metafiles = $this->getMetaFiles(); + #$this->addLogItem('metafiles:'.print_r($this->metafiles,true)); + //add to storage server + foreach ($this->metafiles as $info) { + $r = $this->addFileToStorage($info['file'],$info['type'],$info['id']); + if (PEAR::isError($r)) { + $this->addLogItem("-E- ".date("Ymd-H:i:s"). + " startRestore - addFileToStorage \n". + "(".$put->getMessage()."/".$put->getUserInfo().")\n" + ); + file_put_contents($this->statusFile, 'fault|'.$put->getMessage()."/".$put->getUserInfo()); + return; + } + } + } else { + $this->addLogItem("-E- ".date("Ymd-H:i:s")." startRestore - invalid archive format\n"); + file_put_contents($this->statusFile, 'fault|invalid archive format'); + return; + } + file_put_contents($this->statusFile, 'success'); + // unlink($backupfile); + } + + + /** + * Get the metafiles. + * + * @return array + * array of hasharray with field: + * file : string - metafile path + * type : stirng - audioClip | playlist + * id : string - the backuped gunid + */ + function getMetaFiles() + { + if ($this->loglevel=='debug') { + $this->addLogItem("-I- ".date("Ymd-H:i:s")." getMetaFiles - tmpDir:{$this->tmpDir}\n"); + } + $audioclips = scandir($this->tmpDir.'audioClip/'); + $playlists = scandir($this->tmpDir.'playlist/'); + for ($i = 0; $i < count($audioclips); $i++) { + if (strpos($audioclips[$i],'xml')!==false) + $r[] = array('file' => $this->tmpDir.'audioClip/'.$audioclips[$i], + 'type' => 'audioClip', + 'id' => str_replace('.xml','',$audioclips[$i])); + } + for ($i = 0; $i < count($playlists); $i++) { + if (strpos($playlists[$i],'xml') !== false) + $r[] = array('file' => $this->tmpDir.'playlist/'.$playlists[$i], + 'type' => 'playlist', + 'id' => str_replace('.xml','',$playlists[$i])); + } + return $r; + } + + + /** + * Add the file to the storage server. + * + * @param string $file + * path of metafile + * @param string $type + * restore token + * @param string $sessid + * session id + * + * @return mixed + * true if success or PEAR_error + */ +// function addFileToStorage($file,$type,$gunid) +// { +// if ($this->loglevel=='debug') { +// $this->addLogItem("-I- ".date("Ymd-H:i:s")." addFileToStorage - file:$file | type:$type | id:$gunid\n"); +// } +// require_once("XmlParser.php"); +// $tree = XmlParser::parse($file); +// $mediaFileLP = str_replace('.xml','',$file); +// $mediaFileLP = ($type=='audioClip' && is_file($mediaFileLP))?$mediaFileLP:''; +// $ex = $this->gb->existsFile($this->sessid,$gunid); +// if (PEAR::isError($ex)) { +// $this->addLogItem("-E- ".date("Ymd-H:i:s"). +// " addFileToStorage - existsFile($gunid) ". +// "(".$ex->getMessage()."/".$ex->getUserInfo().")\n" +// ); +// } +// if (!PEAR::isError($ex) && $ex) { // file is exists in storage server +// //replace it +// $id = BasicStor::IdFromGunid($gunid); +// $replace = $this->gb->replaceFile( +// $id, # id int, virt.file's local id +// $mediaFileLP, # mediaFileLP string, local path of media file +// $file, # mdataFileLP string, local path of metadata file +// $this->sessid); # sessid string, session id +// if (PEAR::isError($replace)) { +// $this->addLogItem("-E- ".date("Ymd-H:i:s"). +// " addFileToStorage - replaceFile Error ". +// "(".$replace->getMessage()."/".$replace->getUserInfo().")\n" +// ); +// file_put_contents($this->statusFile, 'fault|'.$replace->getMessage()."/".$replace->getUserInfo()); +// return $replace; +// } +// #$this->addLogItem("replace it \n"); +// } else { +// // add as new +// $name = $tree->children[0]->children[0]->content; +// if (empty($name)) { +// $name = $tree->attrs['title']->val; +// } +// if (empty($name)) { +// $name = '???'; +// } +// if ($this->loglevel=='debug') { +// $this->addLogItem("-I- ".date("Ymd-H:i:s")." putFile\n". +// "$name, $mediaFileLP, $file, {$this->sessid}, $gunid, $type \n" +// ); +// } +// $values = array( +// "filename" => $name, +// "filepath" => $mediaFileLP, +// "metadata" => $file, +// "gunid" => $gunid, +// "filetype" => $type +// ); +// $put = $this->gb->putFile($values, $this->sessid); +// //$this->addLogItem("add as new \n"); +// if (PEAR::isError($put)) { +// $this->addLogItem("-E- ".date("Ymd-H:i:s"). +// " addFileToStorage - putFile Error ". +// "(".$put->getMessage()."/".$put->getUserInfo().")\n" +// ."\n---\n".file_get_contents($file)."\n---\n" +// ); +// file_put_contents($this->statusFile, 'fault|'.$put->getMessage()."/".$put->getUserInfo()); +// //$this->addLogItem("Error Object: ".print_r($put,true)."\n"); +// return $put; +// } +// } +// $ac = StoredFile::RecallByGunid($gunid); +// if (is_null($ac) || PEAR::isError($ac)) { +// return $ac; +// } +// $res = $ac->setState('ready'); +// if (PEAR::isError($res)) { +// return $res; +// } +// return true; +// } + + + /** + * Figure out the environment to the backup. + * + */ + function setEnviroment() + { + global $CC_CONFIG; + if ($this->loglevel=='debug') { + $this->addLogItem("-I- ".date("Ymd-H:i:s")." setEnviroment\n"); + } + $this->statusFile = $CC_CONFIG['accessDir'].'/'.$this->token.'.status'; + $this->tmpDir = '/tmp/ls_restore/'.$this->token.'/'; + $this->rMkDir($this->tmpDir); + } + + + /** + * Add a line to the logfile. + * + * @param string $item + * the new row of log file + */ + function addLogItem($item) + { + $f = fopen ($this->logFile,'a'); + flock($f,LOCK_SH); + fwrite($f,$item); + flock($f,LOCK_UN); + fclose($f); + //echo file_get_contents($this->logFile)."

\n\n"; + } + + + /** + * Delete a directory recursive + * + * @param string $dirname + * path of dir + * + * @return boolean + * is success + */ + function rRmDir($dirname) + { + if (is_dir($dirname)) { + $dir_handle = opendir($dirname); + } + while ($file = readdir($dir_handle)) { + if ($file!="." && $file!="..") { + if (!is_dir($dirname."/".$file)) { + unlink ($dirname."/".$file); + } else { + Restore::rRmDir($dirname."/".$file); + } + } + } + closedir($dir_handle); + rmdir($dirname); + return true; + } + + + /** + * Create a directory recursive + * + * @param string $dirname + * path of dir. + * @param int $mode + * octal - rights of dir. + * @param boolean $recursive + * do it recursive. + * + * @return boolean + */ + function rMkDir($dirname, $mode=0777, $recursive=true) + { + if (is_null($dirname) || $dirname === "" ) { + return false; + } + if (is_dir($dirname) || $dirname === "/" ) { + return true; + } + if ($this->rMkDir(dirname($dirname), $mode, $recursive)) { + return mkdir($dirname, $mode); + } + return false; + } + +} // class Restore +?> diff --git a/application/models/Schedule.php b/application/models/Schedule.php new file mode 100644 index 000000000..3defc158f --- /dev/null +++ b/application/models/Schedule.php @@ -0,0 +1,425 @@ +groupId = $p_groupId; + } + + /** + * Convert a date to an ID by stripping out all characters + * and padding with zeros. + * + * @param string $p_dateStr + */ + public static function dateToId($p_dateStr) { + $p_dateStr = str_replace(":", "", $p_dateStr); + $p_dateStr = str_replace(" ", "", $p_dateStr); + $p_dateStr = str_replace(".", "", $p_dateStr); + $p_dateStr = str_replace("-", "", $p_dateStr); + $p_dateStr = substr($p_dateStr, 0, 17); + $p_dateStr = str_pad($p_dateStr, 17, "0"); + return $p_dateStr; + } + + /** + * Add the two times together, return the result. + * + * @param string $p_baseTime + * Specified as YYYY-MM-DD HH:MM:SS + * + * @param string $p_addTime + * Specified as HH:MM:SS.nnnnnn + * + * @return string + * The end time, to the nearest second. + */ +// protected function calculateEndTime($p_startTime, $p_trackTime) { +// $p_trackTime = substr($p_startTime, 0, ); +// $start = new DateTime(); +// $interval = new DateInterval() +// +// } + + /** + * Add a music clip or playlist to the schedule. + * + * @param $p_datetime + * In the format YYYY-MM-DD HH:MM:SS.mmmmmm + * @param $p_audioFileId + * (optional, either this or $p_playlistId must be set) DB ID of the audio file + * @param $p_playlistId + * (optional, either this of $p_audioFileId must be set) DB ID of the playlist + * @param $p_options + * Does nothing at the moment. + * + * @return int|PEAR_Error + * Return PEAR_Error if the item could not be added. + * Error code 555 is a scheduling conflict. + */ + public function add($p_datetime, $p_audioFileId = null, $p_playlistId = null, $p_options = null) { + global $CC_CONFIG, $CC_DBC; + if (!is_null($p_audioFileId)) { + // Schedule a single audio track + + // Load existing track + $track = StoredFile::Recall($p_audioFileId); + if (is_null($track)) { + return new PEAR_Error("Could not find audio track."); + } + + // Check if there are any conflicts with existing entries + $metadata = $track->getMetadata(); + $length = trim($metadata["length"]); + if (empty($length)) { + return new PEAR_Error("Length is empty."); + } + if (!Schedule::isScheduleEmptyInRange($p_datetime, $length)) { + return new PEAR_Error("Schedule conflict.", 555); + } + + // Insert into the table + $this->groupId = $CC_DBC->GetOne("SELECT nextval('schedule_group_id_seq')"); + $id = $this->dateToId($p_datetime); + $sql = "INSERT INTO ".$CC_CONFIG["scheduleTable"] + ." (id, playlist_id, starts, ends, clip_length, group_id, file_id)" + ." VALUES ($id, 0, TIMESTAMP '$p_datetime', " + ." (TIMESTAMP '$p_datetime' + INTERVAL '$length')," + ." '$length'," + ." {$this->groupId}, $p_audioFileId)"; + $result = $CC_DBC->query($sql); + if (PEAR::isError($result)) { + var_dump($sql); + return $result; + } + return $this->groupId; + + } elseif (!is_null($p_playlistId)){ + // Schedule a whole playlist + + // Load existing playlist + $playlist = Playlist::Recall($p_playlistId); + if (is_null($playlist)) { + return new PEAR_Error("Could not find playlist."); + } + + // Check if there are any conflicts with existing entries + $length = trim($playlist->getLength()); + if (empty($length)) { + return new PEAR_Error("Length is empty."); + } + if (!Schedule::isScheduleEmptyInRange($p_datetime, $length)) { + return new PEAR_Error("Schedule conflict.", 555); + } + + // Insert all items into the schedule + $this->groupId = $CC_DBC->GetOne("SELECT nextval('schedule_group_id_seq')"); + $id = $this->dateToId($p_datetime); + $itemStartTime = $p_datetime; + + $plItems = $playlist->getContents(); + foreach ($plItems as $row) { + $trackLength = $row["cliplength"]; + $sql = "INSERT INTO ".$CC_CONFIG["scheduleTable"] + ." (id, playlist_id, starts, ends, group_id, file_id," + ." clip_length, cue_in, cue_out, fade_in, fade_out)" + ." VALUES ($id, $p_playlistId, TIMESTAMP '$itemStartTime', " + ." (TIMESTAMP '$itemStartTime' + INTERVAL '$trackLength')," + ." '{$this->groupId}', '{$row['file_id']}', '$trackLength', '{$row['cuein']}'," + ." '{$row['cueout']}', '{$row['fadein']}','{$row['fadeout']}')"; + $result = $CC_DBC->query($sql); + if (PEAR::isError($result)) { + var_dump($sql); + return $result; + } + $itemStartTime = $CC_DBC->getOne("SELECT TIMESTAMP '$itemStartTime' + INTERVAL '$trackLength'"); + $id = $this->dateToId($itemStartTime); + } + return $this->groupId; + } + } + + public function addAfter($p_groupId, $p_audioFileId) { + global $CC_CONFIG, $CC_DBC; + // Get the end time for the given entry + $sql = "SELECT ends FROM ".$CC_CONFIG["scheduleTable"] + ." WHERE group_id=$p_groupId"; + $startTime = $CC_DBC->GetOne($sql); + return $this->add($startTime, $p_audioFileId); + } + + public function update() { + + } + + /** + * Remove the group from the schedule. + * Note: does not check if it is in the past, you can remove anything. + * + * @return boolean + * TRUE on success, false if there is no group ID defined. + */ + public function remove() { + global $CC_CONFIG, $CC_DBC; + if (is_null($this->groupId) || !is_numeric($this->groupId)) { + return false; + } + $sql = "DELETE FROM ".$CC_CONFIG["scheduleTable"] + ." WHERE group_id = ".$this->groupId; + + return $CC_DBC->query($sql); + } + + /** + * Return the number of items in this group. + * @return string + */ + public function count() { + global $CC_CONFIG, $CC_DBC; + $sql = "SELECT COUNT(*) FROM {$CC_CONFIG['scheduleTable']}" + ." WHERE group_id={$this->groupId}"; + return $CC_DBC->GetOne($sql); + } + + /* + * Return the list of items in this group as a 2D array. + * @return array + */ + public function getItems() { + global $CC_CONFIG, $CC_DBC; + $sql = "SELECT * FROM {$CC_CONFIG['scheduleTable']}" + ." WHERE group_id={$this->groupId}"; + return $CC_DBC->GetAll($sql); + } + + public function reschedule($toDateTime) { + global $CC_CONFIG, $CC_DBC; +// $sql = "UPDATE ".$CC_CONFIG["scheduleTable"]. " SET id=, starts=,ends=" + } + +} + +class Schedule { + + function __construct() { + + } + + /** + * Return true if there is nothing in the schedule for the given times. + * + * @param string $p_datetime + * @param string $p_length + * + * @return boolean|PEAR_Error + */ + public static function isScheduleEmptyInRange($p_datetime, $p_length) { + global $CC_CONFIG, $CC_DBC; + if (empty($p_length)) { + return new PEAR_Error("Schedule::isSchedulerEmptyInRange: param p_length is empty."); + } + $sql = "SELECT COUNT(*) FROM ".$CC_CONFIG["scheduleTable"] + ." WHERE (starts >= '$p_datetime') " + ." AND (ends <= (TIMESTAMP '$p_datetime' + INTERVAL '$p_length'))"; + //$_SESSION["debug"] = $sql; + $count = $CC_DBC->GetOne($sql); + return ($count == '0'); + } + +// public function onAddTrackToPlaylist($playlistId, $audioTrackId) { +// +// } +// +// public function onRemoveTrackFromPlaylist($playlistId, $audioTrackId) { +// +// } + + /** + * Return TRUE if file is going to be played in the future. + * + * @param string $p_fileId + */ + public function IsFileScheduledInTheFuture($p_fileId) + { + global $CC_CONFIG, $CC_DBC; + $sql = "SELECT COUNT(*) FROM ".$CC_CONFIG["scheduleTable"] + ." WHERE file_id = {$p_fileId} AND starts > NOW()"; + $count = $CC_DBC->GetOne($sql); + if (is_numeric($count) && ($count != '0')) { + return TRUE; + } else { + return FALSE; + } + } + + + /** + * Returns array indexed numberically of: + * "playlistId"/"playlist_id" (aliases to the same thing) + * "start"/"starts" (aliases to the same thing) as YYYY-MM-DD HH:MM:SS.nnnnnn + * "end"/"ends" (aliases to the same thing) as YYYY-MM-DD HH:MM:SS.nnnnnn + * "group_id"/"id" (aliases to the same thing) + * "clip_length" (for playlists only, this is the length of the entire playlist) + * "name" (playlist only) + * "creator" (playlist only) + * "file_id" (audioclip only) + * "count" (number of items in the playlist, always 1 for audioclips. + * Note that playlists with one item will also have count = 1. + * + * @param string $p_fromDateTime + * In the format YYYY-MM-DD HH:MM:SS.nnnnnn + * @param string $p_toDateTime + * In the format YYYY-MM-DD HH:MM:SS.nnnnnn + * @param boolean $p_playlistsOnly + * Retreive playlists as a single item. + * @return array + * Returns empty array if nothing found + */ + public static function GetItems($p_fromDateTime, $p_toDateTime, $p_playlistsOnly = true) { + global $CC_CONFIG, $CC_DBC; + $rows = array(); + if (!$p_playlistsOnly) { + $sql = "SELECT * FROM ".$CC_CONFIG["scheduleTable"] + ." WHERE (starts >= TIMESTAMP '$p_fromDateTime') " + ." AND (ends <= TIMESTAMP '$p_toDateTime')"; + $rows = $CC_DBC->GetAll($sql); + foreach ($rows as &$row) { + $row["count"] = "1"; + $row["playlistId"] = $row["playlist_id"]; + $row["start"] = $row["starts"]; + $row["end"] = $row["ends"]; + $row["id"] = $row["group_id"]; + } + } else { + $sql = "SELECT MIN(name) AS name, MIN(creator) AS creator, group_id, " + ." SUM(clip_length) AS clip_length," + ." MIN(file_id) AS file_id, COUNT(*) as count," + ." MIN(playlist_id) AS playlist_id, MIN(starts) AS starts, MAX(ends) AS ends" + ." FROM ".$CC_CONFIG["scheduleTable"] + ." LEFT JOIN ".$CC_CONFIG["playListTable"]." ON playlist_id = ".$CC_CONFIG["playListTable"].".id" + ." WHERE (starts >= TIMESTAMP '$p_fromDateTime') AND (ends <= TIMESTAMP '$p_toDateTime')" + ." GROUP BY group_id" + ." ORDER BY starts"; + //var_dump($sql); + $rows = $CC_DBC->GetAll($sql); + if (!PEAR::isError($rows)) { + foreach ($rows as &$row) { + $row["playlistId"] = $row["playlist_id"]; + $row["start"] = $row["starts"]; + $row["end"] = $row["ends"]; + $row["id"] = $row["group_id"]; + } + } + } + return $rows; + } + + public function getSchedulerTime() { + + } + + public function getCurrentlyPlaying() { + + } + + public function getNextItem($nextCount = 1) { + + } + + public function getStatus() { + + } + + private static function CcTimeToPypoTime($p_time) { + $p_time = substr($p_time, 0, 19); + $p_time = str_replace(" ", "-", $p_time); + $p_time = str_replace(":", "-", $p_time); + return $p_time; + } + + private static function PypoTimeToCcTime($p_time) { + $t = explode("-", $p_time); + return $t[0]."-".$t[1]."-".$t[2]." ".$t[3].":".$t[4].":00"; + } + + /** + * Export the schedule in json formatted for pypo (the liquidsoap scheduler) + * + * @param string $range + * In the format "YYYY-MM-DD HH:mm:ss" + * @param string $source + * In the format "YYYY-MM-DD HH:mm:ss" + */ + public static function ExportRangeAsJson($p_fromDateTime, $p_toDateTime) + { + global $CC_CONFIG, $CC_DBC; + $range_start = Schedule::PypoTimeToCcTime($p_fromDateTime); + $range_end = Schedule::PypoTimeToCcTime($p_toDateTime); + $range_dt = array('start' => $range_start, 'end' => $range_end); + //var_dump($range_dt); + + // Scheduler wants everything in a playlist + $data = Schedule::GetItems($range_start, $range_end, true); + //echo "
";var_dump($data);
+  	$playlists = array();
+
+		if (is_array($data) && count($data) > 0)
+		{
+			foreach ($data as $dx)
+			{
+		    // Is this the first item in the playlist?
+				$start = $dx['start'];
+				// chop off subseconds
+        $start = substr($start, 0, 19);
+
+        // Start time is the array key, needs to be in the format "YYYY-MM-DD-HH-mm-ss"
+        $pkey = Schedule::CcTimeToPypoTime($start);
+        $timestamp =  strtotime($start);
+				$playlists[$pkey]['source'] = "PLAYLIST";
+				$playlists[$pkey]['x_ident'] = $dx["playlist_id"];
+				$playlists[$pkey]['subtype'] = '1'; // Just needs to be between 1 and 4 inclusive
+				$playlists[$pkey]['timestamp'] = $timestamp;
+				$playlists[$pkey]['duration'] = $dx['clip_length'];
+				$playlists[$pkey]['played'] = '0';
+				$playlists[$pkey]['schedule_id'] = $dx['group_id'];
+			}
+		}
+
+		foreach ($playlists as &$playlist)
+		{
+	    $scheduleGroup = new ScheduleGroup($playlist["schedule_id"]);
+  		$items = $scheduleGroup->getItems();
+			$medias = array();
+			$playlist['subtype'] = '1';
+			foreach ($items as $item)
+			{
+		  	$storedFile = StoredFile::Recall($item["file_id"]);
+				$uri = $storedFile->getFileUrl();
+				$medias[] = array(
+					'id' => $storedFile->getGunid(), //$item["file_id"],
+					'uri' => $uri,
+					'fade_in' => $item["fade_in"],
+					'fade_out' => $item["fade_out"],
+					'fade_cross' => 0,
+					'cue_in' => $item["cue_in"],
+					'cue_out' => $item["cue_out"],
+					);
+			}
+			$playlist['medias'] = $medias;
+		}
+
+		$result = array();
+		$result['status'] = array('range' => $range_dt, 'version' => 0.2);
+		$result['playlists'] = $playlists;
+		$result['check'] = 1;
+
+		print json_encode($result);
+	}
+
+}
+
+?>
\ No newline at end of file
diff --git a/application/models/Shows.php b/application/models/Shows.php
new file mode 100644
index 000000000..9d333fd52
--- /dev/null
+++ b/application/models/Shows.php
@@ -0,0 +1,123 @@
+_userRole = $userType;
+       
+    }
+
+	private function makeFullCalendarEvent($show, $date, $options=array()) {
+
+		$start = $date."T".$show["start_time"];
+		$end = $date."T".$show["end_time"];
+
+		$event = array(
+			"id" => $show["show_id"],
+			"title" => $show["name"],
+			"start" => $start,
+			"end" => $end,
+			"allDay" => false,
+			"description" => $show["description"]
+		);
+
+		foreach($options as $key=>$value) {
+			$event[$key] = $value;
+		}
+
+		if($this->_userRole === "A") {
+			$event["editable"] = true;
+		}
+
+		return $event;
+	}
+
+	public function addShow() {
+
+		$sql = 'INSERT INTO cc_show 
+			("name", "first_show", "last_show", "start_time", "end_time", 
+			"repeats", "day", "description", "show_id")
+			VALUES ()';
+
+	}
+
+	public function getShows($start=NULL, $end=NULL, $weekday=NULL) {
+		global $CC_DBC;
+
+		$sql;
+	
+		$sql_gen = "SELECT * FROM cc_show";
+		$sql = $sql_gen;
+
+		if(!is_null($start) && !is_null($end)) {
+			$sql_range = "(first_show < '{$start}' AND last_show IS NULL) 
+					OR (first_show >= '{$start}' AND first_show < '{$end}') 
+					OR (last_show >= '{$start}' AND last_show < '{$end}')
+					OR (first_show < '{$start}' AND last_show >= '{$end}')";
+
+			$sql = $sql_gen ." WHERE ". $sql_range;
+		}
+		if(!is_null($weekday)){
+			$sql_day = "day = {$weekday}";
+				
+			$sql = $sql_gen ." WHERE (". $sql_day ." AND (". $sql_range ."))";
+		}
+		
+		return  $CC_DBC->GetAll($sql);	
+	}
+
+	public function getFullCalendarEvents($start, $end, $weekday=NULL) {
+		global $CC_DBC;
+		$shows = array();
+
+		$res = $this->getShows($start, $end, $weekday);
+
+		foreach($res as $row) {
+
+			if(!is_null($start)) { 
+
+				$timeDiff = "SELECT date '{$start}' - date '{$row["first_show"]}' as diff";
+				$diff = $CC_DBC->GetOne($timeDiff);
+
+				if($diff > 0) {
+
+					$add = ($diff % 7 === 0) ? $diff : $diff + (7 - $diff % 7);
+
+					$new = "SELECT date '{$row["first_show"]}' + integer '{$add}'";
+					$newDate = $CC_DBC->GetOne($new); 
+				}
+				else {
+					$newDate = $row["first_show"];
+				}
+
+				$shows[] = $this->makeFullCalendarEvent($row, $newDate);
+				
+				$end_epoch = strtotime($end);
+
+				if($row["repeats"]) {
+
+					while(true) {
+
+						$diff = "SELECT date '{$newDate}' + integer '7'";
+						$repeatDate = $CC_DBC->GetOne($diff);
+						$repeat_epoch = strtotime($repeatDate);
+
+						if ($repeat_epoch < $end_epoch ) {
+							$shows[] = $this->makeFullCalendarEvent($row, $repeatDate);
+						}
+						else {
+							break;
+						}
+
+						$newDate = $repeatDate;
+					}					
+				}	
+			}		
+		}
+
+		return $shows;
+	}
+}
diff --git a/application/models/SmilPlaylist.php b/application/models/SmilPlaylist.php
new file mode 100644
index 000000000..dc4ca7f78
--- /dev/null
+++ b/application/models/SmilPlaylist.php
@@ -0,0 +1,313 @@
+lock($gb, $subjid);
+        if (PEAR::isError($r)) {
+        	return $r;
+        }
+        $r = $pl->setMetadata($lspl, 'string', 'playlist');
+        if (PEAR::isError($r)) {
+        	return $r;
+        }
+        $r = $pl->unlock($gb);
+        if (PEAR::isError($r)) {
+        	return $r;
+        }
+        return $pl;
+    }
+
+
+    /**
+     * Import SMIL file to storage.
+     *
+     * @param GreenBox $gb
+     * @param string $data
+     * 		local path to SMIL file
+     * @param hasharray $gunids
+     * 		hash relation from filenames to gunids
+     * @param array $parr
+     * 		array of subjid, aPath, plid, rPath
+     * @return string
+     * 		XML of playlist in Campcaster playlist format
+     */
+    public static function convert2lspl(&$gb, $data, &$gunids, $parr)
+    {
+        extract($parr);
+        $tree = SmilPlaylist::parse($data);
+        if (PEAR::isError($tree)) {
+        	return $tree;
+        }
+        if ($tree->name != 'smil') {
+            return PEAR::raiseError("SmilPlaylist::parse: smil tag expected");
+        }
+        if (isset($tree->children[1])) {
+            return PEAR::raiseError(sprintf(
+                "SmilPlaylist::parse: unexpected tag %s in tag smil",
+                $tree->children[1]->name
+            ));
+        }
+        $res = SmilPlaylistBodyElement::convert2lspl(
+            $gb, $tree->children[0], &$gunids, $parr);
+        return $res;
+    }
+
+} // SmilPlaylist
+
+
+/**
+ * @package Campcaster
+ * @subpackage StorageServer
+ * @copyright 2010 Sourcefabric O.P.S.
+ * @license http://www.gnu.org/licenses/gpl.txt
+ */
+class SmilPlaylistBodyElement {
+
+    public static function convert2lspl(&$gb, &$tree, &$gunids, $parr, $ind='')
+    {
+        extract($parr);
+        $ind2 = $ind.INDCH;
+        if ($tree->name != 'body') {
+            return PEAR::raiseError("SmilPlaylist::parse: body tag expected");
+        }
+        if (isset($tree->children[1])) {
+            return PEAR::raiseError(sprintf(
+                "SmilPlaylist::parse: unexpected tag %s in tag body",
+                $tree->children[1]->name
+            ));
+        }
+        $res = SmilPlaylistParElement::convert2lspl(
+            $gb, $tree->children[0], &$gunids, $parr, $ind2);
+        if (PEAR::isError($res)) {
+        	return $res;
+        }
+        $title = basename($rPath);
+        $playlength = '0';
+        $res = "$ind\n".
+            "$ind\n".
+            "$ind2\n".
+            "$res".
+            "$ind\n";
+        return $res;
+    }
+
+} // class SmilPlaylistBodyElement
+
+
+/**
+ * @package Campcaster
+ * @subpackage StorageServer
+ * @copyright 2010 Sourcefabric O.P.S.
+ * @license http://www.gnu.org/licenses/gpl.txt
+ */
+class SmilPlaylistParElement {
+
+	public static function convert2lspl(&$gb, &$tree, &$gunids, $parr, $ind='')
+    {
+        extract($parr);
+        if ($tree->name != 'par') {
+            return PEAR::raiseError("SmilPlaylist::parse: par tag expected");
+        }
+        $res = '';
+        foreach ($tree->children as $i => $ch) {
+            $ch =& $tree->children[$i];
+            $r = SmilPlaylistAudioElement::convert2lspl($gb, $ch, &$gunids, $parr, $ind.INDCH);
+            if (PEAR::isError($r)) {
+            	return $r;
+            }
+            $res .= $r;
+        }
+        return $res;
+    }
+}
+
+
+/**
+ * @package Campcaster
+ * @subpackage StorageServer
+ * @copyright 2010 Sourcefabric O.P.S.
+ * @license http://www.gnu.org/licenses/gpl.txt
+ */
+class SmilPlaylistAudioElement {
+    public static function convert2lspl(&$gb, &$tree, &$gunids, $parr, $ind='')
+    {
+        extract($parr);
+        $uri = $tree->attrs['src']->val;
+        $gunid  = ( isset($gunids[basename($uri)]) ?  $gunids[basename($uri)] : NULL);
+        $ind2 = $ind.INDCH;
+        if ($tree->name != 'audio') {
+            return PEAR::raiseError("SmilPlaylist::parse: audio tag expected");
+        }
+        if (isset($tree->children[2])) {
+            return PEAR::raiseError(sprintf(
+                "SmilPlaylist::parse: unexpected tag %s in tag audio",
+                $tree->children[2]->name
+            ));
+        }
+        $res = ''; $fadeIn = 0; $fadeOut = 0;
+        foreach ($tree->children as $i => $ch) {
+            $ch =& $tree->children[$i];
+            $r = SmilPlaylistAnimateElement::convert2lspl($gb, $ch, &$gunids, $parr, $ind2);
+            if (PEAR::isError($r)) {
+            	return $r;
+            }
+            switch ($r['type']) {
+                case "fadeIn":  $fadeIn  = $r['val']; break;
+                case "fadeOut": $fadeOut = $r['val']; break;
+            }
+        }
+        if ($fadeIn > 0 || $fadeOut > 0) {
+            $fiGunid = StoredFile::CreateGunid();
+            $fadeIn  = Playlist::secondsToPlaylistTime($fadeIn);
+            $fadeOut = Playlist::secondsToPlaylistTime($fadeOut);
+            $fInfo   = "$ind2\n";
+        } else {
+        	$fInfo = '';
+        }
+        $plElGunid  = StoredFile::CreateGunid();
+        $acGunid     = $gunid;
+        $type = 'audioClip';
+        if (preg_match("|\.([a-zA-Z0-9]+)$|", $uri, $va)) {
+            switch (strtolower($ext = $va[1])) {
+                case "lspl":
+                case "xml":
+                case "smil":
+                case "m3u":
+                    $type = 'playlist';
+                    $acId = $gb->bsImportPlaylistRaw($gunid,
+                        $aPath, $uri, $ext, $gunids, $subjid);
+                    if (PEAR::isError($acId)) {
+                    	return $r;
+                    }
+                   //break;
+                default:
+                    $ac = StoredFile::RecallByGunid($gunid);
+                    if (is_null($ac) || PEAR::isError($ac)) {
+                    	return $ac;
+                    }
+                    $r = $ac->md->getMetadataElement('dcterms:extent');
+                    if (PEAR::isError($r)) {
+                    	return $r;
+                    }
+                    $playlength = $r[0]['value'];
+            }
+        }
+
+        $title = basename($tree->attrs['src']->val);
+        $offset = Playlist::secondsToPlaylistTime($tree->attrs['begin']->val);
+        $clipStart = Playlist::secondsToPlaylistTime($tree->attrs['clipStart']->val);
+        $clipEnd = Playlist::secondsToPlaylistTime($tree->attrs['clipEnd']->val);
+        $clipLength = Playlist::secondsToPlaylistTime($tree->attrs['clipLength']->val);
+        $res = "$ind\n".
+            "$ind2<$type id=\"$acGunid\" playlength=\"$playlength\" title=\"$title\"/>\n".
+            $fInfo.
+            "$ind\n";
+        return $res;
+    }
+} // class SmilPlaylistAudioElement
+
+
+/**
+ * @package Campcaster
+ * @subpackage StorageServer
+ * @copyright 2010 Sourcefabric O.P.S.
+ * @license http://www.gnu.org/licenses/gpl.txt
+ */
+class SmilPlaylistAnimateElement {
+
+	public static function convert2lspl(&$gb, &$tree, &$gunids, $parr, $ind='')
+	{
+        extract($parr);
+        if ($tree->name != 'animate') {
+            return PEAR::raiseError("SmilPlaylist::parse: animate tag expected");
+        }
+        if ($tree->attrs['attributeName']->val == 'soundLevel' &&
+            $tree->attrs['from']->val == '0%' &&
+            $tree->attrs['to']->val == '100%' &&
+            $tree->attrs['calcMode']->val == 'linear' &&
+            $tree->attrs['fill']->val == 'freeze' &&
+            $tree->attrs['begin']->val == '0s' &&
+            preg_match("|^([0-9.]+)s$|", $tree->attrs['end']->val, $va)
+        ) {
+            return array('type'=>'fadeIn', 'val'=>intval($va[1]));
+        }
+        if ($tree->attrs['attributeName']->val == 'soundLevel' &&
+            $tree->attrs['from']->val == '100%' &&
+            $tree->attrs['to']->val == '0%' &&
+            $tree->attrs['calcMode']->val == 'linear' &&
+            $tree->attrs['fill']->val == 'freeze' &&
+            preg_match("|^([0-9.]+)s$|", $tree->attrs['begin']->val, $vaBegin) &&
+            preg_match("|^([0-9.]+)s$|", $tree->attrs['end']->val, $vaEnd)
+        ) {
+            return array('type'=>'fadeOut', 'val'=>($vaEnd[1] - $vaBegin[1]));
+        }
+        return PEAR::raiseError(
+            "SmilPlaylistAnimateElement::convert2lspl: animate parameters too general"
+        );
+    }
+} // class SmilPlaylistAnimateElement
+
+?>
\ No newline at end of file
diff --git a/application/models/StoredFile.php b/application/models/StoredFile.php
new file mode 100644
index 000000000..474445193
--- /dev/null
+++ b/application/models/StoredFile.php
@@ -0,0 +1,1787 @@
+ "format",
+    "ls:bitrate" => "bit_rate",
+  	"ls:samplerate" => "sample_rate",
+    "dcterms:extent" => "length",
+	"dc:title" => "track_title",
+	"dc:description" => "comments",
+	"dc:type" => "genre",
+	"dc:creator" => "artist_name",
+    "dc:source" => "album_title",
+	"ls:channels" => "channels",
+	"ls:filename" => "name",
+	"ls:year" => "year",
+	"ls:url" => "url",
+	"ls:track_num" => "track_number",
+    "ls:mood" => "mood",
+    "ls:bpm" => "bpm",
+    "ls:disc_num" => "disc_number",
+    "ls:rating" => "rating",
+    "ls:encoded_by" => "encoded_by",
+    "dc:publisher" => "label",
+    "ls:composer" => "composer",
+    "ls:encoder" => "encoder",
+    "ls:crc" => "checksum",
+    "ls:lyrics" => "lyrics",
+    "ls:orchestra" => "orchestra",
+    "ls:conductor" => "conductor",
+    "ls:lyricist" => "lyricist",
+    "ls:originallyricist" => "original_lyricist",
+    "ls:radiostationname" => "radio_station_name",
+    "ls:audiofileinfourl" => "info_url",
+    "ls:artisturl" => "artist_url",
+    "ls:audiosourceurl" => "audio_source_url",
+    "ls:radiostationurl" => "radio_station_url",
+    "ls:buycdurl" => "buy_this_url",
+    "ls:isrcnumber" => "isrc_number",
+    "ls:catalognumber" => "catalog_number",
+    "ls:originalartist" => "original_artist",
+    "dc:rights" => "copyright",
+    "dcterms:temporal" => "report_datetime",
+    "dcterms:spatial" => "report_location",
+    "dcterms:entity" => "report_organization",
+    "dc:subject" => "subject",
+    "dc:contributor" => "contributor",
+    "dc:language" => "language");
+
+/**
+ * Track numbers in metadata tags can come in many formats:
+ * "1 of 20", "1/20", "20/1".  This function parses the track
+ * number and gets the real number so that we can sort by it
+ * in the database.
+ *
+ * @param string $p_trackNumber
+ * @return int
+ */
+function camp_parse_track_number($p_trackNumber)
+{
+    $num = trim($p_trackNumber);
+    if (!is_numeric($num)) {
+        $matches = preg_match("/\s*([0-9]+)([^0-9]*)([0-9]*)\s*/", $num, $results);
+        $trackNum = 0;
+        foreach ($results as $result) {
+            if (is_numeric($result)) {
+                if ($trackNum == 0) {
+                    $trackNum = $result;
+                } elseif ($result < $trackNum) {
+                    $trackNum = $result;
+                }
+            }
+        }
+    } else {
+        $trackNum = $num;
+    }
+    return $trackNum;
+}
+
+
+/**
+ * Add data to the global array $mdata, also sets global variables
+ * $titleHaveSet and $titleKey.
+ *
+ * Converts the given string ($val) into UTF-8.
+ *
+ * @param array $p_mdata
+ * 		The array to add the metadata to.
+ * @param string $p_key
+ * 		Metadata key.
+ * @param string $p_val
+ * 		Metadata value.
+ * @param string $p_inputEncoding
+ * 		Encoding type of the input value.
+ */
+function camp_add_metadata(&$p_mdata, $p_key, $p_val, $p_inputEncoding='iso-8859-1')
+{
+    if (!is_null($p_val)) {
+        $data = $p_val;
+        $outputEncoding = 'UTF-8';
+        //if (function_exists('iconv') && ($p_inputEncoding != $outputEncoding) ) {
+        if (function_exists('iconv') && is_string($p_val)) {
+            $newData = @iconv($p_inputEncoding, $outputEncoding, $data);
+            if ($newData === FALSE) {
+                echo "Warning: convert $key data to unicode failed\n";
+            } elseif ($newData != $data) {
+                echo "Converted string: '$data' (".gettype($data).") -> '$newData' (".gettype($newData).").\n";
+                $data = $newData;
+            }
+        }
+        $p_mdata[$p_key] = trim($data);
+    }
+}
+
+
+/**
+ * Return an array with the given audio file's ID3 tags.  The keys in the
+ * array can be:
+ * 
+ * 		dc:format ("mime type")
+ * 		dcterms:extent ("duration")
+ * 		dc:title
+ * 		dc:creator ("artist")
+ * 		dc:source ("album")
+ *      dc:type ("genre")
+ * 		ls:bitrate
+ * 		ls:encoded_by
+ * 		ls:track_num
+ * 		ls:channels
+ * 		ls:year
+ * 		ls:filename
+ * 
+ * + * @param string $p_filename + * @param boolean $p_testonly + * For diagnostic and debugging purposes - setting this to TRUE + * will print out the values found in the file and the ones assigned + * to the return array. + * @return array|PEAR_Error + */ +function camp_get_audio_metadata($p_filename, $p_testonly = false) +{ + $getID3 = new getID3(); + $infoFromFile = $getID3->analyze($p_filename); + if (PEAR::isError($infoFromFile)) { + return $infoFromFile; + } + if (isset($infoFromFile['error'])) { + return new PEAR_Error(array_pop($infoFromFile['error'])); + } + if (!$infoFromFile['bitrate']) { + return new PEAR_Error("File given is not an audio file."); + } + + if ($p_testonly) { + print_r($infoFromFile); + } + $titleKey = 'dc:title'; + $flds = array( + 'dc:format' => array( + array('path'=>"['mime_type']", 'ignoreEnc'=>TRUE), + ), + 'ls:bitrate' => array( + array('path'=>"['bitrate']", 'ignoreEnc'=>TRUE), + array('path'=>"['audio']['bitrate']", 'ignoreEnc'=>TRUE), + ), + 'ls:samplerate' => array( + array('path'=>"['audio']['sample_rate']", 'ignoreEnc'=>TRUE), + ), + 'ls:encoder' => array( + array('path'=>"['audio']['codec']", 'ignoreEnc'=>TRUE), + ), + 'dcterms:extent'=> array( + array('path'=>"['playtime_seconds']", 'ignoreEnc'=>TRUE), + ), + 'ls:composer'=> array( + array('path'=>"['id3v2']['comments']['composer']", 'dataPath'=>"[0]", 'ignoreEnc'=>TRUE), + array('path'=>"['id3v2']['TCOM'][0]", 'dataPath'=>"['data']", 'encPath'=>"['encoding']"), + array('path'=>"['tags']['id3v2']['composer']", 'dataPath'=>"[0]", 'ignoreEnc'=>TRUE), + array('path'=>"['ogg']['comments']['composer']", 'dataPath'=>"[0]", 'encPath'=>"['encoding']"), + array('path'=>"['tags']['vorbiscomment']['composer']", 'dataPath'=>"[0]", 'encPath'=>"['encoding']"), + ), + 'dc:description'=> array( + array('path'=>"['id3v1']['comments']['comment']", 'dataPath'=>"[0]", 'encPath'=>"['encoding']"), + array('path'=>"['id3v2']['comments']['comments']", 'dataPath'=>"[0]", 'ignoreEnc'=>TRUE), + array('path'=>"['id3v2']['COMM'][0]", 'dataPath'=>"['data']", 'encPath'=>"['encoding']"), + array('path'=>"['tags']['id3v2']['comments']", 'dataPath'=>"[0]", 'ignoreEnc'=>TRUE), + array('path'=>"['ogg']['comments']['comment']", 'dataPath'=>"[0]", 'encPath'=>"['encoding']"), + array('path'=>"['tags']['vorbiscomment']['comment']", 'dataPath'=>"[0]", 'encPath'=>"['encoding']"), + ), + 'dc:type'=> array( + array('path'=>"['id3v1']", 'dataPath'=>"['genre']", 'encPath'=>"['encoding']"), + array('path'=>"['id3v2']['comments']['content_type']", 'dataPath'=>"[0]", 'ignoreEnc'=>TRUE), + array('path'=>"['id3v2']['TCON'][0]", 'dataPath'=>"['data']", 'encPath'=>"['encoding']"), + array('path'=>"['ogg']['comments']['genre']", 'dataPath'=>"[0]", 'encPath'=>"['encoding']"), + array('path'=>"['tags']['vorbiscomment']['genre']", 'dataPath'=>"[0]", 'encPath'=>"['encoding']"), + ), + 'dc:title' => array( + array('path'=>"['id3v2']['comments']['title']", 'dataPath'=>"[0]", 'encPath'=>"['encoding']"), + array('path'=>"['id3v2']['TIT2'][0]", 'dataPath'=>"['data']", 'encPath'=>"['encoding']"), + array('path'=>"['id3v2']['TT2'][0]", 'dataPath'=>"['data']", 'encPath'=>"['encoding']"), + array('path'=>"['id3v1']", 'dataPath'=>"['title']", 'encPath'=>"['encoding']"), + array('path'=>"['ogg']['comments']['title']", 'dataPath'=>"[0]", 'encPath'=>"['encoding']"), + array('path'=>"['tags']['vorbiscomment']['title']", 'dataPath'=>"[0]", 'encPath'=>"['encoding']"), + ), + 'dc:creator' => array( + array('path'=>"['id3v2']['comments']['artist']", 'dataPath'=>"[0]", 'encPath'=>"['encoding']"), + array('path'=>"['id3v2']['TPE1'][0]", 'dataPath'=>"['data']", 'encPath'=>"['encoding']"), + array('path'=>"['id3v2']['TP1'][0]", 'dataPath'=>"['data']", 'encPath'=>"['encoding']"), + array('path'=>"['id3v1']", 'dataPath'=>"['artist']", 'encPath'=>"['encoding']"), + array('path'=>"['ogg']['comments']['artist']", 'dataPath'=>"[0]", 'encPath'=>"['encoding']"), + array('path'=>"['tags']['vorbiscomment']['artist']", 'dataPath'=>"[0]", 'encPath'=>"['encoding']"), + ), + 'dc:source' => array( + array('path'=>"['id3v2']['comments']['album']", 'dataPath'=>"[0]", 'encPath'=>"['encoding']"), + array('path'=>"['id3v2']['TALB'][0]", 'dataPath'=>"['data']", 'encPath'=>"['encoding']"), + array('path'=>"['id3v2']['TAL'][0]", 'dataPath'=>"['data']", 'encPath'=>"['encoding']"), + array('path'=>"['ogg']['comments']['album']", 'dataPath'=>"[0]", 'encPath'=>"['encoding']"), + array('path'=>"['tags']['vorbiscomment']['album']", 'dataPath'=>"[0]", 'encPath'=>"['encoding']"), + ), + 'ls:encoded_by' => array( + array('path'=>"['id3v2']['TENC'][0]", 'dataPath'=>"['data']", 'encPath'=>"['encoding']"), + array('path'=>"['id3v2']['TEN'][0]", 'dataPath'=>"['data']", 'encPath'=>"['encoding']"), + array('path'=>"['ogg']['comments']['encoded-by']", 'dataPath'=>"[0]", 'encPath'=>"['encoding']"), + array('path'=>"['tags']['vorbiscomment']['encoded-by']", 'dataPath'=>"[0]", 'encPath'=>"['encoding']"), + ), + 'ls:track_num' => array( + array('path'=>"['id3v2']['TRCK'][0]", 'dataPath'=>"['data']", 'encPath'=>"['encoding']"), + array('path'=>"['id3v2']['TRK'][0]", 'dataPath'=>"['data']", 'encPath'=>"['encoding']"), + array('path'=>"['ogg']['comments']['tracknumber']", 'dataPath'=>"[0]", 'encPath'=>"['encoding']"), + array('path'=>"['tags']['vorbiscomment']['tracknumber']", 'dataPath'=>"[0]", 'encPath'=>"['encoding']"), + ), +// 'ls:genre' => array( +// array('path'=>"['id3v1']", 'dataPath'=>"['genre']", 'encPath'=>"['encoding']"), +// array('path'=>"['id3v2']['TCON'][0]", 'dataPath'=>"['data']", 'encPath'=>"['encoding']"), +// array('path'=>"['id3v2']['comments']['content_type']", 'dataPath'=>"[0]", 'ignoreEnc'=>TRUE), +// array('path'=>"['ogg']['comments']['genre']", 'dataPath'=>"[0]", 'encPath'=>"['encoding']"), +// array('path'=>"['tags']['vorbiscomment']['genre']", 'dataPath'=>"[0]", 'encPath'=>"['encoding']"), +// ), + 'ls:channels' => array( + array('path'=>"['audio']['channels']", 'ignoreEnc'=>TRUE), + ), + 'ls:year' => array( + array('path'=>"['comments']['date']"), + array('path'=>"['ogg']['comments']['date']", 'dataPath'=>"[0]", 'encPath'=>"['encoding']"), + array('path'=>"['tags']['vorbiscomment']['date']", 'dataPath'=>"[0]", 'encPath'=>"['encoding']"), + ), + 'ls:filename' => array( + array('path'=>"['filename']"), + ), + ); + $mdata = array(); + if (isset($infoFromFile['audio'])) { + $mdata['audio'] = $infoFromFile['audio']; + } + if (isset($infoFromFile['playtime_seconds'])) { + $mdata['playtime_seconds'] = $infoFromFile['playtime_seconds']; + } + + $titleHaveSet = FALSE; + foreach ($flds as $key => $getid3keys) { + foreach ($getid3keys as $getid3key) { + $path = $getid3key["path"]; + $ignoreEnc = isset($getid3key["ignoreEnc"])? + $getid3key["ignoreEnc"]:FALSE; + $dataPath = isset($getid3key["dataPath"])?$getid3key["dataPath"]:""; + $encPath = isset($getid3key["encPath"])?$getid3key["encPath"]:""; + $enc = "UTF-8"; + + $tagElement = "\$infoFromFile$path$dataPath"; + eval("\$tagExists = isset($tagElement);"); + if ($tagExists) { + //echo "ignore encoding: ".($ignoreEnc?"yes":"no")."\n"; + //echo "tag exists\n"; + //echo "encode path: $encPath\n"; + eval("\$data = $tagElement;"); + if (!$ignoreEnc && $encPath != "") { + $encodedElement = "\$infoFromFile$path$encPath"; + eval("\$encodedElementExists = isset($encodedElement);"); + if ($encodedElementExists) { + eval("\$enc = $encodedElement;"); + } + } + + // Special case handling for track number + if ($key == "ls:track_num") { + $data = camp_parse_track_number($data); + } + camp_add_metadata($mdata, $key, $data, $enc); + if ($key == $titleKey) { + $titleHaveSet = TRUE; + } + break; + } + } + } + if ($p_testonly) { + var_dump($mdata); + } + + if (!$titleHaveSet || trim($mdata[$titleKey]) == '') { + camp_add_metadata($mdata, $titleKey, basename($p_filename)); + } + return $mdata; +} + + +/** + * StoredFile class + * + * Campcaster file storage support class.
+ * Represents one virtual file in storage. Virtual file has up to two parts: + *
    + *
  • metadata in database - represented by MetaData class
  • + *
  • binary media data in real file
  • + *
+ * + * @package Campcaster + * @subpackage StorageServer + * @copyright 2010 Sourcefabric O.P.S. + * @license http://www.gnu.org/licenses/gpl.txt + * @see MetaData + */ +class StoredFile { + + // *** Variables stored in the database *** + + /** + * @var int + */ + private $id; + + /** + * Unique ID for the file. This is stored in HEX format. It is + * converted to a bigint whenever it is used in a database call. + * + * @var string + */ + public $gunid; + + /** + * The unique ID of the file as it is stored in the database. + * This is for debugging purposes and may not always exist in this + * class. + * + * @var string + */ + //private $gunidBigint; + + /** + * @var string + */ + private $name; + + /** + * @var string + */ + private $mime; + + /** + * Can be 'audioclip'...others might be coming, like webstream. + * + * @var string + */ + private $ftype; + + /** + * Can be 'ready', 'edited', 'incomplete'. + * + * @var string + */ + private $state; + + /** + * @var int + */ + private $currentlyaccessing; + + /** + * @var int + */ + private $editedby; + + /** + * @var timestamp + */ + private $mtime; + + /** + * @var string + */ + private $md5; + + /** + * @var string + */ + private $filepath; + + + // *** Variables NOT stored in the database *** + + /** + * Directory where the file is located. + * + * @var string + */ + private $resDir; + + /** + * @var boolean + */ + private $exists; + + /** + * @var MetaData + */ + public $md; + + /* ========================================================== constructor */ + /** + * Constructor, but shouldn't be externally called + * + * @param string $p_gunid + * globally unique id of file + */ + public function __construct($p_gunid=NULL) + { + $this->gunid = $p_gunid; + if (empty($this->gunid)) { + $this->gunid = StoredFile::generateGunid(); + } + else { + $this->loadMetadata(); + $this->exists = is_file($this->filepath) && is_readable($this->filepath); + } + } + + /** + * For testing only, do not use. + */ + public function __setGunid($p_guid) { + $this->gunid = $p_guid; + } + + /** + * Convert XML name to database column name. Used for backwards compatibility + * with old code. + * + * @param string $p_category + * @return string|null + */ + public static function xmlCategoryToDbColumn($p_category) + { + global $g_metadata_xml_to_db_mapping; + if (array_key_exists($p_category, $g_metadata_xml_to_db_mapping)) { + return $g_metadata_xml_to_db_mapping[$p_category]; + } + return null; + } + + + /** + * Convert database column name to XML name. + * + * @param string $p_dbColumn + * @return string|null + */ + public static function dbColumnToXmlCatagory($p_dbColumn) + { + global $g_metadata_xml_to_db_mapping; + $str = array_search($p_dbColumn, $g_metadata_xml_to_db_mapping); + // make return value consistent with xmlCategoryToDbColumn() + if ($str === FALSE) { + $str = null; + } + return $str; + } + + + /** + * GUNID needs to be set before you call this function. + * + */ + public function loadMetadata() + { + global $CC_CONFIG, $CC_DBC; + $escapedValue = pg_escape_string($this->gunid); + $sql = "SELECT * FROM ".$CC_CONFIG["filesTable"] + ." WHERE gunid='$escapedValue'"; + //var_dump($sql); + $this->md = $CC_DBC->getRow($sql); + //var_dump($this->md); + if (PEAR::isError($this->md)) { + $error = $this->md; + $this->md = null; + return $error; + } + $this->filepath = $this->md["filepath"]; + if (is_null($this->md)) { + $this->md = array(); + return; + } + $compatibilityData = array(); + foreach ($this->md as $key => $value) { + if ($xmlName = StoredFile::dbColumnToXmlCatagory($key)) { + $compatibilityData[$xmlName] = $value; + } + } + //var_dump($compatibilityData); + $this->md = array_merge($this->md, $compatibilityData); + //var_dump($this->md); + //$_SESSION["debug"] = $this->md; + } + + public function setFormat($p_value) + { + $this->md["format"] = $p_value; + } + + public function replaceMetadata($p_values) + { + global $CC_CONFIG, $CC_DBC; + foreach ($p_values as $category => $value) { + $escapedValue = pg_escape_string($value); + $columnName = StoredFile::xmlCategoryToDbColumn($category); + if (!is_null($columnName)) { + $sql = "UPDATE ".$CC_CONFIG["filesTable"] + ." SET $columnName='$escapedValue'" + ." WHERE gunid = '".$this->gunid."'"; + $CC_DBC->query($sql); + } + } + $this->loadMetadata(); + } + + public function clearMetadata() + { + $metadataColumns = array("format", "bit_rate", "sample_rate", "length", + "track_title", "comments", "genre", "artist_name", "channels", "name", + "year", "url", "track_number"); + foreach ($metadataColumns as $columnName) { + if (!is_null($columnName)) { + $sql = "UPDATE ".$CC_CONFIG["filesTable"] + ." SET $columnName=''" + ." WHERE gunid = '".$this->gunid."'"; + $CC_DBC->query($sql); + } + } + } + + + /* ========= 'factory' methods - should be called to construct StoredFile */ + /** + * Create instance of StoredFile object and insert new file + * + * @param array $p_values + * "filepath" - required, local path to media file (where it is before import) + * "id" - optional, local object id, will be generated if not given + * "gunid" - optional, unique id, for insert file with gunid, will be generated if not given + * "filename" - optional, will use "filepath" if not given + * "metadata" - optional, array of extra metadata, will be automatically calculated if not given. + * "mime" - optional, MIME type, highly recommended to pass in, will be automatically calculated if not given. + * "md5" - optional, MD5 sum, highly recommended to pass in, will be automatically calculated if not given. + * + * @param boolean $p_copyMedia + * copy the media file if true, make symlink if false + * + * @return StoredFile|NULL|PEAR_Error + */ + public static function Insert($p_values, $p_copyMedia=TRUE) + { + global $CC_CONFIG, $CC_DBC; + + if (!isset($p_values["filepath"])) { + return new PEAR_Error("StoredFile::Insert: filepath not set."); + } + if (!file_exists($p_values['filepath'])) { + return PEAR::raiseError("StoredFile::Insert: ". + "media file not found ({$p_values['filepath']})"); + } + + $gunid = isset($p_values['gunid'])?$p_values['gunid']:NULL; + + // Create the StoredFile object + $storedFile = new StoredFile($gunid); + + // Get metadata + if (isset($p_values["metadata"])) { + $metadata = $p_values['metadata']; + } else { + $metadata = camp_get_audio_metadata($p_values["filepath"]); + } + + $storedFile->name = isset($p_values['filename']) ? $p_values['filename'] : $p_values["filepath"]; + $storedFile->id = isset($p_values['id']) && is_integer($p_values['id'])?(int)$p_values['id']:null; + // NOTE: POSTGRES-SPECIFIC KEYWORD "DEFAULT" BEING USED, WOULD BE "NULL" IN MYSQL + $sqlId = !is_null($storedFile->id)?"'".$storedFile->id."'":'DEFAULT'; + $storedFile->ftype = isset($p_values['filetype']) ? strtolower($p_values['filetype']) : "audioclip"; + $storedFile->mime = (isset($p_values["mime"]) ? $p_values["mime"] : NULL ); + // $storedFile->filepath = $p_values['filepath']; + if (isset($p_values['md5'])) { + $storedFile->md5 = $p_values['md5']; + } elseif (file_exists($p_values['filepath'])) { + //echo "StoredFile::Insert: WARNING: Having to recalculate MD5 value\n"; + $storedFile->md5 = md5_file($p_values['filepath']); + } + + // Check for duplicates -- return duplicate + $duplicate = StoredFile::RecallByMd5($storedFile->md5); + if ($duplicate) { + return $duplicate; + } + + $storedFile->exists = FALSE; + + // Insert record into the database + $escapedName = pg_escape_string($storedFile->name); + $escapedFtype = pg_escape_string($storedFile->ftype); + $sql = "INSERT INTO ".$CC_CONFIG['filesTable'] + ."(id, name, gunid, mime, state, ftype, mtime, md5)" + ."VALUES ({$sqlId}, '{$escapedName}', " + ." '{$storedFile->gunid}'," + ." '{$storedFile->mime}', 'incomplete', '$escapedFtype'," + ." now(), '{$storedFile->md5}')"; + //$_SESSION["debug"] .= "sql: ".$sql."
"; + //echo $sql."\n"; + $res = $CC_DBC->query($sql); + if (PEAR::isError($res)) { + $CC_DBC->query("ROLLBACK"); + return $res; + } + + if (!is_integer($storedFile->id)) { + // NOTE: POSTGRES-SPECIFIC + $sql = "SELECT currval('".$CC_CONFIG["filesSequence"]."_seq')"; + $storedFile->id = $CC_DBC->getOne($sql); + } + $storedFile->setMetadataBatch($metadata); + + // Save media file + $res = $storedFile->addFile($p_values['filepath'], $p_copyMedia); + if (PEAR::isError($res)) { + echo "StoredFile::Insert -- addFile(): '".$res->getMessage()."'\n"; + return $res; + } + + if (empty($storedFile->mime)) { + //echo "StoredFile::Insert: WARNING: Having to recalculate MIME value\n"; + $storedFile->setMime($storedFile->getMime()); + } + + // Save state + $storedFile->setState('ready'); + + // Recall the object to get all the proper values + $storedFile = StoredFile::RecallByGunid($storedFile->gunid); + return $storedFile; + } + + /** + * Fetch instance of StoreFile object.
+ * Should be supplied with only ONE parameter, all the rest should + * be NULL. + * + * @param int $p_id + * local id + * @param string $p_gunid + * global unique id of file + * @param string $p_md5sum + * MD5 sum of the file + * @return StoredFile|Playlist|NULL + * Return NULL if the object doesnt exist in the DB. + */ + public static function Recall($p_id=null, $p_gunid=null, $p_md5sum=null) + { + global $CC_DBC; + global $CC_CONFIG; + if (!is_null($p_id)) { + $cond = "id='".intval($p_id)."'"; + } elseif (!is_null($p_gunid)) { + $cond = "gunid='$p_gunid'"; + } elseif (!is_null($p_md5sum)) { + $cond = "md5='$p_md5sum'"; + } else { + return null; + } + $sql = "SELECT *" + ." FROM ".$CC_CONFIG['filesTable'] + ." WHERE $cond"; + //echo $sql; + $row = $CC_DBC->getRow($sql); + if (PEAR::isError($row) || is_null($row)) { + return $row; + } + $gunid = $row['gunid']; + $storedFile = new StoredFile($gunid); + $storedFile->id = $row['id']; + $storedFile->name = $row['name']; + $storedFile->mime = $row['mime']; + $storedFile->ftype = $row['ftype']; + $storedFile->state = $row['state']; + $storedFile->currentlyaccessing = $row['currentlyaccessing']; + $storedFile->editedby = $row['editedby']; + $storedFile->mtime = $row['mtime']; + $storedFile->md5 = $row['md5']; + $storedFile->filepath = $row['filepath']; + $storedFile->exists = TRUE; + $storedFile->setFormat($row['ftype']); + return $storedFile; + } + + + /** + * Create instance of StoreFile object and recall existing file + * by gunid. + * + * @param string $p_gunid + * global unique id of file + * @return StoredFile + */ + public static function RecallByGunid($p_gunid='') + { + return StoredFile::Recall(null, $p_gunid); + } + + + /** + * Fetch the StoredFile by looking up the MD5 value. + * + * @param string $p_md5sum + * @return StoredFile|NULL|PEAR_Error + */ + public static function RecallByMd5($p_md5sum) + { + return StoredFile::Recall(null, null, $p_md5sum); + } + + + /** + * Create instance of StoreFile object and recall existing file + * by access token. + * + * @param string $p_token + * access token + * @return StoredFile + */ + public static function RecallByToken($p_token) + { + global $CC_CONFIG, $CC_DBC; + $sql = "SELECT gunid" + ." FROM ".$CC_CONFIG['accessTable'] + ." WHERE token=x'$p_token'::bigint"; + $gunid = $CC_DBC->getOne($sql); + if (PEAR::isError($gunid)) { + return $gunid; + } + if (is_null($gunid)) { + return PEAR::raiseError( + "StoredFile::RecallByToken: invalid token ($p_token)", GBERR_AOBJNEX); + } + return StoredFile::Recall(null, $gunid); + } + + + /** + * Generate the location to store the file. + * It creates the subdirectory if needed. + */ + private function generateFilePath() + { + global $CC_CONFIG, $CC_DBC; + $resDir = $CC_CONFIG['storageDir']."/".substr($this->gunid, 0, 3); + // see Transport::_getResDir too for resDir name create code + if (!is_dir($resDir)) { + mkdir($resDir, 02775); + chmod($resDir, 02775); + } + $info = pathinfo($this->name); + $fileExt = strtolower($info["extension"]); + return "{$resDir}/{$this->gunid}.{$fileExt}"; + } + + /** + * Insert media file to filesystem + * + * @param string $p_localFilePath + * local path + * @param boolean $p_copyMedia + * copy the media file if true, make symlink if false + * @return TRUE|PEAR_Error + */ + public function addFile($p_localFilePath, $p_copyMedia=TRUE) + { + global $CC_CONFIG, $CC_DBC; + if ($this->exists) { + return FALSE; + } + // for files downloaded from remote instance: + if ($p_localFilePath == $this->filepath) { + $this->exists = TRUE; + return TRUE; + } + umask(0002); + $dstFile = ''; + if ($p_copyMedia) { + $dstFile = $this->generateFilePath(); + $r = @copy($p_localFilePath, $dstFile); + if (!$r) { + $this->exists = FALSE; + return PEAR::raiseError( + "StoredFile::addFile: file save failed". + " ($p_localFilePath, {$this->filepath})",GBERR_FILEIO + ); + } + } else { + $dstFile = $p_localFilePath; + $r = TRUE; + //$r = @symlink($p_localFilePath, $dstFile); + } + $this->filepath = $dstFile; + $sqlPath = pg_escape_string($this->filepath); + $sql = "UPDATE ".$CC_CONFIG["filesTable"] + ." SET filepath='{$sqlPath}'" + ." WHERE id={$this->id}"; + //echo $sql."\n"; + $res = $CC_DBC->query($sql); + if (PEAR::isError($res)) { + return $res; + } + $this->exists = TRUE; + return TRUE; + } + + + /** + * Delete and insert media file + * + * @param string $p_localFilePath + * local path + * @return TRUE|PEAR_Error + */ + public function replaceFile($p_localFilePath) + { + // Dont do anything if the source and destination files are + // the same. + if ($this->name == $p_localFilePath) { + return TRUE; + } + + if ($this->exists) { + $r = $this->deleteFile(); + if (PEAR::isError($r)) { + return $r; + } + } + return $this->addFile($p_localFilePath); + } + + + /** + * Return true if file corresponding to the object exists + * + * @return boolean + */ + public function existsFile() + { + return $this->exists; + } + + + /** + * Delete media file from filesystem. + * You cant delete a file if it is being accessed. + * You cant delete a file if it is scheduled to be played in the future. + * The file will be removed from all playlists it is a part of. + * + * @return boolean|PEAR_Error + */ + public function deleteFile() + { + global $CC_CONFIG; + if (!$this->exists) { + return FALSE; + } + if ($this->isAccessed()) { + return PEAR::raiseError( + 'Cannot delete a file that is currently accessed.' + ); + } + + // Check if the file is scheduled to be played in the future + if (Schedule::IsFileScheduledInTheFuture($this->id)) { + return PEAR::raiseError( + 'Cannot delete a file that is scheduled in the future.' + ); + } + + // Delete it from all playlists + //Playlist::DeleteFileFromAllPlaylists($this->id); + + // Only delete the file from filesystem if it has been copied to the + // storage directory. (i.e. dont delete linked files) + if (substr($this->filepath, 0, strlen($CC_CONFIG["storageDir"])) == $CC_CONFIG["storageDir"]) { + // Delete the file + if (!file_exists($this->filepath) || @unlink($this->filepath)) { + $this->exists = FALSE; + return TRUE; + } else { + return PEAR::raiseError( + "StoredFile::deleteFile: unlink failed ({$this->filepath})", + GBERR_FILEIO + ); + } + } else { + $this->exists = FALSE; + return TRUE; + } + } + + + /** + * Analyze file with getid3 module.
+ * Obtain some metadata stored in media file.
+ * This method should be used for prefilling metadata input form. + * + * @return array + * hierarchical hasharray with information about media file + */ + public function analyzeFile() + { + if (!$this->exists) { + return FALSE; + } + $ia = camp_get_audio_metadata($this->filepath); + return $ia; + } + + + /** + * Create instance of StoredFile object and make copy of existing file + * + * @param StoredFile $p_src + * source object + * @param int $p_nid + * new local id + * @return StoredFile + */ + public static function CopyOf(&$p_src, $p_nid) + { + $values = array( + "id" => $p_nid, + "filename" => $p_src->name, + "filepath" => $p_src->getRealFileName(), + "filetype" => $p_src->getType() + ); + $storedFile = StoredFile::Insert($values); + if (PEAR::isError($storedFile)) { + return $storedFile; + } + $storedFile->replaceMetadata($p_src->getAllMetadata(), 'string'); + return $storedFile; + } + + + /** + * Replace existing file with new data. + * + * @param int $p_oid + * NOT USED + * @param string $p_name + * name of file + * @param string $p_localFilePath + * local path to media file + * @param string $p_metadata + * local path to metadata XML file or XML string + * @param string $p_mdataLoc + * 'file'|'string' + * @return TRUE|PEAR_Error + */ +// public function replace($p_oid, $p_name, $p_localFilePath='', $p_metadata='', +// $p_mdataLoc='file') +// { +// global $CC_CONFIG, $CC_DBC; +// $CC_DBC->query("BEGIN"); +// $res = $this->setName($p_name); +// if (PEAR::isError($res)) { +// $CC_DBC->query("ROLLBACK"); +// return $res; +// } +// if ($p_localFilePath != '') { +// $res = $this->setRawMediaData($p_localFilePath); +// } else { +// $res = $this->deleteFile(); +// } +// if (PEAR::isError($res)) { +// $CC_DBC->query("ROLLBACK"); +// return $res; +// } +// if ($p_metadata != '') { +// $res = $this->setMetadata($p_metadata, $p_mdataLoc); +// } else { +//// $res = $this->md->delete(); +// $res = $this->clearMetadata(); +// } +// if (PEAR::isError($res)) { +// $CC_DBC->query("ROLLBACK"); +// return $res; +// } +// $res = $CC_DBC->query("COMMIT"); +// if (PEAR::isError($res)) { +// $CC_DBC->query("ROLLBACK"); +// return $res; +// } +// return TRUE; +// } + + + /** + * Increase access counter, create access token, insert access record. + * + * @param int $parent + * parent token + * @return array + * array with: access URL, access token + */ + public function accessRawMediaData($p_parent='0') + { + $realFname = $this->getRealFileName(); + $ext = $this->getFileExtension(); + $res = BasicStor::bsAccess($realFname, $ext, $this->gunid, 'access', $p_parent); + if (PEAR::isError($res)) { + return $res; + } + $resultArray = + array('url'=>"file://{$res['fname']}", 'token'=>$res['token']); + return $resultArray; + } + + + /** + * Decrease access couter, delete access record. + * + * @param string $p_token + * access token + * @return boolean + */ + public function releaseRawMediaData($p_token) + { + $res = BasicStor::bsRelease($p_token); + if (PEAR::isError($res)) { + return $res; + } + return TRUE; + } + + + /** + * Replace media file only with new binary file + * + * @param string $p_localFilePath + * local path to media file + * @return TRUE|PEAR_Error + */ + public function setRawMediaData($p_localFilePath) + { + $res = $this->replaceFile($p_localFilePath); + if (PEAR::isError($res)) { + return $res; + } + $mime = $this->getMime(); + if ($mime !== FALSE) { + $res = $this->setMime($mime); + if (PEAR::isError($res)) { + return $res; + } + } +// $r = $this->md->regenerateXmlFile(); +// if (PEAR::isError($r)) { +// return $r; +// } + return TRUE; + } + + + private static function NormalizeExtent($v) + { + if (!preg_match("|^\d{2}:\d{2}:\d{2}.\d{6}$|", $v)) { + $s = Playlist::playlistTimeToSeconds($v); + $t = Playlist::secondsToPlaylistTime($s); + return $t; + } + return $v; + } + + + /** + * Replace metadata with new XML file + * + * @param string $p_metadata + * local path to metadata XML file or XML string + * @param string $p_mdataLoc + * 'file'|'string' + * @param string $p_format + * metadata format for validation + * ('audioclip' | 'playlist' | 'webstream' | NULL) + * (NULL = no validation) + * @return boolean + */ +// public function setMetadata($p_metadata, $p_mdataLoc='file', $p_format=NULL) +// { +// global $CC_CONFIG, $CC_DBC; +// $CC_DBC->query("BEGIN"); +// $res = $this->md->replace($p_metadata, $p_mdataLoc, $p_format); +// if (PEAR::isError($res)) { +// $CC_DBC->query("ROLLBACK"); +// return $res; +// } +// $res = $CC_DBC->query("COMMIT"); +// if (PEAR::isError($res)) { +// return $res; +// } +// return TRUE; +// } + + /** + * Set metadata element value + * + * @param string $category + * Metadata element identification (e.g. dc:title) + * @param string $value + * value to store, if NULL then delete record + * @return boolean + */ + public function setMetadataValue($p_category, $p_value) + { + global $CC_CONFIG, $CC_DBC; + if (!is_string($p_category) || is_array($p_value)) { + return FALSE; + } + if ($p_category == 'dcterms:extent') { + $p_value = StoredFile::NormalizeExtent($p_value); + } + $columnName = StoredFile::xmlCategoryToDbColumn($p_category); // Get column name + + if (!is_null($columnName)) { + $escapedValue = pg_escape_string($p_value); + $sql = "UPDATE ".$CC_CONFIG["filesTable"] + ." SET $columnName='$escapedValue'" + ." WHERE id={$this->id}"; + //var_dump($sql); + $res = $CC_DBC->query($sql); + if (PEAR::isError($res)) { + return $res; + } + } + return TRUE; + } + + + /** + * Set metadata values in 'batch' mode + * + * @param array $values + * array of key/value pairs + * (e.g. 'dc:title'=>'New title') + * @return boolean + */ + public function setMetadataBatch($values) + { + global $CC_CONFIG, $CC_DBC; + if (!is_array($values)) { + $values = array($values); + } + if (count($values) == 0) { + return true; + } + foreach ($values as $category => $oneValue) { + $columnName = StoredFile::xmlCategoryToDbColumn($category); + if (!is_null($columnName)) { + if ($category == 'dcterms:extent') { + $oneValue = StoredFile::NormalizeExtent($oneValue); + } + // Since track_number is an integer, you cannot set + // it to be the empty string, so we NULL it instead. + if ($columnName == 'track_number' && empty($oneValue)) { + $sqlPart = "$columnName = NULL"; + } elseif (($columnName == 'length') && (strlen($oneValue) > 8)) { + // Postgres doesnt like it if you try to store really large hour + // values. TODO: We need to fix the underlying problem of getting the + // right values. + $parts = explode(':', $oneValue); + $hour = intval($parts[0]); + if ($hour > 24) { + continue; + } else { + $sqlPart = "$columnName = '$oneValue'"; + } + } else { + $escapedValue = pg_escape_string($oneValue); + $sqlPart = "$columnName = '$escapedValue'"; + } + $sqlValues[] = $sqlPart; + } + } + if (count($sqlValues)==0) { + return TRUE; + } + $sql = "UPDATE ".$CC_CONFIG["filesTable"] + ." SET ".join(",", $sqlValues) + ." WHERE id={$this->id}"; + $CC_DBC->query($sql); + return TRUE; + } + + + /** + * Get metadata as array, indexed by the column names in the database. + * + * @return array + */ + public function getMetadata() + { + return $this->md; + } + + /** + * Get one metadata value. + * + * @param string $p_name + * @return string + */ + public function getMetadataValue($p_name) + { + if (isset($this->md[$p_name])){ + return $this->md[$p_name]; + } else { + return ""; + } + } + + /** + * Rename stored virtual file + * + * @param string $p_newname + * @return TRUE|PEAR_Error + */ + public function setName($p_newname) + { + global $CC_CONFIG, $CC_DBC; + $escapedName = pg_escape_string($p_newname); + $sql = "UPDATE ".$CC_CONFIG['filesTable'] + ." SET name='$escapedName', mtime=now()" + ." WHERE gunid='{$this->gunid}'"; + $res = $CC_DBC->query($sql); + if (PEAR::isError($res)) { + return $res; + } + $this->name = $p_newname; + return TRUE; + } + + + /** + * Set state of virtual file + * + * @param string $p_state + * 'empty'|'incomplete'|'ready'|'edited' + * @param int $p_editedby + * user id | 'NULL' for clear editedBy field + * @return TRUE|PEAR_Error + */ + public function setState($p_state, $p_editedby=NULL) + { + global $CC_CONFIG, $CC_DBC; + $escapedState = pg_escape_string($p_state); + $eb = (!is_null($p_editedby) ? ", editedBy=$p_editedby" : ''); + $sql = "UPDATE ".$CC_CONFIG['filesTable'] + ." SET state='$escapedState'$eb, mtime=now()" + ." WHERE gunid='{$this->gunid}'"; + $res = $CC_DBC->query($sql); + if (PEAR::isError($res)) { + return $res; + } + $this->state = $p_state; + $this->editedby = $p_editedby; + return TRUE; + } + + /** + * Set mime-type of virtual file + * + * @param string $p_mime + * mime-type + * @return boolean|PEAR_Error + */ + public function setMime($p_mime) + { + global $CC_CONFIG, $CC_DBC; + if (!is_string($p_mime)) { + $p_mime = 'application/octet-stream'; + } + $escapedMime = pg_escape_string($p_mime); + $sql = "UPDATE ".$CC_CONFIG['filesTable'] + ." SET mime='$escapedMime', mtime=now()" + ." WHERE gunid='{$this->gunid}'"; + $res = $CC_DBC->query($sql); + if (PEAR::isError($res)) { + return $res; + } + $this->mime = $p_mime; + return TRUE; + } + + + /** + * Set md5 of virtual file + * + * @param string $p_md5sum + * @return boolean|PEAR_Error + */ + public function setMd5($p_md5sum) + { + global $CC_CONFIG, $CC_DBC; + $escapedMd5 = pg_escape_string($p_md5sum); + $sql = "UPDATE ".$CC_CONFIG['filesTable'] + ." SET md5='$escapedMd5', mtime=now()" + ." WHERE gunid='{$this->gunid}'"; + $res = $CC_DBC->query($sql); + if (PEAR::isError($res)) { + return $res; + } + $this->md5 = $p_md5sum; + return TRUE; + } + + + /** + * Delete stored virtual file + * + * @param boolean $p_deleteFile + * + * @return TRUE|PEAR_Error + */ + public function delete($p_deleteFile = true) + { + global $CC_CONFIG, $CC_DBC; + if ($p_deleteFile) { + $res = $this->deleteFile(); + if (PEAR::isError($res)) { + return $res; + } + } + $sql = "SELECT to_hex(token)as token, ext " + ." FROM ".$CC_CONFIG['accessTable'] + ." WHERE gunid='{$this->gunid}'"; + $tokens = $CC_DBC->getAll($sql); + if (is_array($tokens)) { + foreach ($tokens as $i => $item) { + $file = $this->_getAccessFileName($item['token'], $item['ext']); + if (file_exists($file)) { + @unlink($file); + } + } + } + $sql = "DELETE FROM ".$CC_CONFIG['accessTable'] + ." WHERE gunid='{$this->gunid}'"; + $res = $CC_DBC->query($sql); + if (PEAR::isError($res)) { + return $res; + } + $sql = "DELETE FROM ".$CC_CONFIG['filesTable'] + ." WHERE gunid='{$this->gunid}'"; + $res = $CC_DBC->query($sql); + if (PEAR::isError($res)) { + return $res; + } + return TRUE; + } + + + /** + * Returns an array of playlist objects that this file is a part of. + * @return array + */ + public function getPlaylists() { + global $CC_CONFIG, $CC_DBC; + $sql = "SELECT playlist_id " + ." FROM ".$CC_CONFIG['playistTable'] + ." WHERE file_id='{$this->id}'"; + $ids = $CC_DBC->getAll($sql); + $playlists = array(); + if (is_array($ids) && count($ids) > 0) { + foreach ($ids as $id) { + $playlists[] = Playlist::Recall($id); + } + } + return $playlists; + } + + + /** + * Returns true if virtual file is currently in use.
+ * Static or dynamic call is possible. + * + * @param string $p_gunid + * optional (for static call), global unique id + * @return boolean|PEAR_Error + */ + public function isAccessed($p_gunid=NULL) + { + global $CC_CONFIG, $CC_DBC; + if (is_null($p_gunid)) { + return ($this->currentlyaccessing > 0); + } + $sql = "SELECT currentlyAccessing FROM ".$CC_CONFIG['filesTable'] + ." WHERE gunid='$p_gunid'"; + $ca = $CC_DBC->getOne($sql); + if (is_null($ca)) { + return PEAR::raiseError( + "StoredFile::isAccessed: invalid gunid ($p_gunid)", + GBERR_FOBJNEX + ); + } + return ($ca > 0); + } + + + /** + * Returns true if virtual file is edited + * + * @param string $p_playlistId + * playlist global unique ID + * @return boolean + */ + public function isEdited($p_playlistId=NULL) + { + if (is_null($p_playlistId)) { + return ($this->state == 'edited'); + } + $state = $this->getState($p_playlistId); + if ($state != 'edited') { + return FALSE; + } + return TRUE; + } + + + /** + * Returns id of user editing playlist + * + * @param string $p_playlistId + * playlist global unique ID + * @return int|null|PEAR_Error + * id of user editing it + */ + public function isEditedBy($p_playlistId=NULL) + { + global $CC_CONFIG, $CC_DBC; + if (is_null($p_playlistId)) { + $p_playlistId = $this->gunid; + } + $sql = "SELECT editedBy FROM ".$CC_CONFIG['filesTable'] + ." WHERE gunid='$p_playlistId'"; + $ca = $CC_DBC->getOne($sql); + if (PEAR::isError($ca)) { + return $ca; + } + if (is_null($ca)) { + return $ca; + } + return intval($ca); + } + + + /** + * Return local ID of virtual file. + * + * @return int + */ + public function getId() + { + return $this->id; + } + + + /** + * Return global ID of virtual file. + * + * @return string + */ + public function getGunid() + { + return $this->gunid; + } + + + /** + * Returns true if raw media file exists + * @return boolean|PEAR_Error + */ + public function exists() + { + global $CC_CONFIG, $CC_DBC; + $sql = "SELECT gunid " + ." FROM ".$CC_CONFIG['filesTable'] + ." WHERE gunid='{$this->gunid}'"; + $indb = $CC_DBC->getRow($sql); + if (PEAR::isError($indb)) { + return $indb; + } + if (is_null($indb)) { + return FALSE; + } + if ($this->ftype == 'audioclip') { + return $this->existsFile(); + } + return TRUE; + } + + + /** + * Create new global unique id + * @return string + */ + public static function generateGunid() + { + return md5(uniqid("", true)); + +// $ip = (isset($_SERVER['SERVER_ADDR']) ? $_SERVER['SERVER_ADDR'] : ''); +// $initString = microtime().$ip.rand(); +// $hash = md5($initString); +// // non-negative int8 +// $hsd = substr($hash, 0, 1); +// $res = dechex(hexdec($hsd)>>1).substr($hash, 1, 15); +// return StoredFile::NormalizeGunid($res); + } + + + /** + * Pad the gunid with zeros if it isnt 16 digits. + * + * @return string + */ +// public static function NormalizeGunid($p_gunid) +// { +// return str_pad($p_gunid, 16, "0", STR_PAD_LEFT); +// } + + + /** + * Return suitable extension. + * + * @todo make it general - is any tool for it? + * + * @return string + * file extension without a dot + */ + public function getFileExtension() + { + $fname = $this->getName(); + $pos = strrpos($fname, '.'); + if ($pos !== FALSE) { + $ext = substr($fname, $pos+1); + if ($ext !== FALSE) { + return $ext; + } + } + switch (strtolower($this->mime)) { + case "audio/mpeg": + $ext = "mp3"; + break; + case "audio/x-wav": + case "audio/x-wave": + $ext = "wav"; + break; + case "audio/x-ogg": + case "application/x-ogg": + $ext = "ogg"; + break; + default: + $ext = "bin"; + break; + } + return $ext; + } + + + /** + * Get mime-type stored in the file. + * Warning: this function is slow! + * + * @return string + */ + public function getMime() + { + $a = $this->analyzeFile(); + if (PEAR::isError($a)) { + return $a; + } + if (isset($a['dc:format'])) { + return $a['dc:format']; + } + return ''; + } + + + /** + * Convenience function. + * @return string + */ + public function getTitle() + { + return $this->md["title"]; + } + + public function getType() + { + return $this->ftype; + } + + /** + * Get storage-internal file state + * + * @param string $p_gunid + * global unique id of file + * @return string + * see install() + */ + public function getState($p_gunid=NULL) + { + global $CC_CONFIG, $CC_DBC; + if (is_null($p_gunid)) { + return $this->state; + } + $sql = "SELECT state FROM ".$CC_CONFIG['filesTable'] + ." WHERE gunid='$p_gunid'"; + return $CC_DBC->getOne($sql); + } + + + /** + * Get mnemonic file name + * + * @param string $p_gunid + * global unique id of file + * @return string + */ + public function getName($p_gunid=NULL) + { + global $CC_CONFIG, $CC_DBC; + if (is_null($p_gunid)) { + return $this->name; + } + $sql = "SELECT name FROM ".$CC_CONFIG['filesTable'] + ." WHERE gunid='$p_gunid'"; + return $CC_DBC->getOne($sql); + } + + + /** + * Get and optionally create subdirectory in real filesystem for storing + * raw media data. + * + * @return string + */ +// private function _getResDir() +// { +// global $CC_CONFIG, $CC_DBC; +// $resDir = $CC_CONFIG['storageDir']."/".substr($this->gunid, 0, 3); +// //$this->gb->debugLog("$resDir"); +// // see Transport::_getResDir too for resDir name create code +// if (!is_dir($resDir)) { +// mkdir($resDir, 02775); +// chmod($resDir, 02775); +// } +// return $resDir; +// } + + + /** + * Get real filename of raw media data + * + * @return string + */ + public function getRealFileName() + { + return $this->filepath; + } + + /** + * Get the URL to access this file. + */ + public function getFileUrl() + { + global $CC_CONFIG; + return "http://".$CC_CONFIG["storageUrlHost"] + ."api/get_media.php?file_id={$this->gunid}"; + } + + /** + * Get real filename of metadata file + * + * @return string + * @see MetaData + */ + public function getRealMetadataFileName() + { + //return $this->md->getFileName(); + return $this->md["name"]; + } + + + /** + * Create and return name for temporary symlink. + * + * @todo Should be more unique + * @return string + */ + private function _getAccessFileName($p_token, $p_ext='EXT') + { + global $CC_CONFIG; + return $CC_CONFIG['accessDir']."/$p_token.$p_ext"; + } + + public static function getFiles($query=NULL) + { + global $CC_CONFIG, $CC_DBC, $g_metadata_xml_to_db_mapping; + + $sql = "SELECT * FROM ".$CC_CONFIG['filesTable']; + + if(!is_null($query)) { + $ob = " ORDER BY ".$g_metadata_xml_to_db_mapping[$query["category"]]; + $sql = $sql . $ob . " " .$query["order"]; + } + + return $CC_DBC->getAll($sql); + } + + public static function searchFiles($md) + { + global $CC_CONFIG, $CC_DBC, $g_metadata_xml_to_db_mapping; + + $match = array( + "0" => "ILIKE", + "1" => "=", + "2" => "<", + "3" => "<=", + "4" => ">", + "5" => ">=", + "6" => "!=", + ); + + $sql = "SELECT * FROM ".$CC_CONFIG['filesTable']; + + $cond = array(); + foreach(array_keys($md) as $key) { + if(strpos($key, 'row') !== false){ + $t = explode("_", $key); + $row_num = $t[1]; + + $string = $g_metadata_xml_to_db_mapping[$md[$key]["metadata_".$row_num]]; + + $string = $string ." ".$match[$md[$key]["match_".$row_num]]; + + if ($md[$key]["match_".$row_num] === "0") + $string = $string." '%". $md[$key]["search_".$row_num]."%'"; + else + $string = $string." '". $md[$key]["search_".$row_num]."'"; + + $cond[] = $string; + } + } + + $where = " WHERE ". join(" AND ", $cond); + $sql = $sql . $where; + //echo $sql; + + return $CC_DBC->getAll($sql); + } + +} // class StoredFile +?> diff --git a/application/models/Subjects.php b/application/models/Subjects.php new file mode 100644 index 000000000..f99cea1c6 --- /dev/null +++ b/application/models/Subjects.php @@ -0,0 +1,683 @@ +raiseError("Subjects::AddSubj: empty login"); + } + $id = $CC_DBC->nextId($CC_CONFIG['subjSequence']); + if (PEAR::isError($id)) { + return $id; + } + if (!is_null($p_pass) && !$p_passenc) { + $p_pass = md5($p_pass); + } + $sql = "INSERT INTO ".$CC_CONFIG['subjTable']." (id, login, pass, type, realname)" + ." VALUES ($id, '$p_login', ". + (is_null($p_pass) ? "'!', 'G'" : "'$p_pass', 'U'").", + '$p_realname')"; + $r = $CC_DBC->query($sql); + if (PEAR::isError($r)) { + return $r; + } + return $id; + } + + + /** + * Remove subject by uid or by login + * + * @param string $login + * @param int $uid + * @return boolean|PEAR_Error + */ + public static function RemoveSubj($login, $uid=NULL) + { + global $CC_CONFIG, $CC_DBC; + if (is_null($uid)) { + $uid = Subjects::GetSubjId($login); + } + if (PEAR::isError($uid)) { + return $uid; + } + $sql = "DELETE FROM ".$CC_CONFIG['smembTable'] + ." WHERE (uid='$uid' OR gid='$uid') AND mid is null"; + $r = $CC_DBC->query($sql); + if (PEAR::isError($r)) { + return $r; + } + $sql2 = "DELETE FROM ".$CC_CONFIG['subjTable'] + ." WHERE login='$login'"; + $r = $CC_DBC->query($sql2); + if (PEAR::isError($r)) { + return $r; + } + return Subjects::_rebuildRels(); + } // fn removeSubj + + + /** + * Check login and password + * + * @param string $login + * @param string $pass + * optional + * @return boolean|int|PEAR_Error + */ + public static function Authenticate($login, $pass='') + { + global $CC_CONFIG, $CC_DBC; + $cpass = md5($pass); + $sql = "SELECT id FROM ".$CC_CONFIG['subjTable'] + ." WHERE login='$login' AND pass='$cpass' AND type='U'"; + $id = $CC_DBC->getOne($sql); + if (PEAR::isError($id)) { + return $id; + } + return (is_null($id) ? FALSE : $id); + } // fn authenticate + + + /** + * Set lastlogin or lastfail timestamp + * + * @param string $login + * @param boolean $failed + * true=> set lastfail, false=> set lastlogin + * @return boolean|int|PEAR_Error + */ + public static function SetTimeStamp($login, $failed=FALSE) + { + global $CC_CONFIG, $CC_DBC; + $fld = ($failed ? 'lastfail' : 'lastlogin'); + $sql = "UPDATE ".$CC_CONFIG['subjTable']." SET $fld=now()" + ." WHERE login='$login'"; + $r = $CC_DBC->query($sql); + if (PEAR::isError($r)) { + return $r; + } + return TRUE; + } // fn setTimeStamp + + + /** + * Change user password + * + * @param string $login + * @param string $oldpass + * old password (optional for 'superuser mode') + * @param string $pass + * optional + * @param boolean $passenc + * optional, password already encrypted if true + * @return boolean|PEAR_Error + */ + public static function Passwd($login, $oldpass=null, $pass='', $passenc=FALSE) + { + global $CC_CONFIG, $CC_DBC; + if (!$passenc) { + $cpass = md5($pass); + } else { + $cpass = $pass; + } + if (!is_null($oldpass)) { + $oldcpass = md5($oldpass); + $oldpCond = "AND pass='$oldcpass'"; + } else { + $oldpCond = ''; + } + $sql = "UPDATE ".$CC_CONFIG['subjTable']." SET pass='$cpass'" + ." WHERE login='$login' $oldpCond AND type='U'"; + $r = $CC_DBC->query($sql); + if (PEAR::isError($r)) { + return $r; + } + return TRUE; + } // fn passwd + + + /* --------------------------------------------------------------- groups */ + + /** + * Add {login} and direct/indirect members to {gname} and to groups, + * where {gname} is [in]direct member + * + * @param string $login + * @param string $gname + * @return int|PEAR_Error + */ + public static function AddSubjectToGroup($login, $gname) + { + $uid = Subjects::GetSubjId($login); + if (PEAR::isError($uid)) { + return $uid; + } + $gid = Subjects::GetSubjId($gname); + if (PEAR::isError($gid)) { + return $gid; + } + $isgr = Subjects::IsGroup($gid); + if (PEAR::isError($isgr)) { + return $isgr; + } + if (!$isgr) { + return PEAR::raiseError("Subjects::addSubj2Gr: Not a group ($gname)", ALIBERR_NOTGR); + } + // add subject and all [in]direct members to group $gname: + $mid = Subjects::_plainAddSubjectToGroup($uid, $gid); + if (PEAR::isError($mid)) { + return $mid; + } + // add it to all groups where $gname is [in]direct member: + $marr = Subjects::_listRMemb($gid); + if (PEAR::isError($marr)) { + return $marr; + } + foreach ($marr as $k => $v) { + $r = Subjects::_plainAddSubjectToGroup( + $uid, $v['gid'], intval($v['level'])+1, $v['id']); + if (PEAR::isError($r)) { + return $r; + } + } + return $mid; + } // fn addSubj2Gr + + + /** + * Remove subject from group + * + * @param string $login + * @param string $gname + * @return boolean|PEAR_Error + */ + public static function RemoveSubjectFromGroup($login, $gname) + { + global $CC_CONFIG, $CC_DBC; + $uid = Subjects::GetSubjId($login); + if (PEAR::isError($uid)) { + return $uid; + } + $gid = Subjects::GetSubjId($gname); + if (PEAR::isError($gid)) { + return $gid; + } + $sql = "SELECT id FROM ".$CC_CONFIG['smembTable'] + ." WHERE uid='$uid' AND gid='$gid' AND mid is null"; + $mid = $CC_DBC->getOne($sql); + if (is_null($mid)) { + return FALSE; + } + if (PEAR::isError($mid)) { + return $mid; + } + // remove it: + $r = Subjects::_removeMemb($mid); + if (PEAR::isError($r)) { + return $r; + } + // and rebuild indirect memberships: + $r = Subjects::_rebuildRels(); + if (PEAR::isError($r)) { + return $r; + } + return TRUE; + } // fn removeSubjFromGr + + + /* --------------------------------------------------------- info methods */ + + /** + * Get subject id from login + * + * @param string $login + * @return int|PEAR_Error + */ + public static function GetSubjId($login) + { + global $CC_CONFIG; + global $CC_DBC; + $sql = "SELECT id FROM ".$CC_CONFIG['subjTable'] + ." WHERE login='$login'"; + return $CC_DBC->getOne($sql); + } // fn getSubjId + + + /** + * Get subject name (login) from id + * + * @param int $id + * @param string $fld + * @return string|PEAR_Error + */ + public static function GetSubjName($id, $fld='login') + { + global $CC_CONFIG; + global $CC_DBC; + $sql = "SELECT $fld FROM ".$CC_CONFIG['subjTable'] + ." WHERE id='$id'"; + return $CC_DBC->getOne($sql); + } // fn getSubjName + + + /** + * Get one subject from the table. + * + * @param string $p_fieldValue + * @param string $p_fieldName + * @return array + */ + public static function GetSubject($p_fieldValue, $p_fieldName='login') + { + global $CC_CONFIG, $CC_DBC; + if (!in_array($p_fieldName, array("login", "id"))) { + return null; + } + $escapedValue = pg_escape_string($p_fieldValue); + $sql = "SELECT * FROM ".$CC_CONFIG['subjTable'] + ." WHERE $p_fieldName='$escapedValue'"; + $row = $CC_DBC->GetRow($sql); + return $row; + } + + + /** + * Get all subjects + * + * @param string $flds + * @return array|PEAR_Error + */ + public static function GetSubjects($flds='id, login') + { + global $CC_CONFIG, $CC_DBC; + $sql = "SELECT $flds FROM ".$CC_CONFIG['subjTable']; + return $CC_DBC->getAll($sql); + } // fn getSubjects + + + /** + * Get subjects with count of direct members + * + * @return array|PEAR_Error + */ + public static function GetSubjectsWCnt() + { + global $CC_CONFIG, $CC_DBC; + $sql = "SELECT count(m.uid)as cnt, s.id, s.login, s.type" + ." FROM ".$CC_CONFIG['subjTable']." s" + ." LEFT JOIN ".$CC_CONFIG['smembTable']." m ON m.gid=s.id" + ." WHERE m.mid is null" + ." GROUP BY s.id, s.login, s.type" + ." ORDER BY s.id"; + return $CC_DBC->getAll($sql); + } // fn getSubjectsWCnt + + + /** + * Return true if subject is a group + * + * @param int $gid + * @return boolean|PEAR_Error + */ + public static function IsGroup($gid) + { + global $CC_CONFIG, $CC_DBC; + if (empty($gid)) { + return FALSE; + } + $sql = "SELECT type FROM ".$CC_CONFIG['subjTable'] + ." WHERE id='$gid'"; + $r = $CC_DBC->getOne($sql); + if (PEAR::isError($r)) { + return $r; + } + return ($r === 'G'); + } // fn isGroup + + + /** + * List direct members of group + * + * @param int $gid + * @return array|PEAR_Error + */ + public static function ListGroup($gid) + { + global $CC_CONFIG, $CC_DBC; + $sql = "SELECT s.id, s.login, s.type" + ." FROM ".$CC_CONFIG['smembTable']." m, ".$CC_CONFIG['subjTable']." s" + ." WHERE m.uid=s.id AND m.mid is null AND m.gid='$gid'"; + return $CC_DBC->getAll($sql); + } // fn listGroup + + + /** + * Return true if uid is [id]direct member of gid + * + * @param int $uid + * local user id + * @param int $gid + * local group id + * @return boolean + */ + public static function IsMemberOf($uid, $gid) + { + global $CC_CONFIG, $CC_DBC; + $sql = "SELECT count(*)as cnt" + ." FROM ".$CC_CONFIG['smembTable'] + ." WHERE uid='$uid' AND gid='$gid'"; + $res = $CC_DBC->getOne($sql); + if (PEAR::isError($res)) { + return $res; + } + return (intval($res) > 0); + } // fn isMemberOf + + + /* ==================================================== "private" methods */ + + /** + * Create membership record + * + * @param int $uid + * @param int $gid + * @param int $level + * @param int $mid + * @return int|PEAR_Error + */ + private static function _addMemb($uid, $gid, $level=0, $mid='null') + { + global $CC_CONFIG, $CC_DBC; + if ($uid == $gid) { + return PEAR::raiseError("Subjects::_addMemb: uid==gid ($uid)", ALIBERR_BADSMEMB); + } + $sql = "SELECT id, level, mid FROM ".$CC_CONFIG['smembTable'] + ." WHERE uid='$uid' AND gid='$gid' ORDER BY level ASC"; + $a = $CC_DBC->getAll($sql); + if (PEAR::isError($a)) { + return $a; + } + if (count($a) > 0) { + $a0 = $a[0]; + $id = $a0['id']; + if ($level < intval($a0['level'])){ + $sql2 = "UPDATE ".$CC_CONFIG['smembTable'] + ." SET level='$level', mid=$mid WHERE id='{$a0['id']}'"; + $r = $CC_DBC->query($sql2); + if (PEAR::isError($r)) { + return $r; + } + } + } else { + $id = $CC_DBC->nextId($CC_CONFIG['smembSequence']); + if (PEAR::isError($id)) { + return $id; + } + $sql3 = "INSERT INTO ".$CC_CONFIG['smembTable']." (id, uid, gid, level, mid)" + ." VALUES ($id, $uid, $gid, $level, $mid)"; + $r = $CC_DBC->query($sql3); + if (PEAR::isError($r)) { + return $r; + } + } + return $id; + } // fn _addMemb + + + /** + * Remove membership record + * + * @param int $mid + * @return null|PEAR_Error + */ + private static function _removeMemb($mid) + { + global $CC_CONFIG, $CC_DBC; + $sql = "DELETE FROM ".$CC_CONFIG['smembTable'] + ." WHERE id='$mid'"; + return $CC_DBC->query($sql); + } // fn _removeMemb + + + /** + * List [in]direct members of group + * + * @param int $gid + * @param int $uid + * @return array|PEAR_Error + */ + private static function _listMemb($gid, $uid=NULL) + { + global $CC_CONFIG, $CC_DBC; + $sql = "SELECT id, uid, level FROM ".$CC_CONFIG['smembTable'] + ." WHERE gid='$gid'".(is_null($uid) ? '' : " AND uid='$uid'"); + return $CC_DBC->getAll($sql); + } // fn _listMemb + + + /** + * List groups where uid is [in]direct member + * + * @param int $gid + * @param int $uid + * @return array|PEAR_Error + */ + private static function _listRMemb($uid, $gid=NULL) + { + global $CC_CONFIG, $CC_DBC; + $sql = "SELECT id, gid, level FROM ".$CC_CONFIG['smembTable'] + ." WHERE uid='$uid'".(is_null($gid) ? '' : " AND gid='$gid'"); + return $CC_DBC->getAll($sql); + } // fn listRMemb + + + /** + * Add uid and its [in]direct members to gid + * + * @param int $uid + * @param int $gid + * @param int $level + * @param int $rmid + * @return int|PEAR_Error + */ + private static function _plainAddSubjectToGroup($uid, $gid, $level=0, $rmid='null') + { + $mid = Subjects::_addMemb($uid, $gid, $level, $rmid); + if (PEAR::isError($mid)) { + return $mid; + } + $marr = Subjects::_listMemb($uid); + if (PEAR::isError($marr)) { + return $marr; + } + foreach ($marr as $k => $v) { + $r = Subjects::_addMemb( + $v['uid'], $gid, intval($v['level'])+$level+1, $mid + ); + if (PEAR::isError($r)) { + return $r; + } + } + return $mid; + } + + + /** + * Rebuild indirect membership records
+ * it's probably more complicated to do removing without rebuild ... + * + * @return true|PEAR_Error + */ + private static function _rebuildRels() + { + global $CC_CONFIG, $CC_DBC; + $CC_DBC->query("BEGIN"); + $r = $CC_DBC->query("LOCK TABLE ".$CC_CONFIG['smembTable']); + if (PEAR::isError($r)) { + return $r; + } + $sql = "DELETE FROM ".$CC_CONFIG['smembTable'] + ." WHERE mid is not null"; + $r = $CC_DBC->query($sql); + if (PEAR::isError($r)) { + return $r; + } + $arr = $CC_DBC->getAll("SELECT uid, gid FROM ".$CC_CONFIG['smembTable']); + // WHERE mid is null + if (PEAR::isError($arr)) { + return $arr; + } + foreach ($arr as $it) { + $marr = Subjects::_listRMemb($it['gid']); + if (PEAR::isError($marr)) { + return $marr; + } + foreach ($marr as $k => $v) { + $r = Subjects::_plainAddSubjectToGroup( + $it['uid'], $v['gid'], intval($v['level'])+1, $v['id'] + ); + if (PEAR::isError($r)) { + return $r; + } + } + } + $r = $CC_DBC->query("COMMIT"); + if (PEAR::isError($r)) { + return $r; + } + return TRUE; + } // fn _rebuildRels + + + /* =============================================== test and debug methods */ + + /** + * Dump subjects for debug + * + * @param string $indstr + * indentation string + * @param string $ind + * actual indentation + * @return string + */ + public static function DumpSubjects($indstr=' ', $ind='') + { + $r = $ind.join(', ', array_map( + create_function('$v', 'return "{$v[\'login\']}({$v[\'cnt\']})";'), + Subjects::GetSubjectsWCnt() + ))."\n"; + return $r; + } // fn dumpSubjects + + + /** + * Delete all subjects and membership records + * + * @return void + */ + public static function DeleteData() + { + global $CC_CONFIG, $CC_DBC; + $CC_DBC->query("DELETE FROM ".$CC_CONFIG['subjTable']); + $CC_DBC->query("DELETE FROM ".$CC_CONFIG['smembTable']); + //ObjClasses::DeleteData(); + } // fn deleteData + + + /** + * Insert test data + * + * @return array + */ + public function TestData() + { +// $tdata = ObjClasses::TestData(); +// $o['root'] = Subjects::AddSubj('root', 'q'); +// $o['test1'] = Subjects::AddSubj('test1', 'a'); +// $o['test2'] = Subjects::AddSubj('test2', 'a'); +// $o['test3'] = Subjects::AddSubj('test3', 'a'); +// $o['test4'] = Subjects::AddSubj('test4', 'a'); +// $o['test5'] = Subjects::AddSubj('test5', 'a'); +// $o['gr1'] = Subjects::AddSubj('gr1'); +// $o['gr2'] = Subjects::AddSubj('gr2'); +// $o['gr3'] = Subjects::AddSubj('gr3'); +// $o['gr4'] = Subjects::AddSubj('gr4'); +// Subjects::AddSubjectToGroup('test1', 'gr1'); +// Subjects::AddSubjectToGroup('test2', 'gr2'); +// Subjects::AddSubjectToGroup('test3', 'gr3'); +// Subjects::AddSubjectToGroup('test4', 'gr4'); +// Subjects::AddSubjectToGroup('test5', 'gr1'); +// Subjects::AddSubjectToGroup('gr4', 'gr3'); +// Subjects::AddSubjectToGroup('gr3', 'gr2'); +// $tdata['subjects'] = $o; +// return $tdata; + } // fn TestData + + + /** + * Make basic test + * + */ + public static function Test() + { +// $p = ObjClasses::Test(); +// if (PEAR::isError($p)) { +// return $p; +// } +// Subjects::DeleteData(); +// Subjects::TestData(); +// $test_correct = "root(0), test1(0), test2(0), test3(0),". +// " test4(0), test5(0), gr1(2), gr2(2), gr3(2), gr4(1)\n"; +// $test_dump = Subjects::DumpSubjects(); +// Subjects::RemoveSubj('test1'); +// Subjects::RemoveSubj('test3'); +// Subjects::RemoveSubjectFromGroup('test5', 'gr1'); +// Subjects::RemoveSubjectFromGroup('gr3', 'gr2'); +// $test_correct .= "root(0), test2(0), test4(0), test5(0),". +// " gr1(0), gr2(1), gr3(1), gr4(1)\n"; +// $test_dump .= Subjects::DumpSubjects(); +// Subjects::DeleteData(); +// if ($test_dump == $test_correct) { +// $test_log .= "subj: OK\n"; +// return TRUE; +// } else { +// return PEAR::raiseError( +// 'Subjects::test:', 1, PEAR_ERROR_DIE, '%s'. +// "
\ncorrect:\n{$test_correct}\n".
+//                "dump:\n{$test_dump}\n
\n"); +// } + } // fn test + +} // class Subjects +?> \ No newline at end of file diff --git a/application/models/Transport.php b/application/models/Transport.php new file mode 100644 index 000000000..c5e8ce5dd --- /dev/null +++ b/application/models/Transport.php @@ -0,0 +1,1833 @@ + + * over unreliable network and from behind firewall

+ * + * Transport states: + *
    + *
  • init: transport is prepared, but not started + * (e.g. no network connection is present)
  • + *
  • pending: transport is in progress, file is not fully transported to + * target system
  • + *
  • waiting: transport is in progress, but not running now
  • + *
  • finished: transport is finished, but file processing on target side + * is not completed
  • + *
  • closed: processing on target side is completed without errors
  • + *
  • failed: error - error message stored in errmsg field
  • + *
  • paused: transport have been paused
  • + *
+ * + * Transport types: + *
    + *
  • audioclip
  • + *
  • playlist
  • + *
  • metadata
  • + *
  • file
  • + *
+ * + * @package Campcaster + * @subpackage StorageServer + * @copyright 2010 Sourcefabric O.P.S. + * @license http://www.gnu.org/licenses/gpl.txt + */ +class Transport +{ + /** + * @var GreenBox + */ + public $gb; + + /** + * File name + * @var string + */ + private $cronJobScript; + + /** + * wget --read-timeout parameter [s] + * @var int + */ + private $downTimeout = 900; + + /** + * wget --waitretry parameter [s] + * @var int + */ + private $downWaitretry = 10; + + /** + * wget --limit-rate parameter + */ + private $downLimitRate = NULL; +# private $downLimitRate = 500; + + /** + * wget -t parameter + * @var int + */ + private $downRetries = 6; + + /** + * curl --max-time parameter + * @var int + */ + private $upTrMaxTime = 1800; + + /** + * curl --speed-time parameter + * @var int + */ + private $upTrSpeedTime = 30; + + /** + * curl --speed-limit parameter + * @var int + */ + private $upTrSpeedLimit = 30; + + /** + * curl --connect-timeout parameter + * @var int + */ + private $upTrConnectTimeout = 20; + + /** + * curl --limit-rate parameter + * @var int + */ + private $upLimitRate = NULL; +# private $upLimitRate = 500; + + + /** + * Constructor + * + * @param LocStor $gb + * @return Transport + */ + public function __construct(&$gb) + { + $this->gb =& $gb; + $this->cronJobScript = realpath( + dirname(__FILE__). + '/../../storageServer/var/cron/transportCronJob.php' + ); + } + + + /* ==================================================== transport methods */ + /* ------------------------------------------------------- common methods */ + /** + * Common "check" method for transports + * + * @param string $trtok + * transport token + * @return array + * struct/hasharray with fields: + * trtype: string - + * audioclip | playlist | playlistPkg | metadata | file + * state: string - transport state + * init | pending | waiting | finished | closed | failed + * direction: string - up | down + * expectedsize: int - file size in bytes + * realsize: int - currently transported bytes + * expectedsum: string - orginal file checksum + * realsum: string - transported file checksum + * title: string - dc:title or filename etc. + * errmsg: string - error message for failed transports + * ... ? + */ + function getTransportInfo($trtok) + { + $trec = TransportRecord::recall($this, $trtok); + if (PEAR::isError($trec)) { + return $trec; + } + $res = array(); + foreach (array( + 'trtype', 'state', 'direction', 'expectedsize', 'realsize', + 'expectedsum', 'realsum', 'title', 'errmsg' + ) as $k) { + $res[$k] = ( isset($trec->row[$k]) ? $trec->row[$k] : NULL ); + } + if ( ($trec->row['direction'] == 'down') && file_exists($trec->row['localfile']) ){ + $res['realsize'] = filesize($trec->row['localfile']); + $res['realsum'] = $this->_chsum($trec->row['localfile']); + } + if ( ($trec->row['direction'] == 'up') ){ + $check = $this->uploadCheck($trec->row['pdtoken']); + if (!PEAR::isError($check)) { + $res['realsize'] = $check['size']; + $res['realsum'] = $check['realsum']; + } + } + return $res; + } + + + /** + * Turn transports on/off, optionaly return current state. + * (true=On / false=off) + * + * @param string $sessid + * session id + * @param boolean $onOff + * optional (if not used, current state is returned) + * @return boolea + * previous state + */ + function turnOnOffTransports($sessid, $onOff=NULL) + { + require_once('Prefs.php'); + $pr = new Prefs($this->gb); + $group = $CC_CONFIG['StationPrefsGr']; + $key = 'TransportsDenied'; + $res = $pr->loadGroupPref($group, $key); + if (PEAR::isError($res)) { + if ($res->getCode() !== GBERR_PREF) { + return $res; + } else { + $res = FALSE; // default + } + } + $state = !$res; + if (is_null($onOff)) { + return $state; + } + $res = $pr->saveGroupPref($sessid, $group, $key, !$onOff); + if (PEAR::isError($res)) { + return $res; + } + return $state; + } + + + /** + * Pause, resume or cancel transport + * + * @param string $trtok + * transport token + * @param string $action + * pause | resume | cancel + * @return string + * resulting transport state + */ + function doTransportAction($trtok, $action) + { + $trec = TransportRecord::recall($this, $trtok); + if (PEAR::isError($trec)) { + return $trec; + } + if ($trec->getState() == 'closed') { + return PEAR::raiseError( + "Transport::doTransportAction:". + " closed transport token ($trtok)", TRERR_TOK + ); + } + switch ($action) { + case 'pause'; + $newState = 'paused'; + break; + case 'resume'; + $newState = 'waiting'; + break; + case 'cancel'; + $newState = 'closed'; + break; + default: + return PEAR::raiseError( + "Transport::doTransportAction:". + " unknown action ($action)" + ); + } + $res = $trec->setState($newState); + switch ($action) { + case 'pause'; + case 'cancel'; + $trec->killJob(); + } + return $res; + } + + /* ------------- special methods for audioClip/webstream object transport */ + + /** + * Start upload of audioClip/webstream/playlist from local storageServer + * to hub. + * + * @param string $gunid + * global unique id of object being transported + * @param boolean $withContent + * if true, transport playlist content too (optional) + * @param array $pars + * default parameters (optional, internal use) + * @return string + * transport token + */ + function upload2Hub($gunid, $withContent=TRUE, $pars=array()) + { + global $CC_CONFIG, $CC_DBC; + $this->trLog("upload2Hub start: ".strftime("%H:%M:%S")); + switch ($ftype = BasicStor::GetType($gunid)) { + case "audioclip": + case "webstream": + $storedFile = StoredFile::RecallByGunid($gunid); + if (is_null($storedFile) || PEAR::isError($storedFile)) { + return $storedFile; + } + // handle metadata: + $mdfpath = $storedFile->getRealMetadataFileName(); + if (PEAR::isError($mdfpath)) { + return $mdfpath; + } + $mdtrec = $this->_uploadGeneralFileToHub($mdfpath, 'metadata', + array_merge(array('gunid'=>$gunid, 'fname'=>'metadata',), $pars) + ); + if (PEAR::isError($mdtrec)) { + return $mdtrec; + } + // handle raw media file: + $fpath = $storedFile->getRealFileName(); + if (PEAR::isError($fpath)) { + return $fpath; + } + $fname = $storedFile->getName(); + if (PEAR::isError($fname)) { + return $fname; + } + $trec = $this->_uploadGeneralFileToHub($fpath, 'audioclip', + array_merge(array( + 'gunid'=>$gunid, 'fname'=>$fname, 'mdtrtok'=>$mdtrec->trtok, + ), $pars) + ); + if (PEAR::isError($trec)) { + return $trec; + } + $this->startCronJobProcess($mdtrec->trtok); + break; + + case "playlist": + $plid = $gunid; + require_once("Playlist.php"); + $pl = StoredFile::RecallByGunid($plid); + if (is_null($pl) || PEAR::isError($pl)) { + return $pl; + } + $fname = $pl->getName(); + if (PEAR::isError($fname)) { + return $fname; + } + if ($withContent) { + $this->trLog("upload2Hub exportPlaylistOpen BEGIN: ".strftime("%H:%M:%S")); + $res = $this->gb->bsExportPlaylistOpen($plid); + $this->trLog("upload2Hub exportPlaylistOpen END: ".strftime("%H:%M:%S")); + if (PEAR::isError($res)) { + return $res; + } + $tmpn = tempnam($CC_CONFIG['transDir'], 'plExport_'); + $plfpath = "$tmpn.lspl"; + $this->trLog("upload2Hub begin copy: ".strftime("%H:%M:%S")); + copy($res['fname'], $plfpath); + $this->trLog("upload2Hub end copy: ".strftime("%H:%M:%S")); + $res = $this->gb->bsExportPlaylistClose($res['token']); + if (PEAR::isError($res)) { + return $res; + } + $fname = $fname.".lspl"; + $trtype = 'playlistPkg'; + } else { + $plfpath = $pl->getRealMetadataFileName(); + if (PEAR::isError($plfpath)) { + return $plfpath; + } + $trtype = 'playlist'; + } + $trec = $this->_uploadGeneralFileToHub($plfpath, $trtype, + array_merge(array('gunid'=>$plid,'fname'=>$fname,), $pars)); + if (PEAR::isError($trec)) { + return $trec; + } + break; + default: + return PEAR::raiseError("Transport::upload2Hub: ftype not supported ($ftype)"); + } + $this->startCronJobProcess($trec->trtok); + $this->trLog("upload2Hub end: ".strftime("%H:%M:%S")); + return $trec->trtok; + } + + + /** + * Start download of audioClip/webstream/playlist from hub to local + * storageServer + * + * @param int $uid + * local user id of transport owner + * (for downloading file to homedir in storage) + * @param string $gunid + * global unique id of object being transported + * @param boolean $withContent + * if true, transport playlist content too (optional) + * @param array $pars + * default parameters (optional, internal use) + * @return string + * transport token + */ + function downloadFromHub($uid, $gunid, $withContent=TRUE, $pars=array()) + { + $trtype = ($withContent ? 'playlistPkg' : 'unknown' ); + $trec = TransportRecord::create($this, $trtype, 'down', + array_merge(array('gunid'=>$gunid, 'uid'=>$uid), $pars)); + if (PEAR::isError($trec)) { + return $trec; + } + $this->startCronJobProcess($trec->trtok); + return $trec->trtok; + } + + + /* ------------------------------------------------ remote-search methods */ + /** + * Start search job on remote Campcaster instance. + * + * @param array $criteria + * LS criteria format (see localSearch) + * @param string $resultMode + * 'php' | 'xmlrpc' + * @param array $pars + * default parameters (optional, internal use) + * @return string + * transport token + */ + function remoteSearch($criteria, $resultMode='php') + { + global $CC_CONFIG, $CC_DBC; + $criteria['resultMode'] = $resultMode; + + // testing of hub availability and hub account configuration. + $sessid = $this->loginToArchive(); + if (PEAR::isError($sessid)) { + switch(intval($sessid->getCode())) { + case 802: + return PEAR::raiseError("Can't login to Hub ({$sessid->getMessage()})", TRERR_XR_FAIL); + case TRERR_XR_FAIL: + return PEAR::raiseError("Can't connect to Hub ({$sessid->getMessage()})", TRERR_XR_FAIL); + } + return $sessid; + } + $params = array("sessid" => $sessid, "criteria" => $criteria); + $result = $this->xmlrpcCall("locstor.searchMetadata", $params); + //$result = $this->xmlrpcCall("locstor.ping", array("par" => "foo")); + $this->logoutFromArchive($sessid); + return $result; + } + + /** + * Start search job on network hub + * + * @param array $criteria + * LS criteria format (see localSearch) + * @param string $resultMode + * 'php' | 'xmlrpc' + * @param array $pars + * default parameters (optional, internal use) + * @return string + * transport token + */ +// function globalSearch($criteria, $resultMode='php', $pars=array()) +// { +// global $CC_CONFIG, $CC_DBC; +// // testing of hub availability and hub account configuration. +// // it makes searchjob not async - should be removed for real async +// $r = $this->loginToArchive(); +// if (PEAR::isError($r)) { +// switch(intval($r->getCode())) { +// case 802: +// return PEAR::raiseError("Can't login to Hub ({$r->getMessage()})", TRERR_XR_FAIL); +// case TRERR_XR_FAIL: +// return PEAR::raiseError("Can't connect to Hub ({$r->getMessage()})", TRERR_XR_FAIL); +// } +// return $r; +// } +// $this->logoutFromArchive($r); +// $criteria['resultMode'] = $resultMode; +// $localfile = tempnam($CC_CONFIG['transDir'], 'searchjob_'); +// @chmod($localfile, 0660); +// $len = file_put_contents($localfile, serialize($criteria)); +// $trec = $this->_uploadGeneralFileToHub($localfile, 'searchjob', $pars); +// if (PEAR::isError($trec)) { +// return $trec; +// } +// $this->startCronJobProcess($trec->trtok); +// return $trec->trtok; +// } + + + /** + * Get results from search job on network hub + * + * @param string $trtok + * transport token + * @param boolean $andClose + * if TRUE, close transport token + * @return array + * LS search result format (see localSearch) + */ +// function getSearchResults($trtok, $andClose=TRUE) +// { +// $trec = TransportRecord::recall($this, $trtok); +// if (PEAR::isError($trec)) { +// return $trec; +// } +// $row = $trec->row; +// switch ($st = $trec->getState()) { +// case "failed": +// return PEAR::raiseError( +// "Transport::getSearchResults:". +// " global search or results transport failed". +// " ({$trec->row['errmsg']})" +// ); +// case "closed": +///* +// $res = file_get_contents($row['localfile']); +// $results = unserialize($res); +// return $results; +//*/ +// return PEAR::raiseError( +// "Transport::getSearchResults:". +// " closed transport token ($trtok)", TRERR_TOK +// ); +// case "finished": +// if ($row['direction'] == 'down') { +// // really finished +// $res = file_get_contents($row['localfile']); +// $results = unserialize($res); +// if ($andClose) { +// $ret = $this->xmlrpcCall('archive.downloadClose', +// array( +// 'token' => $row['pdtoken'] , +// 'trtype' => $row['trtype'] , +// )); +// if (PEAR::isError($ret)) { +// return $ret; +// } +// @unlink($row['localfile']); +// $r = $trec->close(); +// if (PEAR::isError($r)) { +// return $r; +// } +// } +// return $results; +// } +// // otherwise not really finished - only request upload finished +// default: +// return PEAR::raiseError( +// "Transport::getSearchResults: not finished ($st)", +// TRERR_NOTFIN +// ); +// } +// } + + + /* ------------------------ methods for ls-archive-format file transports */ + /** + * Open async file transfer from local storageServer to network hub, + * file should be ls-archive-format file. + * + * @param string $filePath + * local path to uploaded file + * @param array $pars + * default parameters (optional, internal use) + * @return string + * transport token + */ + function uploadFile2Hub($filePath, $pars=array()) + { + if (!file_exists($filePath)) { + return PEAR::raiseError( + "Transport::uploadFile2Hub: file not found ($filePath)" + ); + } + $trec = $this->_uploadGeneralFileToHub($filePath, 'file', $pars); + if (PEAR::isError($trec)) { + return $trec; + } + $this->startCronJobProcess($trec->trtok); + return $trec->trtok; + } + + + /** + * Open async file transfer from network hub to local storageServer, + * file should be ls-archive-format file. + * + * @param string $url + * readable url + * @param string $chsum + * checksum from remote side + * @param int $size + * filesize from remote side + * @param array $pars + * default parameters (internal use) + * @return array + * trtok: string - transport token + * localfile: string - filepath of downloaded file + */ + function downloadFileFromHub($url, $chsum=NULL, $size=NULL, $pars=array()) + { + global $CC_CONFIG, $CC_DBC; + $tmpn = tempnam($CC_CONFIG['transDir'], 'HITrans_'); + $trec = TransportRecord::create($this, 'file', 'down', + array_merge(array( + 'url' => $url, + 'localfile' => $tmpn, + 'expectedsum' => $chsum, + 'expectedsize' => $size, + ), $pars) + ); + if (PEAR::isError($trec)) { + return $trec; + } + $this->startCronJobProcess($trec->trtok); + return array('trtok'=>$trec->trtok, 'localfile'=>$tmpn); + } + + + /** + * Get list of prepared transfers initiated by hub + * + * @return array + * array of structs/hasharrays with fields: + * trtok: string transport token + */ + function getHubInitiatedTransfers() + { + $ret = $this->xmlrpcCall('archive.listHubInitiatedTransfers', + array('target' => HOSTNAME)); + if (PEAR::isError($ret)) { + return $ret; + } + $res = array(); + foreach ($ret as $it) { + $res[] = array('trtok'=>$it['trtok']); + } + return $res; + } + + + /** + * Start of download initiated by hub + * + * @param int $uid + * local user id of transport owner + * (for downloading file to homedir in storage) + * @param string $rtrtok + * transport token obtained from the getHubInitiatedTransfers method + * @return string + * transport token + */ + function startHubInitiatedTransfer($uid, $rtrtok) + { + $ret = $this->xmlrpcCall('archive.listHubInitiatedTransfers', + array( + 'target' => HOSTNAME, + 'trtok' => $rtrtok, + )); + if (PEAR::isError($ret)) { + return $ret; + } + if (count($ret) != 1) { + return PEAR::raiseError( + "Transport::startHubInitiatedTransfer:". + " wrong number of transports (".count($ret).")" + ); + } + $ta = $ret[0]; + // direction invertation to locstor point of view: + $direction = ( $ta['direction']=='up' ? 'down' : 'up' ); + $gunid = $ta['gunid']; + switch ($direction) { + case "up": + switch ($ta['trtype']) { + case "audioclip": + case "playlist": + case "playlistPkg": + $trtok = $this->upload2Hub($gunid, TRUE, + array('rtrtok'=>$rtrtok)); + if (PEAR::isError($trtok)) { + return $trtok; + } + break; + //case "searchjob": break; // not supported yet + //case "file": break; // probably unusable + default: + return PEAR::raiseError( + "Transport::startHubInitiatedTransfer:". + " wrong direction / transport type combination". + " ({$ta['direction']}/{$ta['trtype']})" + ); + } + break; + case "down": + switch ($ta['trtype']) { + case "audioclip": + case "playlist": + case "playlistPkg": + $trtok = $this->downloadFromHub($uid, $gunid, TRUE, + array('rtrtok'=>$rtrtok)); + if (PEAR::isError($trtok)) { + return $trtok; + } + break; + //case "searchjob": break; // probably unusable + case "file": + $r = $this->downloadFileFromHub( + $ta['url'], $ta['expectedsum'], $ta['expectedsize'], + array('rtrtok'=>$rtrtok)); + if (PEAR::isError($r)) { + return $r; + } + extract($r); // trtok, localfile + break; + default: + return PEAR::raiseError( + "Transport::startHubInitiatedTransfer:". + " wrong direction / transport type combination". + " ({$ta['direction']}/{$ta['trtype']})" + ); + } + break; + default: + return PEAR::raiseError( + "Transport::startHubInitiatedTransfer: ???" + ); + } + $ret = $this->xmlrpcCall('archive.setHubInitiatedTransfer', + array( + 'target' => HOSTNAME, + 'trtok' => $rtrtok, + 'state' => 'waiting', + )); + if (PEAR::isError($ret)) { + return $ret; + } + $this->startCronJobProcess($trtok); + return $trtok; + } + + + /* =============================================== authentication methods */ + + /** + * Login to archive server + * (account info is taken from storageServer's config) + * + * @return string + * sessid or error + */ + function loginToArchive() + { + global $CC_CONFIG; + $res = $this->xmlrpcCall('locstor.login', + array( + 'login' => $CC_CONFIG['archiveAccountLogin'], + 'pass' => $CC_CONFIG['archiveAccountPass'] + )); + if (PEAR::isError($res)) { + return $res; + } + return $res['sessid']; + } + + + /** + * Logout from archive server + * + * @param unknown $sessid + * session id + * @return string + * Bye or error + */ + function logoutFromArchive($sessid) + { + $res = $this->xmlrpcCall('locstor.logout', + array('sessid'=>$sessid)); + return $res; + } + + + /* ========================================================= cron methods */ + /* -------------------------------------------------- common cron methods */ + /** + * Main method for periodical transport tasks - called by cron + * + * @param string $direction + * optional + * @return boolean + * TRUE + */ + function cronMain($direction=NULL) + { + global $CC_CONFIG; + if (is_null($direction)) { + $r = $this->cronMain('up'); + if (PEAR::isError($r)) { + return $r; + } + $r = $this->cronMain('down'); + if (PEAR::isError($r)) { + return $r; + } + return TRUE; + } + // fetch all opened transports + $transports = $this->getTransports($direction); + if (PEAR::isError($transports)) { + $this->trLog("cronMain: DB error"); + return FALSE; + } + if (count($transports) == 0) { + if (TR_LOG_LEVEL > 1) { + $this->trLog("cronMain: $direction - nothing to do."); + } + return TRUE; + } + // ping to archive server: + $r = $this->ping(); + chdir($CC_CONFIG['transDir']); + // for all opened transports: + foreach ($transports as $i => $row) { + $r = $this->startCronJobProcess($row['trtok']); + } // foreach transports + return TRUE; + } + + + /** + * Cron job process starter + * + * @param string $trtok + * transport token + * @return boolean + * status + */ + function startCronJobProcess($trtok) + { + global $CC_CONFIG, $CC_DBC; + if (TR_LOG_LEVEL > 2) { + $redirect = $CC_CONFIG['transDir']."/debug.log"; + } else { + $redirect = "/dev/null"; + } + $redirect_escaped = escapeshellcmd($redirect); + $command = "{$this->cronJobScript} {$trtok}"; + $command_escaped = escapeshellcmd($command); + $command_final = "$command_escaped >> $redirect_escaped 2>&1 &"; + $res = system($command_final, $status); + if ($res === FALSE) { + $this->trLog( + "cronMain: Error on execute cronJobScript with trtok {$trtok}" + ); + return FALSE; + } + return TRUE; + } + + + /** + * Dynamic method caller - wrapper + * + * @param string $trtok + * transport token + * @return mixed + * inherited from called method + */ + function cronCallMethod($trtok) + { + global $CC_CONFIG; + $trec = TransportRecord::recall($this, $trtok); + if (PEAR::isError($trec)) { + return $trec; + } + $row = $trec->row; + $state = $row['state']; + + $states = array('init'=>'init', + 'pending'=>'pending', + 'waiting'=>'waiting', + 'finished'=>'finished', + 'failed'=>'failed', + 'closed'=>'closed'); + $directions = array('up'=>'upload', 'down'=>'download'); + // method name construction: + $mname = "cron"; + if (isset($directions[$row['direction']])) { + $mname .= ucfirst($directions[$row['direction']]); + } else { + return PEAR::raiseError( + "Transport::cronCallMethod: invalid direction ({$row['direction']})" + ); + } + if (isset($states[$state])) { + $mname .= ucfirst($states[$state]); + } else { + return PEAR::raiseError( + "Transport::cronCallMethod: invalid state ({$state})" + ); + } + switch ($state) { + // do nothing if closed, penfing or failed: + case 'closed': // excluded in SQL query too, but let check it here + case 'failed': // -"- + case 'pending': + case 'paused': + return TRUE; + case 'waiting': + require_once('Prefs.php'); + $pr = new Prefs($this->gb); + $group = $CC_CONFIG['StationPrefsGr']; + $key = 'TransportsDenied'; + $res = $pr->loadGroupPref($group, $key); + if (PEAR::isError($res)) { + if ($res->getCode() !== GBERR_PREF) { + return $res; + } else { + $res = FALSE; // default + } + } + // transfers turned off + // if ($res) { return TRUE; break; } + if ($res) { + return PEAR::raiseError( + "Transport::cronCallMethod: transfers turned off" + ); + } + // NO break here! + default: + if (method_exists($this, $mname)) { + // lock the job: + $pid = getmypid(); + $r = $trec->setLock(TRUE, $pid); + if (PEAR::isError($r)) { + return $r; + } + $trec = TransportRecord::recall($this, $trtok); + if (PEAR::isError($trec)) { + $trec->setLock(FALSE); + return $trec; + } + $row = $trec->row; + $state = $row['state']; + + // login to archive server: + $r = $this->loginToArchive(); + if (PEAR::isError($r)) { + $r2 = $trec->setLock(FALSE); + return $r; + } + $asessid = $r; + // method call: + if (TR_LOG_LEVEL > 2) { + $this->trLog("cronCallMethod($pid): $mname($trtok) >"); + } + $ret = call_user_func(array($this, $mname), $row, $asessid); + if (PEAR::isError($ret)) { + $trec->setLock(FALSE); + return $this->_failFatal($ret, $trec); + } + if (TR_LOG_LEVEL > 2) { + $this->trLog("cronCallMethod($pid): $mname($trtok) <"); + } + // unlock the job: + $r = $trec->setLock(FALSE); + if (PEAR::isError($r)) { + return $r; + } + // logout: + $r = $this->logoutFromArchive($asessid); + if (PEAR::isError($r)) { + return $r; + } + return $ret; + } else { + return PEAR::raiseError( + "Transport::cronCallMethod: unknown method ($mname)" + ); + } + } + } + + + /** + * Upload initialization + * + * @param array $row + * row from getTransport results + * @param string $asessid + * session id (from network hub) + * @return mixed + * boolean TRUE or error object + */ + function cronUploadInit($row, $asessid) + { + $trtok = $row['trtok']; + $trec = TransportRecord::recall($this, $trtok); + if (PEAR::isError($trec)) { + return $trec; + } + $ret = $this->xmlrpcCall('archive.uploadOpen', + array( + 'sessid' => $asessid , + 'chsum' => $row['expectedsum'], + )); + if (PEAR::isError($ret)) { + return $ret; + } + $r = $trec->setState('waiting', + array('url'=>$ret['url'], 'pdtoken'=>$ret['token'])); + if (PEAR::isError($r)) { + return $r; + } + return TRUE; + } + + + /** + * Download initialization + * + * @param array $row + * row from getTransport results + * @param string $asessid + * session id (from network hub) + * @return mixed + * boolean TRUE or error object + */ + function cronDownloadInit($row, $asessid) + { + global $CC_CONFIG; + $trtok = $row['trtok']; + $trec = TransportRecord::recall($this, $trtok); + if (PEAR::isError($trec)) { + return $trec; + } + $ret = $this->xmlrpcCall('archive.downloadOpen', + array( + 'sessid'=> $asessid, + 'trtype'=> $row['trtype'], + 'pars'=>array( + 'gunid' => $row['gunid'], + 'token' => $row['pdtoken'], + ), + )); + if (PEAR::isError($ret)) { + return $ret; + } + $trtype = $ret['trtype']; + $title = $ret['title']; + $pars = array(); + switch ($trtype) { +// case "searchjob": +// $r = $trec->setState('waiting', $pars); +// break; + case "file": + $r = $trec->setState('waiting',array_merge($pars, array( + 'trtype'=>$trtype, + 'url'=>$ret['url'], 'pdtoken'=>$ret['token'], + 'expectedsum'=>$ret['chsum'], 'expectedsize'=>$ret['size'], + 'fname'=>$ret['filename'], + 'localfile'=>$CC_CONFIG['transDir']."/$trtok", + ))); + break; + case "audioclip": + $mdtrec = TransportRecord::create($this, 'metadata', 'down', + array('gunid'=>$row['gunid'], 'uid'=>$row['uid'], ) + ); + if (PEAR::isError($mdtrec)) { + return $mdtrec; + } + $this->startCronJobProcess($mdtrec->trtok); + $pars = array('mdtrtok'=>$mdtrec->trtok); + // NO break here ! + default: + $r = $trec->setState('waiting',array_merge($pars, array( + 'trtype'=>$trtype, + 'url'=>$ret['url'], 'pdtoken'=>$ret['token'], + 'expectedsum'=>$ret['chsum'], 'expectedsize'=>$ret['size'], + 'fname'=>$ret['filename'], 'title'=>$title, + 'localfile'=>$CC_CONFIG['transDir']."/$trtok", + ))); + } + if (PEAR::isError($r)) { + return $r; + } + return TRUE; + } + + + /** + * Upload next part of transported file + * + * @param array $row + * row from getTransport results + * @param string $asessid + * session id (from network hub) + * @return mixed + * boolean TRUE or error object + */ + function cronUploadWaiting($row, $asessid) + { + $trtok = $row['trtok']; + $check = $this->uploadCheck($row['pdtoken']); + if (PEAR::isError($check)) { + return $check; + } + // test filesize + if (!file_exists($row['localfile'])) { + return PEAR::raiseError("Transport::cronUploadWaiting:". + " file being uploaded does not exist! ({$row['localfile']})" + ); + } + $trec = TransportRecord::recall($this, $trtok); + if (PEAR::isError($trec)) { + return $trec; + } + $size = escapeshellarg($check['size']); + $localfile = escapeshellarg($row['localfile']); + $url = escapeshellarg($row['url']); + $command = + "curl -f -s -C $size --max-time {$this->upTrMaxTime}". + " --speed-time {$this->upTrSpeedTime}". + " --speed-limit {$this->upTrSpeedLimit}". + " --connect-timeout {$this->upTrConnectTimeout}". + (!is_null($this->upLimitRate)? + " --limit-rate {$this->upLimitRate}" : ""). + " -T $localfile $url"; + $r = $trec->setState('pending', array(), 'waiting'); + if (PEAR::isError($r)) { + return $r; + } + if ($r === FALSE) { + return TRUE; + } + $res = system($command, $status); + + // leave paused and closed transports + $trec2 = TransportRecord::recall($this, $trtok); + if (PEAR::isError($trec)) { + return $trec; + } + $state2 = $trec2->row['state']; + if ($state2 == 'paused' || $state2 == 'closed' ) { + return TRUE; + } + + + // status 18 - Partial file. Only a part of the file was transported. + // status 28 - Timeout. Too long/slow upload, try to resume next time rather. + // status 6 - Couldn't resolve host. + // status 7 - Failed to connect to host. + // status 56 - Failure in receiving network data. Important - this status is + // returned if file is locked on server side + if ($status == 0 || $status == 18 || $status == 28 || $status == 6 || $status == 7 || $status == 56) { + $check = $this->uploadCheck($row['pdtoken']); + if (PEAR::isError($check)) { + return $check; + } + // test checksum + if ($check['status'] == TRUE) { + // finished + $r = $trec->setState('finished', + array('realsum'=>$check['realsum'], 'realsize'=>$check['size'])); + if (PEAR::isError($r)) { + return $r; + } + } else { + if (intval($check['size']) < $row['expectedsize']) { + $r = $trec->setState('waiting', + array('realsum'=>$check['realsum'], 'realsize'=>$check['size'])); + if (PEAR::isError($r)) { + return $r; + } + } else { + // wrong md5 at finish - TODO: start again + // $this->xmlrpcCall('archive.uploadReset', array()); + $trec->fail('file uploaded with bad md5'); + return PEAR::raiseError("Transport::cronUploadWaiting:". + " file uploaded with bad md5 ". + "($trtok: {$check['realsum']}/{$check['expectedsum']})" + ); + } + } + } else { + return PEAR::raiseError("Transport::cronUploadWaiting:". + " wrong return status from curl: $status on $url". + "($trtok)" + ); + } + return TRUE; + } + + + /** + * Download next part of transported file + * + * @param array $row + * row from getTransport results + * @param string $asessid + * session id (from network hub) + * @return mixed + * boolean TRUE or error object + */ + function cronDownloadWaiting($row, $asessid) + { + $trtok = $row['trtok']; + // wget the file + $trec = TransportRecord::recall($this, $trtok); + if (PEAR::isError($trec)) { + return $trec; + } + $localfile = escapeshellarg($row['localfile']); + $url = escapeshellarg($row['url']); + $command = + "wget -q -c". + " --read-timeout={$this->downTimeout}". + " --waitretry={$this->downWaitretry}". + " -t {$this->downRetries}". + (!is_null($this->downLimitRate)? + " --limit-rate={$this->downLimitRate}" : ""). + " -O $localfile $url" + ; + $r = $trec->setState('pending', array(), 'waiting'); + if (PEAR::isError($r)) { + return $r; + } + if ($r === FALSE) { + return TRUE; + } + $res = system($command, $status); + + // leave paused and closed transports + $trec2 = TransportRecord::recall($this, $trtok); + if (PEAR::isError($trec)) { + return $trec; + } + $state2 = $trec2->row['state']; + if ($state2 == 'paused' || $state2 == 'closed' ) { + return TRUE; + } + + // check consistency + $size = filesize($row['localfile']); + if ($size < $row['expectedsize']) { + // not finished - return to the 'waiting' state + $r = $trec->setState('waiting', array('realsize'=>$size)); + if (PEAR::isError($r)) { + return $r; + } + } elseif ($size >= $row['expectedsize']) { + $chsum = $this->_chsum($row['localfile']); + if ($chsum == $row['expectedsum']) { + // mark download as finished + $r = $trec->setState('finished', + array('realsum'=>$chsum, 'realsize'=>$size)); + if (PEAR::isError($r)) { + return $r; + } + } else { + // bad checksum, retry from the scratch + @unlink($row['localfile']); + $r = $trec->setState('waiting', + array('realsum'=>$chsum, 'realsize'=>$size)); + if (PEAR::isError($r)) { + return $r; + } + } + } + return TRUE; + } + + + /** + * Finish the upload + * + * @param array $row + * row from getTransport results + * @param string $asessid + * session id (from network hub) + * @return mixed + * boolean TRUE or error object + */ + function cronUploadFinished($row, $asessid) + { + global $CC_CONFIG; + $trtok = $row['trtok']; + $trec = TransportRecord::recall($this, $trtok); + if (PEAR::isError($trec)) { + return $trec; + } + // don't close metadata transport - audioclip will close it + if ($row['trtype'] == 'metadata') { + return TRUE; + } + // handle metadata transport on audioclip trtype: + if ($row['trtype'] == 'audioclip') { + $mdtrec = TransportRecord::recall($this, $trec->row['mdtrtok']); + if (PEAR::isError($mdtrec)) { + return $mdtrec; + } + switch ($mdtrec->row['state']) { + case 'failed': + case 'closed': + return PEAR::raiseError("Transport::cronUploadFinished:". + " metadata transport in wrong state: {$mdtrec->row['state']}". + " ({$this->trtok})" + ); + break; + // don't close transport with nonfinished metadata transport: + case 'init': + case 'waiting': + case 'pending': + case 'paused': + return TRUE; + default: // finished - ok close parent transport + $mdpdtoken = $mdtrec->row['pdtoken']; + } + } else { + $mdpdtoken = NULL; + } + $ret = $this->xmlrpcCall('archive.uploadClose', + array( + 'token' => $row['pdtoken'] , + 'trtype' => $row['trtype'], + 'pars' => array( + 'gunid' => $row['gunid'], + 'name' => $row['fname'], + 'mdpdtoken' => $mdpdtoken, + ), + )); + if (PEAR::isError($ret)) { + if ($row['trtype'] == 'audioclip') { + $r2 = $mdtrec->close(); + } + return $ret; + } + +// if ($row['trtype'] == 'searchjob') { +// @unlink($row['localfile']); +// $r = $trec->setState('init', array( +// 'direction' => 'down', +// 'pdtoken' => $ret['token'], +// 'expectedsum' => $ret['chsum'], +// 'expectedsize' => $ret['size'], +// 'url' => $ret['url'], +// 'realsize' => 0, +// )); +// $this->startCronJobProcess($trec->trtok); +// } else { + $r = $trec->close(); +// } + if (PEAR::isError($r)) { + return $r; + } + switch ($row['trtype']) { + case 'audioclip': + // close metadata transport: + $r = $mdtrec->close(); + if (PEAR::isError($r)) { + return $r; + } + break; + case 'playlistPkg': + // remove exported playlist (playlist with content) + $ep = $row['localfile']; + @unlink($ep); + if (preg_match("|/(plExport_[^\.]+)\.lspl$|", $ep, $va)) { + list(,$tmpn) = $va; $tmpn = $CC_CONFIG['transDir']."/$tmpn"; + if (file_exists($tmpn)) { + @unlink($tmpn); + } + } + + break; + default: + } + + return TRUE; + } + + + /** + * Finish the download + * + * @param array $row + * row from getTransport results + * @param string $asessid + * session id (from network hub) + * @return mixed + * boolean TRUE or error object + */ + function cronDownloadFinished($row, $asessid) + { + $trtok = $row['trtok']; + $trec = TransportRecord::recall($this, $trtok); + if (PEAR::isError($trec)) { + return $trec; + } + switch ($row['trtype']) { + case "audioclip": + $mdtrtok = $trec->row['mdtrtok']; + $mdtrec = TransportRecord::recall($this, $mdtrtok); + if (PEAR::isError($mdtrec)) { + return $mdtrec; + } + $pid = getmypid(); + $r = $mdtrec->setLock(TRUE, $pid); + if (PEAR::isError($r)) { + return $r; + } + switch ($mdtrec->row['state']) { + // don't close transport with nonfinished metadata transport: + case 'init': + case 'waiting': + case 'pending': + case 'paused': + $r = $mdtrec->setLock(FALSE); + if (PEAR::isError($r)) { + return $r; + } + return TRUE; + case 'finished': // metadata finished, close main transport + $values = array( + "filename" => $row['fname'], + "filepath" => $trec->row['localfile'], + "metadata" => $mdtrec->row['localfile'], + "gunid" => $row['gunid'], + "filetype" => "audioclip" + ); + $storedFile = StoredFile::Insert($values); + if (PEAR::isError($storedFile)) { + $mdtrec->setLock(FALSE); + return $storedFile; + } + $res = $storedFile->getId(); + $ret = $this->xmlrpcCall('archive.downloadClose', + array( + 'token' => $mdtrec->row['pdtoken'] , + 'trtype' => 'metadata' , + )); + if (PEAR::isError($ret)) { + $mdtrec->setLock(FALSE); + return $ret; + } + $r = $mdtrec->close(); + if (PEAR::isError($r)) { + $r2 = $mdtrec->setLock(FALSE); + return $r; + } + @unlink($trec->row['localfile']); + @unlink($mdtrec->row['localfile']); + break; + default: + $r = $mdtrec->setLock(FALSE); + return PEAR::raiseError("Transport::cronDownloadFinished:". + " metadata transport in wrong state: {$mdtrec->row['state']}". + " ({$this->trtok})" + ); + } + $r = $mdtrec->setLock(FALSE); + if (PEAR::isError($r)) { + return $r; + } + break; + case "metadata": +// case "searchjob": + return TRUE; // don't close - getSearchResults should close it + break; + } + $ret = $this->xmlrpcCall('archive.downloadClose', + array( + 'token' => $row['pdtoken'] , + 'trtype' => $row['trtype'] , + )); + if (PEAR::isError($ret)) { + return $ret; + } + switch ($row['trtype']) { + case "playlist": + $values = array( + "filename" => $row['fname'], + "metadata" => $trec->row['localfile'], + "gunid" => $row['gunid'], + "filetype" => "playlist" + ); + $storedFile = StoredFile::Insert($values); + if (PEAR::isError($storedFile)) { + return $storedFile; + } + $res = $storedFile->getId(); + @unlink($row['localfile']); + break; + case "playlistPkg": + $subjid = $trec->row['uid']; + $fname = $trec->row['localfile']; + $res = $this->gb->bsImportPlaylist($fname, $subjid); + if (PEAR::isError($res)) { + return $res; + } + @unlink($fname); + break; + case "audioclip": + case "metadata": +// case "searchjob": + case "file": + break; + default: + return PEAR::raiseError("DEBUG: NotImpl ".var_export($row,TRUE)); + } + if (!is_null($rtrtok = $trec->row['rtrtok'])) { + $ret = $this->xmlrpcCall('archive.setHubInitiatedTransfer', + array( + 'target' => HOSTNAME, + 'trtok' => $rtrtok, + 'state' => 'closed', + )); + if (PEAR::isError($ret)) { + return $ret; + } + } + $r = $trec->close(); + if (PEAR::isError($r)) { + return $r; + } + return TRUE; + } + + + /* ==================================================== auxiliary methods */ + /** + * Prepare upload for general file + * + * @param string $fpath + * local filepath of uploaded file + * @param string $trtype + * transport type + * @param array $pars + * default parameters (optional, internal use) + * @return object - transportRecord instance + */ + function _uploadGeneralFileToHub($fpath, $trtype, $pars=array()) + { + $chsum = $this->_chsum($fpath); + $size = filesize($fpath); + $trec = TransportRecord::create($this, $trtype, 'up', + array_merge(array( + 'localfile'=>$fpath, 'fname'=>basename($fpath), + 'expectedsum'=>$chsum, 'expectedsize'=>$size + ), $pars) + ); + if (PEAR::isError($trec)) { + return $trec; + } + return $trec; + } + + + /** + * Create new transport token + * + * @return string + * transport token + */ + function _createTransportToken() + { + $ip = (isset($_SERVER['SERVER_ADDR']) ? $_SERVER['SERVER_ADDR'] : ''); + $initString = microtime().$ip.rand()."org.mdlf.campcaster"; + $hash = md5($initString); + $res = substr($hash, 0, 16); + return $res; + } + + + /** + * Get all relevant transport records + * + * @param string $direction + * 'up' | 'down' + * @param string $target + * target hostname + * @param string $trtok + * transport token for specific query + * @return array + * array of transportRecords (as hasharrays) + */ + function getTransports($direction=NULL, $target=NULL, $trtok=NULL) + { + global $CC_CONFIG, $CC_DBC; + switch ($direction) { + case 'up': + $dirCond = "direction='up' AND"; + break; + case 'down': + $dirCond = "direction='down' AND"; + break; + default: + $dirCond = ''; + break; + } + if (is_null($target)) { + $targetCond = ""; + } else { + $targetCond = "target='$target' AND"; + } + if (is_null($trtok)) { + $trtokCond = ""; + } else { + $trtokCond = "trtok='$trtok' AND"; + } + $rows = $CC_DBC->getAll(" + SELECT + id, trtok, state, trtype, direction, + to_hex(gunid)as gunid, to_hex(pdtoken)as pdtoken, + fname, localfile, expectedsum, expectedsize, url, + uid, target + FROM ".$CC_CONFIG['transTable']." + WHERE $dirCond $targetCond $trtokCond + state not in ('closed', 'failed', 'paused') + ORDER BY start DESC + "); + if (PEAR::isError($rows)) { + return $rows; + } + foreach ($rows as $i => $row) { + $rows[$i]['pdtoken'] = StoredFile::NormalizeGunid($row['pdtoken']); + $rows[$i]['gunid'] = StoredFile::NormalizeGunid($row['gunid']); + } + return $rows; + } + + + /** + * Check remote state of uploaded file + * + * @param string $pdtoken + * put/download token (from network hub) + * @return array + * hash: chsum, size, url + */ + function uploadCheck($pdtoken) + { + $ret = $this->xmlrpcCall('archive.uploadCheck', + array('token'=>$pdtoken)); + return $ret; + } + + + /** + * Ping to remote Campcaster server + * + * @return string + * network hub response or error object + */ + function ping() + { + $res = $this->xmlrpcCall('ping', + array('par'=>'ping_'.date('H:i:s'))); + return $res; + } + + + /** + * XMLRPC call to network hub. + * + * @param string $method + * method name + * @param array $pars + * call parameters + * @return mixed + * response + */ + function xmlrpcCall($method, $pars=array()) + { + global $CC_CONFIG; + $xrp = XML_RPC_encode($pars); + + $pr = new Prefs($this->gb); + $group = $CC_CONFIG["StationPrefsGr"]; + $key = 'archiveServerLocation'; + $archiveUrl = $pr->loadGroupPref($group, $key, false); + + if ($archiveUrl) { + $archiveUrlInfo = parse_url($archiveUrl); + if ($archiveUrlInfo['port']) { + $port = $archiveUrlInfo['port']; + } + else { + $port = 80; + } + + $c = new XML_RPC_Client($archiveUrlInfo['path'], $archiveUrlInfo['host'], $port); + } + else { + $c = new XML_RPC_Client( + $CC_CONFIG['archiveUrlPath']."/".$CC_CONFIG['archiveXMLRPC'], + $CC_CONFIG['archiveUrlHost'], $CC_CONFIG['archiveUrlPort'] + ); + } + + $f = new XML_RPC_Message($method, array($xrp)); + $r = $c->send($f); + if (!$r) { + return PEAR::raiseError("XML-RPC request failed", TRERR_XR_FAIL); + } elseif ($r->faultCode() > 0) { + return PEAR::raiseError($r->faultString(), $r->faultCode()); + // return PEAR::raiseError($r->faultString(). + // " (code ".$r->faultCode().")", TRERR_XR_FAIL); + } else { + $v = $r->value(); + return XML_RPC_decode($v); + } + } + + + /** + * Checksum of local file + * + * @param string $fpath + * local filepath + * @return string + * checksum + */ + function _chsum($fpath) + { + return md5_file($fpath); + } + + + /** + * Check exception and eventually mark transport as failed + * + * @param mixed $res + * result object to be checked + * @param unknown $trec + * transport record object + * @return unknown + */ + function _failFatal($res, $trec) + { + if (PEAR::isError($res)) { + switch ($res->getCode()) { + // non fatal: + case TRERR_XR_FAIL: + break; + // fatal: + default: + $trec->fail('', $res); + } + } + return $res; + } + + + /** + * Clean up transport jobs + * + * @param string $interval + * psql time interval - older closed jobs will be deleted + * @param boolean $forced + * if true, delete non-closed jobs too + * @return boolean true or error + */ + function _cleanUp($interval='1 minute'/*'1 hour'*/, $forced=FALSE) + { + global $CC_CONFIG, $CC_DBC; + $cond = ($forced ? '' : " AND state='closed' AND lock = 'N'"); + $r = $CC_DBC->query(" + DELETE FROM ".$CC_CONFIG['transTable']." + WHERE ts < now() - interval '$interval'".$cond + ); + if (PEAR::isError($r)) { + return $r; + } + return TRUE; + } + + + /** + * Logging wrapper for PEAR error object + * + * @param string $txt + * log message + * @param PEAR_Error $eo + * @param array $row + * array returned from getRow + * @return mixed + * void or error object + */ + function trLogPear($txt, $eo, $row=NULL) + { + $msg = $txt.$eo->getMessage()." ".$eo->getUserInfo(). + " [".$eo->getCode()."]"; + if (!is_null($row)) { + $trec = TransportRecord::recall($this, $row['trtok']); + if (!PEAR::isError($trec)) { + $trec->setState('failed', array('errmsg'=>$msg)); + } + $msg .= "\n ".serialize($row); + } + $this->trLog($msg); + } + + + /** + * Logging for debug transports + * + * @param string $msg + * log message + * @return mixed + * void or error object + */ + function trLog($msg) + { + global $CC_CONFIG; + $logfile = $CC_CONFIG['transDir']."/activity.log"; + if (FALSE === ($fp = fopen($logfile, "a"))) { + return PEAR::raiseError( + "Transport::trLog: Can't write to log ($logfile)" + ); + } + flock($fp,LOCK_SH); + fputs($fp, "---".date("H:i:s")."---\n $msg\n"); + flock($fp,LOCK_UN); + fclose($fp); + } + + + /* ====================================================== install methods */ + /** + * Delete all transports + * + * @return mixed + * void or error object + */ + function resetData() + { + global $CC_CONFIG, $CC_DBC; + return $CC_DBC->query("DELETE FROM ".$CC_CONFIG['transTable']); + } + +} + +?> diff --git a/application/models/TransportRecord.php b/application/models/TransportRecord.php new file mode 100644 index 000000000..9a491e7eb --- /dev/null +++ b/application/models/TransportRecord.php @@ -0,0 +1,419 @@ +tr =& $tr; + $this->gb =& $tr->gb; + } + + + /** + * Factory method + * + * @param Transport $tr + * @param string $trtype + * transport type (see Transport::install) + * @param string $direction + * 'up' | 'down' + * @param array $defaults + * default parameters (optional, internal use) + * @return TransportRecord + */ + function create(&$tr, $trtype, $direction='up', $defaults=array()) + { + global $CC_DBC, $CC_CONFIG; + $trec = new TransportRecord($tr); + $trec->trtok = $trtok = $tr->_createTransportToken(); + $trec->row = array_merge($defaults, + array('trtype'=>$trtype, 'direction'=>$direction)); + $trec->recalled = TRUE; + if (!isset($defaults['title'])) { + $defaults['title'] = $trec->getTitle(); + if (PEAR::isError($defaults['title'])) { + return $defaults['title']; + } + } + $id = $CC_DBC->nextId($CC_CONFIG['transSequence']); + $names = "id, trtok, direction, state, trtype, start, ts"; + $values = "$id, '$trtok', '$direction', 'init', '$trtype', now(), now()"; + foreach ($defaults as $k => $v) { + $sqlVal = $trec->_getSqlVal($k, $v); + $names .= ", $k"; + $values .= ", $sqlVal"; + } + $query = " + INSERT INTO ".$CC_CONFIG['transTable']." + ($names) + VALUES + ($values) + "; + $res = $CC_DBC->query($query); + if (PEAR::isError($res)) { + return $res; + } + return $trec; + } + + + /** + * Recall transport record from DB + * + * @param Transport $tr + * @param string $trtok + * transport token + * @return TransportRecord + */ + function recall(&$tr, $trtok) + { + global $CC_DBC, $CC_CONFIG; + $trec = new TransportRecord($tr); + $trec->trtok = $trtok; + $row = $CC_DBC->getRow(" + SELECT + id, trtok, state, trtype, direction, + to_hex(gunid)as gunid, to_hex(pdtoken)as pdtoken, + fname, localfile, url, rtrtok, mdtrtok, uid, + expectedsize, realsize, expectedsum, realsum, + errmsg, title, jobpid + FROM ".$CC_CONFIG['transTable']." + WHERE trtok='$trtok' + "); + if (PEAR::isError($row)) { + return $row; + } + if (is_null($row)) { + return PEAR::raiseError("TransportRecord::recall:". + " invalid transport token ($trtok)", TRERR_TOK + ); + } + $row['pdtoken'] = StoredFile::NormalizeGunid($row['pdtoken']); + $row['gunid'] = StoredFile::NormalizeGunid($row['gunid']); + $trec->row = $row; + $trec->recalled = TRUE; + return $trec; + } + + + /** + * Set state of transport record + * + * @param string $newState + * @param array $data + * other data fields to set + * @param string $oldState + * check old state and do nothing if differ + * @param boolean $lock + * check lock and do nothing if differ + * @return boolean success + */ + function setState($newState, $data=array(), $oldState=NULL, $lock=NULL) + { + global $CC_CONFIG, $CC_DBC; + $set = " state='$newState', ts=now()"; + if (!is_null($lock)) { + $slock = ($lock ? 'Y' : 'N'); + $nlock = (!$lock); + $snlock = ($nlock ? 'Y' : 'N'); + $set .= ", lock='$snlock'"; + } + foreach ($data as $k => $v) { + $set .= ", $k=".$this->_getSqlVal($k, $v); + } + $r = $CC_DBC->query(" + UPDATE ".$CC_CONFIG['transTable']." + SET $set + WHERE trtok='{$this->trtok}'". + (is_null($oldState) ? '' : " AND state='$oldState'"). + (is_null($lock) ? '' : " AND lock = '$slock'") + ); + if (PEAR::isError($r)) { + return $r; + } + // return TRUE; + $affRows = $CC_DBC->affectedRows(); + if (PEAR::isError($affRows)) { + return $affRows; + } + return ($affRows == 1); + } + + + /** + * Return state of transport record + * + * @return string + * state + */ + function getState() + { + if (!$this->recalled) { + return PEAR::raiseError("TransportRecord::getState:". + " not recalled ({$this->trtok})", TRERR_TOK + ); + } + return $this->row['state']; + } + + + /** + * Set lock on transport record and save/clear process id + * + * @param boolean $lock + * lock if true, release lock if false + * @param int $pid + * process id + * @return mixed + * true or error + */ + function setLock($lock, $pid=NULL) + { + global $CC_CONFIG, $CC_DBC; + $pidsql = (is_null($pid) ? "NULL" : "$pid" ); + if ($this->dropped) { + return TRUE; + } + $slock = ($lock ? 'Y' : 'N'); + $nlock = (!$lock); + $snlock = ($nlock ? 'Y' : 'N'); + $r = $CC_DBC->query(" + UPDATE ".$CC_CONFIG['transTable']." + SET lock='$slock', jobpid=$pidsql, ts=now() + WHERE trtok='{$this->trtok}' AND lock = '$snlock'" + ); + if (PEAR::isError($r)) { + return $r; + } + $affRows = $CC_DBC->affectedRows(); + if (PEAR::isError($affRows)) { + return $affRows; + } + if ($affRows === 0) { + $ltxt = ($lock ? 'lock' : 'unlock' ); + return PEAR::raiseError( + "TransportRecord::setLock: can't $ltxt ({$this->trtok})" + ); + } + return TRUE; + } + + + /** + * Return type of transport + * + * @return string + * Transport type + */ + function getTransportType() + { + if (!$this->recalled) { + return PEAR::raiseError("TransportRecord::getTransportType:". + " not recalled ({$this->trtok})", TRERR_TOK + ); + } + return $this->row['trtype']; + } + + + /** + * Kill transport job (on pause or cancel) + * + * @return string + * Transport type + */ + function killJob() + { + if (!$this->recalled) { + return PEAR::raiseError("TransportRecord::getTransportType:". + " not recalled ({$this->trtok})", TRERR_TOK + ); + } + $jobpid = $this->row['jobpid']; + $res = system("pkill -P $jobpid", $status); + } + + + /** + * Set state to failed and set error message in transport record + * + * @param string $txt + * base part of error message + * @param PEAR_Error $eo + * (opt.) error msg can be construct from it + * @return mixed + * boolean true or error + */ + function fail($txt='', $eo=NULL) + { + if (!$this->recalled) { + return PEAR::raiseError("TransportRecord::fail:". + " not recalled ({$this->trtok})", TRERR_TOK + ); + } + $msg = $txt; + if (!is_null($eo)) { + $msg .= $eo->getMessage()." ".$eo->getUserInfo(). + " [".$eo->getCode()."]"; + } + $r = $this->setState('failed', array('errmsg'=>$msg)); + if (PEAR::isError($r)) { + return $r; + } + return TRUE; + } + + + /** + * Close transport record + * + * @return mixed + * boolean true or error + */ + function close() + { + global $CC_CONFIG, $CC_DBC; + if (!$this->recalled) { + return PEAR::raiseError("TransportRecord::close:". + " not recalled ({$this->trtok})", TRERR_TOK + ); + } + if (TR_LEAVE_CLOSED) { + $r = $this->setState('closed'); + if (PEAR::isError($r)) { + return $r; + } + } else { + $r = $CC_DBC->query(" + DELETE FROM ".$CC_CONFIG['transTable']." + WHERE trtok='{$this->trtok}' + "); + if (PEAR::isError($r)) { + return $r; + } + $this->recalled = FALSE; + $this->dropped = TRUE; + } + return TRUE; + } + + + /** + * Add field specific envelopes to values (e.g. ' around strings) + * + * @param string $fldName + * field name + * @param mixed $fldVal + * field value + * @return string + */ + function _getSqlVal($fldName, $fldVal) + { + switch ($fldName) { + case 'realsize': + case 'expectedsize': + case 'uid': + return ("$fldVal"!='' ? "$fldVal" : "NULL"); + break; + case 'gunid': + case 'pdtoken': + return "x'$fldVal'::bigint"; + break; + default: + $fldVal = pg_escape_string($fldVal); + return "'$fldVal'"; + break; + } + } + + + /** + * Get title from transported object's metadata (if exists) + * + * @return string + * the title or descriptive string + */ + function getTitle() + { + $defStr = 'unknown'; + $trtype = $this->getTransportType(); //contains recall check + if (PEAR::isError($trtype)) { + return $trtype; + } + switch ($trtype) { + case "audioclip": + case "playlist": + case "playlistPkg": + case "metadata": + $title = $this->gb->bsGetTitle(NULL, $this->row['gunid']); + if (is_null($title)) { + $title = $defStr; + } + if (PEAR::isError($title)) { + if ($title->getCode() == GBERR_FOBJNEX) { + $title = $defStr; + } else { + return $title; + } + } + break; + case "searchjob": + $title = 'searchjob'; + break; + case "file": + $title = ( isset($this->row['localfile']) ? + basename($this->row['localfile']) : 'regular file'); + break; + default: + $title = $defStr; + } + return $title; + } + +} // class TransportRecord +?> \ No newline at end of file diff --git a/application/models/Users.php b/application/models/Users.php new file mode 100644 index 000000000..f7c51c2f2 --- /dev/null +++ b/application/models/Users.php @@ -0,0 +1,36 @@ +GetAll($sql); + } + + public function getHosts() { + return $this->getUsers(array('H', 'A')); + } + +} diff --git a/application/models/Validator.php b/application/models/Validator.php new file mode 100644 index 000000000..a53b9fdf4 --- /dev/null +++ b/application/models/Validator.php @@ -0,0 +1,385 @@ + + *
  • audioClipFormat.php
  • + *
  • webstreamFormat.php
  • + *
  • playlistFormat.php
  • + * + * It probably should be replaced by XML schema validation in the future. + * + * @package Campcaster + * @subpackage StorageServer + * @copyright 2010 Sourcefabric O.P.S. + * @license http://www.gnu.org/licenses/gpl.txt + */ +class Validator { + /** + * Format type of validated document + * @var string + */ + private $format = NULL; + + /** + * Preloaded format tree structure + * @var array + */ + private $formTree = NULL; + + /** + * Gunid of validated file for identification in mass input + * @var string + */ + private $gunid = NULL; + + + /** + * Constructor + * + * @param string $format + * format type of validated document + * @param string $gunid + * gunid of validated file for identification in mass input + */ + public function __construct($format, $gunid) + { + $format = strtolower($format); + $this->format = $format; + $this->gunid = $gunid; + $formats = array( + 'audioclip' => "audioClipFormat", + 'playlist' => "playlistFormat", + 'webstream' => "webstreamFormat", + ); + if (!isset($formats[$format])) { + return $this->_err(VAL_FORMAT); + } + $formatName = $formats[$format]; + $formatFile = dirname(__FILE__)."/$formatName.php"; + if (!file_exists($formatFile)) { + return $this->_err(VAL_FORMAT); + } + require($formatFile); + $this->formTree = $$formatName; + } + + + /** + * Validate document - only wrapper for validateNode method + * + * @param object $data + * validated object tree + * @return mixed + * TRUE or PEAR::error + */ + function validate(&$data) + { + $r = $this->validateNode($data, $this->formTree['_root']); + return $r; + } + + + /** + * Validate one metadata value (on insert/update) + * + * @param string $fname + * parent element name + * @param string $category + * qualif.category name + * @param string $predxml + * 'A' | 'T' (attr or tag) + * @param string $value + * validated element value + * @return TRUE|PEAR_Error + */ + function validateOneValue($fname, $category, $predxml, $value) + { + $formTree =& $this->formTree; + switch ($predxml) { + case 'T': + if (!$this->isChildInFormat($fname, $category)) { + return $this->_err(VAL_UNKNOWNE, "$category in $fname"); + } + break; + case 'A': + if (!$this->isAttrInFormat($fname, $category)) { + return $this->_err(VAL_UNKNOWNA, "$category in $fname"); + } + break; + case 'N': + return TRUE; + break; + default: + return $this->_err(VAL_PREDXML, $predxml); + } + if (isset($formTree[$category]['regexp'])) { + // echo "XXX {$formTree[$fname]['regexp']} / ".$node->content."\n"; + if (!preg_match("|{$formTree[$category]['regexp']}|", $value)) { + return $this->_err(VAL_CONTENT, "$category/$value"); + } + } + } + + + /** + * Validation of one element node from object tree + * + * @param object $node + * validated node + * @param string $fname + * actual name in format structure + * @return mixed + * TRUE or PEAR::error + */ + function validateNode(&$node, $fname) + { + $dname = (($node->ns? $node->ns.":" : '').$node->name); + $formTree =& $this->formTree; + if (DEBUG) { + echo"\nVAL::validateNode: 1 $dname/$fname\n"; + } + // check root node name: + if ($dname != $fname) { + return $this->_err(VAL_ROOT, $fname); + } + // check if this element is defined in format: + if (!isset($formTree[$fname])) { + return $this->_err(VAL_NOTDEF, $fname); + } + // check element content + if (isset($formTree[$fname]['regexp'])) { + // echo "XXX {$formTree[$fname]['regexp']} / ".$node->content."\n"; + if (!preg_match("|{$formTree[$fname]['regexp']}|", $node->content)) { + return $this->_err(VAL_CONTENT, "$fname/{$node->content}"); + } + } + // validate attributes: + $ra = $this->validateAttributes($node, $fname); + if (PEAR::isError($ra)) { + return $ra; + } + // validate children: + $r = $this->validateChildren($node, $fname); + if (PEAR::isError($r)) { + return $r; + } + return TRUE; + } + + + /** + * Validation of attributes + * + * @param object $node + * validated node + * @param string $fname + * actual name in format structure + * @return mixed + * TRUE or PEAR::error + */ + function validateAttributes(&$node, $fname) + { + $formTree =& $this->formTree; + $attrs = array(); + // check if all attrs are permitted here: + foreach ($node->attrs as $i => $attr) { + $aname = (($attr->ns? $attr->ns.":" : '').$attr->name); + $attrs[$aname] =& $node->attrs[$i]; + if (!$this->isAttrInFormat($fname, $aname)) { + return $this->_err(VAL_UNKNOWNA, $aname); + } + // check attribute format + // echo "XXA $aname\n"; + if (isset($formTree[$aname]['regexp'])) { + // echo "XAR {$formTree[$fname]['regexp']} / ".$node->content."\n"; + if (!preg_match("|{$formTree[$aname]['regexp']}|", $attr->val)) { + return $this->_err(VAL_ATTRIB, "$aname [".var_export($attr->val,TRUE)."]"); + } + } + } + // check if all required attrs are here: + if (isset($formTree[$fname]['attrs'])) { + $fattrs =& $formTree[$fname]['attrs']; + if (isset($fattrs['required'])) { + foreach ($fattrs['required'] as $i => $attr) { + if (!isset($attrs[$attr])) { + return $this->_err(VAL_NOREQA, $attr); + } + } + } + } + return TRUE; + } + + + /** + * Validation children nodes + * + * @param object $node + * validated node + * @param string $fname + * actual name in format structure + * @return mixed + * TRUE or PEAR::error + */ + function validateChildren(&$node, $fname) + { + $formTree =& $this->formTree; + $childs = array(); + // check if all children are permitted here: + foreach ($node->children as $i => $ch) { + $chname = (($ch->ns? $ch->ns.":" : '').$ch->name); + // echo "XXE $chname\n"; + if (!$this->isChildInFormat($fname, $chname)) { + return $this->_err(VAL_UNKNOWNE, $chname); + } + // call children recursive: + $r = $this->validateNode($node->children[$i], $chname); + if (PEAR::isError($r)) { + return $r; + } + $childs[$chname] = TRUE; + } + // check if all required children are here: + if (isset($formTree[$fname]['childs'])) { + $fchilds =& $formTree[$fname]['childs']; + if (isset($fchilds['required'])) { + foreach ($fchilds['required'] as $i => $ch) { + if (!isset($childs[$ch])) return $this->_err(VAL_NOREQE, $ch); + } + } + // required one from set + if (isset($fchilds['oneof'])) { + $one = FALSE; + foreach ($fchilds['oneof'] as $i => $ch) { + if (isset($childs[$ch])) { + if ($one) { + return $this->_err(VAL_UNEXPONEOF, "$ch in $fname"); + } + $one = TRUE; + } + } + if (!$one) { + return $this->_err(VAL_NOONEOF); + } + } + } + return TRUE; + } + + + /** + * Test if child is presented in format structure + * + * @param string $fname + * node name in format structure + * @param string $chname + * child node name + * @return boolean + */ + function isChildInFormat($fname, $chname) + { + $listo = $this->isInFormatAs($fname, $chname, 'childs', 'optional'); + $listr = $this->isInFormatAs($fname, $chname, 'childs', 'required'); + $list1 = $this->isInFormatAs($fname, $chname, 'childs', 'oneof'); + return ($listo!==FALSE || $listr!==FALSE || $list1!==FALSE); + } + + + /** + * Test if attribute is presented in format structure + * + * @param string $fname + * node name in format structure + * @param string $aname + * attribute name + * @return boolean + */ + function isAttrInFormat($fname, $aname) + { + $listr = $this->isInFormatAs($fname, $aname, 'attrs', 'required'); + $listi = $this->isInFormatAs($fname, $aname, 'attrs', 'implied'); + $listn = $this->isInFormatAs($fname, $aname, 'attrs', 'normal'); + return ($listr!==FALSE || $listi!==FALSE || $listn!==FALSE); + } + + + /** + * Check if node/attribute is presented in format structure + * + * @param string $fname + * node name in format structure + * @param string $chname + * node/attribute name + * @param string $nType + * 'childs' | 'attrs' + * @param string $reqType + *
      + *
    • for elements: 'required' | 'optional' | 'oneof'
    • + *
    • for attributes: 'required' | 'implied' | 'normal'
    • + *
    + * @return mixed + * boolean/int (index int format array returned if found) + */ + function isInFormatAs($fname, $chname, $nType='childs', $reqType='required') + { + $formTree =& $this->formTree; + $listed = ( + isset($formTree[$fname][$nType][$reqType]) ? + array_search($chname, $formTree[$fname][$nType][$reqType]) : + FALSE + ); + return $listed; + } + + + /** + * Error exception generator + * + * @param int $errno + * erron code + * @param string $par + * optional string for more descriptive error messages + * @return PEAR_Error + */ + function _err($errno, $par='') + { + $msg = array( + VAL_ROOT => 'Wrong root element', + VAL_NOREQE => 'Required element missing', + VAL_NOONEOF => 'One-of element missing', + VAL_UNKNOWNE => 'Unknown element', + VAL_UNKNOWNA => 'Unknown attribute', + VAL_NOTDEF => 'Not defined', + VAL_UNEXPONEOF => 'Unexpected second object from one-of set', + VAL_FORMAT => 'Unknown format', + VAL_CONTENT => 'Invalid content', + VAL_NOREQA => 'Required attribute missing', + VAL_ATTRIB => 'Invalid attribute format', + VAL_PREDXML => 'Invalid predicate type', + ); + return PEAR::raiseError( + "Validator: {$msg[$errno]} #$errno ($par, gunid={$this->gunid})", + $errno + ); + } + + +} // class Validator + +?> \ No newline at end of file diff --git a/application/models/XmlParser.php b/application/models/XmlParser.php new file mode 100644 index 000000000..89d59e123 --- /dev/null +++ b/application/models/XmlParser.php @@ -0,0 +1,398 @@ +ns = $a['namespace']; + $this->name = $a['localPart']; + $this->attrs = $attrs; + $this->nSpaces = $nSpaces; + $this->children = $children; + } +} // class XmlElement + + +/* ================================================================ Attribute */ +/** + * Object representation of one XML attribute + * + * @package Campcaster + * @subpackage StorageServer + * @copyright 2010 Sourcefabric O.P.S. + * @license http://www.gnu.org/licenses/gpl.txt + * @see MetaData + */ +class XmlAttrib { + /** + * Namespace prefix + * @var string + */ + public $ns; + + /** + * Attribute name + * @var string + */ + public $name; + + /** + * Attribute value + * @var string + */ + public $val; + + /** + * @param string $atns + * namespace prefix + * @param string $atnm + * attribute name + * @param string $atv + * attribute value + */ + public function __construct($atns, $atnm, $atv) + { + $this->ns = $atns; + $this->name = $atnm; + $this->val = $atv; + } +} // fn XmlAttrib + + +/* =================================================================== Parser */ +/** + * XML parser object encapsulation + * + * @package Campcaster + * @subpackage StorageServer + * @copyright 2010 Sourcefabric O.P.S. + * @license http://www.gnu.org/licenses/gpl.txt + * @see MetaData + */ +class XmlParser { + /** + * Tree of nodes + * @var array + */ + private $tree = NULL; + + /** + * Parse stack + * @var array + */ + private $stack = array(); + + /** + * Error structure + * @var array + */ + private $err = array(FALSE, ''); + + /** + * @param string $data + * XML string to be parsed + */ + public function __construct($data){ + $xml_parser = xml_parser_create('UTF-8'); + xml_parser_set_option($xml_parser, XML_OPTION_CASE_FOLDING, FALSE); + xml_set_object($xml_parser, $this); + xml_set_element_handler($xml_parser, "startTag", "endTag"); + xml_set_character_data_handler($xml_parser, 'characterData'); + $res = xml_parse($xml_parser, $data, TRUE); + if (!$res) { + $this->err = array(TRUE, + sprintf("XML error: %s at line %d\n", + xml_error_string(xml_get_error_code($xml_parser)), + xml_get_current_line_number($xml_parser) + ) + ); +// var_dump($data); + } + xml_parser_free($xml_parser); + } + + + /** + * Parse XML file or string + * + * @param string $data + * local path to XML file or XML string + * @param string $loc + * location: 'file'|'string' + * @return array + * reference, parse result tree (or PEAR::error) + */ + function &parse($data='', $loc='file') + { + switch ($loc) { + case "file": + if (!is_file($data)) { + return PEAR::raiseError( + "XmlParser::parse: file not found ($data)" + ); + } + if (!is_readable($data)) { + return PEAR::raiseError( + "XmlParser::parse: can't read file ($data)" + ); + } + $data = file_get_contents($data); + case "string": + $parser = new XmlParser($data); + if ($parser->isError()) { + return PEAR::raiseError( + "XmlParser::parse: ".$parser->getError() + ); + } + $tree = $parser->getTree(); + break; + default: + return PEAR::raiseError( + "XmlParser::parse: unsupported source location ($loc)" + ); + } + return $tree; + } + + + /** + * Start tag handler + * + * @param resource $parser + * reference to parser resource + * @param string $fullname + * element name + * @param array $attrs + * array of attributes + * @return none + */ + function startTag($parser, $fullname, $attrs) { + $nSpaces = array(); + foreach ($attrs as $atn => $atv) { + $a = XML_Util::splitQualifiedName($atn); + $atns = $a['namespace']; + $atnm = $a['localPart']; + unset($attrs[$atn]); + if ($atns == 'xmlns') { + $nSpaces[$atnm] = $atv; + } else if ($atns == NULL && $atnm == 'xmlns') { + $nSpaces[''] = $atv; + } else { + $attrs[$atn] = new XmlAttrib($atns, $atnm, $atv); + } + } + $el = new XmlElement($fullname, $attrs, $nSpaces); + array_push($this->stack, $el); + } + + + /** + * End tag handler + * + * @param resource $parser + * reference to parser resource + * @param string $fullname + * element name + * @return none + */ + function endTag($parser, $fullname) { + $cnt = count($this->stack); + if ($cnt > 1) { + $this->stack[$cnt-2]->children[] = $this->stack[$cnt-1]; + $lastEl = array_pop($this->stack); + } else { + $this->tree = $this->stack[0]; + } + } + + + /** + * Character data handler + * + * @param resource $parser + * reference to parser resource + * @param string $data + * @return none + */ + function characterData($parser, $data) { + $cnt = count($this->stack); + if (trim($data)!='') { + $this->stack[$cnt-1]->content .= $data; + } + } + + + /** + * Default handler + * + * @param resource $parser + * reference to parser resource + * @param string $data + * @return none + */ + function defaultHandler($parser, $data) + { + $cnt = count($this->stack); + //if(substr($data, 0, 1) == "&" && substr($data, -1, 1) == ";"){ + // $this->stack[$cnt-1]->content .= trim($data); + //}else{ + $this->stack[$cnt-1]->content .= "*** $data ***"; + //} + } + + + /** + * Return result tree + * + * @return array + * tree structure + */ + function getTree() + { + return $this->tree; + } + + + /** + * Return error string + * + * @return boolean + * whether error occured + */ + function isError() + { + return $this->err[0]; + } + + + /** + * Return error string + * + * @return string + * error message + */ + function getError() + { + return $this->err[1]; + } + + + /* ----------------------------------- auxiliary methos for serialization */ + /** + * Serialize metadata of one file + * + * @return string, serialized XML + */ + function serialize() + { + $res = ''; + $res .= $this->serializeEl($this->tree); + $res .= "\n"; + return $res; + } + + + /** + * Serialize one metadata element + * + * @param el object, element object + * @param lvl int, level for indentation + * @return string, serialized XML + */ + function serializeEl($el, $lvl=0) + { + $ind = str_repeat(" ", $lvl); + $elNs = $el->ns; + $elName = $el->name; + $attrs = XML_Util::attributesToString($el->attrs); + $fullName = ($elNs=='' ? '' : "$elNs:")."$elName"; + $res = "\n{$ind}<{$fullName}{$attrs}>"; + $haveCh = (count($el->children)>0); + foreach ($el->children as $ch) { + $res .= $this->serializeEl($ch, $lvl+1); + } + $res .= XML_Util::replaceEntities("{$el->content}"); + if ($haveCh) { + $res .= "\n{$ind}"; + } + $res .= ""; + return $res; + } + + + /* -------------------------------------------------------- debug methods */ + /** + * Debug dump of tree + * + * @return hash, tree structure + */ + function dump() + { + var_dump($this->tree); + } + +} +?> \ No newline at end of file diff --git a/application/models/audioClipFormat.php b/application/models/audioClipFormat.php new file mode 100644 index 000000000..f7fd506b7 --- /dev/null +++ b/application/models/audioClipFormat.php @@ -0,0 +1,328 @@ +'audioClip', + 'audioClip'=>array( + 'childs'=>array( + 'required'=>array('metadata'), + ), + ), + 'metadata'=>array( + 'childs'=>array( + 'required'=>array( + 'dc:title', 'dcterms:extent' + ), + 'optional'=>array( + 'dc:identifier', + 'dc:creator', 'dc:source', 'ls:genre', + 'ls:year', 'dc:type', 'dc:description', 'dc:format', + 'ls:bpm', 'ls:rating', 'ls:encoded_by', 'ls:track_num', + 'ls:disc_num', 'ls:disc_num', 'dc:publisher', 'ls:composer', + 'ls:bitrate', 'ls:channels', 'ls:samplerate', 'ls:encoder', + 'ls:crc', 'ls:lyrics', 'ls:orchestra', 'ls:conductor', + 'ls:lyricist', 'ls:originallyricist', 'ls:radiostationname', + 'ls:audiofileinfourl', 'ls:artisturl', 'ls:audiosourceurl', + 'ls:radiostationurl', 'ls:buycdurl', 'ls:isrcnumber', + 'ls:catalognumber', 'ls:originalartist', 'dc:rights', + 'ls:license', 'dc:title', 'dcterms:temporal', + 'dcterms:spatial', 'dcterms:entity', 'dc:description', + 'dc:creator', 'dc:subject', 'dc:type', 'dc:format', + 'dc:contributor', 'dc:language', 'dc:rights', + 'dcterms:isPartOf', 'dc:date', + 'dc:publisher', + // extra + 'dcterms:alternative', 'ls:filename', 'ls:mtime', + // added lately by sebastian + 'ls:mood', + ), + ), + 'namespaces'=>array( + 'dc'=>"http://purl.org/dc/elements/1.1/", + 'dcterms'=>"http://purl.org/dc/terms/", + 'xbmf'=>"http://www.streamonthefly.org/xbmf", + 'xsi'=>"http://www.w3.org/2001/XMLSchema-instance", + 'xml'=>"http://www.w3.org/XML/1998/namespace", + ), + ), + 'dc:identifier'=>array( + 'type'=>'Text', + 'auto'=>TRUE, + ), + 'dc:title'=>array( + 'type'=>'Text', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'dcterms:alternative'=>array( + 'type'=>'Text', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'dcterms:extent'=>array( + 'type'=>'Time', +// 'regexp'=>'^\d{2}:\d{2}:\d{2}.\d{6}$', + 'regexp'=>'^((\d{1,2}:)?\d{1,2}:)?\d{1,20}(.\d{1,6})?$', + ), + 'dc:creator'=>array( + 'type'=>'Text', + 'area'=>'Music', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'dc:source'=>array( + 'type'=>'Text', + 'area'=>'Music', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'ls:genre'=>array( + 'type'=>'Menu', + 'area'=>'Music', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'ls:year'=>array( + 'type'=>'Menu', + 'area'=>'Music', + ), + 'dc:type'=>array( + 'type'=>'Menu', + 'area'=>'Music', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'dc:description'=>array( + 'type'=>'Longtext', + 'area'=>'Music', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'dc:format'=>array( + 'type'=>'Menu', + 'area'=>'Music', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'ls:bpm'=>array( + 'type'=>'Number', + 'area'=>'Music', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'ls:rating'=>array( + 'type'=>'Number', + 'area'=>'Music', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'ls:encoded_by'=>array( + 'type'=>'Text', + 'area'=>'Music', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'ls:track_num'=>array( + 'type'=>'Menu', + 'area'=>'Music', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'ls:disc_num'=>array( + 'type'=>'Menu', + 'area'=>'Music', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'ls:disc_num'=>array( + 'type'=>'Menu', + 'area'=>'Music', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'dc:publisher'=>array( + 'type'=>'Text', + 'area'=>'Music', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'ls:composer'=>array( + 'type'=>'Text', + 'area'=>'Music', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'ls:bitrate'=>array( + 'type'=>'Number', + 'area'=>'Music', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'ls:channels'=>array( + 'type'=>'Menu', + 'area'=>'Music', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'ls:samplerate'=>array( + 'type'=>'Menu', + 'area'=>'Music', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'ls:encoder'=>array( + 'type'=>'Text', + 'area'=>'Music', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'ls:crc'=>array( + 'type'=>'Number', + 'area'=>'Music', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'ls:lyrics'=>array( + 'type'=>'Longtext', + 'area'=>'Music', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'ls:orchestra'=>array( + 'type'=>'Text', + 'area'=>'Music', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'ls:conductor'=>array( + 'type'=>'Text', + 'area'=>'Music', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'ls:lyricist'=>array( + 'type'=>'Text', + 'area'=>'Music', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'ls:originallyricist'=>array( + 'type'=>'Text', + 'area'=>'Music', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'ls:radiostationname'=>array( + 'type'=>'Text', + 'area'=>'Music', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'ls:audiofileinfourl'=>array( + 'type'=>'URL', + 'area'=>'Music', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'ls:artisturl'=>array( + 'type'=>'URL', + 'area'=>'Music', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'ls:audiosourceurl'=>array( + 'type'=>'URL', + 'area'=>'Music', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'ls:radiostationurl'=>array( + 'type'=>'URL', + 'area'=>'Music', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'ls:buycdurl'=>array( + 'type'=>'URL', + 'area'=>'Music', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'ls:isrcnumber'=>array( + 'type'=>'Number', + 'area'=>'Music', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'ls:catalognumber'=>array( + 'type'=>'Number', + 'area'=>'Music', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'ls:originalartist'=>array( + 'type'=>'Text', + 'area'=>'Music', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'dc:rights'=>array( + 'type'=>'Text', + 'area'=>'Music', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'ls:license'=>array( + 'type'=>'Text', + 'area'=>'Music', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'dc:title'=>array( + 'type'=>'Text', + 'area'=>'Talk', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'dcterms:temporal'=>array( + 'type'=>'Time/Date', + 'area'=>'Talk', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'dcterms:spatial'=>array( + 'type'=>'Menu', + 'area'=>'Talk', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'dcterms:entity'=>array( + 'type'=>'Text', + 'area'=>'Talk', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'dc:description'=>array( + 'type'=>'Longtext', + 'area'=>'Talk', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'dc:creator'=>array( + 'type'=>'Menu', + 'area'=>'Talk', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'dc:subject'=>array( + 'type'=>'Text', + 'area'=>'Talk', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'dc:type'=>array( + 'type'=>'Menu', + 'area'=>'Talk', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'dc:format'=>array( + 'type'=>'Menu', + 'area'=>'Talk', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'dc:contributor'=>array( + 'type'=>'Text', + 'area'=>'Talk', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'dc:language'=>array( + 'type'=>'Menu', + 'area'=>'Talk', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'dc:rights'=>array( + 'type'=>'Menu', + 'area'=>'Talk', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'dcterms:isPartOf'=>array( + 'type'=>'Text', + 'area'=>'Talk', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'dc:date'=>array( + 'type'=>'Date', + 'area'=>'Talk', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'dc:publisher'=>array( + 'type'=>'Text', + 'area'=>'Talk', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + + 'ls:filename'=>array( + 'type'=>'Text', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'ls:mtime'=>array( + 'type'=>'Int', +// 'regexp'=>'^\d{4}(-\d{2}(-\d{2}(T\d{2}:\d{2}(:\d{2}\.\d+)?(Z)|([\+\-]?\d{2}:\d{2}))?)?)?$', + ), +); + +?> \ No newline at end of file diff --git a/application/models/campcaster/CcAccess.php b/application/models/campcaster/CcAccess.php new file mode 100644 index 000000000..e3171bdfa --- /dev/null +++ b/application/models/campcaster/CcAccess.php @@ -0,0 +1,18 @@ +length; + } + + public function setDbLength($time) + { + $this->length = $time; + //$this->modifiedColumns[] = CcPlaylistcontentsPeer::LENGTH; + return Common::setTimeInSub($this, 'LENGTH', $time); + } + + +} // CcFiles diff --git a/application/models/campcaster/CcFilesPeer.php b/application/models/campcaster/CcFilesPeer.php new file mode 100644 index 000000000..047a8d8bb --- /dev/null +++ b/application/models/campcaster/CcFilesPeer.php @@ -0,0 +1,18 @@ +prepare($sql); + $stmt->bindValue(':p1', $this->getDbId()); + $stmt->execute(); + return $stmt->fetchColumn(); + } + + public function computeLength() + { + $con = Propel::getConnection(CcPlaylistPeer::DATABASE_NAME); + + $sql = 'SELECT SUM('.CcPlaylistcontentsPeer::CLIPLENGTH.') AS length' + . ' FROM ' .CcPlaylistcontentsPeer::TABLE_NAME + . ' WHERE ' .CcPlaylistcontentsPeer::PLAYLIST_ID. ' = :p1'; + + $stmt = $con->prepare($sql); + $stmt->bindValue(':p1', $this->getDbId()); + $stmt->execute(); + return $stmt->fetchColumn(); + } + + +} // CcPlaylist diff --git a/application/models/campcaster/CcPlaylistPeer.php b/application/models/campcaster/CcPlaylistPeer.php new file mode 100644 index 000000000..097895b10 --- /dev/null +++ b/application/models/campcaster/CcPlaylistPeer.php @@ -0,0 +1,18 @@ +fadein; + } + + public function setDbFadein($time) + { + $this->fadein = $time; + //$this->modifiedColumns[] = CcPlaylistcontentsPeer::FADEIN; + Common::setTimeInSub($this, 'FADEIN', $time); + } + + public function getDbFadeout() + { + return $this->fadeout; + } + + public function setDbFadeout($time) + { + $this->fadeout = $time; + //$this->modifiedColumns[] = CcPlaylistcontentsPeer::FADEOUT; + Common::setTimeInSub($this, 'FADEOUT', $time); + } + + public function getDbCuein() + { + return $this->cuein; + } + + public function setDbCuein($time) + { + $this->cuein = $time; + //$this->modifiedColumns[] = CcPlaylistcontentsPeer::CUEIN; + Common::setTimeInSub($this, 'CUEIN', $time); + } + + public function getDbCueout() + { + return $this->cueout; + } + + public function setDbCueout($time) + { + $this->cueout = $time; + //$this->modifiedColumns[] = CcPlaylistcontentsPeer::CUEOUT; + Common::setTimeInSub($this, 'CUEOUT', $time); + } + + public function getDbCliplength() + { + return $this->cliplength; + } + + public function setDbCliplength($time) + { + $this->cliplength = $time; + //$this->modifiedColumns[] = CcPlaylistcontentsPeer::CLIPLENGTH; + Common::setTimeInSub($this, 'CLIPLENGTH', $time); + } + + + + +} // CcPlaylistcontents diff --git a/application/models/campcaster/CcPlaylistcontentsPeer.php b/application/models/campcaster/CcPlaylistcontentsPeer.php new file mode 100644 index 000000000..5fe6cf288 --- /dev/null +++ b/application/models/campcaster/CcPlaylistcontentsPeer.php @@ -0,0 +1,18 @@ +prepare($sql); + $stmt->bindValue(':f1', $time); + $stmt->bindValue(':p1', $row->getDbId()); + $stmt->execute(); + } +} diff --git a/application/models/campcaster/map/CcAccessTableMap.php b/application/models/campcaster/map/CcAccessTableMap.php new file mode 100644 index 000000000..5da2dae21 --- /dev/null +++ b/application/models/campcaster/map/CcAccessTableMap.php @@ -0,0 +1,61 @@ +setName('cc_access'); + $this->setPhpName('CcAccess'); + $this->setClassname('CcAccess'); + $this->setPackage('campcaster'); + $this->setUseIdGenerator(true); + $this->setPrimaryKeyMethodInfo('cc_access_id_seq'); + // columns + $this->addPrimaryKey('ID', 'Id', 'INTEGER', true, null, null); + $this->addColumn('GUNID', 'Gunid', 'CHAR', false, 32, null); + $this->addColumn('TOKEN', 'Token', 'BIGINT', false, null, null); + $this->addColumn('CHSUM', 'Chsum', 'CHAR', true, 32, ''); + $this->addColumn('EXT', 'Ext', 'VARCHAR', true, 128, ''); + $this->addColumn('TYPE', 'Type', 'VARCHAR', true, 20, ''); + $this->addColumn('PARENT', 'Parent', 'BIGINT', false, null, null); + $this->addForeignKey('OWNER', 'Owner', 'INTEGER', 'cc_subjs', 'ID', false, null, null); + $this->addColumn('TS', 'Ts', 'TIMESTAMP', false, null, null); + // validators + } // initialize() + + /** + * Build the RelationMap objects for this table relationships + */ + public function buildRelations() + { + $this->addRelation('CcSubjs', 'CcSubjs', RelationMap::MANY_TO_ONE, array('owner' => 'id', ), null, null); + } // buildRelations() + +} // CcAccessTableMap diff --git a/application/models/campcaster/map/CcBackupTableMap.php b/application/models/campcaster/map/CcBackupTableMap.php new file mode 100644 index 000000000..a2c3f3af7 --- /dev/null +++ b/application/models/campcaster/map/CcBackupTableMap.php @@ -0,0 +1,55 @@ +setName('cc_backup'); + $this->setPhpName('CcBackup'); + $this->setClassname('CcBackup'); + $this->setPackage('campcaster'); + $this->setUseIdGenerator(false); + // columns + $this->addPrimaryKey('TOKEN', 'Token', 'VARCHAR', true, 64, null); + $this->addColumn('SESSIONID', 'Sessionid', 'VARCHAR', true, 64, null); + $this->addColumn('STATUS', 'Status', 'VARCHAR', true, 32, null); + $this->addColumn('FROMTIME', 'Fromtime', 'TIMESTAMP', true, null, null); + $this->addColumn('TOTIME', 'Totime', 'TIMESTAMP', true, null, null); + // validators + } // initialize() + + /** + * Build the RelationMap objects for this table relationships + */ + public function buildRelations() + { + } // buildRelations() + +} // CcBackupTableMap diff --git a/application/models/campcaster/map/CcFilesTableMap.php b/application/models/campcaster/map/CcFilesTableMap.php new file mode 100644 index 000000000..5c8fe05f1 --- /dev/null +++ b/application/models/campcaster/map/CcFilesTableMap.php @@ -0,0 +1,107 @@ +setName('cc_files'); + $this->setPhpName('CcFiles'); + $this->setClassname('CcFiles'); + $this->setPackage('campcaster'); + $this->setUseIdGenerator(true); + $this->setPrimaryKeyMethodInfo('cc_files_id_seq'); + // columns + $this->addPrimaryKey('ID', 'DbId', 'INTEGER', true, null, null); + $this->addColumn('GUNID', 'Gunid', 'CHAR', true, 32, null); + $this->addColumn('NAME', 'Name', 'VARCHAR', true, 255, ''); + $this->addColumn('MIME', 'Mime', 'VARCHAR', true, 255, ''); + $this->addColumn('FTYPE', 'Ftype', 'VARCHAR', true, 128, ''); + $this->addColumn('FILEPATH', 'filepath', 'LONGVARCHAR', false, null, ''); + $this->addColumn('STATE', 'State', 'VARCHAR', true, 128, 'empty'); + $this->addColumn('CURRENTLYACCESSING', 'Currentlyaccessing', 'INTEGER', true, null, 0); + $this->addForeignKey('EDITEDBY', 'Editedby', 'INTEGER', 'cc_subjs', 'ID', false, null, null); + $this->addColumn('MTIME', 'Mtime', 'TIMESTAMP', false, 6, null); + $this->addColumn('MD5', 'Md5', 'CHAR', false, 32, null); + $this->addColumn('TRACK_TITLE', 'TrackTitle', 'VARCHAR', false, 512, null); + $this->addColumn('ARTIST_NAME', 'ArtistName', 'VARCHAR', false, 512, null); + $this->addColumn('BIT_RATE', 'BitRate', 'VARCHAR', false, 32, null); + $this->addColumn('SAMPLE_RATE', 'SampleRate', 'VARCHAR', false, 32, null); + $this->addColumn('FORMAT', 'Format', 'VARCHAR', false, 128, null); + $this->addColumn('LENGTH', 'DbLength', 'TIME', false, null, null); + $this->addColumn('ALBUM_TITLE', 'AlbumTitle', 'VARCHAR', false, 512, null); + $this->addColumn('GENRE', 'Genre', 'VARCHAR', false, 64, null); + $this->addColumn('COMMENTS', 'Comments', 'LONGVARCHAR', false, null, null); + $this->addColumn('YEAR', 'Year', 'VARCHAR', false, 16, null); + $this->addColumn('TRACK_NUMBER', 'TrackNumber', 'INTEGER', false, null, null); + $this->addColumn('CHANNELS', 'Channels', 'INTEGER', false, null, null); + $this->addColumn('URL', 'Url', 'VARCHAR', false, 1024, null); + $this->addColumn('BPM', 'Bpm', 'VARCHAR', false, 8, null); + $this->addColumn('RATING', 'Rating', 'VARCHAR', false, 8, null); + $this->addColumn('ENCODED_BY', 'EncodedBy', 'VARCHAR', false, 255, null); + $this->addColumn('DISC_NUMBER', 'DiscNumber', 'VARCHAR', false, 8, null); + $this->addColumn('MOOD', 'Mood', 'VARCHAR', false, 64, null); + $this->addColumn('LABEL', 'Label', 'VARCHAR', false, 512, null); + $this->addColumn('COMPOSER', 'Composer', 'VARCHAR', false, 512, null); + $this->addColumn('ENCODER', 'Encoder', 'VARCHAR', false, 64, null); + $this->addColumn('CHECKSUM', 'Checksum', 'VARCHAR', false, 256, null); + $this->addColumn('LYRICS', 'Lyrics', 'LONGVARCHAR', false, null, null); + $this->addColumn('ORCHESTRA', 'Orchestra', 'VARCHAR', false, 512, null); + $this->addColumn('CONDUCTOR', 'Conductor', 'VARCHAR', false, 512, null); + $this->addColumn('LYRICIST', 'Lyricist', 'VARCHAR', false, 512, null); + $this->addColumn('ORIGINAL_LYRICIST', 'OriginalLyricist', 'VARCHAR', false, 512, null); + $this->addColumn('RADIO_STATION_NAME', 'RadioStationName', 'VARCHAR', false, 512, null); + $this->addColumn('INFO_URL', 'InfoUrl', 'VARCHAR', false, 512, null); + $this->addColumn('ARTIST_URL', 'ArtistUrl', 'VARCHAR', false, 512, null); + $this->addColumn('AUDIO_SOURCE_URL', 'AudioSourceUrl', 'VARCHAR', false, 512, null); + $this->addColumn('RADIO_STATION_URL', 'RadioStationUrl', 'VARCHAR', false, 512, null); + $this->addColumn('BUY_THIS_URL', 'BuyThisUrl', 'VARCHAR', false, 512, null); + $this->addColumn('ISRC_NUMBER', 'IsrcNumber', 'VARCHAR', false, 512, null); + $this->addColumn('CATALOG_NUMBER', 'CatalogNumber', 'VARCHAR', false, 512, null); + $this->addColumn('ORIGINAL_ARTIST', 'OriginalArtist', 'VARCHAR', false, 512, null); + $this->addColumn('COPYRIGHT', 'Copyright', 'VARCHAR', false, 512, null); + $this->addColumn('REPORT_DATETIME', 'ReportDatetime', 'VARCHAR', false, 32, null); + $this->addColumn('REPORT_LOCATION', 'ReportLocation', 'VARCHAR', false, 512, null); + $this->addColumn('REPORT_ORGANIZATION', 'ReportOrganization', 'VARCHAR', false, 512, null); + $this->addColumn('SUBJECT', 'Subject', 'VARCHAR', false, 512, null); + $this->addColumn('CONTRIBUTOR', 'Contributor', 'VARCHAR', false, 512, null); + $this->addColumn('LANGUAGE', 'Language', 'VARCHAR', false, 512, null); + // validators + } // initialize() + + /** + * Build the RelationMap objects for this table relationships + */ + public function buildRelations() + { + $this->addRelation('CcSubjs', 'CcSubjs', RelationMap::MANY_TO_ONE, array('editedby' => 'id', ), null, null); + $this->addRelation('CcPlaylistcontents', 'CcPlaylistcontents', RelationMap::ONE_TO_MANY, array('id' => 'file_id', ), 'CASCADE', null); + } // buildRelations() + +} // CcFilesTableMap diff --git a/application/models/campcaster/map/CcPermsTableMap.php b/application/models/campcaster/map/CcPermsTableMap.php new file mode 100644 index 000000000..7a869b2c3 --- /dev/null +++ b/application/models/campcaster/map/CcPermsTableMap.php @@ -0,0 +1,56 @@ +setName('cc_perms'); + $this->setPhpName('CcPerms'); + $this->setClassname('CcPerms'); + $this->setPackage('campcaster'); + $this->setUseIdGenerator(false); + // columns + $this->addPrimaryKey('PERMID', 'Permid', 'INTEGER', true, null, null); + $this->addForeignKey('SUBJ', 'Subj', 'INTEGER', 'cc_subjs', 'ID', false, null, null); + $this->addColumn('ACTION', 'Action', 'VARCHAR', false, 20, null); + $this->addColumn('OBJ', 'Obj', 'INTEGER', false, null, null); + $this->addColumn('TYPE', 'Type', 'CHAR', false, 1, null); + // validators + } // initialize() + + /** + * Build the RelationMap objects for this table relationships + */ + public function buildRelations() + { + $this->addRelation('CcSubjs', 'CcSubjs', RelationMap::MANY_TO_ONE, array('subj' => 'id', ), 'CASCADE', null); + } // buildRelations() + +} // CcPermsTableMap diff --git a/application/models/campcaster/map/CcPlaylistTableMap.php b/application/models/campcaster/map/CcPlaylistTableMap.php new file mode 100644 index 000000000..cfb6f23de --- /dev/null +++ b/application/models/campcaster/map/CcPlaylistTableMap.php @@ -0,0 +1,61 @@ +setName('cc_playlist'); + $this->setPhpName('CcPlaylist'); + $this->setClassname('CcPlaylist'); + $this->setPackage('campcaster'); + $this->setUseIdGenerator(true); + $this->setPrimaryKeyMethodInfo('cc_playlist_id_seq'); + // columns + $this->addPrimaryKey('ID', 'DbId', 'INTEGER', true, null, null); + $this->addColumn('NAME', 'DbName', 'VARCHAR', true, 255, ''); + $this->addColumn('STATE', 'DbState', 'VARCHAR', true, 128, 'empty'); + $this->addColumn('CURRENTLYACCESSING', 'DbCurrentlyaccessing', 'INTEGER', true, null, 0); + $this->addForeignKey('EDITEDBY', 'DbEditedby', 'INTEGER', 'cc_subjs', 'ID', false, null, null); + $this->addColumn('MTIME', 'DbMtime', 'TIMESTAMP', false, 6, null); + $this->addColumn('CREATOR', 'DbCreator', 'VARCHAR', false, 32, null); + $this->addColumn('DESCRIPTION', 'DbDescription', 'VARCHAR', false, 512, null); + // validators + } // initialize() + + /** + * Build the RelationMap objects for this table relationships + */ + public function buildRelations() + { + $this->addRelation('CcSubjs', 'CcSubjs', RelationMap::MANY_TO_ONE, array('editedby' => 'id', ), null, null); + $this->addRelation('CcPlaylistcontents', 'CcPlaylistcontents', RelationMap::ONE_TO_MANY, array('id' => 'playlist_id', ), 'CASCADE', null); + } // buildRelations() + +} // CcPlaylistTableMap diff --git a/application/models/campcaster/map/CcPlaylistcontentsTableMap.php b/application/models/campcaster/map/CcPlaylistcontentsTableMap.php new file mode 100644 index 000000000..4ce59117e --- /dev/null +++ b/application/models/campcaster/map/CcPlaylistcontentsTableMap.php @@ -0,0 +1,62 @@ +setName('cc_playlistcontents'); + $this->setPhpName('CcPlaylistcontents'); + $this->setClassname('CcPlaylistcontents'); + $this->setPackage('campcaster'); + $this->setUseIdGenerator(true); + $this->setPrimaryKeyMethodInfo('cc_playlistcontents_id_seq'); + // columns + $this->addPrimaryKey('ID', 'DbId', 'INTEGER', true, null, null); + $this->addForeignKey('PLAYLIST_ID', 'DbPlaylistId', 'INTEGER', 'cc_playlist', 'ID', false, null, null); + $this->addForeignKey('FILE_ID', 'DbFileId', 'INTEGER', 'cc_files', 'ID', false, null, null); + $this->addColumn('POSITION', 'DbPosition', 'INTEGER', false, null, null); + $this->addColumn('CLIPLENGTH', 'DbCliplength', 'TIME', false, null, '00:00:00'); + $this->addColumn('CUEIN', 'DbCuein', 'TIME', false, null, '00:00:00'); + $this->addColumn('CUEOUT', 'DbCueout', 'TIME', false, null, '00:00:00'); + $this->addColumn('FADEIN', 'DbFadein', 'TIME', false, null, '00:00:00'); + $this->addColumn('FADEOUT', 'DbFadeout', 'TIME', false, null, '00:00:00'); + // validators + } // initialize() + + /** + * Build the RelationMap objects for this table relationships + */ + public function buildRelations() + { + $this->addRelation('CcFiles', 'CcFiles', RelationMap::MANY_TO_ONE, array('file_id' => 'id', ), 'CASCADE', null); + $this->addRelation('CcPlaylist', 'CcPlaylist', RelationMap::MANY_TO_ONE, array('playlist_id' => 'id', ), 'CASCADE', null); + } // buildRelations() + +} // CcPlaylistcontentsTableMap diff --git a/application/models/campcaster/map/CcPrefTableMap.php b/application/models/campcaster/map/CcPrefTableMap.php new file mode 100644 index 000000000..7bc77534e --- /dev/null +++ b/application/models/campcaster/map/CcPrefTableMap.php @@ -0,0 +1,56 @@ +setName('cc_pref'); + $this->setPhpName('CcPref'); + $this->setClassname('CcPref'); + $this->setPackage('campcaster'); + $this->setUseIdGenerator(true); + $this->setPrimaryKeyMethodInfo('cc_pref_id_seq'); + // columns + $this->addPrimaryKey('ID', 'Id', 'INTEGER', true, null, null); + $this->addForeignKey('SUBJID', 'Subjid', 'INTEGER', 'cc_subjs', 'ID', false, null, null); + $this->addColumn('KEYSTR', 'Keystr', 'VARCHAR', false, 255, null); + $this->addColumn('VALSTR', 'Valstr', 'LONGVARCHAR', false, null, null); + // validators + } // initialize() + + /** + * Build the RelationMap objects for this table relationships + */ + public function buildRelations() + { + $this->addRelation('CcSubjs', 'CcSubjs', RelationMap::MANY_TO_ONE, array('subjid' => 'id', ), 'CASCADE', null); + } // buildRelations() + +} // CcPrefTableMap diff --git a/application/models/campcaster/map/CcScheduleTableMap.php b/application/models/campcaster/map/CcScheduleTableMap.php new file mode 100644 index 000000000..757998697 --- /dev/null +++ b/application/models/campcaster/map/CcScheduleTableMap.php @@ -0,0 +1,61 @@ +setName('cc_schedule'); + $this->setPhpName('CcSchedule'); + $this->setClassname('CcSchedule'); + $this->setPackage('campcaster'); + $this->setUseIdGenerator(false); + // columns + $this->addPrimaryKey('ID', 'Id', 'BIGINT', true, null, null); + $this->addColumn('PLAYLIST_ID', 'PlaylistId', 'INTEGER', true, null, null); + $this->addColumn('STARTS', 'Starts', 'TIMESTAMP', true, null, null); + $this->addColumn('ENDS', 'Ends', 'TIMESTAMP', true, null, null); + $this->addColumn('GROUP_ID', 'GroupId', 'INTEGER', false, null, null); + $this->addColumn('FILE_ID', 'FileId', 'INTEGER', false, null, null); + $this->addColumn('CLIP_LENGTH', 'ClipLength', 'TIME', false, null, '00:00:00'); + $this->addColumn('FADE_IN', 'FadeIn', 'TIME', false, null, '00:00:00'); + $this->addColumn('FADE_OUT', 'FadeOut', 'TIME', false, null, '00:00:00'); + $this->addColumn('CUE_IN', 'CueIn', 'TIME', false, null, '00:00:00'); + $this->addColumn('CUE_OUT', 'CueOut', 'TIME', false, null, '00:00:00'); + // validators + } // initialize() + + /** + * Build the RelationMap objects for this table relationships + */ + public function buildRelations() + { + } // buildRelations() + +} // CcScheduleTableMap diff --git a/application/models/campcaster/map/CcSessTableMap.php b/application/models/campcaster/map/CcSessTableMap.php new file mode 100644 index 000000000..8e928ab1c --- /dev/null +++ b/application/models/campcaster/map/CcSessTableMap.php @@ -0,0 +1,55 @@ +setName('cc_sess'); + $this->setPhpName('CcSess'); + $this->setClassname('CcSess'); + $this->setPackage('campcaster'); + $this->setUseIdGenerator(false); + // columns + $this->addPrimaryKey('SESSID', 'Sessid', 'CHAR', true, 32, null); + $this->addForeignKey('USERID', 'Userid', 'INTEGER', 'cc_subjs', 'ID', false, null, null); + $this->addColumn('LOGIN', 'Login', 'VARCHAR', false, 255, null); + $this->addColumn('TS', 'Ts', 'TIMESTAMP', false, null, null); + // validators + } // initialize() + + /** + * Build the RelationMap objects for this table relationships + */ + public function buildRelations() + { + $this->addRelation('CcSubjs', 'CcSubjs', RelationMap::MANY_TO_ONE, array('userid' => 'id', ), 'CASCADE', null); + } // buildRelations() + +} // CcSessTableMap diff --git a/application/models/campcaster/map/CcShowTableMap.php b/application/models/campcaster/map/CcShowTableMap.php new file mode 100644 index 000000000..4d927e35b --- /dev/null +++ b/application/models/campcaster/map/CcShowTableMap.php @@ -0,0 +1,60 @@ +setName('cc_show'); + $this->setPhpName('CcShow'); + $this->setClassname('CcShow'); + $this->setPackage('campcaster'); + $this->setUseIdGenerator(true); + $this->setPrimaryKeyMethodInfo('cc_show_id_seq'); + // columns + $this->addPrimaryKey('ID', 'DbId', 'INTEGER', true, null, null); + $this->addColumn('NAME', 'DbName', 'VARCHAR', true, 255, ''); + $this->addColumn('FIRST_SHOW', 'DbFirstShow', 'DATE', true, null, null); + $this->addColumn('LAST_SHOW', 'DbLastShow', 'DATE', false, null, null); + $this->addColumn('START_TIME', 'DbStartTime', 'TIME', true, null, null); + $this->addColumn('END_TIME', 'DbEndTime', 'TIME', true, null, null); + $this->addColumn('REPEATS', 'DbRepeats', 'TINYINT', true, null, null); + $this->addColumn('DAY', 'DbDay', 'TINYINT', true, null, null); + $this->addColumn('DESCRIPTION', 'DbDescription', 'VARCHAR', false, 512, null); + // validators + } // initialize() + + /** + * Build the RelationMap objects for this table relationships + */ + public function buildRelations() + { + } // buildRelations() + +} // CcShowTableMap diff --git a/application/models/campcaster/map/CcSmembTableMap.php b/application/models/campcaster/map/CcSmembTableMap.php new file mode 100644 index 000000000..5cdb44df5 --- /dev/null +++ b/application/models/campcaster/map/CcSmembTableMap.php @@ -0,0 +1,55 @@ +setName('cc_smemb'); + $this->setPhpName('CcSmemb'); + $this->setClassname('CcSmemb'); + $this->setPackage('campcaster'); + $this->setUseIdGenerator(false); + // columns + $this->addPrimaryKey('ID', 'Id', 'INTEGER', true, null, null); + $this->addColumn('UID', 'Uid', 'INTEGER', true, null, 0); + $this->addColumn('GID', 'Gid', 'INTEGER', true, null, 0); + $this->addColumn('LEVEL', 'Level', 'INTEGER', true, null, 0); + $this->addColumn('MID', 'Mid', 'INTEGER', false, null, null); + // validators + } // initialize() + + /** + * Build the RelationMap objects for this table relationships + */ + public function buildRelations() + { + } // buildRelations() + +} // CcSmembTableMap diff --git a/application/models/campcaster/map/CcSubjsTableMap.php b/application/models/campcaster/map/CcSubjsTableMap.php new file mode 100644 index 000000000..a32bb2710 --- /dev/null +++ b/application/models/campcaster/map/CcSubjsTableMap.php @@ -0,0 +1,63 @@ +setName('cc_subjs'); + $this->setPhpName('CcSubjs'); + $this->setClassname('CcSubjs'); + $this->setPackage('campcaster'); + $this->setUseIdGenerator(false); + // columns + $this->addPrimaryKey('ID', 'Id', 'INTEGER', true, null, null); + $this->addColumn('LOGIN', 'Login', 'VARCHAR', true, 255, ''); + $this->addColumn('PASS', 'Pass', 'VARCHAR', true, 255, ''); + $this->addColumn('TYPE', 'Type', 'CHAR', true, 1, 'U'); + $this->addColumn('REALNAME', 'Realname', 'VARCHAR', true, 255, ''); + $this->addColumn('LASTLOGIN', 'Lastlogin', 'TIMESTAMP', false, null, null); + $this->addColumn('LASTFAIL', 'Lastfail', 'TIMESTAMP', false, null, null); + // validators + } // initialize() + + /** + * Build the RelationMap objects for this table relationships + */ + public function buildRelations() + { + $this->addRelation('CcAccess', 'CcAccess', RelationMap::ONE_TO_MANY, array('id' => 'owner', ), null, null); + $this->addRelation('CcFiles', 'CcFiles', RelationMap::ONE_TO_MANY, array('id' => 'editedby', ), null, null); + $this->addRelation('CcPerms', 'CcPerms', RelationMap::ONE_TO_MANY, array('id' => 'subj', ), 'CASCADE', null); + $this->addRelation('CcPlaylist', 'CcPlaylist', RelationMap::ONE_TO_MANY, array('id' => 'editedby', ), null, null); + $this->addRelation('CcPref', 'CcPref', RelationMap::ONE_TO_MANY, array('id' => 'subjid', ), 'CASCADE', null); + $this->addRelation('CcSess', 'CcSess', RelationMap::ONE_TO_MANY, array('id' => 'userid', ), 'CASCADE', null); + } // buildRelations() + +} // CcSubjsTableMap diff --git a/application/models/campcaster/map/CcTransTableMap.php b/application/models/campcaster/map/CcTransTableMap.php new file mode 100644 index 000000000..9ccb711db --- /dev/null +++ b/application/models/campcaster/map/CcTransTableMap.php @@ -0,0 +1,75 @@ +setName('cc_trans'); + $this->setPhpName('CcTrans'); + $this->setClassname('CcTrans'); + $this->setPackage('campcaster'); + $this->setUseIdGenerator(true); + $this->setPrimaryKeyMethodInfo('cc_trans_id_seq'); + // columns + $this->addPrimaryKey('ID', 'Id', 'INTEGER', true, null, null); + $this->addColumn('TRTOK', 'Trtok', 'CHAR', true, 16, null); + $this->addColumn('DIRECTION', 'Direction', 'VARCHAR', true, 128, null); + $this->addColumn('STATE', 'State', 'VARCHAR', true, 128, null); + $this->addColumn('TRTYPE', 'Trtype', 'VARCHAR', true, 128, null); + $this->addColumn('LOCK', 'Lock', 'CHAR', true, 1, 'N'); + $this->addColumn('TARGET', 'Target', 'VARCHAR', false, 255, null); + $this->addColumn('RTRTOK', 'Rtrtok', 'CHAR', false, 16, null); + $this->addColumn('MDTRTOK', 'Mdtrtok', 'CHAR', false, 16, null); + $this->addColumn('GUNID', 'Gunid', 'CHAR', false, 32, null); + $this->addColumn('PDTOKEN', 'Pdtoken', 'BIGINT', false, null, null); + $this->addColumn('URL', 'Url', 'VARCHAR', false, 255, null); + $this->addColumn('LOCALFILE', 'Localfile', 'VARCHAR', false, 255, null); + $this->addColumn('FNAME', 'Fname', 'VARCHAR', false, 255, null); + $this->addColumn('TITLE', 'Title', 'VARCHAR', false, 255, null); + $this->addColumn('EXPECTEDSUM', 'Expectedsum', 'CHAR', false, 32, null); + $this->addColumn('REALSUM', 'Realsum', 'CHAR', false, 32, null); + $this->addColumn('EXPECTEDSIZE', 'Expectedsize', 'INTEGER', false, null, null); + $this->addColumn('REALSIZE', 'Realsize', 'INTEGER', false, null, null); + $this->addColumn('UID', 'Uid', 'INTEGER', false, null, null); + $this->addColumn('ERRMSG', 'Errmsg', 'VARCHAR', false, 255, null); + $this->addColumn('JOBPID', 'Jobpid', 'INTEGER', false, null, null); + $this->addColumn('START', 'Start', 'TIMESTAMP', false, null, null); + $this->addColumn('TS', 'Ts', 'TIMESTAMP', false, null, null); + // validators + } // initialize() + + /** + * Build the RelationMap objects for this table relationships + */ + public function buildRelations() + { + } // buildRelations() + +} // CcTransTableMap diff --git a/application/models/campcaster/om/BaseCcAccess.php b/application/models/campcaster/om/BaseCcAccess.php new file mode 100644 index 000000000..974682fed --- /dev/null +++ b/application/models/campcaster/om/BaseCcAccess.php @@ -0,0 +1,1236 @@ +chsum = ''; + $this->ext = ''; + $this->type = ''; + } + + /** + * Initializes internal state of BaseCcAccess object. + * @see applyDefaults() + */ + public function __construct() + { + parent::__construct(); + $this->applyDefaultValues(); + } + + /** + * Get the [id] column value. + * + * @return int + */ + public function getId() + { + return $this->id; + } + + /** + * Get the [gunid] column value. + * + * @return string + */ + public function getGunid() + { + return $this->gunid; + } + + /** + * Get the [token] column value. + * + * @return string + */ + public function getToken() + { + return $this->token; + } + + /** + * Get the [chsum] column value. + * + * @return string + */ + public function getChsum() + { + return $this->chsum; + } + + /** + * Get the [ext] column value. + * + * @return string + */ + public function getExt() + { + return $this->ext; + } + + /** + * Get the [type] column value. + * + * @return string + */ + public function getType() + { + return $this->type; + } + + /** + * Get the [parent] column value. + * + * @return string + */ + public function getParent() + { + return $this->parent; + } + + /** + * Get the [owner] column value. + * + * @return int + */ + public function getOwner() + { + return $this->owner; + } + + /** + * Get the [optionally formatted] temporal [ts] column value. + * + * + * @param string $format The date/time format string (either date()-style or strftime()-style). + * If format is NULL, then the raw DateTime object will be returned. + * @return mixed Formatted date/time value as string or DateTime object (if format is NULL), NULL if column is NULL + * @throws PropelException - if unable to parse/validate the date/time value. + */ + public function getTs($format = 'Y-m-d H:i:s') + { + if ($this->ts === null) { + return null; + } + + + + try { + $dt = new DateTime($this->ts); + } catch (Exception $x) { + throw new PropelException("Internally stored date/time/timestamp value could not be converted to DateTime: " . var_export($this->ts, true), $x); + } + + if ($format === null) { + // Because propel.useDateTimeClass is TRUE, we return a DateTime object. + return $dt; + } elseif (strpos($format, '%') !== false) { + return strftime($format, $dt->format('U')); + } else { + return $dt->format($format); + } + } + + /** + * Set the value of [id] column. + * + * @param int $v new value + * @return CcAccess The current object (for fluent API support) + */ + public function setId($v) + { + if ($v !== null) { + $v = (int) $v; + } + + if ($this->id !== $v) { + $this->id = $v; + $this->modifiedColumns[] = CcAccessPeer::ID; + } + + return $this; + } // setId() + + /** + * Set the value of [gunid] column. + * + * @param string $v new value + * @return CcAccess The current object (for fluent API support) + */ + public function setGunid($v) + { + if ($v !== null) { + $v = (string) $v; + } + + if ($this->gunid !== $v) { + $this->gunid = $v; + $this->modifiedColumns[] = CcAccessPeer::GUNID; + } + + return $this; + } // setGunid() + + /** + * Set the value of [token] column. + * + * @param string $v new value + * @return CcAccess The current object (for fluent API support) + */ + public function setToken($v) + { + if ($v !== null) { + $v = (string) $v; + } + + if ($this->token !== $v) { + $this->token = $v; + $this->modifiedColumns[] = CcAccessPeer::TOKEN; + } + + return $this; + } // setToken() + + /** + * Set the value of [chsum] column. + * + * @param string $v new value + * @return CcAccess The current object (for fluent API support) + */ + public function setChsum($v) + { + if ($v !== null) { + $v = (string) $v; + } + + if ($this->chsum !== $v || $this->isNew()) { + $this->chsum = $v; + $this->modifiedColumns[] = CcAccessPeer::CHSUM; + } + + return $this; + } // setChsum() + + /** + * Set the value of [ext] column. + * + * @param string $v new value + * @return CcAccess The current object (for fluent API support) + */ + public function setExt($v) + { + if ($v !== null) { + $v = (string) $v; + } + + if ($this->ext !== $v || $this->isNew()) { + $this->ext = $v; + $this->modifiedColumns[] = CcAccessPeer::EXT; + } + + return $this; + } // setExt() + + /** + * Set the value of [type] column. + * + * @param string $v new value + * @return CcAccess The current object (for fluent API support) + */ + public function setType($v) + { + if ($v !== null) { + $v = (string) $v; + } + + if ($this->type !== $v || $this->isNew()) { + $this->type = $v; + $this->modifiedColumns[] = CcAccessPeer::TYPE; + } + + return $this; + } // setType() + + /** + * Set the value of [parent] column. + * + * @param string $v new value + * @return CcAccess The current object (for fluent API support) + */ + public function setParent($v) + { + if ($v !== null) { + $v = (string) $v; + } + + if ($this->parent !== $v) { + $this->parent = $v; + $this->modifiedColumns[] = CcAccessPeer::PARENT; + } + + return $this; + } // setParent() + + /** + * Set the value of [owner] column. + * + * @param int $v new value + * @return CcAccess The current object (for fluent API support) + */ + public function setOwner($v) + { + if ($v !== null) { + $v = (int) $v; + } + + if ($this->owner !== $v) { + $this->owner = $v; + $this->modifiedColumns[] = CcAccessPeer::OWNER; + } + + if ($this->aCcSubjs !== null && $this->aCcSubjs->getId() !== $v) { + $this->aCcSubjs = null; + } + + return $this; + } // setOwner() + + /** + * Sets the value of [ts] column to a normalized version of the date/time value specified. + * + * @param mixed $v string, integer (timestamp), or DateTime value. Empty string will + * be treated as NULL for temporal objects. + * @return CcAccess The current object (for fluent API support) + */ + public function setTs($v) + { + // we treat '' as NULL for temporal objects because DateTime('') == DateTime('now') + // -- which is unexpected, to say the least. + if ($v === null || $v === '') { + $dt = null; + } elseif ($v instanceof DateTime) { + $dt = $v; + } else { + // some string/numeric value passed; we normalize that so that we can + // validate it. + try { + if (is_numeric($v)) { // if it's a unix timestamp + $dt = new DateTime('@'.$v, new DateTimeZone('UTC')); + // We have to explicitly specify and then change the time zone because of a + // DateTime bug: http://bugs.php.net/bug.php?id=43003 + $dt->setTimeZone(new DateTimeZone(date_default_timezone_get())); + } else { + $dt = new DateTime($v); + } + } catch (Exception $x) { + throw new PropelException('Error parsing date/time value: ' . var_export($v, true), $x); + } + } + + if ( $this->ts !== null || $dt !== null ) { + // (nested ifs are a little easier to read in this case) + + $currNorm = ($this->ts !== null && $tmpDt = new DateTime($this->ts)) ? $tmpDt->format('Y-m-d\\TH:i:sO') : null; + $newNorm = ($dt !== null) ? $dt->format('Y-m-d\\TH:i:sO') : null; + + if ( ($currNorm !== $newNorm) // normalized values don't match + ) + { + $this->ts = ($dt ? $dt->format('Y-m-d\\TH:i:sO') : null); + $this->modifiedColumns[] = CcAccessPeer::TS; + } + } // if either are not null + + return $this; + } // setTs() + + /** + * Indicates whether the columns in this object are only set to default values. + * + * This method can be used in conjunction with isModified() to indicate whether an object is both + * modified _and_ has some values set which are non-default. + * + * @return boolean Whether the columns in this object are only been set with default values. + */ + public function hasOnlyDefaultValues() + { + if ($this->chsum !== '') { + return false; + } + + if ($this->ext !== '') { + return false; + } + + if ($this->type !== '') { + return false; + } + + // otherwise, everything was equal, so return TRUE + return true; + } // hasOnlyDefaultValues() + + /** + * Hydrates (populates) the object variables with values from the database resultset. + * + * An offset (0-based "start column") is specified so that objects can be hydrated + * with a subset of the columns in the resultset rows. This is needed, for example, + * for results of JOIN queries where the resultset row includes columns from two or + * more tables. + * + * @param array $row The row returned by PDOStatement->fetch(PDO::FETCH_NUM) + * @param int $startcol 0-based offset column which indicates which restultset column to start with. + * @param boolean $rehydrate Whether this object is being re-hydrated from the database. + * @return int next starting column + * @throws PropelException - Any caught Exception will be rewrapped as a PropelException. + */ + public function hydrate($row, $startcol = 0, $rehydrate = false) + { + try { + + $this->id = ($row[$startcol + 0] !== null) ? (int) $row[$startcol + 0] : null; + $this->gunid = ($row[$startcol + 1] !== null) ? (string) $row[$startcol + 1] : null; + $this->token = ($row[$startcol + 2] !== null) ? (string) $row[$startcol + 2] : null; + $this->chsum = ($row[$startcol + 3] !== null) ? (string) $row[$startcol + 3] : null; + $this->ext = ($row[$startcol + 4] !== null) ? (string) $row[$startcol + 4] : null; + $this->type = ($row[$startcol + 5] !== null) ? (string) $row[$startcol + 5] : null; + $this->parent = ($row[$startcol + 6] !== null) ? (string) $row[$startcol + 6] : null; + $this->owner = ($row[$startcol + 7] !== null) ? (int) $row[$startcol + 7] : null; + $this->ts = ($row[$startcol + 8] !== null) ? (string) $row[$startcol + 8] : null; + $this->resetModified(); + + $this->setNew(false); + + if ($rehydrate) { + $this->ensureConsistency(); + } + + return $startcol + 9; // 9 = CcAccessPeer::NUM_COLUMNS - CcAccessPeer::NUM_LAZY_LOAD_COLUMNS). + + } catch (Exception $e) { + throw new PropelException("Error populating CcAccess object", $e); + } + } + + /** + * Checks and repairs the internal consistency of the object. + * + * This method is executed after an already-instantiated object is re-hydrated + * from the database. It exists to check any foreign keys to make sure that + * the objects related to the current object are correct based on foreign key. + * + * You can override this method in the stub class, but you should always invoke + * the base method from the overridden method (i.e. parent::ensureConsistency()), + * in case your model changes. + * + * @throws PropelException + */ + public function ensureConsistency() + { + + if ($this->aCcSubjs !== null && $this->owner !== $this->aCcSubjs->getId()) { + $this->aCcSubjs = null; + } + } // ensureConsistency + + /** + * Reloads this object from datastore based on primary key and (optionally) resets all associated objects. + * + * This will only work if the object has been saved and has a valid primary key set. + * + * @param boolean $deep (optional) Whether to also de-associated any related objects. + * @param PropelPDO $con (optional) The PropelPDO connection to use. + * @return void + * @throws PropelException - if this object is deleted, unsaved or doesn't have pk match in db + */ + public function reload($deep = false, PropelPDO $con = null) + { + if ($this->isDeleted()) { + throw new PropelException("Cannot reload a deleted object."); + } + + if ($this->isNew()) { + throw new PropelException("Cannot reload an unsaved object."); + } + + if ($con === null) { + $con = Propel::getConnection(CcAccessPeer::DATABASE_NAME, Propel::CONNECTION_READ); + } + + // We don't need to alter the object instance pool; we're just modifying this instance + // already in the pool. + + $stmt = CcAccessPeer::doSelectStmt($this->buildPkeyCriteria(), $con); + $row = $stmt->fetch(PDO::FETCH_NUM); + $stmt->closeCursor(); + if (!$row) { + throw new PropelException('Cannot find matching row in the database to reload object values.'); + } + $this->hydrate($row, 0, true); // rehydrate + + if ($deep) { // also de-associate any related objects? + + $this->aCcSubjs = null; + } // if (deep) + } + + /** + * Removes this object from datastore and sets delete attribute. + * + * @param PropelPDO $con + * @return void + * @throws PropelException + * @see BaseObject::setDeleted() + * @see BaseObject::isDeleted() + */ + public function delete(PropelPDO $con = null) + { + if ($this->isDeleted()) { + throw new PropelException("This object has already been deleted."); + } + + if ($con === null) { + $con = Propel::getConnection(CcAccessPeer::DATABASE_NAME, Propel::CONNECTION_WRITE); + } + + $con->beginTransaction(); + try { + $ret = $this->preDelete($con); + if ($ret) { + CcAccessQuery::create() + ->filterByPrimaryKey($this->getPrimaryKey()) + ->delete($con); + $this->postDelete($con); + $con->commit(); + $this->setDeleted(true); + } else { + $con->commit(); + } + } catch (PropelException $e) { + $con->rollBack(); + throw $e; + } + } + + /** + * Persists this object to the database. + * + * If the object is new, it inserts it; otherwise an update is performed. + * All modified related objects will also be persisted in the doSave() + * method. This method wraps all precipitate database operations in a + * single transaction. + * + * @param PropelPDO $con + * @return int The number of rows affected by this insert/update and any referring fk objects' save() operations. + * @throws PropelException + * @see doSave() + */ + public function save(PropelPDO $con = null) + { + if ($this->isDeleted()) { + throw new PropelException("You cannot save an object that has been deleted."); + } + + if ($con === null) { + $con = Propel::getConnection(CcAccessPeer::DATABASE_NAME, Propel::CONNECTION_WRITE); + } + + $con->beginTransaction(); + $isInsert = $this->isNew(); + try { + $ret = $this->preSave($con); + if ($isInsert) { + $ret = $ret && $this->preInsert($con); + } else { + $ret = $ret && $this->preUpdate($con); + } + if ($ret) { + $affectedRows = $this->doSave($con); + if ($isInsert) { + $this->postInsert($con); + } else { + $this->postUpdate($con); + } + $this->postSave($con); + CcAccessPeer::addInstanceToPool($this); + } else { + $affectedRows = 0; + } + $con->commit(); + return $affectedRows; + } catch (PropelException $e) { + $con->rollBack(); + throw $e; + } + } + + /** + * Performs the work of inserting or updating the row in the database. + * + * If the object is new, it inserts it; otherwise an update is performed. + * All related objects are also updated in this method. + * + * @param PropelPDO $con + * @return int The number of rows affected by this insert/update and any referring fk objects' save() operations. + * @throws PropelException + * @see save() + */ + protected function doSave(PropelPDO $con) + { + $affectedRows = 0; // initialize var to track total num of affected rows + if (!$this->alreadyInSave) { + $this->alreadyInSave = true; + + // We call the save method on the following object(s) if they + // were passed to this object by their coresponding set + // method. This object relates to these object(s) by a + // foreign key reference. + + if ($this->aCcSubjs !== null) { + if ($this->aCcSubjs->isModified() || $this->aCcSubjs->isNew()) { + $affectedRows += $this->aCcSubjs->save($con); + } + $this->setCcSubjs($this->aCcSubjs); + } + + if ($this->isNew() ) { + $this->modifiedColumns[] = CcAccessPeer::ID; + } + + // If this object has been modified, then save it to the database. + if ($this->isModified()) { + if ($this->isNew()) { + $criteria = $this->buildCriteria(); + if ($criteria->keyContainsValue(CcAccessPeer::ID) ) { + throw new PropelException('Cannot insert a value for auto-increment primary key ('.CcAccessPeer::ID.')'); + } + + $pk = BasePeer::doInsert($criteria, $con); + $affectedRows += 1; + $this->setId($pk); //[IMV] update autoincrement primary key + $this->setNew(false); + } else { + $affectedRows += CcAccessPeer::doUpdate($this, $con); + } + + $this->resetModified(); // [HL] After being saved an object is no longer 'modified' + } + + $this->alreadyInSave = false; + + } + return $affectedRows; + } // doSave() + + /** + * Array of ValidationFailed objects. + * @var array ValidationFailed[] + */ + protected $validationFailures = array(); + + /** + * Gets any ValidationFailed objects that resulted from last call to validate(). + * + * + * @return array ValidationFailed[] + * @see validate() + */ + public function getValidationFailures() + { + return $this->validationFailures; + } + + /** + * Validates the objects modified field values and all objects related to this table. + * + * If $columns is either a column name or an array of column names + * only those columns are validated. + * + * @param mixed $columns Column name or an array of column names. + * @return boolean Whether all columns pass validation. + * @see doValidate() + * @see getValidationFailures() + */ + public function validate($columns = null) + { + $res = $this->doValidate($columns); + if ($res === true) { + $this->validationFailures = array(); + return true; + } else { + $this->validationFailures = $res; + return false; + } + } + + /** + * This function performs the validation work for complex object models. + * + * In addition to checking the current object, all related objects will + * also be validated. If all pass then true is returned; otherwise + * an aggreagated array of ValidationFailed objects will be returned. + * + * @param array $columns Array of column names to validate. + * @return mixed true if all validations pass; array of ValidationFailed objets otherwise. + */ + protected function doValidate($columns = null) + { + if (!$this->alreadyInValidation) { + $this->alreadyInValidation = true; + $retval = null; + + $failureMap = array(); + + + // We call the validate method on the following object(s) if they + // were passed to this object by their coresponding set + // method. This object relates to these object(s) by a + // foreign key reference. + + if ($this->aCcSubjs !== null) { + if (!$this->aCcSubjs->validate($columns)) { + $failureMap = array_merge($failureMap, $this->aCcSubjs->getValidationFailures()); + } + } + + + if (($retval = CcAccessPeer::doValidate($this, $columns)) !== true) { + $failureMap = array_merge($failureMap, $retval); + } + + + + $this->alreadyInValidation = false; + } + + return (!empty($failureMap) ? $failureMap : true); + } + + /** + * Retrieves a field from the object by name passed in as a string. + * + * @param string $name name + * @param string $type The type of fieldname the $name is of: + * one of the class type constants BasePeer::TYPE_PHPNAME, BasePeer::TYPE_STUDLYPHPNAME + * BasePeer::TYPE_COLNAME, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_NUM + * @return mixed Value of field. + */ + public function getByName($name, $type = BasePeer::TYPE_PHPNAME) + { + $pos = CcAccessPeer::translateFieldName($name, $type, BasePeer::TYPE_NUM); + $field = $this->getByPosition($pos); + return $field; + } + + /** + * Retrieves a field from the object by Position as specified in the xml schema. + * Zero-based. + * + * @param int $pos position in xml schema + * @return mixed Value of field at $pos + */ + public function getByPosition($pos) + { + switch($pos) { + case 0: + return $this->getId(); + break; + case 1: + return $this->getGunid(); + break; + case 2: + return $this->getToken(); + break; + case 3: + return $this->getChsum(); + break; + case 4: + return $this->getExt(); + break; + case 5: + return $this->getType(); + break; + case 6: + return $this->getParent(); + break; + case 7: + return $this->getOwner(); + break; + case 8: + return $this->getTs(); + break; + default: + return null; + break; + } // switch() + } + + /** + * Exports the object as an array. + * + * You can specify the key type of the array by passing one of the class + * type constants. + * + * @param string $keyType (optional) One of the class type constants BasePeer::TYPE_PHPNAME, BasePeer::TYPE_STUDLYPHPNAME, + * BasePeer::TYPE_COLNAME, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_NUM. + * Defaults to BasePeer::TYPE_PHPNAME. + * @param boolean $includeLazyLoadColumns (optional) Whether to include lazy loaded columns. Defaults to TRUE. + * @param boolean $includeForeignObjects (optional) Whether to include hydrated related objects. Default to FALSE. + * + * @return array an associative array containing the field names (as keys) and field values + */ + public function toArray($keyType = BasePeer::TYPE_PHPNAME, $includeLazyLoadColumns = true, $includeForeignObjects = false) + { + $keys = CcAccessPeer::getFieldNames($keyType); + $result = array( + $keys[0] => $this->getId(), + $keys[1] => $this->getGunid(), + $keys[2] => $this->getToken(), + $keys[3] => $this->getChsum(), + $keys[4] => $this->getExt(), + $keys[5] => $this->getType(), + $keys[6] => $this->getParent(), + $keys[7] => $this->getOwner(), + $keys[8] => $this->getTs(), + ); + if ($includeForeignObjects) { + if (null !== $this->aCcSubjs) { + $result['CcSubjs'] = $this->aCcSubjs->toArray($keyType, $includeLazyLoadColumns, true); + } + } + return $result; + } + + /** + * Sets a field from the object by name passed in as a string. + * + * @param string $name peer name + * @param mixed $value field value + * @param string $type The type of fieldname the $name is of: + * one of the class type constants BasePeer::TYPE_PHPNAME, BasePeer::TYPE_STUDLYPHPNAME + * BasePeer::TYPE_COLNAME, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_NUM + * @return void + */ + public function setByName($name, $value, $type = BasePeer::TYPE_PHPNAME) + { + $pos = CcAccessPeer::translateFieldName($name, $type, BasePeer::TYPE_NUM); + return $this->setByPosition($pos, $value); + } + + /** + * Sets a field from the object by Position as specified in the xml schema. + * Zero-based. + * + * @param int $pos position in xml schema + * @param mixed $value field value + * @return void + */ + public function setByPosition($pos, $value) + { + switch($pos) { + case 0: + $this->setId($value); + break; + case 1: + $this->setGunid($value); + break; + case 2: + $this->setToken($value); + break; + case 3: + $this->setChsum($value); + break; + case 4: + $this->setExt($value); + break; + case 5: + $this->setType($value); + break; + case 6: + $this->setParent($value); + break; + case 7: + $this->setOwner($value); + break; + case 8: + $this->setTs($value); + break; + } // switch() + } + + /** + * Populates the object using an array. + * + * This is particularly useful when populating an object from one of the + * request arrays (e.g. $_POST). This method goes through the column + * names, checking to see whether a matching key exists in populated + * array. If so the setByName() method is called for that column. + * + * You can specify the key type of the array by additionally passing one + * of the class type constants BasePeer::TYPE_PHPNAME, BasePeer::TYPE_STUDLYPHPNAME, + * BasePeer::TYPE_COLNAME, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_NUM. + * The default key type is the column's phpname (e.g. 'AuthorId') + * + * @param array $arr An array to populate the object from. + * @param string $keyType The type of keys the array uses. + * @return void + */ + public function fromArray($arr, $keyType = BasePeer::TYPE_PHPNAME) + { + $keys = CcAccessPeer::getFieldNames($keyType); + + if (array_key_exists($keys[0], $arr)) $this->setId($arr[$keys[0]]); + if (array_key_exists($keys[1], $arr)) $this->setGunid($arr[$keys[1]]); + if (array_key_exists($keys[2], $arr)) $this->setToken($arr[$keys[2]]); + if (array_key_exists($keys[3], $arr)) $this->setChsum($arr[$keys[3]]); + if (array_key_exists($keys[4], $arr)) $this->setExt($arr[$keys[4]]); + if (array_key_exists($keys[5], $arr)) $this->setType($arr[$keys[5]]); + if (array_key_exists($keys[6], $arr)) $this->setParent($arr[$keys[6]]); + if (array_key_exists($keys[7], $arr)) $this->setOwner($arr[$keys[7]]); + if (array_key_exists($keys[8], $arr)) $this->setTs($arr[$keys[8]]); + } + + /** + * Build a Criteria object containing the values of all modified columns in this object. + * + * @return Criteria The Criteria object containing all modified values. + */ + public function buildCriteria() + { + $criteria = new Criteria(CcAccessPeer::DATABASE_NAME); + + if ($this->isColumnModified(CcAccessPeer::ID)) $criteria->add(CcAccessPeer::ID, $this->id); + if ($this->isColumnModified(CcAccessPeer::GUNID)) $criteria->add(CcAccessPeer::GUNID, $this->gunid); + if ($this->isColumnModified(CcAccessPeer::TOKEN)) $criteria->add(CcAccessPeer::TOKEN, $this->token); + if ($this->isColumnModified(CcAccessPeer::CHSUM)) $criteria->add(CcAccessPeer::CHSUM, $this->chsum); + if ($this->isColumnModified(CcAccessPeer::EXT)) $criteria->add(CcAccessPeer::EXT, $this->ext); + if ($this->isColumnModified(CcAccessPeer::TYPE)) $criteria->add(CcAccessPeer::TYPE, $this->type); + if ($this->isColumnModified(CcAccessPeer::PARENT)) $criteria->add(CcAccessPeer::PARENT, $this->parent); + if ($this->isColumnModified(CcAccessPeer::OWNER)) $criteria->add(CcAccessPeer::OWNER, $this->owner); + if ($this->isColumnModified(CcAccessPeer::TS)) $criteria->add(CcAccessPeer::TS, $this->ts); + + return $criteria; + } + + /** + * Builds a Criteria object containing the primary key for this object. + * + * Unlike buildCriteria() this method includes the primary key values regardless + * of whether or not they have been modified. + * + * @return Criteria The Criteria object containing value(s) for primary key(s). + */ + public function buildPkeyCriteria() + { + $criteria = new Criteria(CcAccessPeer::DATABASE_NAME); + $criteria->add(CcAccessPeer::ID, $this->id); + + return $criteria; + } + + /** + * Returns the primary key for this object (row). + * @return int + */ + public function getPrimaryKey() + { + return $this->getId(); + } + + /** + * Generic method to set the primary key (id column). + * + * @param int $key Primary key. + * @return void + */ + public function setPrimaryKey($key) + { + $this->setId($key); + } + + /** + * Returns true if the primary key for this object is null. + * @return boolean + */ + public function isPrimaryKeyNull() + { + return null === $this->getId(); + } + + /** + * Sets contents of passed object to values from current object. + * + * If desired, this method can also make copies of all associated (fkey referrers) + * objects. + * + * @param object $copyObj An object of CcAccess (or compatible) type. + * @param boolean $deepCopy Whether to also copy all rows that refer (by fkey) to the current row. + * @throws PropelException + */ + public function copyInto($copyObj, $deepCopy = false) + { + $copyObj->setGunid($this->gunid); + $copyObj->setToken($this->token); + $copyObj->setChsum($this->chsum); + $copyObj->setExt($this->ext); + $copyObj->setType($this->type); + $copyObj->setParent($this->parent); + $copyObj->setOwner($this->owner); + $copyObj->setTs($this->ts); + + $copyObj->setNew(true); + $copyObj->setId(NULL); // this is a auto-increment column, so set to default value + } + + /** + * Makes a copy of this object that will be inserted as a new row in table when saved. + * It creates a new object filling in the simple attributes, but skipping any primary + * keys that are defined for the table. + * + * If desired, this method can also make copies of all associated (fkey referrers) + * objects. + * + * @param boolean $deepCopy Whether to also copy all rows that refer (by fkey) to the current row. + * @return CcAccess Clone of current object. + * @throws PropelException + */ + public function copy($deepCopy = false) + { + // we use get_class(), because this might be a subclass + $clazz = get_class($this); + $copyObj = new $clazz(); + $this->copyInto($copyObj, $deepCopy); + return $copyObj; + } + + /** + * Returns a peer instance associated with this om. + * + * Since Peer classes are not to have any instance attributes, this method returns the + * same instance for all member of this class. The method could therefore + * be static, but this would prevent one from overriding the behavior. + * + * @return CcAccessPeer + */ + public function getPeer() + { + if (self::$peer === null) { + self::$peer = new CcAccessPeer(); + } + return self::$peer; + } + + /** + * Declares an association between this object and a CcSubjs object. + * + * @param CcSubjs $v + * @return CcAccess The current object (for fluent API support) + * @throws PropelException + */ + public function setCcSubjs(CcSubjs $v = null) + { + if ($v === null) { + $this->setOwner(NULL); + } else { + $this->setOwner($v->getId()); + } + + $this->aCcSubjs = $v; + + // Add binding for other direction of this n:n relationship. + // If this object has already been added to the CcSubjs object, it will not be re-added. + if ($v !== null) { + $v->addCcAccess($this); + } + + return $this; + } + + + /** + * Get the associated CcSubjs object + * + * @param PropelPDO Optional Connection object. + * @return CcSubjs The associated CcSubjs object. + * @throws PropelException + */ + public function getCcSubjs(PropelPDO $con = null) + { + if ($this->aCcSubjs === null && ($this->owner !== null)) { + $this->aCcSubjs = CcSubjsQuery::create()->findPk($this->owner, $con); + /* The following can be used additionally to + guarantee the related object contains a reference + to this object. This level of coupling may, however, be + undesirable since it could result in an only partially populated collection + in the referenced object. + $this->aCcSubjs->addCcAccesss($this); + */ + } + return $this->aCcSubjs; + } + + /** + * Clears the current object and sets all attributes to their default values + */ + public function clear() + { + $this->id = null; + $this->gunid = null; + $this->token = null; + $this->chsum = null; + $this->ext = null; + $this->type = null; + $this->parent = null; + $this->owner = null; + $this->ts = null; + $this->alreadyInSave = false; + $this->alreadyInValidation = false; + $this->clearAllReferences(); + $this->applyDefaultValues(); + $this->resetModified(); + $this->setNew(true); + $this->setDeleted(false); + } + + /** + * Resets all collections of referencing foreign keys. + * + * This method is a user-space workaround for PHP's inability to garbage collect objects + * with circular references. This is currently necessary when using Propel in certain + * daemon or large-volumne/high-memory operations. + * + * @param boolean $deep Whether to also clear the references on all associated objects. + */ + public function clearAllReferences($deep = false) + { + if ($deep) { + } // if ($deep) + + $this->aCcSubjs = null; + } + + /** + * Catches calls to virtual methods + */ + public function __call($name, $params) + { + if (preg_match('/get(\w+)/', $name, $matches)) { + $virtualColumn = $matches[1]; + if ($this->hasVirtualColumn($virtualColumn)) { + return $this->getVirtualColumn($virtualColumn); + } + // no lcfirst in php<5.3... + $virtualColumn[0] = strtolower($virtualColumn[0]); + if ($this->hasVirtualColumn($virtualColumn)) { + return $this->getVirtualColumn($virtualColumn); + } + } + throw new PropelException('Call to undefined method: ' . $name); + } + +} // BaseCcAccess diff --git a/application/models/campcaster/om/BaseCcAccessPeer.php b/application/models/campcaster/om/BaseCcAccessPeer.php new file mode 100644 index 000000000..f6614d330 --- /dev/null +++ b/application/models/campcaster/om/BaseCcAccessPeer.php @@ -0,0 +1,1008 @@ + array ('Id', 'Gunid', 'Token', 'Chsum', 'Ext', 'Type', 'Parent', 'Owner', 'Ts', ), + BasePeer::TYPE_STUDLYPHPNAME => array ('id', 'gunid', 'token', 'chsum', 'ext', 'type', 'parent', 'owner', 'ts', ), + BasePeer::TYPE_COLNAME => array (self::ID, self::GUNID, self::TOKEN, self::CHSUM, self::EXT, self::TYPE, self::PARENT, self::OWNER, self::TS, ), + BasePeer::TYPE_RAW_COLNAME => array ('ID', 'GUNID', 'TOKEN', 'CHSUM', 'EXT', 'TYPE', 'PARENT', 'OWNER', 'TS', ), + BasePeer::TYPE_FIELDNAME => array ('id', 'gunid', 'token', 'chsum', 'ext', 'type', 'parent', 'owner', 'ts', ), + BasePeer::TYPE_NUM => array (0, 1, 2, 3, 4, 5, 6, 7, 8, ) + ); + + /** + * holds an array of keys for quick access to the fieldnames array + * + * first dimension keys are the type constants + * e.g. self::$fieldNames[BasePeer::TYPE_PHPNAME]['Id'] = 0 + */ + private static $fieldKeys = array ( + BasePeer::TYPE_PHPNAME => array ('Id' => 0, 'Gunid' => 1, 'Token' => 2, 'Chsum' => 3, 'Ext' => 4, 'Type' => 5, 'Parent' => 6, 'Owner' => 7, 'Ts' => 8, ), + BasePeer::TYPE_STUDLYPHPNAME => array ('id' => 0, 'gunid' => 1, 'token' => 2, 'chsum' => 3, 'ext' => 4, 'type' => 5, 'parent' => 6, 'owner' => 7, 'ts' => 8, ), + BasePeer::TYPE_COLNAME => array (self::ID => 0, self::GUNID => 1, self::TOKEN => 2, self::CHSUM => 3, self::EXT => 4, self::TYPE => 5, self::PARENT => 6, self::OWNER => 7, self::TS => 8, ), + BasePeer::TYPE_RAW_COLNAME => array ('ID' => 0, 'GUNID' => 1, 'TOKEN' => 2, 'CHSUM' => 3, 'EXT' => 4, 'TYPE' => 5, 'PARENT' => 6, 'OWNER' => 7, 'TS' => 8, ), + BasePeer::TYPE_FIELDNAME => array ('id' => 0, 'gunid' => 1, 'token' => 2, 'chsum' => 3, 'ext' => 4, 'type' => 5, 'parent' => 6, 'owner' => 7, 'ts' => 8, ), + BasePeer::TYPE_NUM => array (0, 1, 2, 3, 4, 5, 6, 7, 8, ) + ); + + /** + * Translates a fieldname to another type + * + * @param string $name field name + * @param string $fromType One of the class type constants BasePeer::TYPE_PHPNAME, BasePeer::TYPE_STUDLYPHPNAME + * BasePeer::TYPE_COLNAME, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_NUM + * @param string $toType One of the class type constants + * @return string translated name of the field. + * @throws PropelException - if the specified name could not be found in the fieldname mappings. + */ + static public function translateFieldName($name, $fromType, $toType) + { + $toNames = self::getFieldNames($toType); + $key = isset(self::$fieldKeys[$fromType][$name]) ? self::$fieldKeys[$fromType][$name] : null; + if ($key === null) { + throw new PropelException("'$name' could not be found in the field names of type '$fromType'. These are: " . print_r(self::$fieldKeys[$fromType], true)); + } + return $toNames[$key]; + } + + /** + * Returns an array of field names. + * + * @param string $type The type of fieldnames to return: + * One of the class type constants BasePeer::TYPE_PHPNAME, BasePeer::TYPE_STUDLYPHPNAME + * BasePeer::TYPE_COLNAME, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_NUM + * @return array A list of field names + */ + + static public function getFieldNames($type = BasePeer::TYPE_PHPNAME) + { + if (!array_key_exists($type, self::$fieldNames)) { + throw new PropelException('Method getFieldNames() expects the parameter $type to be one of the class constants BasePeer::TYPE_PHPNAME, BasePeer::TYPE_STUDLYPHPNAME, BasePeer::TYPE_COLNAME, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_NUM. ' . $type . ' was given.'); + } + return self::$fieldNames[$type]; + } + + /** + * Convenience method which changes table.column to alias.column. + * + * Using this method you can maintain SQL abstraction while using column aliases. + * + * $c->addAlias("alias1", TablePeer::TABLE_NAME); + * $c->addJoin(TablePeer::alias("alias1", TablePeer::PRIMARY_KEY_COLUMN), TablePeer::PRIMARY_KEY_COLUMN); + * + * @param string $alias The alias for the current table. + * @param string $column The column name for current table. (i.e. CcAccessPeer::COLUMN_NAME). + * @return string + */ + public static function alias($alias, $column) + { + return str_replace(CcAccessPeer::TABLE_NAME.'.', $alias.'.', $column); + } + + /** + * Add all the columns needed to create a new object. + * + * Note: any columns that were marked with lazyLoad="true" in the + * XML schema will not be added to the select list and only loaded + * on demand. + * + * @param Criteria $criteria object containing the columns to add. + * @param string $alias optional table alias + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function addSelectColumns(Criteria $criteria, $alias = null) + { + if (null === $alias) { + $criteria->addSelectColumn(CcAccessPeer::ID); + $criteria->addSelectColumn(CcAccessPeer::GUNID); + $criteria->addSelectColumn(CcAccessPeer::TOKEN); + $criteria->addSelectColumn(CcAccessPeer::CHSUM); + $criteria->addSelectColumn(CcAccessPeer::EXT); + $criteria->addSelectColumn(CcAccessPeer::TYPE); + $criteria->addSelectColumn(CcAccessPeer::PARENT); + $criteria->addSelectColumn(CcAccessPeer::OWNER); + $criteria->addSelectColumn(CcAccessPeer::TS); + } else { + $criteria->addSelectColumn($alias . '.ID'); + $criteria->addSelectColumn($alias . '.GUNID'); + $criteria->addSelectColumn($alias . '.TOKEN'); + $criteria->addSelectColumn($alias . '.CHSUM'); + $criteria->addSelectColumn($alias . '.EXT'); + $criteria->addSelectColumn($alias . '.TYPE'); + $criteria->addSelectColumn($alias . '.PARENT'); + $criteria->addSelectColumn($alias . '.OWNER'); + $criteria->addSelectColumn($alias . '.TS'); + } + } + + /** + * Returns the number of rows matching criteria. + * + * @param Criteria $criteria + * @param boolean $distinct Whether to select only distinct columns; deprecated: use Criteria->setDistinct() instead. + * @param PropelPDO $con + * @return int Number of matching rows. + */ + public static function doCount(Criteria $criteria, $distinct = false, PropelPDO $con = null) + { + // we may modify criteria, so copy it first + $criteria = clone $criteria; + + // We need to set the primary table name, since in the case that there are no WHERE columns + // it will be impossible for the BasePeer::createSelectSql() method to determine which + // tables go into the FROM clause. + $criteria->setPrimaryTableName(CcAccessPeer::TABLE_NAME); + + if ($distinct && !in_array(Criteria::DISTINCT, $criteria->getSelectModifiers())) { + $criteria->setDistinct(); + } + + if (!$criteria->hasSelectClause()) { + CcAccessPeer::addSelectColumns($criteria); + } + + $criteria->clearOrderByColumns(); // ORDER BY won't ever affect the count + $criteria->setDbName(self::DATABASE_NAME); // Set the correct dbName + + if ($con === null) { + $con = Propel::getConnection(CcAccessPeer::DATABASE_NAME, Propel::CONNECTION_READ); + } + // BasePeer returns a PDOStatement + $stmt = BasePeer::doCount($criteria, $con); + + if ($row = $stmt->fetch(PDO::FETCH_NUM)) { + $count = (int) $row[0]; + } else { + $count = 0; // no rows returned; we infer that means 0 matches. + } + $stmt->closeCursor(); + return $count; + } + /** + * Method to select one object from the DB. + * + * @param Criteria $criteria object used to create the SELECT statement. + * @param PropelPDO $con + * @return CcAccess + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function doSelectOne(Criteria $criteria, PropelPDO $con = null) + { + $critcopy = clone $criteria; + $critcopy->setLimit(1); + $objects = CcAccessPeer::doSelect($critcopy, $con); + if ($objects) { + return $objects[0]; + } + return null; + } + /** + * Method to do selects. + * + * @param Criteria $criteria The Criteria object used to build the SELECT statement. + * @param PropelPDO $con + * @return array Array of selected Objects + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function doSelect(Criteria $criteria, PropelPDO $con = null) + { + return CcAccessPeer::populateObjects(CcAccessPeer::doSelectStmt($criteria, $con)); + } + /** + * Prepares the Criteria object and uses the parent doSelect() method to execute a PDOStatement. + * + * Use this method directly if you want to work with an executed statement durirectly (for example + * to perform your own object hydration). + * + * @param Criteria $criteria The Criteria object used to build the SELECT statement. + * @param PropelPDO $con The connection to use + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + * @return PDOStatement The executed PDOStatement object. + * @see BasePeer::doSelect() + */ + public static function doSelectStmt(Criteria $criteria, PropelPDO $con = null) + { + if ($con === null) { + $con = Propel::getConnection(CcAccessPeer::DATABASE_NAME, Propel::CONNECTION_READ); + } + + if (!$criteria->hasSelectClause()) { + $criteria = clone $criteria; + CcAccessPeer::addSelectColumns($criteria); + } + + // Set the correct dbName + $criteria->setDbName(self::DATABASE_NAME); + + // BasePeer returns a PDOStatement + return BasePeer::doSelect($criteria, $con); + } + /** + * Adds an object to the instance pool. + * + * Propel keeps cached copies of objects in an instance pool when they are retrieved + * from the database. In some cases -- especially when you override doSelect*() + * methods in your stub classes -- you may need to explicitly add objects + * to the cache in order to ensure that the same objects are always returned by doSelect*() + * and retrieveByPK*() calls. + * + * @param CcAccess $value A CcAccess object. + * @param string $key (optional) key to use for instance map (for performance boost if key was already calculated externally). + */ + public static function addInstanceToPool(CcAccess $obj, $key = null) + { + if (Propel::isInstancePoolingEnabled()) { + if ($key === null) { + $key = (string) $obj->getId(); + } // if key === null + self::$instances[$key] = $obj; + } + } + + /** + * Removes an object from the instance pool. + * + * Propel keeps cached copies of objects in an instance pool when they are retrieved + * from the database. In some cases -- especially when you override doDelete + * methods in your stub classes -- you may need to explicitly remove objects + * from the cache in order to prevent returning objects that no longer exist. + * + * @param mixed $value A CcAccess object or a primary key value. + */ + public static function removeInstanceFromPool($value) + { + if (Propel::isInstancePoolingEnabled() && $value !== null) { + if (is_object($value) && $value instanceof CcAccess) { + $key = (string) $value->getId(); + } elseif (is_scalar($value)) { + // assume we've been passed a primary key + $key = (string) $value; + } else { + $e = new PropelException("Invalid value passed to removeInstanceFromPool(). Expected primary key or CcAccess object; got " . (is_object($value) ? get_class($value) . ' object.' : var_export($value,true))); + throw $e; + } + + unset(self::$instances[$key]); + } + } // removeInstanceFromPool() + + /** + * Retrieves a string version of the primary key from the DB resultset row that can be used to uniquely identify a row in this table. + * + * For tables with a single-column primary key, that simple pkey value will be returned. For tables with + * a multi-column primary key, a serialize()d version of the primary key will be returned. + * + * @param string $key The key (@see getPrimaryKeyHash()) for this instance. + * @return CcAccess Found object or NULL if 1) no instance exists for specified key or 2) instance pooling has been disabled. + * @see getPrimaryKeyHash() + */ + public static function getInstanceFromPool($key) + { + if (Propel::isInstancePoolingEnabled()) { + if (isset(self::$instances[$key])) { + return self::$instances[$key]; + } + } + return null; // just to be explicit + } + + /** + * Clear the instance pool. + * + * @return void + */ + public static function clearInstancePool() + { + self::$instances = array(); + } + + /** + * Method to invalidate the instance pool of all tables related to cc_access + * by a foreign key with ON DELETE CASCADE + */ + public static function clearRelatedInstancePool() + { + } + + /** + * Retrieves a string version of the primary key from the DB resultset row that can be used to uniquely identify a row in this table. + * + * For tables with a single-column primary key, that simple pkey value will be returned. For tables with + * a multi-column primary key, a serialize()d version of the primary key will be returned. + * + * @param array $row PropelPDO resultset row. + * @param int $startcol The 0-based offset for reading from the resultset row. + * @return string A string version of PK or NULL if the components of primary key in result array are all null. + */ + public static function getPrimaryKeyHashFromRow($row, $startcol = 0) + { + // If the PK cannot be derived from the row, return NULL. + if ($row[$startcol] === null) { + return null; + } + return (string) $row[$startcol]; + } + + /** + * Retrieves the primary key from the DB resultset row + * For tables with a single-column primary key, that simple pkey value will be returned. For tables with + * a multi-column primary key, an array of the primary key columns will be returned. + * + * @param array $row PropelPDO resultset row. + * @param int $startcol The 0-based offset for reading from the resultset row. + * @return mixed The primary key of the row + */ + public static function getPrimaryKeyFromRow($row, $startcol = 0) + { + return (int) $row[$startcol]; + } + + /** + * The returned array will contain objects of the default type or + * objects that inherit from the default. + * + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function populateObjects(PDOStatement $stmt) + { + $results = array(); + + // set the class once to avoid overhead in the loop + $cls = CcAccessPeer::getOMClass(false); + // populate the object(s) + while ($row = $stmt->fetch(PDO::FETCH_NUM)) { + $key = CcAccessPeer::getPrimaryKeyHashFromRow($row, 0); + if (null !== ($obj = CcAccessPeer::getInstanceFromPool($key))) { + // We no longer rehydrate the object, since this can cause data loss. + // See http://www.propelorm.org/ticket/509 + // $obj->hydrate($row, 0, true); // rehydrate + $results[] = $obj; + } else { + $obj = new $cls(); + $obj->hydrate($row); + $results[] = $obj; + CcAccessPeer::addInstanceToPool($obj, $key); + } // if key exists + } + $stmt->closeCursor(); + return $results; + } + /** + * Populates an object of the default type or an object that inherit from the default. + * + * @param array $row PropelPDO resultset row. + * @param int $startcol The 0-based offset for reading from the resultset row. + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + * @return array (CcAccess object, last column rank) + */ + public static function populateObject($row, $startcol = 0) + { + $key = CcAccessPeer::getPrimaryKeyHashFromRow($row, $startcol); + if (null !== ($obj = CcAccessPeer::getInstanceFromPool($key))) { + // We no longer rehydrate the object, since this can cause data loss. + // See http://www.propelorm.org/ticket/509 + // $obj->hydrate($row, $startcol, true); // rehydrate + $col = $startcol + CcAccessPeer::NUM_COLUMNS; + } else { + $cls = CcAccessPeer::OM_CLASS; + $obj = new $cls(); + $col = $obj->hydrate($row, $startcol); + CcAccessPeer::addInstanceToPool($obj, $key); + } + return array($obj, $col); + } + + /** + * Returns the number of rows matching criteria, joining the related CcSubjs table + * + * @param Criteria $criteria + * @param boolean $distinct Whether to select only distinct columns; deprecated: use Criteria->setDistinct() instead. + * @param PropelPDO $con + * @param String $join_behavior the type of joins to use, defaults to Criteria::LEFT_JOIN + * @return int Number of matching rows. + */ + public static function doCountJoinCcSubjs(Criteria $criteria, $distinct = false, PropelPDO $con = null, $join_behavior = Criteria::LEFT_JOIN) + { + // we're going to modify criteria, so copy it first + $criteria = clone $criteria; + + // We need to set the primary table name, since in the case that there are no WHERE columns + // it will be impossible for the BasePeer::createSelectSql() method to determine which + // tables go into the FROM clause. + $criteria->setPrimaryTableName(CcAccessPeer::TABLE_NAME); + + if ($distinct && !in_array(Criteria::DISTINCT, $criteria->getSelectModifiers())) { + $criteria->setDistinct(); + } + + if (!$criteria->hasSelectClause()) { + CcAccessPeer::addSelectColumns($criteria); + } + + $criteria->clearOrderByColumns(); // ORDER BY won't ever affect the count + + // Set the correct dbName + $criteria->setDbName(self::DATABASE_NAME); + + if ($con === null) { + $con = Propel::getConnection(CcAccessPeer::DATABASE_NAME, Propel::CONNECTION_READ); + } + + $criteria->addJoin(CcAccessPeer::OWNER, CcSubjsPeer::ID, $join_behavior); + + $stmt = BasePeer::doCount($criteria, $con); + + if ($row = $stmt->fetch(PDO::FETCH_NUM)) { + $count = (int) $row[0]; + } else { + $count = 0; // no rows returned; we infer that means 0 matches. + } + $stmt->closeCursor(); + return $count; + } + + + /** + * Selects a collection of CcAccess objects pre-filled with their CcSubjs objects. + * @param Criteria $criteria + * @param PropelPDO $con + * @param String $join_behavior the type of joins to use, defaults to Criteria::LEFT_JOIN + * @return array Array of CcAccess objects. + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function doSelectJoinCcSubjs(Criteria $criteria, $con = null, $join_behavior = Criteria::LEFT_JOIN) + { + $criteria = clone $criteria; + + // Set the correct dbName if it has not been overridden + if ($criteria->getDbName() == Propel::getDefaultDB()) { + $criteria->setDbName(self::DATABASE_NAME); + } + + CcAccessPeer::addSelectColumns($criteria); + $startcol = (CcAccessPeer::NUM_COLUMNS - CcAccessPeer::NUM_LAZY_LOAD_COLUMNS); + CcSubjsPeer::addSelectColumns($criteria); + + $criteria->addJoin(CcAccessPeer::OWNER, CcSubjsPeer::ID, $join_behavior); + + $stmt = BasePeer::doSelect($criteria, $con); + $results = array(); + + while ($row = $stmt->fetch(PDO::FETCH_NUM)) { + $key1 = CcAccessPeer::getPrimaryKeyHashFromRow($row, 0); + if (null !== ($obj1 = CcAccessPeer::getInstanceFromPool($key1))) { + // We no longer rehydrate the object, since this can cause data loss. + // See http://www.propelorm.org/ticket/509 + // $obj1->hydrate($row, 0, true); // rehydrate + } else { + + $cls = CcAccessPeer::getOMClass(false); + + $obj1 = new $cls(); + $obj1->hydrate($row); + CcAccessPeer::addInstanceToPool($obj1, $key1); + } // if $obj1 already loaded + + $key2 = CcSubjsPeer::getPrimaryKeyHashFromRow($row, $startcol); + if ($key2 !== null) { + $obj2 = CcSubjsPeer::getInstanceFromPool($key2); + if (!$obj2) { + + $cls = CcSubjsPeer::getOMClass(false); + + $obj2 = new $cls(); + $obj2->hydrate($row, $startcol); + CcSubjsPeer::addInstanceToPool($obj2, $key2); + } // if obj2 already loaded + + // Add the $obj1 (CcAccess) to $obj2 (CcSubjs) + $obj2->addCcAccess($obj1); + + } // if joined row was not null + + $results[] = $obj1; + } + $stmt->closeCursor(); + return $results; + } + + + /** + * Returns the number of rows matching criteria, joining all related tables + * + * @param Criteria $criteria + * @param boolean $distinct Whether to select only distinct columns; deprecated: use Criteria->setDistinct() instead. + * @param PropelPDO $con + * @param String $join_behavior the type of joins to use, defaults to Criteria::LEFT_JOIN + * @return int Number of matching rows. + */ + public static function doCountJoinAll(Criteria $criteria, $distinct = false, PropelPDO $con = null, $join_behavior = Criteria::LEFT_JOIN) + { + // we're going to modify criteria, so copy it first + $criteria = clone $criteria; + + // We need to set the primary table name, since in the case that there are no WHERE columns + // it will be impossible for the BasePeer::createSelectSql() method to determine which + // tables go into the FROM clause. + $criteria->setPrimaryTableName(CcAccessPeer::TABLE_NAME); + + if ($distinct && !in_array(Criteria::DISTINCT, $criteria->getSelectModifiers())) { + $criteria->setDistinct(); + } + + if (!$criteria->hasSelectClause()) { + CcAccessPeer::addSelectColumns($criteria); + } + + $criteria->clearOrderByColumns(); // ORDER BY won't ever affect the count + + // Set the correct dbName + $criteria->setDbName(self::DATABASE_NAME); + + if ($con === null) { + $con = Propel::getConnection(CcAccessPeer::DATABASE_NAME, Propel::CONNECTION_READ); + } + + $criteria->addJoin(CcAccessPeer::OWNER, CcSubjsPeer::ID, $join_behavior); + + $stmt = BasePeer::doCount($criteria, $con); + + if ($row = $stmt->fetch(PDO::FETCH_NUM)) { + $count = (int) $row[0]; + } else { + $count = 0; // no rows returned; we infer that means 0 matches. + } + $stmt->closeCursor(); + return $count; + } + + /** + * Selects a collection of CcAccess objects pre-filled with all related objects. + * + * @param Criteria $criteria + * @param PropelPDO $con + * @param String $join_behavior the type of joins to use, defaults to Criteria::LEFT_JOIN + * @return array Array of CcAccess objects. + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function doSelectJoinAll(Criteria $criteria, $con = null, $join_behavior = Criteria::LEFT_JOIN) + { + $criteria = clone $criteria; + + // Set the correct dbName if it has not been overridden + if ($criteria->getDbName() == Propel::getDefaultDB()) { + $criteria->setDbName(self::DATABASE_NAME); + } + + CcAccessPeer::addSelectColumns($criteria); + $startcol2 = (CcAccessPeer::NUM_COLUMNS - CcAccessPeer::NUM_LAZY_LOAD_COLUMNS); + + CcSubjsPeer::addSelectColumns($criteria); + $startcol3 = $startcol2 + (CcSubjsPeer::NUM_COLUMNS - CcSubjsPeer::NUM_LAZY_LOAD_COLUMNS); + + $criteria->addJoin(CcAccessPeer::OWNER, CcSubjsPeer::ID, $join_behavior); + + $stmt = BasePeer::doSelect($criteria, $con); + $results = array(); + + while ($row = $stmt->fetch(PDO::FETCH_NUM)) { + $key1 = CcAccessPeer::getPrimaryKeyHashFromRow($row, 0); + if (null !== ($obj1 = CcAccessPeer::getInstanceFromPool($key1))) { + // We no longer rehydrate the object, since this can cause data loss. + // See http://www.propelorm.org/ticket/509 + // $obj1->hydrate($row, 0, true); // rehydrate + } else { + $cls = CcAccessPeer::getOMClass(false); + + $obj1 = new $cls(); + $obj1->hydrate($row); + CcAccessPeer::addInstanceToPool($obj1, $key1); + } // if obj1 already loaded + + // Add objects for joined CcSubjs rows + + $key2 = CcSubjsPeer::getPrimaryKeyHashFromRow($row, $startcol2); + if ($key2 !== null) { + $obj2 = CcSubjsPeer::getInstanceFromPool($key2); + if (!$obj2) { + + $cls = CcSubjsPeer::getOMClass(false); + + $obj2 = new $cls(); + $obj2->hydrate($row, $startcol2); + CcSubjsPeer::addInstanceToPool($obj2, $key2); + } // if obj2 loaded + + // Add the $obj1 (CcAccess) to the collection in $obj2 (CcSubjs) + $obj2->addCcAccess($obj1); + } // if joined row not null + + $results[] = $obj1; + } + $stmt->closeCursor(); + return $results; + } + + /** + * Returns the TableMap related to this peer. + * This method is not needed for general use but a specific application could have a need. + * @return TableMap + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function getTableMap() + { + return Propel::getDatabaseMap(self::DATABASE_NAME)->getTable(self::TABLE_NAME); + } + + /** + * Add a TableMap instance to the database for this peer class. + */ + public static function buildTableMap() + { + $dbMap = Propel::getDatabaseMap(BaseCcAccessPeer::DATABASE_NAME); + if (!$dbMap->hasTable(BaseCcAccessPeer::TABLE_NAME)) + { + $dbMap->addTableObject(new CcAccessTableMap()); + } + } + + /** + * The class that the Peer will make instances of. + * + * If $withPrefix is true, the returned path + * uses a dot-path notation which is tranalted into a path + * relative to a location on the PHP include_path. + * (e.g. path.to.MyClass -> 'path/to/MyClass.php') + * + * @param boolean $withPrefix Whether or not to return the path with the class name + * @return string path.to.ClassName + */ + public static function getOMClass($withPrefix = true) + { + return $withPrefix ? CcAccessPeer::CLASS_DEFAULT : CcAccessPeer::OM_CLASS; + } + + /** + * Method perform an INSERT on the database, given a CcAccess or Criteria object. + * + * @param mixed $values Criteria or CcAccess object containing data that is used to create the INSERT statement. + * @param PropelPDO $con the PropelPDO connection to use + * @return mixed The new primary key. + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function doInsert($values, PropelPDO $con = null) + { + if ($con === null) { + $con = Propel::getConnection(CcAccessPeer::DATABASE_NAME, Propel::CONNECTION_WRITE); + } + + if ($values instanceof Criteria) { + $criteria = clone $values; // rename for clarity + } else { + $criteria = $values->buildCriteria(); // build Criteria from CcAccess object + } + + if ($criteria->containsKey(CcAccessPeer::ID) && $criteria->keyContainsValue(CcAccessPeer::ID) ) { + throw new PropelException('Cannot insert a value for auto-increment primary key ('.CcAccessPeer::ID.')'); + } + + + // Set the correct dbName + $criteria->setDbName(self::DATABASE_NAME); + + try { + // use transaction because $criteria could contain info + // for more than one table (I guess, conceivably) + $con->beginTransaction(); + $pk = BasePeer::doInsert($criteria, $con); + $con->commit(); + } catch(PropelException $e) { + $con->rollBack(); + throw $e; + } + + return $pk; + } + + /** + * Method perform an UPDATE on the database, given a CcAccess or Criteria object. + * + * @param mixed $values Criteria or CcAccess object containing data that is used to create the UPDATE statement. + * @param PropelPDO $con The connection to use (specify PropelPDO connection object to exert more control over transactions). + * @return int The number of affected rows (if supported by underlying database driver). + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function doUpdate($values, PropelPDO $con = null) + { + if ($con === null) { + $con = Propel::getConnection(CcAccessPeer::DATABASE_NAME, Propel::CONNECTION_WRITE); + } + + $selectCriteria = new Criteria(self::DATABASE_NAME); + + if ($values instanceof Criteria) { + $criteria = clone $values; // rename for clarity + + $comparison = $criteria->getComparison(CcAccessPeer::ID); + $value = $criteria->remove(CcAccessPeer::ID); + if ($value) { + $selectCriteria->add(CcAccessPeer::ID, $value, $comparison); + } else { + $selectCriteria->setPrimaryTableName(CcAccessPeer::TABLE_NAME); + } + + } else { // $values is CcAccess object + $criteria = $values->buildCriteria(); // gets full criteria + $selectCriteria = $values->buildPkeyCriteria(); // gets criteria w/ primary key(s) + } + + // set the correct dbName + $criteria->setDbName(self::DATABASE_NAME); + + return BasePeer::doUpdate($selectCriteria, $criteria, $con); + } + + /** + * Method to DELETE all rows from the cc_access table. + * + * @return int The number of affected rows (if supported by underlying database driver). + */ + public static function doDeleteAll($con = null) + { + if ($con === null) { + $con = Propel::getConnection(CcAccessPeer::DATABASE_NAME, Propel::CONNECTION_WRITE); + } + $affectedRows = 0; // initialize var to track total num of affected rows + try { + // use transaction because $criteria could contain info + // for more than one table or we could emulating ON DELETE CASCADE, etc. + $con->beginTransaction(); + $affectedRows += BasePeer::doDeleteAll(CcAccessPeer::TABLE_NAME, $con, CcAccessPeer::DATABASE_NAME); + // Because this db requires some delete cascade/set null emulation, we have to + // clear the cached instance *after* the emulation has happened (since + // instances get re-added by the select statement contained therein). + CcAccessPeer::clearInstancePool(); + CcAccessPeer::clearRelatedInstancePool(); + $con->commit(); + return $affectedRows; + } catch (PropelException $e) { + $con->rollBack(); + throw $e; + } + } + + /** + * Method perform a DELETE on the database, given a CcAccess or Criteria object OR a primary key value. + * + * @param mixed $values Criteria or CcAccess object or primary key or array of primary keys + * which is used to create the DELETE statement + * @param PropelPDO $con the connection to use + * @return int The number of affected rows (if supported by underlying database driver). This includes CASCADE-related rows + * if supported by native driver or if emulated using Propel. + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function doDelete($values, PropelPDO $con = null) + { + if ($con === null) { + $con = Propel::getConnection(CcAccessPeer::DATABASE_NAME, Propel::CONNECTION_WRITE); + } + + if ($values instanceof Criteria) { + // invalidate the cache for all objects of this type, since we have no + // way of knowing (without running a query) what objects should be invalidated + // from the cache based on this Criteria. + CcAccessPeer::clearInstancePool(); + // rename for clarity + $criteria = clone $values; + } elseif ($values instanceof CcAccess) { // it's a model object + // invalidate the cache for this single object + CcAccessPeer::removeInstanceFromPool($values); + // create criteria based on pk values + $criteria = $values->buildPkeyCriteria(); + } else { // it's a primary key, or an array of pks + $criteria = new Criteria(self::DATABASE_NAME); + $criteria->add(CcAccessPeer::ID, (array) $values, Criteria::IN); + // invalidate the cache for this object(s) + foreach ((array) $values as $singleval) { + CcAccessPeer::removeInstanceFromPool($singleval); + } + } + + // Set the correct dbName + $criteria->setDbName(self::DATABASE_NAME); + + $affectedRows = 0; // initialize var to track total num of affected rows + + try { + // use transaction because $criteria could contain info + // for more than one table or we could emulating ON DELETE CASCADE, etc. + $con->beginTransaction(); + + $affectedRows += BasePeer::doDelete($criteria, $con); + CcAccessPeer::clearRelatedInstancePool(); + $con->commit(); + return $affectedRows; + } catch (PropelException $e) { + $con->rollBack(); + throw $e; + } + } + + /** + * Validates all modified columns of given CcAccess object. + * If parameter $columns is either a single column name or an array of column names + * than only those columns are validated. + * + * NOTICE: This does not apply to primary or foreign keys for now. + * + * @param CcAccess $obj The object to validate. + * @param mixed $cols Column name or array of column names. + * + * @return mixed TRUE if all columns are valid or the error message of the first invalid column. + */ + public static function doValidate(CcAccess $obj, $cols = null) + { + $columns = array(); + + if ($cols) { + $dbMap = Propel::getDatabaseMap(CcAccessPeer::DATABASE_NAME); + $tableMap = $dbMap->getTable(CcAccessPeer::TABLE_NAME); + + if (! is_array($cols)) { + $cols = array($cols); + } + + foreach ($cols as $colName) { + if ($tableMap->containsColumn($colName)) { + $get = 'get' . $tableMap->getColumn($colName)->getPhpName(); + $columns[$colName] = $obj->$get(); + } + } + } else { + + } + + return BasePeer::doValidate(CcAccessPeer::DATABASE_NAME, CcAccessPeer::TABLE_NAME, $columns); + } + + /** + * Retrieve a single object by pkey. + * + * @param int $pk the primary key. + * @param PropelPDO $con the connection to use + * @return CcAccess + */ + public static function retrieveByPK($pk, PropelPDO $con = null) + { + + if (null !== ($obj = CcAccessPeer::getInstanceFromPool((string) $pk))) { + return $obj; + } + + if ($con === null) { + $con = Propel::getConnection(CcAccessPeer::DATABASE_NAME, Propel::CONNECTION_READ); + } + + $criteria = new Criteria(CcAccessPeer::DATABASE_NAME); + $criteria->add(CcAccessPeer::ID, $pk); + + $v = CcAccessPeer::doSelect($criteria, $con); + + return !empty($v) > 0 ? $v[0] : null; + } + + /** + * Retrieve multiple objects by pkey. + * + * @param array $pks List of primary keys + * @param PropelPDO $con the connection to use + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function retrieveByPKs($pks, PropelPDO $con = null) + { + if ($con === null) { + $con = Propel::getConnection(CcAccessPeer::DATABASE_NAME, Propel::CONNECTION_READ); + } + + $objs = null; + if (empty($pks)) { + $objs = array(); + } else { + $criteria = new Criteria(CcAccessPeer::DATABASE_NAME); + $criteria->add(CcAccessPeer::ID, $pks, Criteria::IN); + $objs = CcAccessPeer::doSelect($criteria, $con); + } + return $objs; + } + +} // BaseCcAccessPeer + +// This is the static code needed to register the TableMap for this table with the main Propel class. +// +BaseCcAccessPeer::buildTableMap(); + diff --git a/application/models/campcaster/om/BaseCcAccessQuery.php b/application/models/campcaster/om/BaseCcAccessQuery.php new file mode 100644 index 000000000..d1e6b14c9 --- /dev/null +++ b/application/models/campcaster/om/BaseCcAccessQuery.php @@ -0,0 +1,477 @@ +setModelAlias($modelAlias); + } + if ($criteria instanceof Criteria) { + $query->mergeWith($criteria); + } + return $query; + } + + /** + * Find object by primary key + * Use instance pooling to avoid a database query if the object exists + * + * $obj = $c->findPk(12, $con); + * + * @param mixed $key Primary key to use for the query + * @param PropelPDO $con an optional connection object + * + * @return CcAccess|array|mixed the result, formatted by the current formatter + */ + public function findPk($key, $con = null) + { + if ((null !== ($obj = CcAccessPeer::getInstanceFromPool((string) $key))) && $this->getFormatter()->isObjectFormatter()) { + // the object is alredy in the instance pool + return $obj; + } else { + // the object has not been requested yet, or the formatter is not an object formatter + $criteria = $this->isKeepQuery() ? clone $this : $this; + $stmt = $criteria + ->filterByPrimaryKey($key) + ->getSelectStatement($con); + return $criteria->getFormatter()->init($criteria)->formatOne($stmt); + } + } + + /** + * Find objects by primary key + * + * $objs = $c->findPks(array(12, 56, 832), $con); + * + * @param array $keys Primary keys to use for the query + * @param PropelPDO $con an optional connection object + * + * @return PropelObjectCollection|array|mixed the list of results, formatted by the current formatter + */ + public function findPks($keys, $con = null) + { + $criteria = $this->isKeepQuery() ? clone $this : $this; + return $this + ->filterByPrimaryKeys($keys) + ->find($con); + } + + /** + * Filter the query by primary key + * + * @param mixed $key Primary key to use for the query + * + * @return CcAccessQuery The current query, for fluid interface + */ + public function filterByPrimaryKey($key) + { + return $this->addUsingAlias(CcAccessPeer::ID, $key, Criteria::EQUAL); + } + + /** + * Filter the query by a list of primary keys + * + * @param array $keys The list of primary key to use for the query + * + * @return CcAccessQuery The current query, for fluid interface + */ + public function filterByPrimaryKeys($keys) + { + return $this->addUsingAlias(CcAccessPeer::ID, $keys, Criteria::IN); + } + + /** + * Filter the query on the id column + * + * @param int|array $id The value to use as filter. + * Accepts an associative array('min' => $minValue, 'max' => $maxValue) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcAccessQuery The current query, for fluid interface + */ + public function filterById($id = null, $comparison = null) + { + if (is_array($id) && null === $comparison) { + $comparison = Criteria::IN; + } + return $this->addUsingAlias(CcAccessPeer::ID, $id, $comparison); + } + + /** + * Filter the query on the gunid column + * + * @param string $gunid The value to use as filter. + * Accepts wildcards (* and % trigger a LIKE) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcAccessQuery The current query, for fluid interface + */ + public function filterByGunid($gunid = null, $comparison = null) + { + if (null === $comparison) { + if (is_array($gunid)) { + $comparison = Criteria::IN; + } elseif (preg_match('/[\%\*]/', $gunid)) { + $gunid = str_replace('*', '%', $gunid); + $comparison = Criteria::LIKE; + } + } + return $this->addUsingAlias(CcAccessPeer::GUNID, $gunid, $comparison); + } + + /** + * Filter the query on the token column + * + * @param string|array $token The value to use as filter. + * Accepts an associative array('min' => $minValue, 'max' => $maxValue) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcAccessQuery The current query, for fluid interface + */ + public function filterByToken($token = null, $comparison = null) + { + if (is_array($token)) { + $useMinMax = false; + if (isset($token['min'])) { + $this->addUsingAlias(CcAccessPeer::TOKEN, $token['min'], Criteria::GREATER_EQUAL); + $useMinMax = true; + } + if (isset($token['max'])) { + $this->addUsingAlias(CcAccessPeer::TOKEN, $token['max'], Criteria::LESS_EQUAL); + $useMinMax = true; + } + if ($useMinMax) { + return $this; + } + if (null === $comparison) { + $comparison = Criteria::IN; + } + } + return $this->addUsingAlias(CcAccessPeer::TOKEN, $token, $comparison); + } + + /** + * Filter the query on the chsum column + * + * @param string $chsum The value to use as filter. + * Accepts wildcards (* and % trigger a LIKE) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcAccessQuery The current query, for fluid interface + */ + public function filterByChsum($chsum = null, $comparison = null) + { + if (null === $comparison) { + if (is_array($chsum)) { + $comparison = Criteria::IN; + } elseif (preg_match('/[\%\*]/', $chsum)) { + $chsum = str_replace('*', '%', $chsum); + $comparison = Criteria::LIKE; + } + } + return $this->addUsingAlias(CcAccessPeer::CHSUM, $chsum, $comparison); + } + + /** + * Filter the query on the ext column + * + * @param string $ext The value to use as filter. + * Accepts wildcards (* and % trigger a LIKE) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcAccessQuery The current query, for fluid interface + */ + public function filterByExt($ext = null, $comparison = null) + { + if (null === $comparison) { + if (is_array($ext)) { + $comparison = Criteria::IN; + } elseif (preg_match('/[\%\*]/', $ext)) { + $ext = str_replace('*', '%', $ext); + $comparison = Criteria::LIKE; + } + } + return $this->addUsingAlias(CcAccessPeer::EXT, $ext, $comparison); + } + + /** + * Filter the query on the type column + * + * @param string $type The value to use as filter. + * Accepts wildcards (* and % trigger a LIKE) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcAccessQuery The current query, for fluid interface + */ + public function filterByType($type = null, $comparison = null) + { + if (null === $comparison) { + if (is_array($type)) { + $comparison = Criteria::IN; + } elseif (preg_match('/[\%\*]/', $type)) { + $type = str_replace('*', '%', $type); + $comparison = Criteria::LIKE; + } + } + return $this->addUsingAlias(CcAccessPeer::TYPE, $type, $comparison); + } + + /** + * Filter the query on the parent column + * + * @param string|array $parent The value to use as filter. + * Accepts an associative array('min' => $minValue, 'max' => $maxValue) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcAccessQuery The current query, for fluid interface + */ + public function filterByParent($parent = null, $comparison = null) + { + if (is_array($parent)) { + $useMinMax = false; + if (isset($parent['min'])) { + $this->addUsingAlias(CcAccessPeer::PARENT, $parent['min'], Criteria::GREATER_EQUAL); + $useMinMax = true; + } + if (isset($parent['max'])) { + $this->addUsingAlias(CcAccessPeer::PARENT, $parent['max'], Criteria::LESS_EQUAL); + $useMinMax = true; + } + if ($useMinMax) { + return $this; + } + if (null === $comparison) { + $comparison = Criteria::IN; + } + } + return $this->addUsingAlias(CcAccessPeer::PARENT, $parent, $comparison); + } + + /** + * Filter the query on the owner column + * + * @param int|array $owner The value to use as filter. + * Accepts an associative array('min' => $minValue, 'max' => $maxValue) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcAccessQuery The current query, for fluid interface + */ + public function filterByOwner($owner = null, $comparison = null) + { + if (is_array($owner)) { + $useMinMax = false; + if (isset($owner['min'])) { + $this->addUsingAlias(CcAccessPeer::OWNER, $owner['min'], Criteria::GREATER_EQUAL); + $useMinMax = true; + } + if (isset($owner['max'])) { + $this->addUsingAlias(CcAccessPeer::OWNER, $owner['max'], Criteria::LESS_EQUAL); + $useMinMax = true; + } + if ($useMinMax) { + return $this; + } + if (null === $comparison) { + $comparison = Criteria::IN; + } + } + return $this->addUsingAlias(CcAccessPeer::OWNER, $owner, $comparison); + } + + /** + * Filter the query on the ts column + * + * @param string|array $ts The value to use as filter. + * Accepts an associative array('min' => $minValue, 'max' => $maxValue) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcAccessQuery The current query, for fluid interface + */ + public function filterByTs($ts = null, $comparison = null) + { + if (is_array($ts)) { + $useMinMax = false; + if (isset($ts['min'])) { + $this->addUsingAlias(CcAccessPeer::TS, $ts['min'], Criteria::GREATER_EQUAL); + $useMinMax = true; + } + if (isset($ts['max'])) { + $this->addUsingAlias(CcAccessPeer::TS, $ts['max'], Criteria::LESS_EQUAL); + $useMinMax = true; + } + if ($useMinMax) { + return $this; + } + if (null === $comparison) { + $comparison = Criteria::IN; + } + } + return $this->addUsingAlias(CcAccessPeer::TS, $ts, $comparison); + } + + /** + * Filter the query by a related CcSubjs object + * + * @param CcSubjs $ccSubjs the related object to use as filter + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcAccessQuery The current query, for fluid interface + */ + public function filterByCcSubjs($ccSubjs, $comparison = null) + { + return $this + ->addUsingAlias(CcAccessPeer::OWNER, $ccSubjs->getId(), $comparison); + } + + /** + * Adds a JOIN clause to the query using the CcSubjs relation + * + * @param string $relationAlias optional alias for the relation + * @param string $joinType Accepted values are null, 'left join', 'right join', 'inner join' + * + * @return CcAccessQuery The current query, for fluid interface + */ + public function joinCcSubjs($relationAlias = '', $joinType = Criteria::LEFT_JOIN) + { + $tableMap = $this->getTableMap(); + $relationMap = $tableMap->getRelation('CcSubjs'); + + // create a ModelJoin object for this join + $join = new ModelJoin(); + $join->setJoinType($joinType); + $join->setRelationMap($relationMap, $this->useAliasInSQL ? $this->getModelAlias() : null, $relationAlias); + if ($previousJoin = $this->getPreviousJoin()) { + $join->setPreviousJoin($previousJoin); + } + + // add the ModelJoin to the current object + if($relationAlias) { + $this->addAlias($relationAlias, $relationMap->getRightTable()->getName()); + $this->addJoinObject($join, $relationAlias); + } else { + $this->addJoinObject($join, 'CcSubjs'); + } + + return $this; + } + + /** + * Use the CcSubjs relation CcSubjs object + * + * @see useQuery() + * + * @param string $relationAlias optional alias for the relation, + * to be used as main alias in the secondary query + * @param string $joinType Accepted values are null, 'left join', 'right join', 'inner join' + * + * @return CcSubjsQuery A secondary query class using the current class as primary query + */ + public function useCcSubjsQuery($relationAlias = '', $joinType = Criteria::LEFT_JOIN) + { + return $this + ->joinCcSubjs($relationAlias, $joinType) + ->useQuery($relationAlias ? $relationAlias : 'CcSubjs', 'CcSubjsQuery'); + } + + /** + * Exclude object from result + * + * @param CcAccess $ccAccess Object to remove from the list of results + * + * @return CcAccessQuery The current query, for fluid interface + */ + public function prune($ccAccess = null) + { + if ($ccAccess) { + $this->addUsingAlias(CcAccessPeer::ID, $ccAccess->getId(), Criteria::NOT_EQUAL); + } + + return $this; + } + +} // BaseCcAccessQuery diff --git a/application/models/campcaster/om/BaseCcBackup.php b/application/models/campcaster/om/BaseCcBackup.php new file mode 100644 index 000000000..37d8fb83b --- /dev/null +++ b/application/models/campcaster/om/BaseCcBackup.php @@ -0,0 +1,956 @@ +token; + } + + /** + * Get the [sessionid] column value. + * + * @return string + */ + public function getSessionid() + { + return $this->sessionid; + } + + /** + * Get the [status] column value. + * + * @return string + */ + public function getStatus() + { + return $this->status; + } + + /** + * Get the [optionally formatted] temporal [fromtime] column value. + * + * + * @param string $format The date/time format string (either date()-style or strftime()-style). + * If format is NULL, then the raw DateTime object will be returned. + * @return mixed Formatted date/time value as string or DateTime object (if format is NULL), NULL if column is NULL + * @throws PropelException - if unable to parse/validate the date/time value. + */ + public function getFromtime($format = 'Y-m-d H:i:s') + { + if ($this->fromtime === null) { + return null; + } + + + + try { + $dt = new DateTime($this->fromtime); + } catch (Exception $x) { + throw new PropelException("Internally stored date/time/timestamp value could not be converted to DateTime: " . var_export($this->fromtime, true), $x); + } + + if ($format === null) { + // Because propel.useDateTimeClass is TRUE, we return a DateTime object. + return $dt; + } elseif (strpos($format, '%') !== false) { + return strftime($format, $dt->format('U')); + } else { + return $dt->format($format); + } + } + + /** + * Get the [optionally formatted] temporal [totime] column value. + * + * + * @param string $format The date/time format string (either date()-style or strftime()-style). + * If format is NULL, then the raw DateTime object will be returned. + * @return mixed Formatted date/time value as string or DateTime object (if format is NULL), NULL if column is NULL + * @throws PropelException - if unable to parse/validate the date/time value. + */ + public function getTotime($format = 'Y-m-d H:i:s') + { + if ($this->totime === null) { + return null; + } + + + + try { + $dt = new DateTime($this->totime); + } catch (Exception $x) { + throw new PropelException("Internally stored date/time/timestamp value could not be converted to DateTime: " . var_export($this->totime, true), $x); + } + + if ($format === null) { + // Because propel.useDateTimeClass is TRUE, we return a DateTime object. + return $dt; + } elseif (strpos($format, '%') !== false) { + return strftime($format, $dt->format('U')); + } else { + return $dt->format($format); + } + } + + /** + * Set the value of [token] column. + * + * @param string $v new value + * @return CcBackup The current object (for fluent API support) + */ + public function setToken($v) + { + if ($v !== null) { + $v = (string) $v; + } + + if ($this->token !== $v) { + $this->token = $v; + $this->modifiedColumns[] = CcBackupPeer::TOKEN; + } + + return $this; + } // setToken() + + /** + * Set the value of [sessionid] column. + * + * @param string $v new value + * @return CcBackup The current object (for fluent API support) + */ + public function setSessionid($v) + { + if ($v !== null) { + $v = (string) $v; + } + + if ($this->sessionid !== $v) { + $this->sessionid = $v; + $this->modifiedColumns[] = CcBackupPeer::SESSIONID; + } + + return $this; + } // setSessionid() + + /** + * Set the value of [status] column. + * + * @param string $v new value + * @return CcBackup The current object (for fluent API support) + */ + public function setStatus($v) + { + if ($v !== null) { + $v = (string) $v; + } + + if ($this->status !== $v) { + $this->status = $v; + $this->modifiedColumns[] = CcBackupPeer::STATUS; + } + + return $this; + } // setStatus() + + /** + * Sets the value of [fromtime] column to a normalized version of the date/time value specified. + * + * @param mixed $v string, integer (timestamp), or DateTime value. Empty string will + * be treated as NULL for temporal objects. + * @return CcBackup The current object (for fluent API support) + */ + public function setFromtime($v) + { + // we treat '' as NULL for temporal objects because DateTime('') == DateTime('now') + // -- which is unexpected, to say the least. + if ($v === null || $v === '') { + $dt = null; + } elseif ($v instanceof DateTime) { + $dt = $v; + } else { + // some string/numeric value passed; we normalize that so that we can + // validate it. + try { + if (is_numeric($v)) { // if it's a unix timestamp + $dt = new DateTime('@'.$v, new DateTimeZone('UTC')); + // We have to explicitly specify and then change the time zone because of a + // DateTime bug: http://bugs.php.net/bug.php?id=43003 + $dt->setTimeZone(new DateTimeZone(date_default_timezone_get())); + } else { + $dt = new DateTime($v); + } + } catch (Exception $x) { + throw new PropelException('Error parsing date/time value: ' . var_export($v, true), $x); + } + } + + if ( $this->fromtime !== null || $dt !== null ) { + // (nested ifs are a little easier to read in this case) + + $currNorm = ($this->fromtime !== null && $tmpDt = new DateTime($this->fromtime)) ? $tmpDt->format('Y-m-d\\TH:i:sO') : null; + $newNorm = ($dt !== null) ? $dt->format('Y-m-d\\TH:i:sO') : null; + + if ( ($currNorm !== $newNorm) // normalized values don't match + ) + { + $this->fromtime = ($dt ? $dt->format('Y-m-d\\TH:i:sO') : null); + $this->modifiedColumns[] = CcBackupPeer::FROMTIME; + } + } // if either are not null + + return $this; + } // setFromtime() + + /** + * Sets the value of [totime] column to a normalized version of the date/time value specified. + * + * @param mixed $v string, integer (timestamp), or DateTime value. Empty string will + * be treated as NULL for temporal objects. + * @return CcBackup The current object (for fluent API support) + */ + public function setTotime($v) + { + // we treat '' as NULL for temporal objects because DateTime('') == DateTime('now') + // -- which is unexpected, to say the least. + if ($v === null || $v === '') { + $dt = null; + } elseif ($v instanceof DateTime) { + $dt = $v; + } else { + // some string/numeric value passed; we normalize that so that we can + // validate it. + try { + if (is_numeric($v)) { // if it's a unix timestamp + $dt = new DateTime('@'.$v, new DateTimeZone('UTC')); + // We have to explicitly specify and then change the time zone because of a + // DateTime bug: http://bugs.php.net/bug.php?id=43003 + $dt->setTimeZone(new DateTimeZone(date_default_timezone_get())); + } else { + $dt = new DateTime($v); + } + } catch (Exception $x) { + throw new PropelException('Error parsing date/time value: ' . var_export($v, true), $x); + } + } + + if ( $this->totime !== null || $dt !== null ) { + // (nested ifs are a little easier to read in this case) + + $currNorm = ($this->totime !== null && $tmpDt = new DateTime($this->totime)) ? $tmpDt->format('Y-m-d\\TH:i:sO') : null; + $newNorm = ($dt !== null) ? $dt->format('Y-m-d\\TH:i:sO') : null; + + if ( ($currNorm !== $newNorm) // normalized values don't match + ) + { + $this->totime = ($dt ? $dt->format('Y-m-d\\TH:i:sO') : null); + $this->modifiedColumns[] = CcBackupPeer::TOTIME; + } + } // if either are not null + + return $this; + } // setTotime() + + /** + * Indicates whether the columns in this object are only set to default values. + * + * This method can be used in conjunction with isModified() to indicate whether an object is both + * modified _and_ has some values set which are non-default. + * + * @return boolean Whether the columns in this object are only been set with default values. + */ + public function hasOnlyDefaultValues() + { + // otherwise, everything was equal, so return TRUE + return true; + } // hasOnlyDefaultValues() + + /** + * Hydrates (populates) the object variables with values from the database resultset. + * + * An offset (0-based "start column") is specified so that objects can be hydrated + * with a subset of the columns in the resultset rows. This is needed, for example, + * for results of JOIN queries where the resultset row includes columns from two or + * more tables. + * + * @param array $row The row returned by PDOStatement->fetch(PDO::FETCH_NUM) + * @param int $startcol 0-based offset column which indicates which restultset column to start with. + * @param boolean $rehydrate Whether this object is being re-hydrated from the database. + * @return int next starting column + * @throws PropelException - Any caught Exception will be rewrapped as a PropelException. + */ + public function hydrate($row, $startcol = 0, $rehydrate = false) + { + try { + + $this->token = ($row[$startcol + 0] !== null) ? (string) $row[$startcol + 0] : null; + $this->sessionid = ($row[$startcol + 1] !== null) ? (string) $row[$startcol + 1] : null; + $this->status = ($row[$startcol + 2] !== null) ? (string) $row[$startcol + 2] : null; + $this->fromtime = ($row[$startcol + 3] !== null) ? (string) $row[$startcol + 3] : null; + $this->totime = ($row[$startcol + 4] !== null) ? (string) $row[$startcol + 4] : null; + $this->resetModified(); + + $this->setNew(false); + + if ($rehydrate) { + $this->ensureConsistency(); + } + + return $startcol + 5; // 5 = CcBackupPeer::NUM_COLUMNS - CcBackupPeer::NUM_LAZY_LOAD_COLUMNS). + + } catch (Exception $e) { + throw new PropelException("Error populating CcBackup object", $e); + } + } + + /** + * Checks and repairs the internal consistency of the object. + * + * This method is executed after an already-instantiated object is re-hydrated + * from the database. It exists to check any foreign keys to make sure that + * the objects related to the current object are correct based on foreign key. + * + * You can override this method in the stub class, but you should always invoke + * the base method from the overridden method (i.e. parent::ensureConsistency()), + * in case your model changes. + * + * @throws PropelException + */ + public function ensureConsistency() + { + + } // ensureConsistency + + /** + * Reloads this object from datastore based on primary key and (optionally) resets all associated objects. + * + * This will only work if the object has been saved and has a valid primary key set. + * + * @param boolean $deep (optional) Whether to also de-associated any related objects. + * @param PropelPDO $con (optional) The PropelPDO connection to use. + * @return void + * @throws PropelException - if this object is deleted, unsaved or doesn't have pk match in db + */ + public function reload($deep = false, PropelPDO $con = null) + { + if ($this->isDeleted()) { + throw new PropelException("Cannot reload a deleted object."); + } + + if ($this->isNew()) { + throw new PropelException("Cannot reload an unsaved object."); + } + + if ($con === null) { + $con = Propel::getConnection(CcBackupPeer::DATABASE_NAME, Propel::CONNECTION_READ); + } + + // We don't need to alter the object instance pool; we're just modifying this instance + // already in the pool. + + $stmt = CcBackupPeer::doSelectStmt($this->buildPkeyCriteria(), $con); + $row = $stmt->fetch(PDO::FETCH_NUM); + $stmt->closeCursor(); + if (!$row) { + throw new PropelException('Cannot find matching row in the database to reload object values.'); + } + $this->hydrate($row, 0, true); // rehydrate + + if ($deep) { // also de-associate any related objects? + + } // if (deep) + } + + /** + * Removes this object from datastore and sets delete attribute. + * + * @param PropelPDO $con + * @return void + * @throws PropelException + * @see BaseObject::setDeleted() + * @see BaseObject::isDeleted() + */ + public function delete(PropelPDO $con = null) + { + if ($this->isDeleted()) { + throw new PropelException("This object has already been deleted."); + } + + if ($con === null) { + $con = Propel::getConnection(CcBackupPeer::DATABASE_NAME, Propel::CONNECTION_WRITE); + } + + $con->beginTransaction(); + try { + $ret = $this->preDelete($con); + if ($ret) { + CcBackupQuery::create() + ->filterByPrimaryKey($this->getPrimaryKey()) + ->delete($con); + $this->postDelete($con); + $con->commit(); + $this->setDeleted(true); + } else { + $con->commit(); + } + } catch (PropelException $e) { + $con->rollBack(); + throw $e; + } + } + + /** + * Persists this object to the database. + * + * If the object is new, it inserts it; otherwise an update is performed. + * All modified related objects will also be persisted in the doSave() + * method. This method wraps all precipitate database operations in a + * single transaction. + * + * @param PropelPDO $con + * @return int The number of rows affected by this insert/update and any referring fk objects' save() operations. + * @throws PropelException + * @see doSave() + */ + public function save(PropelPDO $con = null) + { + if ($this->isDeleted()) { + throw new PropelException("You cannot save an object that has been deleted."); + } + + if ($con === null) { + $con = Propel::getConnection(CcBackupPeer::DATABASE_NAME, Propel::CONNECTION_WRITE); + } + + $con->beginTransaction(); + $isInsert = $this->isNew(); + try { + $ret = $this->preSave($con); + if ($isInsert) { + $ret = $ret && $this->preInsert($con); + } else { + $ret = $ret && $this->preUpdate($con); + } + if ($ret) { + $affectedRows = $this->doSave($con); + if ($isInsert) { + $this->postInsert($con); + } else { + $this->postUpdate($con); + } + $this->postSave($con); + CcBackupPeer::addInstanceToPool($this); + } else { + $affectedRows = 0; + } + $con->commit(); + return $affectedRows; + } catch (PropelException $e) { + $con->rollBack(); + throw $e; + } + } + + /** + * Performs the work of inserting or updating the row in the database. + * + * If the object is new, it inserts it; otherwise an update is performed. + * All related objects are also updated in this method. + * + * @param PropelPDO $con + * @return int The number of rows affected by this insert/update and any referring fk objects' save() operations. + * @throws PropelException + * @see save() + */ + protected function doSave(PropelPDO $con) + { + $affectedRows = 0; // initialize var to track total num of affected rows + if (!$this->alreadyInSave) { + $this->alreadyInSave = true; + + + // If this object has been modified, then save it to the database. + if ($this->isModified()) { + if ($this->isNew()) { + $criteria = $this->buildCriteria(); + $pk = BasePeer::doInsert($criteria, $con); + $affectedRows = 1; + $this->setNew(false); + } else { + $affectedRows = CcBackupPeer::doUpdate($this, $con); + } + + $this->resetModified(); // [HL] After being saved an object is no longer 'modified' + } + + $this->alreadyInSave = false; + + } + return $affectedRows; + } // doSave() + + /** + * Array of ValidationFailed objects. + * @var array ValidationFailed[] + */ + protected $validationFailures = array(); + + /** + * Gets any ValidationFailed objects that resulted from last call to validate(). + * + * + * @return array ValidationFailed[] + * @see validate() + */ + public function getValidationFailures() + { + return $this->validationFailures; + } + + /** + * Validates the objects modified field values and all objects related to this table. + * + * If $columns is either a column name or an array of column names + * only those columns are validated. + * + * @param mixed $columns Column name or an array of column names. + * @return boolean Whether all columns pass validation. + * @see doValidate() + * @see getValidationFailures() + */ + public function validate($columns = null) + { + $res = $this->doValidate($columns); + if ($res === true) { + $this->validationFailures = array(); + return true; + } else { + $this->validationFailures = $res; + return false; + } + } + + /** + * This function performs the validation work for complex object models. + * + * In addition to checking the current object, all related objects will + * also be validated. If all pass then true is returned; otherwise + * an aggreagated array of ValidationFailed objects will be returned. + * + * @param array $columns Array of column names to validate. + * @return mixed true if all validations pass; array of ValidationFailed objets otherwise. + */ + protected function doValidate($columns = null) + { + if (!$this->alreadyInValidation) { + $this->alreadyInValidation = true; + $retval = null; + + $failureMap = array(); + + + if (($retval = CcBackupPeer::doValidate($this, $columns)) !== true) { + $failureMap = array_merge($failureMap, $retval); + } + + + + $this->alreadyInValidation = false; + } + + return (!empty($failureMap) ? $failureMap : true); + } + + /** + * Retrieves a field from the object by name passed in as a string. + * + * @param string $name name + * @param string $type The type of fieldname the $name is of: + * one of the class type constants BasePeer::TYPE_PHPNAME, BasePeer::TYPE_STUDLYPHPNAME + * BasePeer::TYPE_COLNAME, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_NUM + * @return mixed Value of field. + */ + public function getByName($name, $type = BasePeer::TYPE_PHPNAME) + { + $pos = CcBackupPeer::translateFieldName($name, $type, BasePeer::TYPE_NUM); + $field = $this->getByPosition($pos); + return $field; + } + + /** + * Retrieves a field from the object by Position as specified in the xml schema. + * Zero-based. + * + * @param int $pos position in xml schema + * @return mixed Value of field at $pos + */ + public function getByPosition($pos) + { + switch($pos) { + case 0: + return $this->getToken(); + break; + case 1: + return $this->getSessionid(); + break; + case 2: + return $this->getStatus(); + break; + case 3: + return $this->getFromtime(); + break; + case 4: + return $this->getTotime(); + break; + default: + return null; + break; + } // switch() + } + + /** + * Exports the object as an array. + * + * You can specify the key type of the array by passing one of the class + * type constants. + * + * @param string $keyType (optional) One of the class type constants BasePeer::TYPE_PHPNAME, BasePeer::TYPE_STUDLYPHPNAME, + * BasePeer::TYPE_COLNAME, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_NUM. + * Defaults to BasePeer::TYPE_PHPNAME. + * @param boolean $includeLazyLoadColumns (optional) Whether to include lazy loaded columns. Defaults to TRUE. + * + * @return array an associative array containing the field names (as keys) and field values + */ + public function toArray($keyType = BasePeer::TYPE_PHPNAME, $includeLazyLoadColumns = true) + { + $keys = CcBackupPeer::getFieldNames($keyType); + $result = array( + $keys[0] => $this->getToken(), + $keys[1] => $this->getSessionid(), + $keys[2] => $this->getStatus(), + $keys[3] => $this->getFromtime(), + $keys[4] => $this->getTotime(), + ); + return $result; + } + + /** + * Sets a field from the object by name passed in as a string. + * + * @param string $name peer name + * @param mixed $value field value + * @param string $type The type of fieldname the $name is of: + * one of the class type constants BasePeer::TYPE_PHPNAME, BasePeer::TYPE_STUDLYPHPNAME + * BasePeer::TYPE_COLNAME, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_NUM + * @return void + */ + public function setByName($name, $value, $type = BasePeer::TYPE_PHPNAME) + { + $pos = CcBackupPeer::translateFieldName($name, $type, BasePeer::TYPE_NUM); + return $this->setByPosition($pos, $value); + } + + /** + * Sets a field from the object by Position as specified in the xml schema. + * Zero-based. + * + * @param int $pos position in xml schema + * @param mixed $value field value + * @return void + */ + public function setByPosition($pos, $value) + { + switch($pos) { + case 0: + $this->setToken($value); + break; + case 1: + $this->setSessionid($value); + break; + case 2: + $this->setStatus($value); + break; + case 3: + $this->setFromtime($value); + break; + case 4: + $this->setTotime($value); + break; + } // switch() + } + + /** + * Populates the object using an array. + * + * This is particularly useful when populating an object from one of the + * request arrays (e.g. $_POST). This method goes through the column + * names, checking to see whether a matching key exists in populated + * array. If so the setByName() method is called for that column. + * + * You can specify the key type of the array by additionally passing one + * of the class type constants BasePeer::TYPE_PHPNAME, BasePeer::TYPE_STUDLYPHPNAME, + * BasePeer::TYPE_COLNAME, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_NUM. + * The default key type is the column's phpname (e.g. 'AuthorId') + * + * @param array $arr An array to populate the object from. + * @param string $keyType The type of keys the array uses. + * @return void + */ + public function fromArray($arr, $keyType = BasePeer::TYPE_PHPNAME) + { + $keys = CcBackupPeer::getFieldNames($keyType); + + if (array_key_exists($keys[0], $arr)) $this->setToken($arr[$keys[0]]); + if (array_key_exists($keys[1], $arr)) $this->setSessionid($arr[$keys[1]]); + if (array_key_exists($keys[2], $arr)) $this->setStatus($arr[$keys[2]]); + if (array_key_exists($keys[3], $arr)) $this->setFromtime($arr[$keys[3]]); + if (array_key_exists($keys[4], $arr)) $this->setTotime($arr[$keys[4]]); + } + + /** + * Build a Criteria object containing the values of all modified columns in this object. + * + * @return Criteria The Criteria object containing all modified values. + */ + public function buildCriteria() + { + $criteria = new Criteria(CcBackupPeer::DATABASE_NAME); + + if ($this->isColumnModified(CcBackupPeer::TOKEN)) $criteria->add(CcBackupPeer::TOKEN, $this->token); + if ($this->isColumnModified(CcBackupPeer::SESSIONID)) $criteria->add(CcBackupPeer::SESSIONID, $this->sessionid); + if ($this->isColumnModified(CcBackupPeer::STATUS)) $criteria->add(CcBackupPeer::STATUS, $this->status); + if ($this->isColumnModified(CcBackupPeer::FROMTIME)) $criteria->add(CcBackupPeer::FROMTIME, $this->fromtime); + if ($this->isColumnModified(CcBackupPeer::TOTIME)) $criteria->add(CcBackupPeer::TOTIME, $this->totime); + + return $criteria; + } + + /** + * Builds a Criteria object containing the primary key for this object. + * + * Unlike buildCriteria() this method includes the primary key values regardless + * of whether or not they have been modified. + * + * @return Criteria The Criteria object containing value(s) for primary key(s). + */ + public function buildPkeyCriteria() + { + $criteria = new Criteria(CcBackupPeer::DATABASE_NAME); + $criteria->add(CcBackupPeer::TOKEN, $this->token); + + return $criteria; + } + + /** + * Returns the primary key for this object (row). + * @return string + */ + public function getPrimaryKey() + { + return $this->getToken(); + } + + /** + * Generic method to set the primary key (token column). + * + * @param string $key Primary key. + * @return void + */ + public function setPrimaryKey($key) + { + $this->setToken($key); + } + + /** + * Returns true if the primary key for this object is null. + * @return boolean + */ + public function isPrimaryKeyNull() + { + return null === $this->getToken(); + } + + /** + * Sets contents of passed object to values from current object. + * + * If desired, this method can also make copies of all associated (fkey referrers) + * objects. + * + * @param object $copyObj An object of CcBackup (or compatible) type. + * @param boolean $deepCopy Whether to also copy all rows that refer (by fkey) to the current row. + * @throws PropelException + */ + public function copyInto($copyObj, $deepCopy = false) + { + $copyObj->setToken($this->token); + $copyObj->setSessionid($this->sessionid); + $copyObj->setStatus($this->status); + $copyObj->setFromtime($this->fromtime); + $copyObj->setTotime($this->totime); + + $copyObj->setNew(true); + } + + /** + * Makes a copy of this object that will be inserted as a new row in table when saved. + * It creates a new object filling in the simple attributes, but skipping any primary + * keys that are defined for the table. + * + * If desired, this method can also make copies of all associated (fkey referrers) + * objects. + * + * @param boolean $deepCopy Whether to also copy all rows that refer (by fkey) to the current row. + * @return CcBackup Clone of current object. + * @throws PropelException + */ + public function copy($deepCopy = false) + { + // we use get_class(), because this might be a subclass + $clazz = get_class($this); + $copyObj = new $clazz(); + $this->copyInto($copyObj, $deepCopy); + return $copyObj; + } + + /** + * Returns a peer instance associated with this om. + * + * Since Peer classes are not to have any instance attributes, this method returns the + * same instance for all member of this class. The method could therefore + * be static, but this would prevent one from overriding the behavior. + * + * @return CcBackupPeer + */ + public function getPeer() + { + if (self::$peer === null) { + self::$peer = new CcBackupPeer(); + } + return self::$peer; + } + + /** + * Clears the current object and sets all attributes to their default values + */ + public function clear() + { + $this->token = null; + $this->sessionid = null; + $this->status = null; + $this->fromtime = null; + $this->totime = null; + $this->alreadyInSave = false; + $this->alreadyInValidation = false; + $this->clearAllReferences(); + $this->resetModified(); + $this->setNew(true); + $this->setDeleted(false); + } + + /** + * Resets all collections of referencing foreign keys. + * + * This method is a user-space workaround for PHP's inability to garbage collect objects + * with circular references. This is currently necessary when using Propel in certain + * daemon or large-volumne/high-memory operations. + * + * @param boolean $deep Whether to also clear the references on all associated objects. + */ + public function clearAllReferences($deep = false) + { + if ($deep) { + } // if ($deep) + + } + + /** + * Catches calls to virtual methods + */ + public function __call($name, $params) + { + if (preg_match('/get(\w+)/', $name, $matches)) { + $virtualColumn = $matches[1]; + if ($this->hasVirtualColumn($virtualColumn)) { + return $this->getVirtualColumn($virtualColumn); + } + // no lcfirst in php<5.3... + $virtualColumn[0] = strtolower($virtualColumn[0]); + if ($this->hasVirtualColumn($virtualColumn)) { + return $this->getVirtualColumn($virtualColumn); + } + } + throw new PropelException('Call to undefined method: ' . $name); + } + +} // BaseCcBackup diff --git a/application/models/campcaster/om/BaseCcBackupPeer.php b/application/models/campcaster/om/BaseCcBackupPeer.php new file mode 100644 index 000000000..b8c6a3066 --- /dev/null +++ b/application/models/campcaster/om/BaseCcBackupPeer.php @@ -0,0 +1,750 @@ + array ('Token', 'Sessionid', 'Status', 'Fromtime', 'Totime', ), + BasePeer::TYPE_STUDLYPHPNAME => array ('token', 'sessionid', 'status', 'fromtime', 'totime', ), + BasePeer::TYPE_COLNAME => array (self::TOKEN, self::SESSIONID, self::STATUS, self::FROMTIME, self::TOTIME, ), + BasePeer::TYPE_RAW_COLNAME => array ('TOKEN', 'SESSIONID', 'STATUS', 'FROMTIME', 'TOTIME', ), + BasePeer::TYPE_FIELDNAME => array ('token', 'sessionid', 'status', 'fromtime', 'totime', ), + BasePeer::TYPE_NUM => array (0, 1, 2, 3, 4, ) + ); + + /** + * holds an array of keys for quick access to the fieldnames array + * + * first dimension keys are the type constants + * e.g. self::$fieldNames[BasePeer::TYPE_PHPNAME]['Id'] = 0 + */ + private static $fieldKeys = array ( + BasePeer::TYPE_PHPNAME => array ('Token' => 0, 'Sessionid' => 1, 'Status' => 2, 'Fromtime' => 3, 'Totime' => 4, ), + BasePeer::TYPE_STUDLYPHPNAME => array ('token' => 0, 'sessionid' => 1, 'status' => 2, 'fromtime' => 3, 'totime' => 4, ), + BasePeer::TYPE_COLNAME => array (self::TOKEN => 0, self::SESSIONID => 1, self::STATUS => 2, self::FROMTIME => 3, self::TOTIME => 4, ), + BasePeer::TYPE_RAW_COLNAME => array ('TOKEN' => 0, 'SESSIONID' => 1, 'STATUS' => 2, 'FROMTIME' => 3, 'TOTIME' => 4, ), + BasePeer::TYPE_FIELDNAME => array ('token' => 0, 'sessionid' => 1, 'status' => 2, 'fromtime' => 3, 'totime' => 4, ), + BasePeer::TYPE_NUM => array (0, 1, 2, 3, 4, ) + ); + + /** + * Translates a fieldname to another type + * + * @param string $name field name + * @param string $fromType One of the class type constants BasePeer::TYPE_PHPNAME, BasePeer::TYPE_STUDLYPHPNAME + * BasePeer::TYPE_COLNAME, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_NUM + * @param string $toType One of the class type constants + * @return string translated name of the field. + * @throws PropelException - if the specified name could not be found in the fieldname mappings. + */ + static public function translateFieldName($name, $fromType, $toType) + { + $toNames = self::getFieldNames($toType); + $key = isset(self::$fieldKeys[$fromType][$name]) ? self::$fieldKeys[$fromType][$name] : null; + if ($key === null) { + throw new PropelException("'$name' could not be found in the field names of type '$fromType'. These are: " . print_r(self::$fieldKeys[$fromType], true)); + } + return $toNames[$key]; + } + + /** + * Returns an array of field names. + * + * @param string $type The type of fieldnames to return: + * One of the class type constants BasePeer::TYPE_PHPNAME, BasePeer::TYPE_STUDLYPHPNAME + * BasePeer::TYPE_COLNAME, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_NUM + * @return array A list of field names + */ + + static public function getFieldNames($type = BasePeer::TYPE_PHPNAME) + { + if (!array_key_exists($type, self::$fieldNames)) { + throw new PropelException('Method getFieldNames() expects the parameter $type to be one of the class constants BasePeer::TYPE_PHPNAME, BasePeer::TYPE_STUDLYPHPNAME, BasePeer::TYPE_COLNAME, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_NUM. ' . $type . ' was given.'); + } + return self::$fieldNames[$type]; + } + + /** + * Convenience method which changes table.column to alias.column. + * + * Using this method you can maintain SQL abstraction while using column aliases. + * + * $c->addAlias("alias1", TablePeer::TABLE_NAME); + * $c->addJoin(TablePeer::alias("alias1", TablePeer::PRIMARY_KEY_COLUMN), TablePeer::PRIMARY_KEY_COLUMN); + * + * @param string $alias The alias for the current table. + * @param string $column The column name for current table. (i.e. CcBackupPeer::COLUMN_NAME). + * @return string + */ + public static function alias($alias, $column) + { + return str_replace(CcBackupPeer::TABLE_NAME.'.', $alias.'.', $column); + } + + /** + * Add all the columns needed to create a new object. + * + * Note: any columns that were marked with lazyLoad="true" in the + * XML schema will not be added to the select list and only loaded + * on demand. + * + * @param Criteria $criteria object containing the columns to add. + * @param string $alias optional table alias + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function addSelectColumns(Criteria $criteria, $alias = null) + { + if (null === $alias) { + $criteria->addSelectColumn(CcBackupPeer::TOKEN); + $criteria->addSelectColumn(CcBackupPeer::SESSIONID); + $criteria->addSelectColumn(CcBackupPeer::STATUS); + $criteria->addSelectColumn(CcBackupPeer::FROMTIME); + $criteria->addSelectColumn(CcBackupPeer::TOTIME); + } else { + $criteria->addSelectColumn($alias . '.TOKEN'); + $criteria->addSelectColumn($alias . '.SESSIONID'); + $criteria->addSelectColumn($alias . '.STATUS'); + $criteria->addSelectColumn($alias . '.FROMTIME'); + $criteria->addSelectColumn($alias . '.TOTIME'); + } + } + + /** + * Returns the number of rows matching criteria. + * + * @param Criteria $criteria + * @param boolean $distinct Whether to select only distinct columns; deprecated: use Criteria->setDistinct() instead. + * @param PropelPDO $con + * @return int Number of matching rows. + */ + public static function doCount(Criteria $criteria, $distinct = false, PropelPDO $con = null) + { + // we may modify criteria, so copy it first + $criteria = clone $criteria; + + // We need to set the primary table name, since in the case that there are no WHERE columns + // it will be impossible for the BasePeer::createSelectSql() method to determine which + // tables go into the FROM clause. + $criteria->setPrimaryTableName(CcBackupPeer::TABLE_NAME); + + if ($distinct && !in_array(Criteria::DISTINCT, $criteria->getSelectModifiers())) { + $criteria->setDistinct(); + } + + if (!$criteria->hasSelectClause()) { + CcBackupPeer::addSelectColumns($criteria); + } + + $criteria->clearOrderByColumns(); // ORDER BY won't ever affect the count + $criteria->setDbName(self::DATABASE_NAME); // Set the correct dbName + + if ($con === null) { + $con = Propel::getConnection(CcBackupPeer::DATABASE_NAME, Propel::CONNECTION_READ); + } + // BasePeer returns a PDOStatement + $stmt = BasePeer::doCount($criteria, $con); + + if ($row = $stmt->fetch(PDO::FETCH_NUM)) { + $count = (int) $row[0]; + } else { + $count = 0; // no rows returned; we infer that means 0 matches. + } + $stmt->closeCursor(); + return $count; + } + /** + * Method to select one object from the DB. + * + * @param Criteria $criteria object used to create the SELECT statement. + * @param PropelPDO $con + * @return CcBackup + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function doSelectOne(Criteria $criteria, PropelPDO $con = null) + { + $critcopy = clone $criteria; + $critcopy->setLimit(1); + $objects = CcBackupPeer::doSelect($critcopy, $con); + if ($objects) { + return $objects[0]; + } + return null; + } + /** + * Method to do selects. + * + * @param Criteria $criteria The Criteria object used to build the SELECT statement. + * @param PropelPDO $con + * @return array Array of selected Objects + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function doSelect(Criteria $criteria, PropelPDO $con = null) + { + return CcBackupPeer::populateObjects(CcBackupPeer::doSelectStmt($criteria, $con)); + } + /** + * Prepares the Criteria object and uses the parent doSelect() method to execute a PDOStatement. + * + * Use this method directly if you want to work with an executed statement durirectly (for example + * to perform your own object hydration). + * + * @param Criteria $criteria The Criteria object used to build the SELECT statement. + * @param PropelPDO $con The connection to use + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + * @return PDOStatement The executed PDOStatement object. + * @see BasePeer::doSelect() + */ + public static function doSelectStmt(Criteria $criteria, PropelPDO $con = null) + { + if ($con === null) { + $con = Propel::getConnection(CcBackupPeer::DATABASE_NAME, Propel::CONNECTION_READ); + } + + if (!$criteria->hasSelectClause()) { + $criteria = clone $criteria; + CcBackupPeer::addSelectColumns($criteria); + } + + // Set the correct dbName + $criteria->setDbName(self::DATABASE_NAME); + + // BasePeer returns a PDOStatement + return BasePeer::doSelect($criteria, $con); + } + /** + * Adds an object to the instance pool. + * + * Propel keeps cached copies of objects in an instance pool when they are retrieved + * from the database. In some cases -- especially when you override doSelect*() + * methods in your stub classes -- you may need to explicitly add objects + * to the cache in order to ensure that the same objects are always returned by doSelect*() + * and retrieveByPK*() calls. + * + * @param CcBackup $value A CcBackup object. + * @param string $key (optional) key to use for instance map (for performance boost if key was already calculated externally). + */ + public static function addInstanceToPool(CcBackup $obj, $key = null) + { + if (Propel::isInstancePoolingEnabled()) { + if ($key === null) { + $key = (string) $obj->getToken(); + } // if key === null + self::$instances[$key] = $obj; + } + } + + /** + * Removes an object from the instance pool. + * + * Propel keeps cached copies of objects in an instance pool when they are retrieved + * from the database. In some cases -- especially when you override doDelete + * methods in your stub classes -- you may need to explicitly remove objects + * from the cache in order to prevent returning objects that no longer exist. + * + * @param mixed $value A CcBackup object or a primary key value. + */ + public static function removeInstanceFromPool($value) + { + if (Propel::isInstancePoolingEnabled() && $value !== null) { + if (is_object($value) && $value instanceof CcBackup) { + $key = (string) $value->getToken(); + } elseif (is_scalar($value)) { + // assume we've been passed a primary key + $key = (string) $value; + } else { + $e = new PropelException("Invalid value passed to removeInstanceFromPool(). Expected primary key or CcBackup object; got " . (is_object($value) ? get_class($value) . ' object.' : var_export($value,true))); + throw $e; + } + + unset(self::$instances[$key]); + } + } // removeInstanceFromPool() + + /** + * Retrieves a string version of the primary key from the DB resultset row that can be used to uniquely identify a row in this table. + * + * For tables with a single-column primary key, that simple pkey value will be returned. For tables with + * a multi-column primary key, a serialize()d version of the primary key will be returned. + * + * @param string $key The key (@see getPrimaryKeyHash()) for this instance. + * @return CcBackup Found object or NULL if 1) no instance exists for specified key or 2) instance pooling has been disabled. + * @see getPrimaryKeyHash() + */ + public static function getInstanceFromPool($key) + { + if (Propel::isInstancePoolingEnabled()) { + if (isset(self::$instances[$key])) { + return self::$instances[$key]; + } + } + return null; // just to be explicit + } + + /** + * Clear the instance pool. + * + * @return void + */ + public static function clearInstancePool() + { + self::$instances = array(); + } + + /** + * Method to invalidate the instance pool of all tables related to cc_backup + * by a foreign key with ON DELETE CASCADE + */ + public static function clearRelatedInstancePool() + { + } + + /** + * Retrieves a string version of the primary key from the DB resultset row that can be used to uniquely identify a row in this table. + * + * For tables with a single-column primary key, that simple pkey value will be returned. For tables with + * a multi-column primary key, a serialize()d version of the primary key will be returned. + * + * @param array $row PropelPDO resultset row. + * @param int $startcol The 0-based offset for reading from the resultset row. + * @return string A string version of PK or NULL if the components of primary key in result array are all null. + */ + public static function getPrimaryKeyHashFromRow($row, $startcol = 0) + { + // If the PK cannot be derived from the row, return NULL. + if ($row[$startcol] === null) { + return null; + } + return (string) $row[$startcol]; + } + + /** + * Retrieves the primary key from the DB resultset row + * For tables with a single-column primary key, that simple pkey value will be returned. For tables with + * a multi-column primary key, an array of the primary key columns will be returned. + * + * @param array $row PropelPDO resultset row. + * @param int $startcol The 0-based offset for reading from the resultset row. + * @return mixed The primary key of the row + */ + public static function getPrimaryKeyFromRow($row, $startcol = 0) + { + return (string) $row[$startcol]; + } + + /** + * The returned array will contain objects of the default type or + * objects that inherit from the default. + * + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function populateObjects(PDOStatement $stmt) + { + $results = array(); + + // set the class once to avoid overhead in the loop + $cls = CcBackupPeer::getOMClass(false); + // populate the object(s) + while ($row = $stmt->fetch(PDO::FETCH_NUM)) { + $key = CcBackupPeer::getPrimaryKeyHashFromRow($row, 0); + if (null !== ($obj = CcBackupPeer::getInstanceFromPool($key))) { + // We no longer rehydrate the object, since this can cause data loss. + // See http://www.propelorm.org/ticket/509 + // $obj->hydrate($row, 0, true); // rehydrate + $results[] = $obj; + } else { + $obj = new $cls(); + $obj->hydrate($row); + $results[] = $obj; + CcBackupPeer::addInstanceToPool($obj, $key); + } // if key exists + } + $stmt->closeCursor(); + return $results; + } + /** + * Populates an object of the default type or an object that inherit from the default. + * + * @param array $row PropelPDO resultset row. + * @param int $startcol The 0-based offset for reading from the resultset row. + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + * @return array (CcBackup object, last column rank) + */ + public static function populateObject($row, $startcol = 0) + { + $key = CcBackupPeer::getPrimaryKeyHashFromRow($row, $startcol); + if (null !== ($obj = CcBackupPeer::getInstanceFromPool($key))) { + // We no longer rehydrate the object, since this can cause data loss. + // See http://www.propelorm.org/ticket/509 + // $obj->hydrate($row, $startcol, true); // rehydrate + $col = $startcol + CcBackupPeer::NUM_COLUMNS; + } else { + $cls = CcBackupPeer::OM_CLASS; + $obj = new $cls(); + $col = $obj->hydrate($row, $startcol); + CcBackupPeer::addInstanceToPool($obj, $key); + } + return array($obj, $col); + } + /** + * Returns the TableMap related to this peer. + * This method is not needed for general use but a specific application could have a need. + * @return TableMap + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function getTableMap() + { + return Propel::getDatabaseMap(self::DATABASE_NAME)->getTable(self::TABLE_NAME); + } + + /** + * Add a TableMap instance to the database for this peer class. + */ + public static function buildTableMap() + { + $dbMap = Propel::getDatabaseMap(BaseCcBackupPeer::DATABASE_NAME); + if (!$dbMap->hasTable(BaseCcBackupPeer::TABLE_NAME)) + { + $dbMap->addTableObject(new CcBackupTableMap()); + } + } + + /** + * The class that the Peer will make instances of. + * + * If $withPrefix is true, the returned path + * uses a dot-path notation which is tranalted into a path + * relative to a location on the PHP include_path. + * (e.g. path.to.MyClass -> 'path/to/MyClass.php') + * + * @param boolean $withPrefix Whether or not to return the path with the class name + * @return string path.to.ClassName + */ + public static function getOMClass($withPrefix = true) + { + return $withPrefix ? CcBackupPeer::CLASS_DEFAULT : CcBackupPeer::OM_CLASS; + } + + /** + * Method perform an INSERT on the database, given a CcBackup or Criteria object. + * + * @param mixed $values Criteria or CcBackup object containing data that is used to create the INSERT statement. + * @param PropelPDO $con the PropelPDO connection to use + * @return mixed The new primary key. + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function doInsert($values, PropelPDO $con = null) + { + if ($con === null) { + $con = Propel::getConnection(CcBackupPeer::DATABASE_NAME, Propel::CONNECTION_WRITE); + } + + if ($values instanceof Criteria) { + $criteria = clone $values; // rename for clarity + } else { + $criteria = $values->buildCriteria(); // build Criteria from CcBackup object + } + + + // Set the correct dbName + $criteria->setDbName(self::DATABASE_NAME); + + try { + // use transaction because $criteria could contain info + // for more than one table (I guess, conceivably) + $con->beginTransaction(); + $pk = BasePeer::doInsert($criteria, $con); + $con->commit(); + } catch(PropelException $e) { + $con->rollBack(); + throw $e; + } + + return $pk; + } + + /** + * Method perform an UPDATE on the database, given a CcBackup or Criteria object. + * + * @param mixed $values Criteria or CcBackup object containing data that is used to create the UPDATE statement. + * @param PropelPDO $con The connection to use (specify PropelPDO connection object to exert more control over transactions). + * @return int The number of affected rows (if supported by underlying database driver). + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function doUpdate($values, PropelPDO $con = null) + { + if ($con === null) { + $con = Propel::getConnection(CcBackupPeer::DATABASE_NAME, Propel::CONNECTION_WRITE); + } + + $selectCriteria = new Criteria(self::DATABASE_NAME); + + if ($values instanceof Criteria) { + $criteria = clone $values; // rename for clarity + + $comparison = $criteria->getComparison(CcBackupPeer::TOKEN); + $value = $criteria->remove(CcBackupPeer::TOKEN); + if ($value) { + $selectCriteria->add(CcBackupPeer::TOKEN, $value, $comparison); + } else { + $selectCriteria->setPrimaryTableName(CcBackupPeer::TABLE_NAME); + } + + } else { // $values is CcBackup object + $criteria = $values->buildCriteria(); // gets full criteria + $selectCriteria = $values->buildPkeyCriteria(); // gets criteria w/ primary key(s) + } + + // set the correct dbName + $criteria->setDbName(self::DATABASE_NAME); + + return BasePeer::doUpdate($selectCriteria, $criteria, $con); + } + + /** + * Method to DELETE all rows from the cc_backup table. + * + * @return int The number of affected rows (if supported by underlying database driver). + */ + public static function doDeleteAll($con = null) + { + if ($con === null) { + $con = Propel::getConnection(CcBackupPeer::DATABASE_NAME, Propel::CONNECTION_WRITE); + } + $affectedRows = 0; // initialize var to track total num of affected rows + try { + // use transaction because $criteria could contain info + // for more than one table or we could emulating ON DELETE CASCADE, etc. + $con->beginTransaction(); + $affectedRows += BasePeer::doDeleteAll(CcBackupPeer::TABLE_NAME, $con, CcBackupPeer::DATABASE_NAME); + // Because this db requires some delete cascade/set null emulation, we have to + // clear the cached instance *after* the emulation has happened (since + // instances get re-added by the select statement contained therein). + CcBackupPeer::clearInstancePool(); + CcBackupPeer::clearRelatedInstancePool(); + $con->commit(); + return $affectedRows; + } catch (PropelException $e) { + $con->rollBack(); + throw $e; + } + } + + /** + * Method perform a DELETE on the database, given a CcBackup or Criteria object OR a primary key value. + * + * @param mixed $values Criteria or CcBackup object or primary key or array of primary keys + * which is used to create the DELETE statement + * @param PropelPDO $con the connection to use + * @return int The number of affected rows (if supported by underlying database driver). This includes CASCADE-related rows + * if supported by native driver or if emulated using Propel. + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function doDelete($values, PropelPDO $con = null) + { + if ($con === null) { + $con = Propel::getConnection(CcBackupPeer::DATABASE_NAME, Propel::CONNECTION_WRITE); + } + + if ($values instanceof Criteria) { + // invalidate the cache for all objects of this type, since we have no + // way of knowing (without running a query) what objects should be invalidated + // from the cache based on this Criteria. + CcBackupPeer::clearInstancePool(); + // rename for clarity + $criteria = clone $values; + } elseif ($values instanceof CcBackup) { // it's a model object + // invalidate the cache for this single object + CcBackupPeer::removeInstanceFromPool($values); + // create criteria based on pk values + $criteria = $values->buildPkeyCriteria(); + } else { // it's a primary key, or an array of pks + $criteria = new Criteria(self::DATABASE_NAME); + $criteria->add(CcBackupPeer::TOKEN, (array) $values, Criteria::IN); + // invalidate the cache for this object(s) + foreach ((array) $values as $singleval) { + CcBackupPeer::removeInstanceFromPool($singleval); + } + } + + // Set the correct dbName + $criteria->setDbName(self::DATABASE_NAME); + + $affectedRows = 0; // initialize var to track total num of affected rows + + try { + // use transaction because $criteria could contain info + // for more than one table or we could emulating ON DELETE CASCADE, etc. + $con->beginTransaction(); + + $affectedRows += BasePeer::doDelete($criteria, $con); + CcBackupPeer::clearRelatedInstancePool(); + $con->commit(); + return $affectedRows; + } catch (PropelException $e) { + $con->rollBack(); + throw $e; + } + } + + /** + * Validates all modified columns of given CcBackup object. + * If parameter $columns is either a single column name or an array of column names + * than only those columns are validated. + * + * NOTICE: This does not apply to primary or foreign keys for now. + * + * @param CcBackup $obj The object to validate. + * @param mixed $cols Column name or array of column names. + * + * @return mixed TRUE if all columns are valid or the error message of the first invalid column. + */ + public static function doValidate(CcBackup $obj, $cols = null) + { + $columns = array(); + + if ($cols) { + $dbMap = Propel::getDatabaseMap(CcBackupPeer::DATABASE_NAME); + $tableMap = $dbMap->getTable(CcBackupPeer::TABLE_NAME); + + if (! is_array($cols)) { + $cols = array($cols); + } + + foreach ($cols as $colName) { + if ($tableMap->containsColumn($colName)) { + $get = 'get' . $tableMap->getColumn($colName)->getPhpName(); + $columns[$colName] = $obj->$get(); + } + } + } else { + + } + + return BasePeer::doValidate(CcBackupPeer::DATABASE_NAME, CcBackupPeer::TABLE_NAME, $columns); + } + + /** + * Retrieve a single object by pkey. + * + * @param string $pk the primary key. + * @param PropelPDO $con the connection to use + * @return CcBackup + */ + public static function retrieveByPK($pk, PropelPDO $con = null) + { + + if (null !== ($obj = CcBackupPeer::getInstanceFromPool((string) $pk))) { + return $obj; + } + + if ($con === null) { + $con = Propel::getConnection(CcBackupPeer::DATABASE_NAME, Propel::CONNECTION_READ); + } + + $criteria = new Criteria(CcBackupPeer::DATABASE_NAME); + $criteria->add(CcBackupPeer::TOKEN, $pk); + + $v = CcBackupPeer::doSelect($criteria, $con); + + return !empty($v) > 0 ? $v[0] : null; + } + + /** + * Retrieve multiple objects by pkey. + * + * @param array $pks List of primary keys + * @param PropelPDO $con the connection to use + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function retrieveByPKs($pks, PropelPDO $con = null) + { + if ($con === null) { + $con = Propel::getConnection(CcBackupPeer::DATABASE_NAME, Propel::CONNECTION_READ); + } + + $objs = null; + if (empty($pks)) { + $objs = array(); + } else { + $criteria = new Criteria(CcBackupPeer::DATABASE_NAME); + $criteria->add(CcBackupPeer::TOKEN, $pks, Criteria::IN); + $objs = CcBackupPeer::doSelect($criteria, $con); + } + return $objs; + } + +} // BaseCcBackupPeer + +// This is the static code needed to register the TableMap for this table with the main Propel class. +// +BaseCcBackupPeer::buildTableMap(); + diff --git a/application/models/campcaster/om/BaseCcBackupQuery.php b/application/models/campcaster/om/BaseCcBackupQuery.php new file mode 100644 index 000000000..f74f068bf --- /dev/null +++ b/application/models/campcaster/om/BaseCcBackupQuery.php @@ -0,0 +1,292 @@ +setModelAlias($modelAlias); + } + if ($criteria instanceof Criteria) { + $query->mergeWith($criteria); + } + return $query; + } + + /** + * Find object by primary key + * Use instance pooling to avoid a database query if the object exists + * + * $obj = $c->findPk(12, $con); + * + * @param mixed $key Primary key to use for the query + * @param PropelPDO $con an optional connection object + * + * @return CcBackup|array|mixed the result, formatted by the current formatter + */ + public function findPk($key, $con = null) + { + if ((null !== ($obj = CcBackupPeer::getInstanceFromPool((string) $key))) && $this->getFormatter()->isObjectFormatter()) { + // the object is alredy in the instance pool + return $obj; + } else { + // the object has not been requested yet, or the formatter is not an object formatter + $criteria = $this->isKeepQuery() ? clone $this : $this; + $stmt = $criteria + ->filterByPrimaryKey($key) + ->getSelectStatement($con); + return $criteria->getFormatter()->init($criteria)->formatOne($stmt); + } + } + + /** + * Find objects by primary key + * + * $objs = $c->findPks(array(12, 56, 832), $con); + * + * @param array $keys Primary keys to use for the query + * @param PropelPDO $con an optional connection object + * + * @return PropelObjectCollection|array|mixed the list of results, formatted by the current formatter + */ + public function findPks($keys, $con = null) + { + $criteria = $this->isKeepQuery() ? clone $this : $this; + return $this + ->filterByPrimaryKeys($keys) + ->find($con); + } + + /** + * Filter the query by primary key + * + * @param mixed $key Primary key to use for the query + * + * @return CcBackupQuery The current query, for fluid interface + */ + public function filterByPrimaryKey($key) + { + return $this->addUsingAlias(CcBackupPeer::TOKEN, $key, Criteria::EQUAL); + } + + /** + * Filter the query by a list of primary keys + * + * @param array $keys The list of primary key to use for the query + * + * @return CcBackupQuery The current query, for fluid interface + */ + public function filterByPrimaryKeys($keys) + { + return $this->addUsingAlias(CcBackupPeer::TOKEN, $keys, Criteria::IN); + } + + /** + * Filter the query on the token column + * + * @param string $token The value to use as filter. + * Accepts wildcards (* and % trigger a LIKE) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcBackupQuery The current query, for fluid interface + */ + public function filterByToken($token = null, $comparison = null) + { + if (null === $comparison) { + if (is_array($token)) { + $comparison = Criteria::IN; + } elseif (preg_match('/[\%\*]/', $token)) { + $token = str_replace('*', '%', $token); + $comparison = Criteria::LIKE; + } + } + return $this->addUsingAlias(CcBackupPeer::TOKEN, $token, $comparison); + } + + /** + * Filter the query on the sessionid column + * + * @param string $sessionid The value to use as filter. + * Accepts wildcards (* and % trigger a LIKE) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcBackupQuery The current query, for fluid interface + */ + public function filterBySessionid($sessionid = null, $comparison = null) + { + if (null === $comparison) { + if (is_array($sessionid)) { + $comparison = Criteria::IN; + } elseif (preg_match('/[\%\*]/', $sessionid)) { + $sessionid = str_replace('*', '%', $sessionid); + $comparison = Criteria::LIKE; + } + } + return $this->addUsingAlias(CcBackupPeer::SESSIONID, $sessionid, $comparison); + } + + /** + * Filter the query on the status column + * + * @param string $status The value to use as filter. + * Accepts wildcards (* and % trigger a LIKE) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcBackupQuery The current query, for fluid interface + */ + public function filterByStatus($status = null, $comparison = null) + { + if (null === $comparison) { + if (is_array($status)) { + $comparison = Criteria::IN; + } elseif (preg_match('/[\%\*]/', $status)) { + $status = str_replace('*', '%', $status); + $comparison = Criteria::LIKE; + } + } + return $this->addUsingAlias(CcBackupPeer::STATUS, $status, $comparison); + } + + /** + * Filter the query on the fromtime column + * + * @param string|array $fromtime The value to use as filter. + * Accepts an associative array('min' => $minValue, 'max' => $maxValue) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcBackupQuery The current query, for fluid interface + */ + public function filterByFromtime($fromtime = null, $comparison = null) + { + if (is_array($fromtime)) { + $useMinMax = false; + if (isset($fromtime['min'])) { + $this->addUsingAlias(CcBackupPeer::FROMTIME, $fromtime['min'], Criteria::GREATER_EQUAL); + $useMinMax = true; + } + if (isset($fromtime['max'])) { + $this->addUsingAlias(CcBackupPeer::FROMTIME, $fromtime['max'], Criteria::LESS_EQUAL); + $useMinMax = true; + } + if ($useMinMax) { + return $this; + } + if (null === $comparison) { + $comparison = Criteria::IN; + } + } + return $this->addUsingAlias(CcBackupPeer::FROMTIME, $fromtime, $comparison); + } + + /** + * Filter the query on the totime column + * + * @param string|array $totime The value to use as filter. + * Accepts an associative array('min' => $minValue, 'max' => $maxValue) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcBackupQuery The current query, for fluid interface + */ + public function filterByTotime($totime = null, $comparison = null) + { + if (is_array($totime)) { + $useMinMax = false; + if (isset($totime['min'])) { + $this->addUsingAlias(CcBackupPeer::TOTIME, $totime['min'], Criteria::GREATER_EQUAL); + $useMinMax = true; + } + if (isset($totime['max'])) { + $this->addUsingAlias(CcBackupPeer::TOTIME, $totime['max'], Criteria::LESS_EQUAL); + $useMinMax = true; + } + if ($useMinMax) { + return $this; + } + if (null === $comparison) { + $comparison = Criteria::IN; + } + } + return $this->addUsingAlias(CcBackupPeer::TOTIME, $totime, $comparison); + } + + /** + * Exclude object from result + * + * @param CcBackup $ccBackup Object to remove from the list of results + * + * @return CcBackupQuery The current query, for fluid interface + */ + public function prune($ccBackup = null) + { + if ($ccBackup) { + $this->addUsingAlias(CcBackupPeer::TOKEN, $ccBackup->getToken(), Criteria::NOT_EQUAL); + } + + return $this; + } + +} // BaseCcBackupQuery diff --git a/application/models/campcaster/om/BaseCcFiles.php b/application/models/campcaster/om/BaseCcFiles.php new file mode 100644 index 000000000..94d732f04 --- /dev/null +++ b/application/models/campcaster/om/BaseCcFiles.php @@ -0,0 +1,3643 @@ +name = ''; + $this->mime = ''; + $this->ftype = ''; + $this->filepath = ''; + $this->state = 'empty'; + $this->currentlyaccessing = 0; + } + + /** + * Initializes internal state of BaseCcFiles object. + * @see applyDefaults() + */ + public function __construct() + { + parent::__construct(); + $this->applyDefaultValues(); + } + + /** + * Get the [id] column value. + * + * @return int + */ + public function getDbId() + { + return $this->id; + } + + /** + * Get the [gunid] column value. + * + * @return string + */ + public function getGunid() + { + return $this->gunid; + } + + /** + * Get the [name] column value. + * + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * Get the [mime] column value. + * + * @return string + */ + public function getMime() + { + return $this->mime; + } + + /** + * Get the [ftype] column value. + * + * @return string + */ + public function getFtype() + { + return $this->ftype; + } + + /** + * Get the [filepath] column value. + * + * @return string + */ + public function getfilepath() + { + return $this->filepath; + } + + /** + * Get the [state] column value. + * + * @return string + */ + public function getState() + { + return $this->state; + } + + /** + * Get the [currentlyaccessing] column value. + * + * @return int + */ + public function getCurrentlyaccessing() + { + return $this->currentlyaccessing; + } + + /** + * Get the [editedby] column value. + * + * @return int + */ + public function getEditedby() + { + return $this->editedby; + } + + /** + * Get the [optionally formatted] temporal [mtime] column value. + * + * + * @param string $format The date/time format string (either date()-style or strftime()-style). + * If format is NULL, then the raw DateTime object will be returned. + * @return mixed Formatted date/time value as string or DateTime object (if format is NULL), NULL if column is NULL + * @throws PropelException - if unable to parse/validate the date/time value. + */ + public function getMtime($format = 'Y-m-d H:i:s') + { + if ($this->mtime === null) { + return null; + } + + + + try { + $dt = new DateTime($this->mtime); + } catch (Exception $x) { + throw new PropelException("Internally stored date/time/timestamp value could not be converted to DateTime: " . var_export($this->mtime, true), $x); + } + + if ($format === null) { + // Because propel.useDateTimeClass is TRUE, we return a DateTime object. + return $dt; + } elseif (strpos($format, '%') !== false) { + return strftime($format, $dt->format('U')); + } else { + return $dt->format($format); + } + } + + /** + * Get the [md5] column value. + * + * @return string + */ + public function getMd5() + { + return $this->md5; + } + + /** + * Get the [track_title] column value. + * + * @return string + */ + public function getTrackTitle() + { + return $this->track_title; + } + + /** + * Get the [artist_name] column value. + * + * @return string + */ + public function getArtistName() + { + return $this->artist_name; + } + + /** + * Get the [bit_rate] column value. + * + * @return string + */ + public function getBitRate() + { + return $this->bit_rate; + } + + /** + * Get the [sample_rate] column value. + * + * @return string + */ + public function getSampleRate() + { + return $this->sample_rate; + } + + /** + * Get the [format] column value. + * + * @return string + */ + public function getFormat() + { + return $this->format; + } + + /** + * Get the [optionally formatted] temporal [length] column value. + * + * + * @param string $format The date/time format string (either date()-style or strftime()-style). + * If format is NULL, then the raw DateTime object will be returned. + * @return mixed Formatted date/time value as string or DateTime object (if format is NULL), NULL if column is NULL + * @throws PropelException - if unable to parse/validate the date/time value. + */ + public function getDbLength($format = '%X') + { + if ($this->length === null) { + return null; + } + + + + try { + $dt = new DateTime($this->length); + } catch (Exception $x) { + throw new PropelException("Internally stored date/time/timestamp value could not be converted to DateTime: " . var_export($this->length, true), $x); + } + + if ($format === null) { + // Because propel.useDateTimeClass is TRUE, we return a DateTime object. + return $dt; + } elseif (strpos($format, '%') !== false) { + return strftime($format, $dt->format('U')); + } else { + return $dt->format($format); + } + } + + /** + * Get the [album_title] column value. + * + * @return string + */ + public function getAlbumTitle() + { + return $this->album_title; + } + + /** + * Get the [genre] column value. + * + * @return string + */ + public function getGenre() + { + return $this->genre; + } + + /** + * Get the [comments] column value. + * + * @return string + */ + public function getComments() + { + return $this->comments; + } + + /** + * Get the [year] column value. + * + * @return string + */ + public function getYear() + { + return $this->year; + } + + /** + * Get the [track_number] column value. + * + * @return int + */ + public function getTrackNumber() + { + return $this->track_number; + } + + /** + * Get the [channels] column value. + * + * @return int + */ + public function getChannels() + { + return $this->channels; + } + + /** + * Get the [url] column value. + * + * @return string + */ + public function getUrl() + { + return $this->url; + } + + /** + * Get the [bpm] column value. + * + * @return string + */ + public function getBpm() + { + return $this->bpm; + } + + /** + * Get the [rating] column value. + * + * @return string + */ + public function getRating() + { + return $this->rating; + } + + /** + * Get the [encoded_by] column value. + * + * @return string + */ + public function getEncodedBy() + { + return $this->encoded_by; + } + + /** + * Get the [disc_number] column value. + * + * @return string + */ + public function getDiscNumber() + { + return $this->disc_number; + } + + /** + * Get the [mood] column value. + * + * @return string + */ + public function getMood() + { + return $this->mood; + } + + /** + * Get the [label] column value. + * + * @return string + */ + public function getLabel() + { + return $this->label; + } + + /** + * Get the [composer] column value. + * + * @return string + */ + public function getComposer() + { + return $this->composer; + } + + /** + * Get the [encoder] column value. + * + * @return string + */ + public function getEncoder() + { + return $this->encoder; + } + + /** + * Get the [checksum] column value. + * + * @return string + */ + public function getChecksum() + { + return $this->checksum; + } + + /** + * Get the [lyrics] column value. + * + * @return string + */ + public function getLyrics() + { + return $this->lyrics; + } + + /** + * Get the [orchestra] column value. + * + * @return string + */ + public function getOrchestra() + { + return $this->orchestra; + } + + /** + * Get the [conductor] column value. + * + * @return string + */ + public function getConductor() + { + return $this->conductor; + } + + /** + * Get the [lyricist] column value. + * + * @return string + */ + public function getLyricist() + { + return $this->lyricist; + } + + /** + * Get the [original_lyricist] column value. + * + * @return string + */ + public function getOriginalLyricist() + { + return $this->original_lyricist; + } + + /** + * Get the [radio_station_name] column value. + * + * @return string + */ + public function getRadioStationName() + { + return $this->radio_station_name; + } + + /** + * Get the [info_url] column value. + * + * @return string + */ + public function getInfoUrl() + { + return $this->info_url; + } + + /** + * Get the [artist_url] column value. + * + * @return string + */ + public function getArtistUrl() + { + return $this->artist_url; + } + + /** + * Get the [audio_source_url] column value. + * + * @return string + */ + public function getAudioSourceUrl() + { + return $this->audio_source_url; + } + + /** + * Get the [radio_station_url] column value. + * + * @return string + */ + public function getRadioStationUrl() + { + return $this->radio_station_url; + } + + /** + * Get the [buy_this_url] column value. + * + * @return string + */ + public function getBuyThisUrl() + { + return $this->buy_this_url; + } + + /** + * Get the [isrc_number] column value. + * + * @return string + */ + public function getIsrcNumber() + { + return $this->isrc_number; + } + + /** + * Get the [catalog_number] column value. + * + * @return string + */ + public function getCatalogNumber() + { + return $this->catalog_number; + } + + /** + * Get the [original_artist] column value. + * + * @return string + */ + public function getOriginalArtist() + { + return $this->original_artist; + } + + /** + * Get the [copyright] column value. + * + * @return string + */ + public function getCopyright() + { + return $this->copyright; + } + + /** + * Get the [report_datetime] column value. + * + * @return string + */ + public function getReportDatetime() + { + return $this->report_datetime; + } + + /** + * Get the [report_location] column value. + * + * @return string + */ + public function getReportLocation() + { + return $this->report_location; + } + + /** + * Get the [report_organization] column value. + * + * @return string + */ + public function getReportOrganization() + { + return $this->report_organization; + } + + /** + * Get the [subject] column value. + * + * @return string + */ + public function getSubject() + { + return $this->subject; + } + + /** + * Get the [contributor] column value. + * + * @return string + */ + public function getContributor() + { + return $this->contributor; + } + + /** + * Get the [language] column value. + * + * @return string + */ + public function getLanguage() + { + return $this->language; + } + + /** + * Set the value of [id] column. + * + * @param int $v new value + * @return CcFiles The current object (for fluent API support) + */ + public function setDbId($v) + { + if ($v !== null) { + $v = (int) $v; + } + + if ($this->id !== $v) { + $this->id = $v; + $this->modifiedColumns[] = CcFilesPeer::ID; + } + + return $this; + } // setDbId() + + /** + * Set the value of [gunid] column. + * + * @param string $v new value + * @return CcFiles The current object (for fluent API support) + */ + public function setGunid($v) + { + if ($v !== null) { + $v = (string) $v; + } + + if ($this->gunid !== $v) { + $this->gunid = $v; + $this->modifiedColumns[] = CcFilesPeer::GUNID; + } + + return $this; + } // setGunid() + + /** + * Set the value of [name] column. + * + * @param string $v new value + * @return CcFiles The current object (for fluent API support) + */ + public function setName($v) + { + if ($v !== null) { + $v = (string) $v; + } + + if ($this->name !== $v || $this->isNew()) { + $this->name = $v; + $this->modifiedColumns[] = CcFilesPeer::NAME; + } + + return $this; + } // setName() + + /** + * Set the value of [mime] column. + * + * @param string $v new value + * @return CcFiles The current object (for fluent API support) + */ + public function setMime($v) + { + if ($v !== null) { + $v = (string) $v; + } + + if ($this->mime !== $v || $this->isNew()) { + $this->mime = $v; + $this->modifiedColumns[] = CcFilesPeer::MIME; + } + + return $this; + } // setMime() + + /** + * Set the value of [ftype] column. + * + * @param string $v new value + * @return CcFiles The current object (for fluent API support) + */ + public function setFtype($v) + { + if ($v !== null) { + $v = (string) $v; + } + + if ($this->ftype !== $v || $this->isNew()) { + $this->ftype = $v; + $this->modifiedColumns[] = CcFilesPeer::FTYPE; + } + + return $this; + } // setFtype() + + /** + * Set the value of [filepath] column. + * + * @param string $v new value + * @return CcFiles The current object (for fluent API support) + */ + public function setfilepath($v) + { + if ($v !== null) { + $v = (string) $v; + } + + if ($this->filepath !== $v || $this->isNew()) { + $this->filepath = $v; + $this->modifiedColumns[] = CcFilesPeer::FILEPATH; + } + + return $this; + } // setfilepath() + + /** + * Set the value of [state] column. + * + * @param string $v new value + * @return CcFiles The current object (for fluent API support) + */ + public function setState($v) + { + if ($v !== null) { + $v = (string) $v; + } + + if ($this->state !== $v || $this->isNew()) { + $this->state = $v; + $this->modifiedColumns[] = CcFilesPeer::STATE; + } + + return $this; + } // setState() + + /** + * Set the value of [currentlyaccessing] column. + * + * @param int $v new value + * @return CcFiles The current object (for fluent API support) + */ + public function setCurrentlyaccessing($v) + { + if ($v !== null) { + $v = (int) $v; + } + + if ($this->currentlyaccessing !== $v || $this->isNew()) { + $this->currentlyaccessing = $v; + $this->modifiedColumns[] = CcFilesPeer::CURRENTLYACCESSING; + } + + return $this; + } // setCurrentlyaccessing() + + /** + * Set the value of [editedby] column. + * + * @param int $v new value + * @return CcFiles The current object (for fluent API support) + */ + public function setEditedby($v) + { + if ($v !== null) { + $v = (int) $v; + } + + if ($this->editedby !== $v) { + $this->editedby = $v; + $this->modifiedColumns[] = CcFilesPeer::EDITEDBY; + } + + if ($this->aCcSubjs !== null && $this->aCcSubjs->getId() !== $v) { + $this->aCcSubjs = null; + } + + return $this; + } // setEditedby() + + /** + * Sets the value of [mtime] column to a normalized version of the date/time value specified. + * + * @param mixed $v string, integer (timestamp), or DateTime value. Empty string will + * be treated as NULL for temporal objects. + * @return CcFiles The current object (for fluent API support) + */ + public function setMtime($v) + { + // we treat '' as NULL for temporal objects because DateTime('') == DateTime('now') + // -- which is unexpected, to say the least. + if ($v === null || $v === '') { + $dt = null; + } elseif ($v instanceof DateTime) { + $dt = $v; + } else { + // some string/numeric value passed; we normalize that so that we can + // validate it. + try { + if (is_numeric($v)) { // if it's a unix timestamp + $dt = new DateTime('@'.$v, new DateTimeZone('UTC')); + // We have to explicitly specify and then change the time zone because of a + // DateTime bug: http://bugs.php.net/bug.php?id=43003 + $dt->setTimeZone(new DateTimeZone(date_default_timezone_get())); + } else { + $dt = new DateTime($v); + } + } catch (Exception $x) { + throw new PropelException('Error parsing date/time value: ' . var_export($v, true), $x); + } + } + + if ( $this->mtime !== null || $dt !== null ) { + // (nested ifs are a little easier to read in this case) + + $currNorm = ($this->mtime !== null && $tmpDt = new DateTime($this->mtime)) ? $tmpDt->format('Y-m-d\\TH:i:sO') : null; + $newNorm = ($dt !== null) ? $dt->format('Y-m-d\\TH:i:sO') : null; + + if ( ($currNorm !== $newNorm) // normalized values don't match + ) + { + $this->mtime = ($dt ? $dt->format('Y-m-d\\TH:i:sO') : null); + $this->modifiedColumns[] = CcFilesPeer::MTIME; + } + } // if either are not null + + return $this; + } // setMtime() + + /** + * Set the value of [md5] column. + * + * @param string $v new value + * @return CcFiles The current object (for fluent API support) + */ + public function setMd5($v) + { + if ($v !== null) { + $v = (string) $v; + } + + if ($this->md5 !== $v) { + $this->md5 = $v; + $this->modifiedColumns[] = CcFilesPeer::MD5; + } + + return $this; + } // setMd5() + + /** + * Set the value of [track_title] column. + * + * @param string $v new value + * @return CcFiles The current object (for fluent API support) + */ + public function setTrackTitle($v) + { + if ($v !== null) { + $v = (string) $v; + } + + if ($this->track_title !== $v) { + $this->track_title = $v; + $this->modifiedColumns[] = CcFilesPeer::TRACK_TITLE; + } + + return $this; + } // setTrackTitle() + + /** + * Set the value of [artist_name] column. + * + * @param string $v new value + * @return CcFiles The current object (for fluent API support) + */ + public function setArtistName($v) + { + if ($v !== null) { + $v = (string) $v; + } + + if ($this->artist_name !== $v) { + $this->artist_name = $v; + $this->modifiedColumns[] = CcFilesPeer::ARTIST_NAME; + } + + return $this; + } // setArtistName() + + /** + * Set the value of [bit_rate] column. + * + * @param string $v new value + * @return CcFiles The current object (for fluent API support) + */ + public function setBitRate($v) + { + if ($v !== null) { + $v = (string) $v; + } + + if ($this->bit_rate !== $v) { + $this->bit_rate = $v; + $this->modifiedColumns[] = CcFilesPeer::BIT_RATE; + } + + return $this; + } // setBitRate() + + /** + * Set the value of [sample_rate] column. + * + * @param string $v new value + * @return CcFiles The current object (for fluent API support) + */ + public function setSampleRate($v) + { + if ($v !== null) { + $v = (string) $v; + } + + if ($this->sample_rate !== $v) { + $this->sample_rate = $v; + $this->modifiedColumns[] = CcFilesPeer::SAMPLE_RATE; + } + + return $this; + } // setSampleRate() + + /** + * Set the value of [format] column. + * + * @param string $v new value + * @return CcFiles The current object (for fluent API support) + */ + public function setFormat($v) + { + if ($v !== null) { + $v = (string) $v; + } + + if ($this->format !== $v) { + $this->format = $v; + $this->modifiedColumns[] = CcFilesPeer::FORMAT; + } + + return $this; + } // setFormat() + + /** + * Sets the value of [length] column to a normalized version of the date/time value specified. + * + * @param mixed $v string, integer (timestamp), or DateTime value. Empty string will + * be treated as NULL for temporal objects. + * @return CcFiles The current object (for fluent API support) + */ + public function setDbLength($v) + { + // we treat '' as NULL for temporal objects because DateTime('') == DateTime('now') + // -- which is unexpected, to say the least. + if ($v === null || $v === '') { + $dt = null; + } elseif ($v instanceof DateTime) { + $dt = $v; + } else { + // some string/numeric value passed; we normalize that so that we can + // validate it. + try { + if (is_numeric($v)) { // if it's a unix timestamp + $dt = new DateTime('@'.$v, new DateTimeZone('UTC')); + // We have to explicitly specify and then change the time zone because of a + // DateTime bug: http://bugs.php.net/bug.php?id=43003 + $dt->setTimeZone(new DateTimeZone(date_default_timezone_get())); + } else { + $dt = new DateTime($v); + } + } catch (Exception $x) { + throw new PropelException('Error parsing date/time value: ' . var_export($v, true), $x); + } + } + + if ( $this->length !== null || $dt !== null ) { + // (nested ifs are a little easier to read in this case) + + $currNorm = ($this->length !== null && $tmpDt = new DateTime($this->length)) ? $tmpDt->format('H:i:s') : null; + $newNorm = ($dt !== null) ? $dt->format('H:i:s') : null; + + if ( ($currNorm !== $newNorm) // normalized values don't match + ) + { + $this->length = ($dt ? $dt->format('H:i:s') : null); + $this->modifiedColumns[] = CcFilesPeer::LENGTH; + } + } // if either are not null + + return $this; + } // setDbLength() + + /** + * Set the value of [album_title] column. + * + * @param string $v new value + * @return CcFiles The current object (for fluent API support) + */ + public function setAlbumTitle($v) + { + if ($v !== null) { + $v = (string) $v; + } + + if ($this->album_title !== $v) { + $this->album_title = $v; + $this->modifiedColumns[] = CcFilesPeer::ALBUM_TITLE; + } + + return $this; + } // setAlbumTitle() + + /** + * Set the value of [genre] column. + * + * @param string $v new value + * @return CcFiles The current object (for fluent API support) + */ + public function setGenre($v) + { + if ($v !== null) { + $v = (string) $v; + } + + if ($this->genre !== $v) { + $this->genre = $v; + $this->modifiedColumns[] = CcFilesPeer::GENRE; + } + + return $this; + } // setGenre() + + /** + * Set the value of [comments] column. + * + * @param string $v new value + * @return CcFiles The current object (for fluent API support) + */ + public function setComments($v) + { + if ($v !== null) { + $v = (string) $v; + } + + if ($this->comments !== $v) { + $this->comments = $v; + $this->modifiedColumns[] = CcFilesPeer::COMMENTS; + } + + return $this; + } // setComments() + + /** + * Set the value of [year] column. + * + * @param string $v new value + * @return CcFiles The current object (for fluent API support) + */ + public function setYear($v) + { + if ($v !== null) { + $v = (string) $v; + } + + if ($this->year !== $v) { + $this->year = $v; + $this->modifiedColumns[] = CcFilesPeer::YEAR; + } + + return $this; + } // setYear() + + /** + * Set the value of [track_number] column. + * + * @param int $v new value + * @return CcFiles The current object (for fluent API support) + */ + public function setTrackNumber($v) + { + if ($v !== null) { + $v = (int) $v; + } + + if ($this->track_number !== $v) { + $this->track_number = $v; + $this->modifiedColumns[] = CcFilesPeer::TRACK_NUMBER; + } + + return $this; + } // setTrackNumber() + + /** + * Set the value of [channels] column. + * + * @param int $v new value + * @return CcFiles The current object (for fluent API support) + */ + public function setChannels($v) + { + if ($v !== null) { + $v = (int) $v; + } + + if ($this->channels !== $v) { + $this->channels = $v; + $this->modifiedColumns[] = CcFilesPeer::CHANNELS; + } + + return $this; + } // setChannels() + + /** + * Set the value of [url] column. + * + * @param string $v new value + * @return CcFiles The current object (for fluent API support) + */ + public function setUrl($v) + { + if ($v !== null) { + $v = (string) $v; + } + + if ($this->url !== $v) { + $this->url = $v; + $this->modifiedColumns[] = CcFilesPeer::URL; + } + + return $this; + } // setUrl() + + /** + * Set the value of [bpm] column. + * + * @param string $v new value + * @return CcFiles The current object (for fluent API support) + */ + public function setBpm($v) + { + if ($v !== null) { + $v = (string) $v; + } + + if ($this->bpm !== $v) { + $this->bpm = $v; + $this->modifiedColumns[] = CcFilesPeer::BPM; + } + + return $this; + } // setBpm() + + /** + * Set the value of [rating] column. + * + * @param string $v new value + * @return CcFiles The current object (for fluent API support) + */ + public function setRating($v) + { + if ($v !== null) { + $v = (string) $v; + } + + if ($this->rating !== $v) { + $this->rating = $v; + $this->modifiedColumns[] = CcFilesPeer::RATING; + } + + return $this; + } // setRating() + + /** + * Set the value of [encoded_by] column. + * + * @param string $v new value + * @return CcFiles The current object (for fluent API support) + */ + public function setEncodedBy($v) + { + if ($v !== null) { + $v = (string) $v; + } + + if ($this->encoded_by !== $v) { + $this->encoded_by = $v; + $this->modifiedColumns[] = CcFilesPeer::ENCODED_BY; + } + + return $this; + } // setEncodedBy() + + /** + * Set the value of [disc_number] column. + * + * @param string $v new value + * @return CcFiles The current object (for fluent API support) + */ + public function setDiscNumber($v) + { + if ($v !== null) { + $v = (string) $v; + } + + if ($this->disc_number !== $v) { + $this->disc_number = $v; + $this->modifiedColumns[] = CcFilesPeer::DISC_NUMBER; + } + + return $this; + } // setDiscNumber() + + /** + * Set the value of [mood] column. + * + * @param string $v new value + * @return CcFiles The current object (for fluent API support) + */ + public function setMood($v) + { + if ($v !== null) { + $v = (string) $v; + } + + if ($this->mood !== $v) { + $this->mood = $v; + $this->modifiedColumns[] = CcFilesPeer::MOOD; + } + + return $this; + } // setMood() + + /** + * Set the value of [label] column. + * + * @param string $v new value + * @return CcFiles The current object (for fluent API support) + */ + public function setLabel($v) + { + if ($v !== null) { + $v = (string) $v; + } + + if ($this->label !== $v) { + $this->label = $v; + $this->modifiedColumns[] = CcFilesPeer::LABEL; + } + + return $this; + } // setLabel() + + /** + * Set the value of [composer] column. + * + * @param string $v new value + * @return CcFiles The current object (for fluent API support) + */ + public function setComposer($v) + { + if ($v !== null) { + $v = (string) $v; + } + + if ($this->composer !== $v) { + $this->composer = $v; + $this->modifiedColumns[] = CcFilesPeer::COMPOSER; + } + + return $this; + } // setComposer() + + /** + * Set the value of [encoder] column. + * + * @param string $v new value + * @return CcFiles The current object (for fluent API support) + */ + public function setEncoder($v) + { + if ($v !== null) { + $v = (string) $v; + } + + if ($this->encoder !== $v) { + $this->encoder = $v; + $this->modifiedColumns[] = CcFilesPeer::ENCODER; + } + + return $this; + } // setEncoder() + + /** + * Set the value of [checksum] column. + * + * @param string $v new value + * @return CcFiles The current object (for fluent API support) + */ + public function setChecksum($v) + { + if ($v !== null) { + $v = (string) $v; + } + + if ($this->checksum !== $v) { + $this->checksum = $v; + $this->modifiedColumns[] = CcFilesPeer::CHECKSUM; + } + + return $this; + } // setChecksum() + + /** + * Set the value of [lyrics] column. + * + * @param string $v new value + * @return CcFiles The current object (for fluent API support) + */ + public function setLyrics($v) + { + if ($v !== null) { + $v = (string) $v; + } + + if ($this->lyrics !== $v) { + $this->lyrics = $v; + $this->modifiedColumns[] = CcFilesPeer::LYRICS; + } + + return $this; + } // setLyrics() + + /** + * Set the value of [orchestra] column. + * + * @param string $v new value + * @return CcFiles The current object (for fluent API support) + */ + public function setOrchestra($v) + { + if ($v !== null) { + $v = (string) $v; + } + + if ($this->orchestra !== $v) { + $this->orchestra = $v; + $this->modifiedColumns[] = CcFilesPeer::ORCHESTRA; + } + + return $this; + } // setOrchestra() + + /** + * Set the value of [conductor] column. + * + * @param string $v new value + * @return CcFiles The current object (for fluent API support) + */ + public function setConductor($v) + { + if ($v !== null) { + $v = (string) $v; + } + + if ($this->conductor !== $v) { + $this->conductor = $v; + $this->modifiedColumns[] = CcFilesPeer::CONDUCTOR; + } + + return $this; + } // setConductor() + + /** + * Set the value of [lyricist] column. + * + * @param string $v new value + * @return CcFiles The current object (for fluent API support) + */ + public function setLyricist($v) + { + if ($v !== null) { + $v = (string) $v; + } + + if ($this->lyricist !== $v) { + $this->lyricist = $v; + $this->modifiedColumns[] = CcFilesPeer::LYRICIST; + } + + return $this; + } // setLyricist() + + /** + * Set the value of [original_lyricist] column. + * + * @param string $v new value + * @return CcFiles The current object (for fluent API support) + */ + public function setOriginalLyricist($v) + { + if ($v !== null) { + $v = (string) $v; + } + + if ($this->original_lyricist !== $v) { + $this->original_lyricist = $v; + $this->modifiedColumns[] = CcFilesPeer::ORIGINAL_LYRICIST; + } + + return $this; + } // setOriginalLyricist() + + /** + * Set the value of [radio_station_name] column. + * + * @param string $v new value + * @return CcFiles The current object (for fluent API support) + */ + public function setRadioStationName($v) + { + if ($v !== null) { + $v = (string) $v; + } + + if ($this->radio_station_name !== $v) { + $this->radio_station_name = $v; + $this->modifiedColumns[] = CcFilesPeer::RADIO_STATION_NAME; + } + + return $this; + } // setRadioStationName() + + /** + * Set the value of [info_url] column. + * + * @param string $v new value + * @return CcFiles The current object (for fluent API support) + */ + public function setInfoUrl($v) + { + if ($v !== null) { + $v = (string) $v; + } + + if ($this->info_url !== $v) { + $this->info_url = $v; + $this->modifiedColumns[] = CcFilesPeer::INFO_URL; + } + + return $this; + } // setInfoUrl() + + /** + * Set the value of [artist_url] column. + * + * @param string $v new value + * @return CcFiles The current object (for fluent API support) + */ + public function setArtistUrl($v) + { + if ($v !== null) { + $v = (string) $v; + } + + if ($this->artist_url !== $v) { + $this->artist_url = $v; + $this->modifiedColumns[] = CcFilesPeer::ARTIST_URL; + } + + return $this; + } // setArtistUrl() + + /** + * Set the value of [audio_source_url] column. + * + * @param string $v new value + * @return CcFiles The current object (for fluent API support) + */ + public function setAudioSourceUrl($v) + { + if ($v !== null) { + $v = (string) $v; + } + + if ($this->audio_source_url !== $v) { + $this->audio_source_url = $v; + $this->modifiedColumns[] = CcFilesPeer::AUDIO_SOURCE_URL; + } + + return $this; + } // setAudioSourceUrl() + + /** + * Set the value of [radio_station_url] column. + * + * @param string $v new value + * @return CcFiles The current object (for fluent API support) + */ + public function setRadioStationUrl($v) + { + if ($v !== null) { + $v = (string) $v; + } + + if ($this->radio_station_url !== $v) { + $this->radio_station_url = $v; + $this->modifiedColumns[] = CcFilesPeer::RADIO_STATION_URL; + } + + return $this; + } // setRadioStationUrl() + + /** + * Set the value of [buy_this_url] column. + * + * @param string $v new value + * @return CcFiles The current object (for fluent API support) + */ + public function setBuyThisUrl($v) + { + if ($v !== null) { + $v = (string) $v; + } + + if ($this->buy_this_url !== $v) { + $this->buy_this_url = $v; + $this->modifiedColumns[] = CcFilesPeer::BUY_THIS_URL; + } + + return $this; + } // setBuyThisUrl() + + /** + * Set the value of [isrc_number] column. + * + * @param string $v new value + * @return CcFiles The current object (for fluent API support) + */ + public function setIsrcNumber($v) + { + if ($v !== null) { + $v = (string) $v; + } + + if ($this->isrc_number !== $v) { + $this->isrc_number = $v; + $this->modifiedColumns[] = CcFilesPeer::ISRC_NUMBER; + } + + return $this; + } // setIsrcNumber() + + /** + * Set the value of [catalog_number] column. + * + * @param string $v new value + * @return CcFiles The current object (for fluent API support) + */ + public function setCatalogNumber($v) + { + if ($v !== null) { + $v = (string) $v; + } + + if ($this->catalog_number !== $v) { + $this->catalog_number = $v; + $this->modifiedColumns[] = CcFilesPeer::CATALOG_NUMBER; + } + + return $this; + } // setCatalogNumber() + + /** + * Set the value of [original_artist] column. + * + * @param string $v new value + * @return CcFiles The current object (for fluent API support) + */ + public function setOriginalArtist($v) + { + if ($v !== null) { + $v = (string) $v; + } + + if ($this->original_artist !== $v) { + $this->original_artist = $v; + $this->modifiedColumns[] = CcFilesPeer::ORIGINAL_ARTIST; + } + + return $this; + } // setOriginalArtist() + + /** + * Set the value of [copyright] column. + * + * @param string $v new value + * @return CcFiles The current object (for fluent API support) + */ + public function setCopyright($v) + { + if ($v !== null) { + $v = (string) $v; + } + + if ($this->copyright !== $v) { + $this->copyright = $v; + $this->modifiedColumns[] = CcFilesPeer::COPYRIGHT; + } + + return $this; + } // setCopyright() + + /** + * Set the value of [report_datetime] column. + * + * @param string $v new value + * @return CcFiles The current object (for fluent API support) + */ + public function setReportDatetime($v) + { + if ($v !== null) { + $v = (string) $v; + } + + if ($this->report_datetime !== $v) { + $this->report_datetime = $v; + $this->modifiedColumns[] = CcFilesPeer::REPORT_DATETIME; + } + + return $this; + } // setReportDatetime() + + /** + * Set the value of [report_location] column. + * + * @param string $v new value + * @return CcFiles The current object (for fluent API support) + */ + public function setReportLocation($v) + { + if ($v !== null) { + $v = (string) $v; + } + + if ($this->report_location !== $v) { + $this->report_location = $v; + $this->modifiedColumns[] = CcFilesPeer::REPORT_LOCATION; + } + + return $this; + } // setReportLocation() + + /** + * Set the value of [report_organization] column. + * + * @param string $v new value + * @return CcFiles The current object (for fluent API support) + */ + public function setReportOrganization($v) + { + if ($v !== null) { + $v = (string) $v; + } + + if ($this->report_organization !== $v) { + $this->report_organization = $v; + $this->modifiedColumns[] = CcFilesPeer::REPORT_ORGANIZATION; + } + + return $this; + } // setReportOrganization() + + /** + * Set the value of [subject] column. + * + * @param string $v new value + * @return CcFiles The current object (for fluent API support) + */ + public function setSubject($v) + { + if ($v !== null) { + $v = (string) $v; + } + + if ($this->subject !== $v) { + $this->subject = $v; + $this->modifiedColumns[] = CcFilesPeer::SUBJECT; + } + + return $this; + } // setSubject() + + /** + * Set the value of [contributor] column. + * + * @param string $v new value + * @return CcFiles The current object (for fluent API support) + */ + public function setContributor($v) + { + if ($v !== null) { + $v = (string) $v; + } + + if ($this->contributor !== $v) { + $this->contributor = $v; + $this->modifiedColumns[] = CcFilesPeer::CONTRIBUTOR; + } + + return $this; + } // setContributor() + + /** + * Set the value of [language] column. + * + * @param string $v new value + * @return CcFiles The current object (for fluent API support) + */ + public function setLanguage($v) + { + if ($v !== null) { + $v = (string) $v; + } + + if ($this->language !== $v) { + $this->language = $v; + $this->modifiedColumns[] = CcFilesPeer::LANGUAGE; + } + + return $this; + } // setLanguage() + + /** + * Indicates whether the columns in this object are only set to default values. + * + * This method can be used in conjunction with isModified() to indicate whether an object is both + * modified _and_ has some values set which are non-default. + * + * @return boolean Whether the columns in this object are only been set with default values. + */ + public function hasOnlyDefaultValues() + { + if ($this->name !== '') { + return false; + } + + if ($this->mime !== '') { + return false; + } + + if ($this->ftype !== '') { + return false; + } + + if ($this->filepath !== '') { + return false; + } + + if ($this->state !== 'empty') { + return false; + } + + if ($this->currentlyaccessing !== 0) { + return false; + } + + // otherwise, everything was equal, so return TRUE + return true; + } // hasOnlyDefaultValues() + + /** + * Hydrates (populates) the object variables with values from the database resultset. + * + * An offset (0-based "start column") is specified so that objects can be hydrated + * with a subset of the columns in the resultset rows. This is needed, for example, + * for results of JOIN queries where the resultset row includes columns from two or + * more tables. + * + * @param array $row The row returned by PDOStatement->fetch(PDO::FETCH_NUM) + * @param int $startcol 0-based offset column which indicates which restultset column to start with. + * @param boolean $rehydrate Whether this object is being re-hydrated from the database. + * @return int next starting column + * @throws PropelException - Any caught Exception will be rewrapped as a PropelException. + */ + public function hydrate($row, $startcol = 0, $rehydrate = false) + { + try { + + $this->id = ($row[$startcol + 0] !== null) ? (int) $row[$startcol + 0] : null; + $this->gunid = ($row[$startcol + 1] !== null) ? (string) $row[$startcol + 1] : null; + $this->name = ($row[$startcol + 2] !== null) ? (string) $row[$startcol + 2] : null; + $this->mime = ($row[$startcol + 3] !== null) ? (string) $row[$startcol + 3] : null; + $this->ftype = ($row[$startcol + 4] !== null) ? (string) $row[$startcol + 4] : null; + $this->filepath = ($row[$startcol + 5] !== null) ? (string) $row[$startcol + 5] : null; + $this->state = ($row[$startcol + 6] !== null) ? (string) $row[$startcol + 6] : null; + $this->currentlyaccessing = ($row[$startcol + 7] !== null) ? (int) $row[$startcol + 7] : null; + $this->editedby = ($row[$startcol + 8] !== null) ? (int) $row[$startcol + 8] : null; + $this->mtime = ($row[$startcol + 9] !== null) ? (string) $row[$startcol + 9] : null; + $this->md5 = ($row[$startcol + 10] !== null) ? (string) $row[$startcol + 10] : null; + $this->track_title = ($row[$startcol + 11] !== null) ? (string) $row[$startcol + 11] : null; + $this->artist_name = ($row[$startcol + 12] !== null) ? (string) $row[$startcol + 12] : null; + $this->bit_rate = ($row[$startcol + 13] !== null) ? (string) $row[$startcol + 13] : null; + $this->sample_rate = ($row[$startcol + 14] !== null) ? (string) $row[$startcol + 14] : null; + $this->format = ($row[$startcol + 15] !== null) ? (string) $row[$startcol + 15] : null; + $this->length = ($row[$startcol + 16] !== null) ? (string) $row[$startcol + 16] : null; + $this->album_title = ($row[$startcol + 17] !== null) ? (string) $row[$startcol + 17] : null; + $this->genre = ($row[$startcol + 18] !== null) ? (string) $row[$startcol + 18] : null; + $this->comments = ($row[$startcol + 19] !== null) ? (string) $row[$startcol + 19] : null; + $this->year = ($row[$startcol + 20] !== null) ? (string) $row[$startcol + 20] : null; + $this->track_number = ($row[$startcol + 21] !== null) ? (int) $row[$startcol + 21] : null; + $this->channels = ($row[$startcol + 22] !== null) ? (int) $row[$startcol + 22] : null; + $this->url = ($row[$startcol + 23] !== null) ? (string) $row[$startcol + 23] : null; + $this->bpm = ($row[$startcol + 24] !== null) ? (string) $row[$startcol + 24] : null; + $this->rating = ($row[$startcol + 25] !== null) ? (string) $row[$startcol + 25] : null; + $this->encoded_by = ($row[$startcol + 26] !== null) ? (string) $row[$startcol + 26] : null; + $this->disc_number = ($row[$startcol + 27] !== null) ? (string) $row[$startcol + 27] : null; + $this->mood = ($row[$startcol + 28] !== null) ? (string) $row[$startcol + 28] : null; + $this->label = ($row[$startcol + 29] !== null) ? (string) $row[$startcol + 29] : null; + $this->composer = ($row[$startcol + 30] !== null) ? (string) $row[$startcol + 30] : null; + $this->encoder = ($row[$startcol + 31] !== null) ? (string) $row[$startcol + 31] : null; + $this->checksum = ($row[$startcol + 32] !== null) ? (string) $row[$startcol + 32] : null; + $this->lyrics = ($row[$startcol + 33] !== null) ? (string) $row[$startcol + 33] : null; + $this->orchestra = ($row[$startcol + 34] !== null) ? (string) $row[$startcol + 34] : null; + $this->conductor = ($row[$startcol + 35] !== null) ? (string) $row[$startcol + 35] : null; + $this->lyricist = ($row[$startcol + 36] !== null) ? (string) $row[$startcol + 36] : null; + $this->original_lyricist = ($row[$startcol + 37] !== null) ? (string) $row[$startcol + 37] : null; + $this->radio_station_name = ($row[$startcol + 38] !== null) ? (string) $row[$startcol + 38] : null; + $this->info_url = ($row[$startcol + 39] !== null) ? (string) $row[$startcol + 39] : null; + $this->artist_url = ($row[$startcol + 40] !== null) ? (string) $row[$startcol + 40] : null; + $this->audio_source_url = ($row[$startcol + 41] !== null) ? (string) $row[$startcol + 41] : null; + $this->radio_station_url = ($row[$startcol + 42] !== null) ? (string) $row[$startcol + 42] : null; + $this->buy_this_url = ($row[$startcol + 43] !== null) ? (string) $row[$startcol + 43] : null; + $this->isrc_number = ($row[$startcol + 44] !== null) ? (string) $row[$startcol + 44] : null; + $this->catalog_number = ($row[$startcol + 45] !== null) ? (string) $row[$startcol + 45] : null; + $this->original_artist = ($row[$startcol + 46] !== null) ? (string) $row[$startcol + 46] : null; + $this->copyright = ($row[$startcol + 47] !== null) ? (string) $row[$startcol + 47] : null; + $this->report_datetime = ($row[$startcol + 48] !== null) ? (string) $row[$startcol + 48] : null; + $this->report_location = ($row[$startcol + 49] !== null) ? (string) $row[$startcol + 49] : null; + $this->report_organization = ($row[$startcol + 50] !== null) ? (string) $row[$startcol + 50] : null; + $this->subject = ($row[$startcol + 51] !== null) ? (string) $row[$startcol + 51] : null; + $this->contributor = ($row[$startcol + 52] !== null) ? (string) $row[$startcol + 52] : null; + $this->language = ($row[$startcol + 53] !== null) ? (string) $row[$startcol + 53] : null; + $this->resetModified(); + + $this->setNew(false); + + if ($rehydrate) { + $this->ensureConsistency(); + } + + return $startcol + 54; // 54 = CcFilesPeer::NUM_COLUMNS - CcFilesPeer::NUM_LAZY_LOAD_COLUMNS). + + } catch (Exception $e) { + throw new PropelException("Error populating CcFiles object", $e); + } + } + + /** + * Checks and repairs the internal consistency of the object. + * + * This method is executed after an already-instantiated object is re-hydrated + * from the database. It exists to check any foreign keys to make sure that + * the objects related to the current object are correct based on foreign key. + * + * You can override this method in the stub class, but you should always invoke + * the base method from the overridden method (i.e. parent::ensureConsistency()), + * in case your model changes. + * + * @throws PropelException + */ + public function ensureConsistency() + { + + if ($this->aCcSubjs !== null && $this->editedby !== $this->aCcSubjs->getId()) { + $this->aCcSubjs = null; + } + } // ensureConsistency + + /** + * Reloads this object from datastore based on primary key and (optionally) resets all associated objects. + * + * This will only work if the object has been saved and has a valid primary key set. + * + * @param boolean $deep (optional) Whether to also de-associated any related objects. + * @param PropelPDO $con (optional) The PropelPDO connection to use. + * @return void + * @throws PropelException - if this object is deleted, unsaved or doesn't have pk match in db + */ + public function reload($deep = false, PropelPDO $con = null) + { + if ($this->isDeleted()) { + throw new PropelException("Cannot reload a deleted object."); + } + + if ($this->isNew()) { + throw new PropelException("Cannot reload an unsaved object."); + } + + if ($con === null) { + $con = Propel::getConnection(CcFilesPeer::DATABASE_NAME, Propel::CONNECTION_READ); + } + + // We don't need to alter the object instance pool; we're just modifying this instance + // already in the pool. + + $stmt = CcFilesPeer::doSelectStmt($this->buildPkeyCriteria(), $con); + $row = $stmt->fetch(PDO::FETCH_NUM); + $stmt->closeCursor(); + if (!$row) { + throw new PropelException('Cannot find matching row in the database to reload object values.'); + } + $this->hydrate($row, 0, true); // rehydrate + + if ($deep) { // also de-associate any related objects? + + $this->aCcSubjs = null; + $this->collCcPlaylistcontentss = null; + + } // if (deep) + } + + /** + * Removes this object from datastore and sets delete attribute. + * + * @param PropelPDO $con + * @return void + * @throws PropelException + * @see BaseObject::setDeleted() + * @see BaseObject::isDeleted() + */ + public function delete(PropelPDO $con = null) + { + if ($this->isDeleted()) { + throw new PropelException("This object has already been deleted."); + } + + if ($con === null) { + $con = Propel::getConnection(CcFilesPeer::DATABASE_NAME, Propel::CONNECTION_WRITE); + } + + $con->beginTransaction(); + try { + $ret = $this->preDelete($con); + if ($ret) { + CcFilesQuery::create() + ->filterByPrimaryKey($this->getPrimaryKey()) + ->delete($con); + $this->postDelete($con); + $con->commit(); + $this->setDeleted(true); + } else { + $con->commit(); + } + } catch (PropelException $e) { + $con->rollBack(); + throw $e; + } + } + + /** + * Persists this object to the database. + * + * If the object is new, it inserts it; otherwise an update is performed. + * All modified related objects will also be persisted in the doSave() + * method. This method wraps all precipitate database operations in a + * single transaction. + * + * @param PropelPDO $con + * @return int The number of rows affected by this insert/update and any referring fk objects' save() operations. + * @throws PropelException + * @see doSave() + */ + public function save(PropelPDO $con = null) + { + if ($this->isDeleted()) { + throw new PropelException("You cannot save an object that has been deleted."); + } + + if ($con === null) { + $con = Propel::getConnection(CcFilesPeer::DATABASE_NAME, Propel::CONNECTION_WRITE); + } + + $con->beginTransaction(); + $isInsert = $this->isNew(); + try { + $ret = $this->preSave($con); + if ($isInsert) { + $ret = $ret && $this->preInsert($con); + } else { + $ret = $ret && $this->preUpdate($con); + } + if ($ret) { + $affectedRows = $this->doSave($con); + if ($isInsert) { + $this->postInsert($con); + } else { + $this->postUpdate($con); + } + $this->postSave($con); + CcFilesPeer::addInstanceToPool($this); + } else { + $affectedRows = 0; + } + $con->commit(); + return $affectedRows; + } catch (PropelException $e) { + $con->rollBack(); + throw $e; + } + } + + /** + * Performs the work of inserting or updating the row in the database. + * + * If the object is new, it inserts it; otherwise an update is performed. + * All related objects are also updated in this method. + * + * @param PropelPDO $con + * @return int The number of rows affected by this insert/update and any referring fk objects' save() operations. + * @throws PropelException + * @see save() + */ + protected function doSave(PropelPDO $con) + { + $affectedRows = 0; // initialize var to track total num of affected rows + if (!$this->alreadyInSave) { + $this->alreadyInSave = true; + + // We call the save method on the following object(s) if they + // were passed to this object by their coresponding set + // method. This object relates to these object(s) by a + // foreign key reference. + + if ($this->aCcSubjs !== null) { + if ($this->aCcSubjs->isModified() || $this->aCcSubjs->isNew()) { + $affectedRows += $this->aCcSubjs->save($con); + } + $this->setCcSubjs($this->aCcSubjs); + } + + if ($this->isNew() ) { + $this->modifiedColumns[] = CcFilesPeer::ID; + } + + // If this object has been modified, then save it to the database. + if ($this->isModified()) { + if ($this->isNew()) { + $criteria = $this->buildCriteria(); + if ($criteria->keyContainsValue(CcFilesPeer::ID) ) { + throw new PropelException('Cannot insert a value for auto-increment primary key ('.CcFilesPeer::ID.')'); + } + + $pk = BasePeer::doInsert($criteria, $con); + $affectedRows += 1; + $this->setDbId($pk); //[IMV] update autoincrement primary key + $this->setNew(false); + } else { + $affectedRows += CcFilesPeer::doUpdate($this, $con); + } + + $this->resetModified(); // [HL] After being saved an object is no longer 'modified' + } + + if ($this->collCcPlaylistcontentss !== null) { + foreach ($this->collCcPlaylistcontentss as $referrerFK) { + if (!$referrerFK->isDeleted()) { + $affectedRows += $referrerFK->save($con); + } + } + } + + $this->alreadyInSave = false; + + } + return $affectedRows; + } // doSave() + + /** + * Array of ValidationFailed objects. + * @var array ValidationFailed[] + */ + protected $validationFailures = array(); + + /** + * Gets any ValidationFailed objects that resulted from last call to validate(). + * + * + * @return array ValidationFailed[] + * @see validate() + */ + public function getValidationFailures() + { + return $this->validationFailures; + } + + /** + * Validates the objects modified field values and all objects related to this table. + * + * If $columns is either a column name or an array of column names + * only those columns are validated. + * + * @param mixed $columns Column name or an array of column names. + * @return boolean Whether all columns pass validation. + * @see doValidate() + * @see getValidationFailures() + */ + public function validate($columns = null) + { + $res = $this->doValidate($columns); + if ($res === true) { + $this->validationFailures = array(); + return true; + } else { + $this->validationFailures = $res; + return false; + } + } + + /** + * This function performs the validation work for complex object models. + * + * In addition to checking the current object, all related objects will + * also be validated. If all pass then true is returned; otherwise + * an aggreagated array of ValidationFailed objects will be returned. + * + * @param array $columns Array of column names to validate. + * @return mixed true if all validations pass; array of ValidationFailed objets otherwise. + */ + protected function doValidate($columns = null) + { + if (!$this->alreadyInValidation) { + $this->alreadyInValidation = true; + $retval = null; + + $failureMap = array(); + + + // We call the validate method on the following object(s) if they + // were passed to this object by their coresponding set + // method. This object relates to these object(s) by a + // foreign key reference. + + if ($this->aCcSubjs !== null) { + if (!$this->aCcSubjs->validate($columns)) { + $failureMap = array_merge($failureMap, $this->aCcSubjs->getValidationFailures()); + } + } + + + if (($retval = CcFilesPeer::doValidate($this, $columns)) !== true) { + $failureMap = array_merge($failureMap, $retval); + } + + + if ($this->collCcPlaylistcontentss !== null) { + foreach ($this->collCcPlaylistcontentss as $referrerFK) { + if (!$referrerFK->validate($columns)) { + $failureMap = array_merge($failureMap, $referrerFK->getValidationFailures()); + } + } + } + + + $this->alreadyInValidation = false; + } + + return (!empty($failureMap) ? $failureMap : true); + } + + /** + * Retrieves a field from the object by name passed in as a string. + * + * @param string $name name + * @param string $type The type of fieldname the $name is of: + * one of the class type constants BasePeer::TYPE_PHPNAME, BasePeer::TYPE_STUDLYPHPNAME + * BasePeer::TYPE_COLNAME, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_NUM + * @return mixed Value of field. + */ + public function getByName($name, $type = BasePeer::TYPE_PHPNAME) + { + $pos = CcFilesPeer::translateFieldName($name, $type, BasePeer::TYPE_NUM); + $field = $this->getByPosition($pos); + return $field; + } + + /** + * Retrieves a field from the object by Position as specified in the xml schema. + * Zero-based. + * + * @param int $pos position in xml schema + * @return mixed Value of field at $pos + */ + public function getByPosition($pos) + { + switch($pos) { + case 0: + return $this->getDbId(); + break; + case 1: + return $this->getGunid(); + break; + case 2: + return $this->getName(); + break; + case 3: + return $this->getMime(); + break; + case 4: + return $this->getFtype(); + break; + case 5: + return $this->getfilepath(); + break; + case 6: + return $this->getState(); + break; + case 7: + return $this->getCurrentlyaccessing(); + break; + case 8: + return $this->getEditedby(); + break; + case 9: + return $this->getMtime(); + break; + case 10: + return $this->getMd5(); + break; + case 11: + return $this->getTrackTitle(); + break; + case 12: + return $this->getArtistName(); + break; + case 13: + return $this->getBitRate(); + break; + case 14: + return $this->getSampleRate(); + break; + case 15: + return $this->getFormat(); + break; + case 16: + return $this->getDbLength(); + break; + case 17: + return $this->getAlbumTitle(); + break; + case 18: + return $this->getGenre(); + break; + case 19: + return $this->getComments(); + break; + case 20: + return $this->getYear(); + break; + case 21: + return $this->getTrackNumber(); + break; + case 22: + return $this->getChannels(); + break; + case 23: + return $this->getUrl(); + break; + case 24: + return $this->getBpm(); + break; + case 25: + return $this->getRating(); + break; + case 26: + return $this->getEncodedBy(); + break; + case 27: + return $this->getDiscNumber(); + break; + case 28: + return $this->getMood(); + break; + case 29: + return $this->getLabel(); + break; + case 30: + return $this->getComposer(); + break; + case 31: + return $this->getEncoder(); + break; + case 32: + return $this->getChecksum(); + break; + case 33: + return $this->getLyrics(); + break; + case 34: + return $this->getOrchestra(); + break; + case 35: + return $this->getConductor(); + break; + case 36: + return $this->getLyricist(); + break; + case 37: + return $this->getOriginalLyricist(); + break; + case 38: + return $this->getRadioStationName(); + break; + case 39: + return $this->getInfoUrl(); + break; + case 40: + return $this->getArtistUrl(); + break; + case 41: + return $this->getAudioSourceUrl(); + break; + case 42: + return $this->getRadioStationUrl(); + break; + case 43: + return $this->getBuyThisUrl(); + break; + case 44: + return $this->getIsrcNumber(); + break; + case 45: + return $this->getCatalogNumber(); + break; + case 46: + return $this->getOriginalArtist(); + break; + case 47: + return $this->getCopyright(); + break; + case 48: + return $this->getReportDatetime(); + break; + case 49: + return $this->getReportLocation(); + break; + case 50: + return $this->getReportOrganization(); + break; + case 51: + return $this->getSubject(); + break; + case 52: + return $this->getContributor(); + break; + case 53: + return $this->getLanguage(); + break; + default: + return null; + break; + } // switch() + } + + /** + * Exports the object as an array. + * + * You can specify the key type of the array by passing one of the class + * type constants. + * + * @param string $keyType (optional) One of the class type constants BasePeer::TYPE_PHPNAME, BasePeer::TYPE_STUDLYPHPNAME, + * BasePeer::TYPE_COLNAME, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_NUM. + * Defaults to BasePeer::TYPE_PHPNAME. + * @param boolean $includeLazyLoadColumns (optional) Whether to include lazy loaded columns. Defaults to TRUE. + * @param boolean $includeForeignObjects (optional) Whether to include hydrated related objects. Default to FALSE. + * + * @return array an associative array containing the field names (as keys) and field values + */ + public function toArray($keyType = BasePeer::TYPE_PHPNAME, $includeLazyLoadColumns = true, $includeForeignObjects = false) + { + $keys = CcFilesPeer::getFieldNames($keyType); + $result = array( + $keys[0] => $this->getDbId(), + $keys[1] => $this->getGunid(), + $keys[2] => $this->getName(), + $keys[3] => $this->getMime(), + $keys[4] => $this->getFtype(), + $keys[5] => $this->getfilepath(), + $keys[6] => $this->getState(), + $keys[7] => $this->getCurrentlyaccessing(), + $keys[8] => $this->getEditedby(), + $keys[9] => $this->getMtime(), + $keys[10] => $this->getMd5(), + $keys[11] => $this->getTrackTitle(), + $keys[12] => $this->getArtistName(), + $keys[13] => $this->getBitRate(), + $keys[14] => $this->getSampleRate(), + $keys[15] => $this->getFormat(), + $keys[16] => $this->getDbLength(), + $keys[17] => $this->getAlbumTitle(), + $keys[18] => $this->getGenre(), + $keys[19] => $this->getComments(), + $keys[20] => $this->getYear(), + $keys[21] => $this->getTrackNumber(), + $keys[22] => $this->getChannels(), + $keys[23] => $this->getUrl(), + $keys[24] => $this->getBpm(), + $keys[25] => $this->getRating(), + $keys[26] => $this->getEncodedBy(), + $keys[27] => $this->getDiscNumber(), + $keys[28] => $this->getMood(), + $keys[29] => $this->getLabel(), + $keys[30] => $this->getComposer(), + $keys[31] => $this->getEncoder(), + $keys[32] => $this->getChecksum(), + $keys[33] => $this->getLyrics(), + $keys[34] => $this->getOrchestra(), + $keys[35] => $this->getConductor(), + $keys[36] => $this->getLyricist(), + $keys[37] => $this->getOriginalLyricist(), + $keys[38] => $this->getRadioStationName(), + $keys[39] => $this->getInfoUrl(), + $keys[40] => $this->getArtistUrl(), + $keys[41] => $this->getAudioSourceUrl(), + $keys[42] => $this->getRadioStationUrl(), + $keys[43] => $this->getBuyThisUrl(), + $keys[44] => $this->getIsrcNumber(), + $keys[45] => $this->getCatalogNumber(), + $keys[46] => $this->getOriginalArtist(), + $keys[47] => $this->getCopyright(), + $keys[48] => $this->getReportDatetime(), + $keys[49] => $this->getReportLocation(), + $keys[50] => $this->getReportOrganization(), + $keys[51] => $this->getSubject(), + $keys[52] => $this->getContributor(), + $keys[53] => $this->getLanguage(), + ); + if ($includeForeignObjects) { + if (null !== $this->aCcSubjs) { + $result['CcSubjs'] = $this->aCcSubjs->toArray($keyType, $includeLazyLoadColumns, true); + } + } + return $result; + } + + /** + * Sets a field from the object by name passed in as a string. + * + * @param string $name peer name + * @param mixed $value field value + * @param string $type The type of fieldname the $name is of: + * one of the class type constants BasePeer::TYPE_PHPNAME, BasePeer::TYPE_STUDLYPHPNAME + * BasePeer::TYPE_COLNAME, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_NUM + * @return void + */ + public function setByName($name, $value, $type = BasePeer::TYPE_PHPNAME) + { + $pos = CcFilesPeer::translateFieldName($name, $type, BasePeer::TYPE_NUM); + return $this->setByPosition($pos, $value); + } + + /** + * Sets a field from the object by Position as specified in the xml schema. + * Zero-based. + * + * @param int $pos position in xml schema + * @param mixed $value field value + * @return void + */ + public function setByPosition($pos, $value) + { + switch($pos) { + case 0: + $this->setDbId($value); + break; + case 1: + $this->setGunid($value); + break; + case 2: + $this->setName($value); + break; + case 3: + $this->setMime($value); + break; + case 4: + $this->setFtype($value); + break; + case 5: + $this->setfilepath($value); + break; + case 6: + $this->setState($value); + break; + case 7: + $this->setCurrentlyaccessing($value); + break; + case 8: + $this->setEditedby($value); + break; + case 9: + $this->setMtime($value); + break; + case 10: + $this->setMd5($value); + break; + case 11: + $this->setTrackTitle($value); + break; + case 12: + $this->setArtistName($value); + break; + case 13: + $this->setBitRate($value); + break; + case 14: + $this->setSampleRate($value); + break; + case 15: + $this->setFormat($value); + break; + case 16: + $this->setDbLength($value); + break; + case 17: + $this->setAlbumTitle($value); + break; + case 18: + $this->setGenre($value); + break; + case 19: + $this->setComments($value); + break; + case 20: + $this->setYear($value); + break; + case 21: + $this->setTrackNumber($value); + break; + case 22: + $this->setChannels($value); + break; + case 23: + $this->setUrl($value); + break; + case 24: + $this->setBpm($value); + break; + case 25: + $this->setRating($value); + break; + case 26: + $this->setEncodedBy($value); + break; + case 27: + $this->setDiscNumber($value); + break; + case 28: + $this->setMood($value); + break; + case 29: + $this->setLabel($value); + break; + case 30: + $this->setComposer($value); + break; + case 31: + $this->setEncoder($value); + break; + case 32: + $this->setChecksum($value); + break; + case 33: + $this->setLyrics($value); + break; + case 34: + $this->setOrchestra($value); + break; + case 35: + $this->setConductor($value); + break; + case 36: + $this->setLyricist($value); + break; + case 37: + $this->setOriginalLyricist($value); + break; + case 38: + $this->setRadioStationName($value); + break; + case 39: + $this->setInfoUrl($value); + break; + case 40: + $this->setArtistUrl($value); + break; + case 41: + $this->setAudioSourceUrl($value); + break; + case 42: + $this->setRadioStationUrl($value); + break; + case 43: + $this->setBuyThisUrl($value); + break; + case 44: + $this->setIsrcNumber($value); + break; + case 45: + $this->setCatalogNumber($value); + break; + case 46: + $this->setOriginalArtist($value); + break; + case 47: + $this->setCopyright($value); + break; + case 48: + $this->setReportDatetime($value); + break; + case 49: + $this->setReportLocation($value); + break; + case 50: + $this->setReportOrganization($value); + break; + case 51: + $this->setSubject($value); + break; + case 52: + $this->setContributor($value); + break; + case 53: + $this->setLanguage($value); + break; + } // switch() + } + + /** + * Populates the object using an array. + * + * This is particularly useful when populating an object from one of the + * request arrays (e.g. $_POST). This method goes through the column + * names, checking to see whether a matching key exists in populated + * array. If so the setByName() method is called for that column. + * + * You can specify the key type of the array by additionally passing one + * of the class type constants BasePeer::TYPE_PHPNAME, BasePeer::TYPE_STUDLYPHPNAME, + * BasePeer::TYPE_COLNAME, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_NUM. + * The default key type is the column's phpname (e.g. 'AuthorId') + * + * @param array $arr An array to populate the object from. + * @param string $keyType The type of keys the array uses. + * @return void + */ + public function fromArray($arr, $keyType = BasePeer::TYPE_PHPNAME) + { + $keys = CcFilesPeer::getFieldNames($keyType); + + if (array_key_exists($keys[0], $arr)) $this->setDbId($arr[$keys[0]]); + if (array_key_exists($keys[1], $arr)) $this->setGunid($arr[$keys[1]]); + if (array_key_exists($keys[2], $arr)) $this->setName($arr[$keys[2]]); + if (array_key_exists($keys[3], $arr)) $this->setMime($arr[$keys[3]]); + if (array_key_exists($keys[4], $arr)) $this->setFtype($arr[$keys[4]]); + if (array_key_exists($keys[5], $arr)) $this->setfilepath($arr[$keys[5]]); + if (array_key_exists($keys[6], $arr)) $this->setState($arr[$keys[6]]); + if (array_key_exists($keys[7], $arr)) $this->setCurrentlyaccessing($arr[$keys[7]]); + if (array_key_exists($keys[8], $arr)) $this->setEditedby($arr[$keys[8]]); + if (array_key_exists($keys[9], $arr)) $this->setMtime($arr[$keys[9]]); + if (array_key_exists($keys[10], $arr)) $this->setMd5($arr[$keys[10]]); + if (array_key_exists($keys[11], $arr)) $this->setTrackTitle($arr[$keys[11]]); + if (array_key_exists($keys[12], $arr)) $this->setArtistName($arr[$keys[12]]); + if (array_key_exists($keys[13], $arr)) $this->setBitRate($arr[$keys[13]]); + if (array_key_exists($keys[14], $arr)) $this->setSampleRate($arr[$keys[14]]); + if (array_key_exists($keys[15], $arr)) $this->setFormat($arr[$keys[15]]); + if (array_key_exists($keys[16], $arr)) $this->setDbLength($arr[$keys[16]]); + if (array_key_exists($keys[17], $arr)) $this->setAlbumTitle($arr[$keys[17]]); + if (array_key_exists($keys[18], $arr)) $this->setGenre($arr[$keys[18]]); + if (array_key_exists($keys[19], $arr)) $this->setComments($arr[$keys[19]]); + if (array_key_exists($keys[20], $arr)) $this->setYear($arr[$keys[20]]); + if (array_key_exists($keys[21], $arr)) $this->setTrackNumber($arr[$keys[21]]); + if (array_key_exists($keys[22], $arr)) $this->setChannels($arr[$keys[22]]); + if (array_key_exists($keys[23], $arr)) $this->setUrl($arr[$keys[23]]); + if (array_key_exists($keys[24], $arr)) $this->setBpm($arr[$keys[24]]); + if (array_key_exists($keys[25], $arr)) $this->setRating($arr[$keys[25]]); + if (array_key_exists($keys[26], $arr)) $this->setEncodedBy($arr[$keys[26]]); + if (array_key_exists($keys[27], $arr)) $this->setDiscNumber($arr[$keys[27]]); + if (array_key_exists($keys[28], $arr)) $this->setMood($arr[$keys[28]]); + if (array_key_exists($keys[29], $arr)) $this->setLabel($arr[$keys[29]]); + if (array_key_exists($keys[30], $arr)) $this->setComposer($arr[$keys[30]]); + if (array_key_exists($keys[31], $arr)) $this->setEncoder($arr[$keys[31]]); + if (array_key_exists($keys[32], $arr)) $this->setChecksum($arr[$keys[32]]); + if (array_key_exists($keys[33], $arr)) $this->setLyrics($arr[$keys[33]]); + if (array_key_exists($keys[34], $arr)) $this->setOrchestra($arr[$keys[34]]); + if (array_key_exists($keys[35], $arr)) $this->setConductor($arr[$keys[35]]); + if (array_key_exists($keys[36], $arr)) $this->setLyricist($arr[$keys[36]]); + if (array_key_exists($keys[37], $arr)) $this->setOriginalLyricist($arr[$keys[37]]); + if (array_key_exists($keys[38], $arr)) $this->setRadioStationName($arr[$keys[38]]); + if (array_key_exists($keys[39], $arr)) $this->setInfoUrl($arr[$keys[39]]); + if (array_key_exists($keys[40], $arr)) $this->setArtistUrl($arr[$keys[40]]); + if (array_key_exists($keys[41], $arr)) $this->setAudioSourceUrl($arr[$keys[41]]); + if (array_key_exists($keys[42], $arr)) $this->setRadioStationUrl($arr[$keys[42]]); + if (array_key_exists($keys[43], $arr)) $this->setBuyThisUrl($arr[$keys[43]]); + if (array_key_exists($keys[44], $arr)) $this->setIsrcNumber($arr[$keys[44]]); + if (array_key_exists($keys[45], $arr)) $this->setCatalogNumber($arr[$keys[45]]); + if (array_key_exists($keys[46], $arr)) $this->setOriginalArtist($arr[$keys[46]]); + if (array_key_exists($keys[47], $arr)) $this->setCopyright($arr[$keys[47]]); + if (array_key_exists($keys[48], $arr)) $this->setReportDatetime($arr[$keys[48]]); + if (array_key_exists($keys[49], $arr)) $this->setReportLocation($arr[$keys[49]]); + if (array_key_exists($keys[50], $arr)) $this->setReportOrganization($arr[$keys[50]]); + if (array_key_exists($keys[51], $arr)) $this->setSubject($arr[$keys[51]]); + if (array_key_exists($keys[52], $arr)) $this->setContributor($arr[$keys[52]]); + if (array_key_exists($keys[53], $arr)) $this->setLanguage($arr[$keys[53]]); + } + + /** + * Build a Criteria object containing the values of all modified columns in this object. + * + * @return Criteria The Criteria object containing all modified values. + */ + public function buildCriteria() + { + $criteria = new Criteria(CcFilesPeer::DATABASE_NAME); + + if ($this->isColumnModified(CcFilesPeer::ID)) $criteria->add(CcFilesPeer::ID, $this->id); + if ($this->isColumnModified(CcFilesPeer::GUNID)) $criteria->add(CcFilesPeer::GUNID, $this->gunid); + if ($this->isColumnModified(CcFilesPeer::NAME)) $criteria->add(CcFilesPeer::NAME, $this->name); + if ($this->isColumnModified(CcFilesPeer::MIME)) $criteria->add(CcFilesPeer::MIME, $this->mime); + if ($this->isColumnModified(CcFilesPeer::FTYPE)) $criteria->add(CcFilesPeer::FTYPE, $this->ftype); + if ($this->isColumnModified(CcFilesPeer::FILEPATH)) $criteria->add(CcFilesPeer::FILEPATH, $this->filepath); + if ($this->isColumnModified(CcFilesPeer::STATE)) $criteria->add(CcFilesPeer::STATE, $this->state); + if ($this->isColumnModified(CcFilesPeer::CURRENTLYACCESSING)) $criteria->add(CcFilesPeer::CURRENTLYACCESSING, $this->currentlyaccessing); + if ($this->isColumnModified(CcFilesPeer::EDITEDBY)) $criteria->add(CcFilesPeer::EDITEDBY, $this->editedby); + if ($this->isColumnModified(CcFilesPeer::MTIME)) $criteria->add(CcFilesPeer::MTIME, $this->mtime); + if ($this->isColumnModified(CcFilesPeer::MD5)) $criteria->add(CcFilesPeer::MD5, $this->md5); + if ($this->isColumnModified(CcFilesPeer::TRACK_TITLE)) $criteria->add(CcFilesPeer::TRACK_TITLE, $this->track_title); + if ($this->isColumnModified(CcFilesPeer::ARTIST_NAME)) $criteria->add(CcFilesPeer::ARTIST_NAME, $this->artist_name); + if ($this->isColumnModified(CcFilesPeer::BIT_RATE)) $criteria->add(CcFilesPeer::BIT_RATE, $this->bit_rate); + if ($this->isColumnModified(CcFilesPeer::SAMPLE_RATE)) $criteria->add(CcFilesPeer::SAMPLE_RATE, $this->sample_rate); + if ($this->isColumnModified(CcFilesPeer::FORMAT)) $criteria->add(CcFilesPeer::FORMAT, $this->format); + if ($this->isColumnModified(CcFilesPeer::LENGTH)) $criteria->add(CcFilesPeer::LENGTH, $this->length); + if ($this->isColumnModified(CcFilesPeer::ALBUM_TITLE)) $criteria->add(CcFilesPeer::ALBUM_TITLE, $this->album_title); + if ($this->isColumnModified(CcFilesPeer::GENRE)) $criteria->add(CcFilesPeer::GENRE, $this->genre); + if ($this->isColumnModified(CcFilesPeer::COMMENTS)) $criteria->add(CcFilesPeer::COMMENTS, $this->comments); + if ($this->isColumnModified(CcFilesPeer::YEAR)) $criteria->add(CcFilesPeer::YEAR, $this->year); + if ($this->isColumnModified(CcFilesPeer::TRACK_NUMBER)) $criteria->add(CcFilesPeer::TRACK_NUMBER, $this->track_number); + if ($this->isColumnModified(CcFilesPeer::CHANNELS)) $criteria->add(CcFilesPeer::CHANNELS, $this->channels); + if ($this->isColumnModified(CcFilesPeer::URL)) $criteria->add(CcFilesPeer::URL, $this->url); + if ($this->isColumnModified(CcFilesPeer::BPM)) $criteria->add(CcFilesPeer::BPM, $this->bpm); + if ($this->isColumnModified(CcFilesPeer::RATING)) $criteria->add(CcFilesPeer::RATING, $this->rating); + if ($this->isColumnModified(CcFilesPeer::ENCODED_BY)) $criteria->add(CcFilesPeer::ENCODED_BY, $this->encoded_by); + if ($this->isColumnModified(CcFilesPeer::DISC_NUMBER)) $criteria->add(CcFilesPeer::DISC_NUMBER, $this->disc_number); + if ($this->isColumnModified(CcFilesPeer::MOOD)) $criteria->add(CcFilesPeer::MOOD, $this->mood); + if ($this->isColumnModified(CcFilesPeer::LABEL)) $criteria->add(CcFilesPeer::LABEL, $this->label); + if ($this->isColumnModified(CcFilesPeer::COMPOSER)) $criteria->add(CcFilesPeer::COMPOSER, $this->composer); + if ($this->isColumnModified(CcFilesPeer::ENCODER)) $criteria->add(CcFilesPeer::ENCODER, $this->encoder); + if ($this->isColumnModified(CcFilesPeer::CHECKSUM)) $criteria->add(CcFilesPeer::CHECKSUM, $this->checksum); + if ($this->isColumnModified(CcFilesPeer::LYRICS)) $criteria->add(CcFilesPeer::LYRICS, $this->lyrics); + if ($this->isColumnModified(CcFilesPeer::ORCHESTRA)) $criteria->add(CcFilesPeer::ORCHESTRA, $this->orchestra); + if ($this->isColumnModified(CcFilesPeer::CONDUCTOR)) $criteria->add(CcFilesPeer::CONDUCTOR, $this->conductor); + if ($this->isColumnModified(CcFilesPeer::LYRICIST)) $criteria->add(CcFilesPeer::LYRICIST, $this->lyricist); + if ($this->isColumnModified(CcFilesPeer::ORIGINAL_LYRICIST)) $criteria->add(CcFilesPeer::ORIGINAL_LYRICIST, $this->original_lyricist); + if ($this->isColumnModified(CcFilesPeer::RADIO_STATION_NAME)) $criteria->add(CcFilesPeer::RADIO_STATION_NAME, $this->radio_station_name); + if ($this->isColumnModified(CcFilesPeer::INFO_URL)) $criteria->add(CcFilesPeer::INFO_URL, $this->info_url); + if ($this->isColumnModified(CcFilesPeer::ARTIST_URL)) $criteria->add(CcFilesPeer::ARTIST_URL, $this->artist_url); + if ($this->isColumnModified(CcFilesPeer::AUDIO_SOURCE_URL)) $criteria->add(CcFilesPeer::AUDIO_SOURCE_URL, $this->audio_source_url); + if ($this->isColumnModified(CcFilesPeer::RADIO_STATION_URL)) $criteria->add(CcFilesPeer::RADIO_STATION_URL, $this->radio_station_url); + if ($this->isColumnModified(CcFilesPeer::BUY_THIS_URL)) $criteria->add(CcFilesPeer::BUY_THIS_URL, $this->buy_this_url); + if ($this->isColumnModified(CcFilesPeer::ISRC_NUMBER)) $criteria->add(CcFilesPeer::ISRC_NUMBER, $this->isrc_number); + if ($this->isColumnModified(CcFilesPeer::CATALOG_NUMBER)) $criteria->add(CcFilesPeer::CATALOG_NUMBER, $this->catalog_number); + if ($this->isColumnModified(CcFilesPeer::ORIGINAL_ARTIST)) $criteria->add(CcFilesPeer::ORIGINAL_ARTIST, $this->original_artist); + if ($this->isColumnModified(CcFilesPeer::COPYRIGHT)) $criteria->add(CcFilesPeer::COPYRIGHT, $this->copyright); + if ($this->isColumnModified(CcFilesPeer::REPORT_DATETIME)) $criteria->add(CcFilesPeer::REPORT_DATETIME, $this->report_datetime); + if ($this->isColumnModified(CcFilesPeer::REPORT_LOCATION)) $criteria->add(CcFilesPeer::REPORT_LOCATION, $this->report_location); + if ($this->isColumnModified(CcFilesPeer::REPORT_ORGANIZATION)) $criteria->add(CcFilesPeer::REPORT_ORGANIZATION, $this->report_organization); + if ($this->isColumnModified(CcFilesPeer::SUBJECT)) $criteria->add(CcFilesPeer::SUBJECT, $this->subject); + if ($this->isColumnModified(CcFilesPeer::CONTRIBUTOR)) $criteria->add(CcFilesPeer::CONTRIBUTOR, $this->contributor); + if ($this->isColumnModified(CcFilesPeer::LANGUAGE)) $criteria->add(CcFilesPeer::LANGUAGE, $this->language); + + return $criteria; + } + + /** + * Builds a Criteria object containing the primary key for this object. + * + * Unlike buildCriteria() this method includes the primary key values regardless + * of whether or not they have been modified. + * + * @return Criteria The Criteria object containing value(s) for primary key(s). + */ + public function buildPkeyCriteria() + { + $criteria = new Criteria(CcFilesPeer::DATABASE_NAME); + $criteria->add(CcFilesPeer::ID, $this->id); + + return $criteria; + } + + /** + * Returns the primary key for this object (row). + * @return int + */ + public function getPrimaryKey() + { + return $this->getDbId(); + } + + /** + * Generic method to set the primary key (id column). + * + * @param int $key Primary key. + * @return void + */ + public function setPrimaryKey($key) + { + $this->setDbId($key); + } + + /** + * Returns true if the primary key for this object is null. + * @return boolean + */ + public function isPrimaryKeyNull() + { + return null === $this->getDbId(); + } + + /** + * Sets contents of passed object to values from current object. + * + * If desired, this method can also make copies of all associated (fkey referrers) + * objects. + * + * @param object $copyObj An object of CcFiles (or compatible) type. + * @param boolean $deepCopy Whether to also copy all rows that refer (by fkey) to the current row. + * @throws PropelException + */ + public function copyInto($copyObj, $deepCopy = false) + { + $copyObj->setGunid($this->gunid); + $copyObj->setName($this->name); + $copyObj->setMime($this->mime); + $copyObj->setFtype($this->ftype); + $copyObj->setfilepath($this->filepath); + $copyObj->setState($this->state); + $copyObj->setCurrentlyaccessing($this->currentlyaccessing); + $copyObj->setEditedby($this->editedby); + $copyObj->setMtime($this->mtime); + $copyObj->setMd5($this->md5); + $copyObj->setTrackTitle($this->track_title); + $copyObj->setArtistName($this->artist_name); + $copyObj->setBitRate($this->bit_rate); + $copyObj->setSampleRate($this->sample_rate); + $copyObj->setFormat($this->format); + $copyObj->setDbLength($this->length); + $copyObj->setAlbumTitle($this->album_title); + $copyObj->setGenre($this->genre); + $copyObj->setComments($this->comments); + $copyObj->setYear($this->year); + $copyObj->setTrackNumber($this->track_number); + $copyObj->setChannels($this->channels); + $copyObj->setUrl($this->url); + $copyObj->setBpm($this->bpm); + $copyObj->setRating($this->rating); + $copyObj->setEncodedBy($this->encoded_by); + $copyObj->setDiscNumber($this->disc_number); + $copyObj->setMood($this->mood); + $copyObj->setLabel($this->label); + $copyObj->setComposer($this->composer); + $copyObj->setEncoder($this->encoder); + $copyObj->setChecksum($this->checksum); + $copyObj->setLyrics($this->lyrics); + $copyObj->setOrchestra($this->orchestra); + $copyObj->setConductor($this->conductor); + $copyObj->setLyricist($this->lyricist); + $copyObj->setOriginalLyricist($this->original_lyricist); + $copyObj->setRadioStationName($this->radio_station_name); + $copyObj->setInfoUrl($this->info_url); + $copyObj->setArtistUrl($this->artist_url); + $copyObj->setAudioSourceUrl($this->audio_source_url); + $copyObj->setRadioStationUrl($this->radio_station_url); + $copyObj->setBuyThisUrl($this->buy_this_url); + $copyObj->setIsrcNumber($this->isrc_number); + $copyObj->setCatalogNumber($this->catalog_number); + $copyObj->setOriginalArtist($this->original_artist); + $copyObj->setCopyright($this->copyright); + $copyObj->setReportDatetime($this->report_datetime); + $copyObj->setReportLocation($this->report_location); + $copyObj->setReportOrganization($this->report_organization); + $copyObj->setSubject($this->subject); + $copyObj->setContributor($this->contributor); + $copyObj->setLanguage($this->language); + + if ($deepCopy) { + // important: temporarily setNew(false) because this affects the behavior of + // the getter/setter methods for fkey referrer objects. + $copyObj->setNew(false); + + foreach ($this->getCcPlaylistcontentss() as $relObj) { + if ($relObj !== $this) { // ensure that we don't try to copy a reference to ourselves + $copyObj->addCcPlaylistcontents($relObj->copy($deepCopy)); + } + } + + } // if ($deepCopy) + + + $copyObj->setNew(true); + $copyObj->setDbId(NULL); // this is a auto-increment column, so set to default value + } + + /** + * Makes a copy of this object that will be inserted as a new row in table when saved. + * It creates a new object filling in the simple attributes, but skipping any primary + * keys that are defined for the table. + * + * If desired, this method can also make copies of all associated (fkey referrers) + * objects. + * + * @param boolean $deepCopy Whether to also copy all rows that refer (by fkey) to the current row. + * @return CcFiles Clone of current object. + * @throws PropelException + */ + public function copy($deepCopy = false) + { + // we use get_class(), because this might be a subclass + $clazz = get_class($this); + $copyObj = new $clazz(); + $this->copyInto($copyObj, $deepCopy); + return $copyObj; + } + + /** + * Returns a peer instance associated with this om. + * + * Since Peer classes are not to have any instance attributes, this method returns the + * same instance for all member of this class. The method could therefore + * be static, but this would prevent one from overriding the behavior. + * + * @return CcFilesPeer + */ + public function getPeer() + { + if (self::$peer === null) { + self::$peer = new CcFilesPeer(); + } + return self::$peer; + } + + /** + * Declares an association between this object and a CcSubjs object. + * + * @param CcSubjs $v + * @return CcFiles The current object (for fluent API support) + * @throws PropelException + */ + public function setCcSubjs(CcSubjs $v = null) + { + if ($v === null) { + $this->setEditedby(NULL); + } else { + $this->setEditedby($v->getId()); + } + + $this->aCcSubjs = $v; + + // Add binding for other direction of this n:n relationship. + // If this object has already been added to the CcSubjs object, it will not be re-added. + if ($v !== null) { + $v->addCcFiles($this); + } + + return $this; + } + + + /** + * Get the associated CcSubjs object + * + * @param PropelPDO Optional Connection object. + * @return CcSubjs The associated CcSubjs object. + * @throws PropelException + */ + public function getCcSubjs(PropelPDO $con = null) + { + if ($this->aCcSubjs === null && ($this->editedby !== null)) { + $this->aCcSubjs = CcSubjsQuery::create()->findPk($this->editedby, $con); + /* The following can be used additionally to + guarantee the related object contains a reference + to this object. This level of coupling may, however, be + undesirable since it could result in an only partially populated collection + in the referenced object. + $this->aCcSubjs->addCcFiless($this); + */ + } + return $this->aCcSubjs; + } + + /** + * Clears out the collCcPlaylistcontentss collection + * + * This does not modify the database; however, it will remove any associated objects, causing + * them to be refetched by subsequent calls to accessor method. + * + * @return void + * @see addCcPlaylistcontentss() + */ + public function clearCcPlaylistcontentss() + { + $this->collCcPlaylistcontentss = null; // important to set this to NULL since that means it is uninitialized + } + + /** + * Initializes the collCcPlaylistcontentss collection. + * + * By default this just sets the collCcPlaylistcontentss collection to an empty array (like clearcollCcPlaylistcontentss()); + * however, you may wish to override this method in your stub class to provide setting appropriate + * to your application -- for example, setting the initial array to the values stored in database. + * + * @return void + */ + public function initCcPlaylistcontentss() + { + $this->collCcPlaylistcontentss = new PropelObjectCollection(); + $this->collCcPlaylistcontentss->setModel('CcPlaylistcontents'); + } + + /** + * Gets an array of CcPlaylistcontents objects which contain a foreign key that references this object. + * + * If the $criteria is not null, it is used to always fetch the results from the database. + * Otherwise the results are fetched from the database the first time, then cached. + * Next time the same method is called without $criteria, the cached collection is returned. + * If this CcFiles is new, it will return + * an empty collection or the current collection; the criteria is ignored on a new object. + * + * @param Criteria $criteria optional Criteria object to narrow the query + * @param PropelPDO $con optional connection object + * @return PropelCollection|array CcPlaylistcontents[] List of CcPlaylistcontents objects + * @throws PropelException + */ + public function getCcPlaylistcontentss($criteria = null, PropelPDO $con = null) + { + if(null === $this->collCcPlaylistcontentss || null !== $criteria) { + if ($this->isNew() && null === $this->collCcPlaylistcontentss) { + // return empty collection + $this->initCcPlaylistcontentss(); + } else { + $collCcPlaylistcontentss = CcPlaylistcontentsQuery::create(null, $criteria) + ->filterByCcFiles($this) + ->find($con); + if (null !== $criteria) { + return $collCcPlaylistcontentss; + } + $this->collCcPlaylistcontentss = $collCcPlaylistcontentss; + } + } + return $this->collCcPlaylistcontentss; + } + + /** + * Returns the number of related CcPlaylistcontents objects. + * + * @param Criteria $criteria + * @param boolean $distinct + * @param PropelPDO $con + * @return int Count of related CcPlaylistcontents objects. + * @throws PropelException + */ + public function countCcPlaylistcontentss(Criteria $criteria = null, $distinct = false, PropelPDO $con = null) + { + if(null === $this->collCcPlaylistcontentss || null !== $criteria) { + if ($this->isNew() && null === $this->collCcPlaylistcontentss) { + return 0; + } else { + $query = CcPlaylistcontentsQuery::create(null, $criteria); + if($distinct) { + $query->distinct(); + } + return $query + ->filterByCcFiles($this) + ->count($con); + } + } else { + return count($this->collCcPlaylistcontentss); + } + } + + /** + * Method called to associate a CcPlaylistcontents object to this object + * through the CcPlaylistcontents foreign key attribute. + * + * @param CcPlaylistcontents $l CcPlaylistcontents + * @return void + * @throws PropelException + */ + public function addCcPlaylistcontents(CcPlaylistcontents $l) + { + if ($this->collCcPlaylistcontentss === null) { + $this->initCcPlaylistcontentss(); + } + if (!$this->collCcPlaylistcontentss->contains($l)) { // only add it if the **same** object is not already associated + $this->collCcPlaylistcontentss[]= $l; + $l->setCcFiles($this); + } + } + + + /** + * If this collection has already been initialized with + * an identical criteria, it returns the collection. + * Otherwise if this CcFiles is new, it will return + * an empty collection; or if this CcFiles has previously + * been saved, it will retrieve related CcPlaylistcontentss from storage. + * + * This method is protected by default in order to keep the public + * api reasonable. You can provide public methods for those you + * actually need in CcFiles. + * + * @param Criteria $criteria optional Criteria object to narrow the query + * @param PropelPDO $con optional connection object + * @param string $join_behavior optional join type to use (defaults to Criteria::LEFT_JOIN) + * @return PropelCollection|array CcPlaylistcontents[] List of CcPlaylistcontents objects + */ + public function getCcPlaylistcontentssJoinCcPlaylist($criteria = null, $con = null, $join_behavior = Criteria::LEFT_JOIN) + { + $query = CcPlaylistcontentsQuery::create(null, $criteria); + $query->joinWith('CcPlaylist', $join_behavior); + + return $this->getCcPlaylistcontentss($query, $con); + } + + /** + * Clears the current object and sets all attributes to their default values + */ + public function clear() + { + $this->id = null; + $this->gunid = null; + $this->name = null; + $this->mime = null; + $this->ftype = null; + $this->filepath = null; + $this->state = null; + $this->currentlyaccessing = null; + $this->editedby = null; + $this->mtime = null; + $this->md5 = null; + $this->track_title = null; + $this->artist_name = null; + $this->bit_rate = null; + $this->sample_rate = null; + $this->format = null; + $this->length = null; + $this->album_title = null; + $this->genre = null; + $this->comments = null; + $this->year = null; + $this->track_number = null; + $this->channels = null; + $this->url = null; + $this->bpm = null; + $this->rating = null; + $this->encoded_by = null; + $this->disc_number = null; + $this->mood = null; + $this->label = null; + $this->composer = null; + $this->encoder = null; + $this->checksum = null; + $this->lyrics = null; + $this->orchestra = null; + $this->conductor = null; + $this->lyricist = null; + $this->original_lyricist = null; + $this->radio_station_name = null; + $this->info_url = null; + $this->artist_url = null; + $this->audio_source_url = null; + $this->radio_station_url = null; + $this->buy_this_url = null; + $this->isrc_number = null; + $this->catalog_number = null; + $this->original_artist = null; + $this->copyright = null; + $this->report_datetime = null; + $this->report_location = null; + $this->report_organization = null; + $this->subject = null; + $this->contributor = null; + $this->language = null; + $this->alreadyInSave = false; + $this->alreadyInValidation = false; + $this->clearAllReferences(); + $this->applyDefaultValues(); + $this->resetModified(); + $this->setNew(true); + $this->setDeleted(false); + } + + /** + * Resets all collections of referencing foreign keys. + * + * This method is a user-space workaround for PHP's inability to garbage collect objects + * with circular references. This is currently necessary when using Propel in certain + * daemon or large-volumne/high-memory operations. + * + * @param boolean $deep Whether to also clear the references on all associated objects. + */ + public function clearAllReferences($deep = false) + { + if ($deep) { + if ($this->collCcPlaylistcontentss) { + foreach ((array) $this->collCcPlaylistcontentss as $o) { + $o->clearAllReferences($deep); + } + } + } // if ($deep) + + $this->collCcPlaylistcontentss = null; + $this->aCcSubjs = null; + } + + /** + * Catches calls to virtual methods + */ + public function __call($name, $params) + { + if (preg_match('/get(\w+)/', $name, $matches)) { + $virtualColumn = $matches[1]; + if ($this->hasVirtualColumn($virtualColumn)) { + return $this->getVirtualColumn($virtualColumn); + } + // no lcfirst in php<5.3... + $virtualColumn[0] = strtolower($virtualColumn[0]); + if ($this->hasVirtualColumn($virtualColumn)) { + return $this->getVirtualColumn($virtualColumn); + } + } + throw new PropelException('Call to undefined method: ' . $name); + } + +} // BaseCcFiles diff --git a/application/models/campcaster/om/BaseCcFilesPeer.php b/application/models/campcaster/om/BaseCcFilesPeer.php new file mode 100644 index 000000000..8f48b5fc9 --- /dev/null +++ b/application/models/campcaster/om/BaseCcFilesPeer.php @@ -0,0 +1,1236 @@ + array ('DbId', 'Gunid', 'Name', 'Mime', 'Ftype', 'filepath', 'State', 'Currentlyaccessing', 'Editedby', 'Mtime', 'Md5', 'TrackTitle', 'ArtistName', 'BitRate', 'SampleRate', 'Format', 'DbLength', 'AlbumTitle', 'Genre', 'Comments', 'Year', 'TrackNumber', 'Channels', 'Url', 'Bpm', 'Rating', 'EncodedBy', 'DiscNumber', 'Mood', 'Label', 'Composer', 'Encoder', 'Checksum', 'Lyrics', 'Orchestra', 'Conductor', 'Lyricist', 'OriginalLyricist', 'RadioStationName', 'InfoUrl', 'ArtistUrl', 'AudioSourceUrl', 'RadioStationUrl', 'BuyThisUrl', 'IsrcNumber', 'CatalogNumber', 'OriginalArtist', 'Copyright', 'ReportDatetime', 'ReportLocation', 'ReportOrganization', 'Subject', 'Contributor', 'Language', ), + BasePeer::TYPE_STUDLYPHPNAME => array ('dbId', 'gunid', 'name', 'mime', 'ftype', 'filepath', 'state', 'currentlyaccessing', 'editedby', 'mtime', 'md5', 'trackTitle', 'artistName', 'bitRate', 'sampleRate', 'format', 'dbLength', 'albumTitle', 'genre', 'comments', 'year', 'trackNumber', 'channels', 'url', 'bpm', 'rating', 'encodedBy', 'discNumber', 'mood', 'label', 'composer', 'encoder', 'checksum', 'lyrics', 'orchestra', 'conductor', 'lyricist', 'originalLyricist', 'radioStationName', 'infoUrl', 'artistUrl', 'audioSourceUrl', 'radioStationUrl', 'buyThisUrl', 'isrcNumber', 'catalogNumber', 'originalArtist', 'copyright', 'reportDatetime', 'reportLocation', 'reportOrganization', 'subject', 'contributor', 'language', ), + BasePeer::TYPE_COLNAME => array (self::ID, self::GUNID, self::NAME, self::MIME, self::FTYPE, self::FILEPATH, self::STATE, self::CURRENTLYACCESSING, self::EDITEDBY, self::MTIME, self::MD5, self::TRACK_TITLE, self::ARTIST_NAME, self::BIT_RATE, self::SAMPLE_RATE, self::FORMAT, self::LENGTH, self::ALBUM_TITLE, self::GENRE, self::COMMENTS, self::YEAR, self::TRACK_NUMBER, self::CHANNELS, self::URL, self::BPM, self::RATING, self::ENCODED_BY, self::DISC_NUMBER, self::MOOD, self::LABEL, self::COMPOSER, self::ENCODER, self::CHECKSUM, self::LYRICS, self::ORCHESTRA, self::CONDUCTOR, self::LYRICIST, self::ORIGINAL_LYRICIST, self::RADIO_STATION_NAME, self::INFO_URL, self::ARTIST_URL, self::AUDIO_SOURCE_URL, self::RADIO_STATION_URL, self::BUY_THIS_URL, self::ISRC_NUMBER, self::CATALOG_NUMBER, self::ORIGINAL_ARTIST, self::COPYRIGHT, self::REPORT_DATETIME, self::REPORT_LOCATION, self::REPORT_ORGANIZATION, self::SUBJECT, self::CONTRIBUTOR, self::LANGUAGE, ), + BasePeer::TYPE_RAW_COLNAME => array ('ID', 'GUNID', 'NAME', 'MIME', 'FTYPE', 'FILEPATH', 'STATE', 'CURRENTLYACCESSING', 'EDITEDBY', 'MTIME', 'MD5', 'TRACK_TITLE', 'ARTIST_NAME', 'BIT_RATE', 'SAMPLE_RATE', 'FORMAT', 'LENGTH', 'ALBUM_TITLE', 'GENRE', 'COMMENTS', 'YEAR', 'TRACK_NUMBER', 'CHANNELS', 'URL', 'BPM', 'RATING', 'ENCODED_BY', 'DISC_NUMBER', 'MOOD', 'LABEL', 'COMPOSER', 'ENCODER', 'CHECKSUM', 'LYRICS', 'ORCHESTRA', 'CONDUCTOR', 'LYRICIST', 'ORIGINAL_LYRICIST', 'RADIO_STATION_NAME', 'INFO_URL', 'ARTIST_URL', 'AUDIO_SOURCE_URL', 'RADIO_STATION_URL', 'BUY_THIS_URL', 'ISRC_NUMBER', 'CATALOG_NUMBER', 'ORIGINAL_ARTIST', 'COPYRIGHT', 'REPORT_DATETIME', 'REPORT_LOCATION', 'REPORT_ORGANIZATION', 'SUBJECT', 'CONTRIBUTOR', 'LANGUAGE', ), + BasePeer::TYPE_FIELDNAME => array ('id', 'gunid', 'name', 'mime', 'ftype', 'filepath', 'state', 'currentlyaccessing', 'editedby', 'mtime', 'md5', 'track_title', 'artist_name', 'bit_rate', 'sample_rate', 'format', 'length', 'album_title', 'genre', 'comments', 'year', 'track_number', 'channels', 'url', 'bpm', 'rating', 'encoded_by', 'disc_number', 'mood', 'label', 'composer', 'encoder', 'checksum', 'lyrics', 'orchestra', 'conductor', 'lyricist', 'original_lyricist', 'radio_station_name', 'info_url', 'artist_url', 'audio_source_url', 'radio_station_url', 'buy_this_url', 'isrc_number', 'catalog_number', 'original_artist', 'copyright', 'report_datetime', 'report_location', 'report_organization', 'subject', 'contributor', 'language', ), + BasePeer::TYPE_NUM => array (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, ) + ); + + /** + * holds an array of keys for quick access to the fieldnames array + * + * first dimension keys are the type constants + * e.g. self::$fieldNames[BasePeer::TYPE_PHPNAME]['Id'] = 0 + */ + private static $fieldKeys = array ( + BasePeer::TYPE_PHPNAME => array ('DbId' => 0, 'Gunid' => 1, 'Name' => 2, 'Mime' => 3, 'Ftype' => 4, 'filepath' => 5, 'State' => 6, 'Currentlyaccessing' => 7, 'Editedby' => 8, 'Mtime' => 9, 'Md5' => 10, 'TrackTitle' => 11, 'ArtistName' => 12, 'BitRate' => 13, 'SampleRate' => 14, 'Format' => 15, 'DbLength' => 16, 'AlbumTitle' => 17, 'Genre' => 18, 'Comments' => 19, 'Year' => 20, 'TrackNumber' => 21, 'Channels' => 22, 'Url' => 23, 'Bpm' => 24, 'Rating' => 25, 'EncodedBy' => 26, 'DiscNumber' => 27, 'Mood' => 28, 'Label' => 29, 'Composer' => 30, 'Encoder' => 31, 'Checksum' => 32, 'Lyrics' => 33, 'Orchestra' => 34, 'Conductor' => 35, 'Lyricist' => 36, 'OriginalLyricist' => 37, 'RadioStationName' => 38, 'InfoUrl' => 39, 'ArtistUrl' => 40, 'AudioSourceUrl' => 41, 'RadioStationUrl' => 42, 'BuyThisUrl' => 43, 'IsrcNumber' => 44, 'CatalogNumber' => 45, 'OriginalArtist' => 46, 'Copyright' => 47, 'ReportDatetime' => 48, 'ReportLocation' => 49, 'ReportOrganization' => 50, 'Subject' => 51, 'Contributor' => 52, 'Language' => 53, ), + BasePeer::TYPE_STUDLYPHPNAME => array ('dbId' => 0, 'gunid' => 1, 'name' => 2, 'mime' => 3, 'ftype' => 4, 'filepath' => 5, 'state' => 6, 'currentlyaccessing' => 7, 'editedby' => 8, 'mtime' => 9, 'md5' => 10, 'trackTitle' => 11, 'artistName' => 12, 'bitRate' => 13, 'sampleRate' => 14, 'format' => 15, 'dbLength' => 16, 'albumTitle' => 17, 'genre' => 18, 'comments' => 19, 'year' => 20, 'trackNumber' => 21, 'channels' => 22, 'url' => 23, 'bpm' => 24, 'rating' => 25, 'encodedBy' => 26, 'discNumber' => 27, 'mood' => 28, 'label' => 29, 'composer' => 30, 'encoder' => 31, 'checksum' => 32, 'lyrics' => 33, 'orchestra' => 34, 'conductor' => 35, 'lyricist' => 36, 'originalLyricist' => 37, 'radioStationName' => 38, 'infoUrl' => 39, 'artistUrl' => 40, 'audioSourceUrl' => 41, 'radioStationUrl' => 42, 'buyThisUrl' => 43, 'isrcNumber' => 44, 'catalogNumber' => 45, 'originalArtist' => 46, 'copyright' => 47, 'reportDatetime' => 48, 'reportLocation' => 49, 'reportOrganization' => 50, 'subject' => 51, 'contributor' => 52, 'language' => 53, ), + BasePeer::TYPE_COLNAME => array (self::ID => 0, self::GUNID => 1, self::NAME => 2, self::MIME => 3, self::FTYPE => 4, self::FILEPATH => 5, self::STATE => 6, self::CURRENTLYACCESSING => 7, self::EDITEDBY => 8, self::MTIME => 9, self::MD5 => 10, self::TRACK_TITLE => 11, self::ARTIST_NAME => 12, self::BIT_RATE => 13, self::SAMPLE_RATE => 14, self::FORMAT => 15, self::LENGTH => 16, self::ALBUM_TITLE => 17, self::GENRE => 18, self::COMMENTS => 19, self::YEAR => 20, self::TRACK_NUMBER => 21, self::CHANNELS => 22, self::URL => 23, self::BPM => 24, self::RATING => 25, self::ENCODED_BY => 26, self::DISC_NUMBER => 27, self::MOOD => 28, self::LABEL => 29, self::COMPOSER => 30, self::ENCODER => 31, self::CHECKSUM => 32, self::LYRICS => 33, self::ORCHESTRA => 34, self::CONDUCTOR => 35, self::LYRICIST => 36, self::ORIGINAL_LYRICIST => 37, self::RADIO_STATION_NAME => 38, self::INFO_URL => 39, self::ARTIST_URL => 40, self::AUDIO_SOURCE_URL => 41, self::RADIO_STATION_URL => 42, self::BUY_THIS_URL => 43, self::ISRC_NUMBER => 44, self::CATALOG_NUMBER => 45, self::ORIGINAL_ARTIST => 46, self::COPYRIGHT => 47, self::REPORT_DATETIME => 48, self::REPORT_LOCATION => 49, self::REPORT_ORGANIZATION => 50, self::SUBJECT => 51, self::CONTRIBUTOR => 52, self::LANGUAGE => 53, ), + BasePeer::TYPE_RAW_COLNAME => array ('ID' => 0, 'GUNID' => 1, 'NAME' => 2, 'MIME' => 3, 'FTYPE' => 4, 'FILEPATH' => 5, 'STATE' => 6, 'CURRENTLYACCESSING' => 7, 'EDITEDBY' => 8, 'MTIME' => 9, 'MD5' => 10, 'TRACK_TITLE' => 11, 'ARTIST_NAME' => 12, 'BIT_RATE' => 13, 'SAMPLE_RATE' => 14, 'FORMAT' => 15, 'LENGTH' => 16, 'ALBUM_TITLE' => 17, 'GENRE' => 18, 'COMMENTS' => 19, 'YEAR' => 20, 'TRACK_NUMBER' => 21, 'CHANNELS' => 22, 'URL' => 23, 'BPM' => 24, 'RATING' => 25, 'ENCODED_BY' => 26, 'DISC_NUMBER' => 27, 'MOOD' => 28, 'LABEL' => 29, 'COMPOSER' => 30, 'ENCODER' => 31, 'CHECKSUM' => 32, 'LYRICS' => 33, 'ORCHESTRA' => 34, 'CONDUCTOR' => 35, 'LYRICIST' => 36, 'ORIGINAL_LYRICIST' => 37, 'RADIO_STATION_NAME' => 38, 'INFO_URL' => 39, 'ARTIST_URL' => 40, 'AUDIO_SOURCE_URL' => 41, 'RADIO_STATION_URL' => 42, 'BUY_THIS_URL' => 43, 'ISRC_NUMBER' => 44, 'CATALOG_NUMBER' => 45, 'ORIGINAL_ARTIST' => 46, 'COPYRIGHT' => 47, 'REPORT_DATETIME' => 48, 'REPORT_LOCATION' => 49, 'REPORT_ORGANIZATION' => 50, 'SUBJECT' => 51, 'CONTRIBUTOR' => 52, 'LANGUAGE' => 53, ), + BasePeer::TYPE_FIELDNAME => array ('id' => 0, 'gunid' => 1, 'name' => 2, 'mime' => 3, 'ftype' => 4, 'filepath' => 5, 'state' => 6, 'currentlyaccessing' => 7, 'editedby' => 8, 'mtime' => 9, 'md5' => 10, 'track_title' => 11, 'artist_name' => 12, 'bit_rate' => 13, 'sample_rate' => 14, 'format' => 15, 'length' => 16, 'album_title' => 17, 'genre' => 18, 'comments' => 19, 'year' => 20, 'track_number' => 21, 'channels' => 22, 'url' => 23, 'bpm' => 24, 'rating' => 25, 'encoded_by' => 26, 'disc_number' => 27, 'mood' => 28, 'label' => 29, 'composer' => 30, 'encoder' => 31, 'checksum' => 32, 'lyrics' => 33, 'orchestra' => 34, 'conductor' => 35, 'lyricist' => 36, 'original_lyricist' => 37, 'radio_station_name' => 38, 'info_url' => 39, 'artist_url' => 40, 'audio_source_url' => 41, 'radio_station_url' => 42, 'buy_this_url' => 43, 'isrc_number' => 44, 'catalog_number' => 45, 'original_artist' => 46, 'copyright' => 47, 'report_datetime' => 48, 'report_location' => 49, 'report_organization' => 50, 'subject' => 51, 'contributor' => 52, 'language' => 53, ), + BasePeer::TYPE_NUM => array (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, ) + ); + + /** + * Translates a fieldname to another type + * + * @param string $name field name + * @param string $fromType One of the class type constants BasePeer::TYPE_PHPNAME, BasePeer::TYPE_STUDLYPHPNAME + * BasePeer::TYPE_COLNAME, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_NUM + * @param string $toType One of the class type constants + * @return string translated name of the field. + * @throws PropelException - if the specified name could not be found in the fieldname mappings. + */ + static public function translateFieldName($name, $fromType, $toType) + { + $toNames = self::getFieldNames($toType); + $key = isset(self::$fieldKeys[$fromType][$name]) ? self::$fieldKeys[$fromType][$name] : null; + if ($key === null) { + throw new PropelException("'$name' could not be found in the field names of type '$fromType'. These are: " . print_r(self::$fieldKeys[$fromType], true)); + } + return $toNames[$key]; + } + + /** + * Returns an array of field names. + * + * @param string $type The type of fieldnames to return: + * One of the class type constants BasePeer::TYPE_PHPNAME, BasePeer::TYPE_STUDLYPHPNAME + * BasePeer::TYPE_COLNAME, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_NUM + * @return array A list of field names + */ + + static public function getFieldNames($type = BasePeer::TYPE_PHPNAME) + { + if (!array_key_exists($type, self::$fieldNames)) { + throw new PropelException('Method getFieldNames() expects the parameter $type to be one of the class constants BasePeer::TYPE_PHPNAME, BasePeer::TYPE_STUDLYPHPNAME, BasePeer::TYPE_COLNAME, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_NUM. ' . $type . ' was given.'); + } + return self::$fieldNames[$type]; + } + + /** + * Convenience method which changes table.column to alias.column. + * + * Using this method you can maintain SQL abstraction while using column aliases. + * + * $c->addAlias("alias1", TablePeer::TABLE_NAME); + * $c->addJoin(TablePeer::alias("alias1", TablePeer::PRIMARY_KEY_COLUMN), TablePeer::PRIMARY_KEY_COLUMN); + * + * @param string $alias The alias for the current table. + * @param string $column The column name for current table. (i.e. CcFilesPeer::COLUMN_NAME). + * @return string + */ + public static function alias($alias, $column) + { + return str_replace(CcFilesPeer::TABLE_NAME.'.', $alias.'.', $column); + } + + /** + * Add all the columns needed to create a new object. + * + * Note: any columns that were marked with lazyLoad="true" in the + * XML schema will not be added to the select list and only loaded + * on demand. + * + * @param Criteria $criteria object containing the columns to add. + * @param string $alias optional table alias + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function addSelectColumns(Criteria $criteria, $alias = null) + { + if (null === $alias) { + $criteria->addSelectColumn(CcFilesPeer::ID); + $criteria->addSelectColumn(CcFilesPeer::GUNID); + $criteria->addSelectColumn(CcFilesPeer::NAME); + $criteria->addSelectColumn(CcFilesPeer::MIME); + $criteria->addSelectColumn(CcFilesPeer::FTYPE); + $criteria->addSelectColumn(CcFilesPeer::FILEPATH); + $criteria->addSelectColumn(CcFilesPeer::STATE); + $criteria->addSelectColumn(CcFilesPeer::CURRENTLYACCESSING); + $criteria->addSelectColumn(CcFilesPeer::EDITEDBY); + $criteria->addSelectColumn(CcFilesPeer::MTIME); + $criteria->addSelectColumn(CcFilesPeer::MD5); + $criteria->addSelectColumn(CcFilesPeer::TRACK_TITLE); + $criteria->addSelectColumn(CcFilesPeer::ARTIST_NAME); + $criteria->addSelectColumn(CcFilesPeer::BIT_RATE); + $criteria->addSelectColumn(CcFilesPeer::SAMPLE_RATE); + $criteria->addSelectColumn(CcFilesPeer::FORMAT); + $criteria->addSelectColumn(CcFilesPeer::LENGTH); + $criteria->addSelectColumn(CcFilesPeer::ALBUM_TITLE); + $criteria->addSelectColumn(CcFilesPeer::GENRE); + $criteria->addSelectColumn(CcFilesPeer::COMMENTS); + $criteria->addSelectColumn(CcFilesPeer::YEAR); + $criteria->addSelectColumn(CcFilesPeer::TRACK_NUMBER); + $criteria->addSelectColumn(CcFilesPeer::CHANNELS); + $criteria->addSelectColumn(CcFilesPeer::URL); + $criteria->addSelectColumn(CcFilesPeer::BPM); + $criteria->addSelectColumn(CcFilesPeer::RATING); + $criteria->addSelectColumn(CcFilesPeer::ENCODED_BY); + $criteria->addSelectColumn(CcFilesPeer::DISC_NUMBER); + $criteria->addSelectColumn(CcFilesPeer::MOOD); + $criteria->addSelectColumn(CcFilesPeer::LABEL); + $criteria->addSelectColumn(CcFilesPeer::COMPOSER); + $criteria->addSelectColumn(CcFilesPeer::ENCODER); + $criteria->addSelectColumn(CcFilesPeer::CHECKSUM); + $criteria->addSelectColumn(CcFilesPeer::LYRICS); + $criteria->addSelectColumn(CcFilesPeer::ORCHESTRA); + $criteria->addSelectColumn(CcFilesPeer::CONDUCTOR); + $criteria->addSelectColumn(CcFilesPeer::LYRICIST); + $criteria->addSelectColumn(CcFilesPeer::ORIGINAL_LYRICIST); + $criteria->addSelectColumn(CcFilesPeer::RADIO_STATION_NAME); + $criteria->addSelectColumn(CcFilesPeer::INFO_URL); + $criteria->addSelectColumn(CcFilesPeer::ARTIST_URL); + $criteria->addSelectColumn(CcFilesPeer::AUDIO_SOURCE_URL); + $criteria->addSelectColumn(CcFilesPeer::RADIO_STATION_URL); + $criteria->addSelectColumn(CcFilesPeer::BUY_THIS_URL); + $criteria->addSelectColumn(CcFilesPeer::ISRC_NUMBER); + $criteria->addSelectColumn(CcFilesPeer::CATALOG_NUMBER); + $criteria->addSelectColumn(CcFilesPeer::ORIGINAL_ARTIST); + $criteria->addSelectColumn(CcFilesPeer::COPYRIGHT); + $criteria->addSelectColumn(CcFilesPeer::REPORT_DATETIME); + $criteria->addSelectColumn(CcFilesPeer::REPORT_LOCATION); + $criteria->addSelectColumn(CcFilesPeer::REPORT_ORGANIZATION); + $criteria->addSelectColumn(CcFilesPeer::SUBJECT); + $criteria->addSelectColumn(CcFilesPeer::CONTRIBUTOR); + $criteria->addSelectColumn(CcFilesPeer::LANGUAGE); + } else { + $criteria->addSelectColumn($alias . '.ID'); + $criteria->addSelectColumn($alias . '.GUNID'); + $criteria->addSelectColumn($alias . '.NAME'); + $criteria->addSelectColumn($alias . '.MIME'); + $criteria->addSelectColumn($alias . '.FTYPE'); + $criteria->addSelectColumn($alias . '.FILEPATH'); + $criteria->addSelectColumn($alias . '.STATE'); + $criteria->addSelectColumn($alias . '.CURRENTLYACCESSING'); + $criteria->addSelectColumn($alias . '.EDITEDBY'); + $criteria->addSelectColumn($alias . '.MTIME'); + $criteria->addSelectColumn($alias . '.MD5'); + $criteria->addSelectColumn($alias . '.TRACK_TITLE'); + $criteria->addSelectColumn($alias . '.ARTIST_NAME'); + $criteria->addSelectColumn($alias . '.BIT_RATE'); + $criteria->addSelectColumn($alias . '.SAMPLE_RATE'); + $criteria->addSelectColumn($alias . '.FORMAT'); + $criteria->addSelectColumn($alias . '.LENGTH'); + $criteria->addSelectColumn($alias . '.ALBUM_TITLE'); + $criteria->addSelectColumn($alias . '.GENRE'); + $criteria->addSelectColumn($alias . '.COMMENTS'); + $criteria->addSelectColumn($alias . '.YEAR'); + $criteria->addSelectColumn($alias . '.TRACK_NUMBER'); + $criteria->addSelectColumn($alias . '.CHANNELS'); + $criteria->addSelectColumn($alias . '.URL'); + $criteria->addSelectColumn($alias . '.BPM'); + $criteria->addSelectColumn($alias . '.RATING'); + $criteria->addSelectColumn($alias . '.ENCODED_BY'); + $criteria->addSelectColumn($alias . '.DISC_NUMBER'); + $criteria->addSelectColumn($alias . '.MOOD'); + $criteria->addSelectColumn($alias . '.LABEL'); + $criteria->addSelectColumn($alias . '.COMPOSER'); + $criteria->addSelectColumn($alias . '.ENCODER'); + $criteria->addSelectColumn($alias . '.CHECKSUM'); + $criteria->addSelectColumn($alias . '.LYRICS'); + $criteria->addSelectColumn($alias . '.ORCHESTRA'); + $criteria->addSelectColumn($alias . '.CONDUCTOR'); + $criteria->addSelectColumn($alias . '.LYRICIST'); + $criteria->addSelectColumn($alias . '.ORIGINAL_LYRICIST'); + $criteria->addSelectColumn($alias . '.RADIO_STATION_NAME'); + $criteria->addSelectColumn($alias . '.INFO_URL'); + $criteria->addSelectColumn($alias . '.ARTIST_URL'); + $criteria->addSelectColumn($alias . '.AUDIO_SOURCE_URL'); + $criteria->addSelectColumn($alias . '.RADIO_STATION_URL'); + $criteria->addSelectColumn($alias . '.BUY_THIS_URL'); + $criteria->addSelectColumn($alias . '.ISRC_NUMBER'); + $criteria->addSelectColumn($alias . '.CATALOG_NUMBER'); + $criteria->addSelectColumn($alias . '.ORIGINAL_ARTIST'); + $criteria->addSelectColumn($alias . '.COPYRIGHT'); + $criteria->addSelectColumn($alias . '.REPORT_DATETIME'); + $criteria->addSelectColumn($alias . '.REPORT_LOCATION'); + $criteria->addSelectColumn($alias . '.REPORT_ORGANIZATION'); + $criteria->addSelectColumn($alias . '.SUBJECT'); + $criteria->addSelectColumn($alias . '.CONTRIBUTOR'); + $criteria->addSelectColumn($alias . '.LANGUAGE'); + } + } + + /** + * Returns the number of rows matching criteria. + * + * @param Criteria $criteria + * @param boolean $distinct Whether to select only distinct columns; deprecated: use Criteria->setDistinct() instead. + * @param PropelPDO $con + * @return int Number of matching rows. + */ + public static function doCount(Criteria $criteria, $distinct = false, PropelPDO $con = null) + { + // we may modify criteria, so copy it first + $criteria = clone $criteria; + + // We need to set the primary table name, since in the case that there are no WHERE columns + // it will be impossible for the BasePeer::createSelectSql() method to determine which + // tables go into the FROM clause. + $criteria->setPrimaryTableName(CcFilesPeer::TABLE_NAME); + + if ($distinct && !in_array(Criteria::DISTINCT, $criteria->getSelectModifiers())) { + $criteria->setDistinct(); + } + + if (!$criteria->hasSelectClause()) { + CcFilesPeer::addSelectColumns($criteria); + } + + $criteria->clearOrderByColumns(); // ORDER BY won't ever affect the count + $criteria->setDbName(self::DATABASE_NAME); // Set the correct dbName + + if ($con === null) { + $con = Propel::getConnection(CcFilesPeer::DATABASE_NAME, Propel::CONNECTION_READ); + } + // BasePeer returns a PDOStatement + $stmt = BasePeer::doCount($criteria, $con); + + if ($row = $stmt->fetch(PDO::FETCH_NUM)) { + $count = (int) $row[0]; + } else { + $count = 0; // no rows returned; we infer that means 0 matches. + } + $stmt->closeCursor(); + return $count; + } + /** + * Method to select one object from the DB. + * + * @param Criteria $criteria object used to create the SELECT statement. + * @param PropelPDO $con + * @return CcFiles + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function doSelectOne(Criteria $criteria, PropelPDO $con = null) + { + $critcopy = clone $criteria; + $critcopy->setLimit(1); + $objects = CcFilesPeer::doSelect($critcopy, $con); + if ($objects) { + return $objects[0]; + } + return null; + } + /** + * Method to do selects. + * + * @param Criteria $criteria The Criteria object used to build the SELECT statement. + * @param PropelPDO $con + * @return array Array of selected Objects + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function doSelect(Criteria $criteria, PropelPDO $con = null) + { + return CcFilesPeer::populateObjects(CcFilesPeer::doSelectStmt($criteria, $con)); + } + /** + * Prepares the Criteria object and uses the parent doSelect() method to execute a PDOStatement. + * + * Use this method directly if you want to work with an executed statement durirectly (for example + * to perform your own object hydration). + * + * @param Criteria $criteria The Criteria object used to build the SELECT statement. + * @param PropelPDO $con The connection to use + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + * @return PDOStatement The executed PDOStatement object. + * @see BasePeer::doSelect() + */ + public static function doSelectStmt(Criteria $criteria, PropelPDO $con = null) + { + if ($con === null) { + $con = Propel::getConnection(CcFilesPeer::DATABASE_NAME, Propel::CONNECTION_READ); + } + + if (!$criteria->hasSelectClause()) { + $criteria = clone $criteria; + CcFilesPeer::addSelectColumns($criteria); + } + + // Set the correct dbName + $criteria->setDbName(self::DATABASE_NAME); + + // BasePeer returns a PDOStatement + return BasePeer::doSelect($criteria, $con); + } + /** + * Adds an object to the instance pool. + * + * Propel keeps cached copies of objects in an instance pool when they are retrieved + * from the database. In some cases -- especially when you override doSelect*() + * methods in your stub classes -- you may need to explicitly add objects + * to the cache in order to ensure that the same objects are always returned by doSelect*() + * and retrieveByPK*() calls. + * + * @param CcFiles $value A CcFiles object. + * @param string $key (optional) key to use for instance map (for performance boost if key was already calculated externally). + */ + public static function addInstanceToPool(CcFiles $obj, $key = null) + { + if (Propel::isInstancePoolingEnabled()) { + if ($key === null) { + $key = (string) $obj->getDbId(); + } // if key === null + self::$instances[$key] = $obj; + } + } + + /** + * Removes an object from the instance pool. + * + * Propel keeps cached copies of objects in an instance pool when they are retrieved + * from the database. In some cases -- especially when you override doDelete + * methods in your stub classes -- you may need to explicitly remove objects + * from the cache in order to prevent returning objects that no longer exist. + * + * @param mixed $value A CcFiles object or a primary key value. + */ + public static function removeInstanceFromPool($value) + { + if (Propel::isInstancePoolingEnabled() && $value !== null) { + if (is_object($value) && $value instanceof CcFiles) { + $key = (string) $value->getDbId(); + } elseif (is_scalar($value)) { + // assume we've been passed a primary key + $key = (string) $value; + } else { + $e = new PropelException("Invalid value passed to removeInstanceFromPool(). Expected primary key or CcFiles object; got " . (is_object($value) ? get_class($value) . ' object.' : var_export($value,true))); + throw $e; + } + + unset(self::$instances[$key]); + } + } // removeInstanceFromPool() + + /** + * Retrieves a string version of the primary key from the DB resultset row that can be used to uniquely identify a row in this table. + * + * For tables with a single-column primary key, that simple pkey value will be returned. For tables with + * a multi-column primary key, a serialize()d version of the primary key will be returned. + * + * @param string $key The key (@see getPrimaryKeyHash()) for this instance. + * @return CcFiles Found object or NULL if 1) no instance exists for specified key or 2) instance pooling has been disabled. + * @see getPrimaryKeyHash() + */ + public static function getInstanceFromPool($key) + { + if (Propel::isInstancePoolingEnabled()) { + if (isset(self::$instances[$key])) { + return self::$instances[$key]; + } + } + return null; // just to be explicit + } + + /** + * Clear the instance pool. + * + * @return void + */ + public static function clearInstancePool() + { + self::$instances = array(); + } + + /** + * Method to invalidate the instance pool of all tables related to cc_files + * by a foreign key with ON DELETE CASCADE + */ + public static function clearRelatedInstancePool() + { + // Invalidate objects in CcPlaylistcontentsPeer instance pool, + // since one or more of them may be deleted by ON DELETE CASCADE/SETNULL rule. + CcPlaylistcontentsPeer::clearInstancePool(); + } + + /** + * Retrieves a string version of the primary key from the DB resultset row that can be used to uniquely identify a row in this table. + * + * For tables with a single-column primary key, that simple pkey value will be returned. For tables with + * a multi-column primary key, a serialize()d version of the primary key will be returned. + * + * @param array $row PropelPDO resultset row. + * @param int $startcol The 0-based offset for reading from the resultset row. + * @return string A string version of PK or NULL if the components of primary key in result array are all null. + */ + public static function getPrimaryKeyHashFromRow($row, $startcol = 0) + { + // If the PK cannot be derived from the row, return NULL. + if ($row[$startcol] === null) { + return null; + } + return (string) $row[$startcol]; + } + + /** + * Retrieves the primary key from the DB resultset row + * For tables with a single-column primary key, that simple pkey value will be returned. For tables with + * a multi-column primary key, an array of the primary key columns will be returned. + * + * @param array $row PropelPDO resultset row. + * @param int $startcol The 0-based offset for reading from the resultset row. + * @return mixed The primary key of the row + */ + public static function getPrimaryKeyFromRow($row, $startcol = 0) + { + return (int) $row[$startcol]; + } + + /** + * The returned array will contain objects of the default type or + * objects that inherit from the default. + * + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function populateObjects(PDOStatement $stmt) + { + $results = array(); + + // set the class once to avoid overhead in the loop + $cls = CcFilesPeer::getOMClass(false); + // populate the object(s) + while ($row = $stmt->fetch(PDO::FETCH_NUM)) { + $key = CcFilesPeer::getPrimaryKeyHashFromRow($row, 0); + if (null !== ($obj = CcFilesPeer::getInstanceFromPool($key))) { + // We no longer rehydrate the object, since this can cause data loss. + // See http://www.propelorm.org/ticket/509 + // $obj->hydrate($row, 0, true); // rehydrate + $results[] = $obj; + } else { + $obj = new $cls(); + $obj->hydrate($row); + $results[] = $obj; + CcFilesPeer::addInstanceToPool($obj, $key); + } // if key exists + } + $stmt->closeCursor(); + return $results; + } + /** + * Populates an object of the default type or an object that inherit from the default. + * + * @param array $row PropelPDO resultset row. + * @param int $startcol The 0-based offset for reading from the resultset row. + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + * @return array (CcFiles object, last column rank) + */ + public static function populateObject($row, $startcol = 0) + { + $key = CcFilesPeer::getPrimaryKeyHashFromRow($row, $startcol); + if (null !== ($obj = CcFilesPeer::getInstanceFromPool($key))) { + // We no longer rehydrate the object, since this can cause data loss. + // See http://www.propelorm.org/ticket/509 + // $obj->hydrate($row, $startcol, true); // rehydrate + $col = $startcol + CcFilesPeer::NUM_COLUMNS; + } else { + $cls = CcFilesPeer::OM_CLASS; + $obj = new $cls(); + $col = $obj->hydrate($row, $startcol); + CcFilesPeer::addInstanceToPool($obj, $key); + } + return array($obj, $col); + } + + /** + * Returns the number of rows matching criteria, joining the related CcSubjs table + * + * @param Criteria $criteria + * @param boolean $distinct Whether to select only distinct columns; deprecated: use Criteria->setDistinct() instead. + * @param PropelPDO $con + * @param String $join_behavior the type of joins to use, defaults to Criteria::LEFT_JOIN + * @return int Number of matching rows. + */ + public static function doCountJoinCcSubjs(Criteria $criteria, $distinct = false, PropelPDO $con = null, $join_behavior = Criteria::LEFT_JOIN) + { + // we're going to modify criteria, so copy it first + $criteria = clone $criteria; + + // We need to set the primary table name, since in the case that there are no WHERE columns + // it will be impossible for the BasePeer::createSelectSql() method to determine which + // tables go into the FROM clause. + $criteria->setPrimaryTableName(CcFilesPeer::TABLE_NAME); + + if ($distinct && !in_array(Criteria::DISTINCT, $criteria->getSelectModifiers())) { + $criteria->setDistinct(); + } + + if (!$criteria->hasSelectClause()) { + CcFilesPeer::addSelectColumns($criteria); + } + + $criteria->clearOrderByColumns(); // ORDER BY won't ever affect the count + + // Set the correct dbName + $criteria->setDbName(self::DATABASE_NAME); + + if ($con === null) { + $con = Propel::getConnection(CcFilesPeer::DATABASE_NAME, Propel::CONNECTION_READ); + } + + $criteria->addJoin(CcFilesPeer::EDITEDBY, CcSubjsPeer::ID, $join_behavior); + + $stmt = BasePeer::doCount($criteria, $con); + + if ($row = $stmt->fetch(PDO::FETCH_NUM)) { + $count = (int) $row[0]; + } else { + $count = 0; // no rows returned; we infer that means 0 matches. + } + $stmt->closeCursor(); + return $count; + } + + + /** + * Selects a collection of CcFiles objects pre-filled with their CcSubjs objects. + * @param Criteria $criteria + * @param PropelPDO $con + * @param String $join_behavior the type of joins to use, defaults to Criteria::LEFT_JOIN + * @return array Array of CcFiles objects. + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function doSelectJoinCcSubjs(Criteria $criteria, $con = null, $join_behavior = Criteria::LEFT_JOIN) + { + $criteria = clone $criteria; + + // Set the correct dbName if it has not been overridden + if ($criteria->getDbName() == Propel::getDefaultDB()) { + $criteria->setDbName(self::DATABASE_NAME); + } + + CcFilesPeer::addSelectColumns($criteria); + $startcol = (CcFilesPeer::NUM_COLUMNS - CcFilesPeer::NUM_LAZY_LOAD_COLUMNS); + CcSubjsPeer::addSelectColumns($criteria); + + $criteria->addJoin(CcFilesPeer::EDITEDBY, CcSubjsPeer::ID, $join_behavior); + + $stmt = BasePeer::doSelect($criteria, $con); + $results = array(); + + while ($row = $stmt->fetch(PDO::FETCH_NUM)) { + $key1 = CcFilesPeer::getPrimaryKeyHashFromRow($row, 0); + if (null !== ($obj1 = CcFilesPeer::getInstanceFromPool($key1))) { + // We no longer rehydrate the object, since this can cause data loss. + // See http://www.propelorm.org/ticket/509 + // $obj1->hydrate($row, 0, true); // rehydrate + } else { + + $cls = CcFilesPeer::getOMClass(false); + + $obj1 = new $cls(); + $obj1->hydrate($row); + CcFilesPeer::addInstanceToPool($obj1, $key1); + } // if $obj1 already loaded + + $key2 = CcSubjsPeer::getPrimaryKeyHashFromRow($row, $startcol); + if ($key2 !== null) { + $obj2 = CcSubjsPeer::getInstanceFromPool($key2); + if (!$obj2) { + + $cls = CcSubjsPeer::getOMClass(false); + + $obj2 = new $cls(); + $obj2->hydrate($row, $startcol); + CcSubjsPeer::addInstanceToPool($obj2, $key2); + } // if obj2 already loaded + + // Add the $obj1 (CcFiles) to $obj2 (CcSubjs) + $obj2->addCcFiles($obj1); + + } // if joined row was not null + + $results[] = $obj1; + } + $stmt->closeCursor(); + return $results; + } + + + /** + * Returns the number of rows matching criteria, joining all related tables + * + * @param Criteria $criteria + * @param boolean $distinct Whether to select only distinct columns; deprecated: use Criteria->setDistinct() instead. + * @param PropelPDO $con + * @param String $join_behavior the type of joins to use, defaults to Criteria::LEFT_JOIN + * @return int Number of matching rows. + */ + public static function doCountJoinAll(Criteria $criteria, $distinct = false, PropelPDO $con = null, $join_behavior = Criteria::LEFT_JOIN) + { + // we're going to modify criteria, so copy it first + $criteria = clone $criteria; + + // We need to set the primary table name, since in the case that there are no WHERE columns + // it will be impossible for the BasePeer::createSelectSql() method to determine which + // tables go into the FROM clause. + $criteria->setPrimaryTableName(CcFilesPeer::TABLE_NAME); + + if ($distinct && !in_array(Criteria::DISTINCT, $criteria->getSelectModifiers())) { + $criteria->setDistinct(); + } + + if (!$criteria->hasSelectClause()) { + CcFilesPeer::addSelectColumns($criteria); + } + + $criteria->clearOrderByColumns(); // ORDER BY won't ever affect the count + + // Set the correct dbName + $criteria->setDbName(self::DATABASE_NAME); + + if ($con === null) { + $con = Propel::getConnection(CcFilesPeer::DATABASE_NAME, Propel::CONNECTION_READ); + } + + $criteria->addJoin(CcFilesPeer::EDITEDBY, CcSubjsPeer::ID, $join_behavior); + + $stmt = BasePeer::doCount($criteria, $con); + + if ($row = $stmt->fetch(PDO::FETCH_NUM)) { + $count = (int) $row[0]; + } else { + $count = 0; // no rows returned; we infer that means 0 matches. + } + $stmt->closeCursor(); + return $count; + } + + /** + * Selects a collection of CcFiles objects pre-filled with all related objects. + * + * @param Criteria $criteria + * @param PropelPDO $con + * @param String $join_behavior the type of joins to use, defaults to Criteria::LEFT_JOIN + * @return array Array of CcFiles objects. + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function doSelectJoinAll(Criteria $criteria, $con = null, $join_behavior = Criteria::LEFT_JOIN) + { + $criteria = clone $criteria; + + // Set the correct dbName if it has not been overridden + if ($criteria->getDbName() == Propel::getDefaultDB()) { + $criteria->setDbName(self::DATABASE_NAME); + } + + CcFilesPeer::addSelectColumns($criteria); + $startcol2 = (CcFilesPeer::NUM_COLUMNS - CcFilesPeer::NUM_LAZY_LOAD_COLUMNS); + + CcSubjsPeer::addSelectColumns($criteria); + $startcol3 = $startcol2 + (CcSubjsPeer::NUM_COLUMNS - CcSubjsPeer::NUM_LAZY_LOAD_COLUMNS); + + $criteria->addJoin(CcFilesPeer::EDITEDBY, CcSubjsPeer::ID, $join_behavior); + + $stmt = BasePeer::doSelect($criteria, $con); + $results = array(); + + while ($row = $stmt->fetch(PDO::FETCH_NUM)) { + $key1 = CcFilesPeer::getPrimaryKeyHashFromRow($row, 0); + if (null !== ($obj1 = CcFilesPeer::getInstanceFromPool($key1))) { + // We no longer rehydrate the object, since this can cause data loss. + // See http://www.propelorm.org/ticket/509 + // $obj1->hydrate($row, 0, true); // rehydrate + } else { + $cls = CcFilesPeer::getOMClass(false); + + $obj1 = new $cls(); + $obj1->hydrate($row); + CcFilesPeer::addInstanceToPool($obj1, $key1); + } // if obj1 already loaded + + // Add objects for joined CcSubjs rows + + $key2 = CcSubjsPeer::getPrimaryKeyHashFromRow($row, $startcol2); + if ($key2 !== null) { + $obj2 = CcSubjsPeer::getInstanceFromPool($key2); + if (!$obj2) { + + $cls = CcSubjsPeer::getOMClass(false); + + $obj2 = new $cls(); + $obj2->hydrate($row, $startcol2); + CcSubjsPeer::addInstanceToPool($obj2, $key2); + } // if obj2 loaded + + // Add the $obj1 (CcFiles) to the collection in $obj2 (CcSubjs) + $obj2->addCcFiles($obj1); + } // if joined row not null + + $results[] = $obj1; + } + $stmt->closeCursor(); + return $results; + } + + /** + * Returns the TableMap related to this peer. + * This method is not needed for general use but a specific application could have a need. + * @return TableMap + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function getTableMap() + { + return Propel::getDatabaseMap(self::DATABASE_NAME)->getTable(self::TABLE_NAME); + } + + /** + * Add a TableMap instance to the database for this peer class. + */ + public static function buildTableMap() + { + $dbMap = Propel::getDatabaseMap(BaseCcFilesPeer::DATABASE_NAME); + if (!$dbMap->hasTable(BaseCcFilesPeer::TABLE_NAME)) + { + $dbMap->addTableObject(new CcFilesTableMap()); + } + } + + /** + * The class that the Peer will make instances of. + * + * If $withPrefix is true, the returned path + * uses a dot-path notation which is tranalted into a path + * relative to a location on the PHP include_path. + * (e.g. path.to.MyClass -> 'path/to/MyClass.php') + * + * @param boolean $withPrefix Whether or not to return the path with the class name + * @return string path.to.ClassName + */ + public static function getOMClass($withPrefix = true) + { + return $withPrefix ? CcFilesPeer::CLASS_DEFAULT : CcFilesPeer::OM_CLASS; + } + + /** + * Method perform an INSERT on the database, given a CcFiles or Criteria object. + * + * @param mixed $values Criteria or CcFiles object containing data that is used to create the INSERT statement. + * @param PropelPDO $con the PropelPDO connection to use + * @return mixed The new primary key. + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function doInsert($values, PropelPDO $con = null) + { + if ($con === null) { + $con = Propel::getConnection(CcFilesPeer::DATABASE_NAME, Propel::CONNECTION_WRITE); + } + + if ($values instanceof Criteria) { + $criteria = clone $values; // rename for clarity + } else { + $criteria = $values->buildCriteria(); // build Criteria from CcFiles object + } + + if ($criteria->containsKey(CcFilesPeer::ID) && $criteria->keyContainsValue(CcFilesPeer::ID) ) { + throw new PropelException('Cannot insert a value for auto-increment primary key ('.CcFilesPeer::ID.')'); + } + + + // Set the correct dbName + $criteria->setDbName(self::DATABASE_NAME); + + try { + // use transaction because $criteria could contain info + // for more than one table (I guess, conceivably) + $con->beginTransaction(); + $pk = BasePeer::doInsert($criteria, $con); + $con->commit(); + } catch(PropelException $e) { + $con->rollBack(); + throw $e; + } + + return $pk; + } + + /** + * Method perform an UPDATE on the database, given a CcFiles or Criteria object. + * + * @param mixed $values Criteria or CcFiles object containing data that is used to create the UPDATE statement. + * @param PropelPDO $con The connection to use (specify PropelPDO connection object to exert more control over transactions). + * @return int The number of affected rows (if supported by underlying database driver). + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function doUpdate($values, PropelPDO $con = null) + { + if ($con === null) { + $con = Propel::getConnection(CcFilesPeer::DATABASE_NAME, Propel::CONNECTION_WRITE); + } + + $selectCriteria = new Criteria(self::DATABASE_NAME); + + if ($values instanceof Criteria) { + $criteria = clone $values; // rename for clarity + + $comparison = $criteria->getComparison(CcFilesPeer::ID); + $value = $criteria->remove(CcFilesPeer::ID); + if ($value) { + $selectCriteria->add(CcFilesPeer::ID, $value, $comparison); + } else { + $selectCriteria->setPrimaryTableName(CcFilesPeer::TABLE_NAME); + } + + } else { // $values is CcFiles object + $criteria = $values->buildCriteria(); // gets full criteria + $selectCriteria = $values->buildPkeyCriteria(); // gets criteria w/ primary key(s) + } + + // set the correct dbName + $criteria->setDbName(self::DATABASE_NAME); + + return BasePeer::doUpdate($selectCriteria, $criteria, $con); + } + + /** + * Method to DELETE all rows from the cc_files table. + * + * @return int The number of affected rows (if supported by underlying database driver). + */ + public static function doDeleteAll($con = null) + { + if ($con === null) { + $con = Propel::getConnection(CcFilesPeer::DATABASE_NAME, Propel::CONNECTION_WRITE); + } + $affectedRows = 0; // initialize var to track total num of affected rows + try { + // use transaction because $criteria could contain info + // for more than one table or we could emulating ON DELETE CASCADE, etc. + $con->beginTransaction(); + $affectedRows += BasePeer::doDeleteAll(CcFilesPeer::TABLE_NAME, $con, CcFilesPeer::DATABASE_NAME); + // Because this db requires some delete cascade/set null emulation, we have to + // clear the cached instance *after* the emulation has happened (since + // instances get re-added by the select statement contained therein). + CcFilesPeer::clearInstancePool(); + CcFilesPeer::clearRelatedInstancePool(); + $con->commit(); + return $affectedRows; + } catch (PropelException $e) { + $con->rollBack(); + throw $e; + } + } + + /** + * Method perform a DELETE on the database, given a CcFiles or Criteria object OR a primary key value. + * + * @param mixed $values Criteria or CcFiles object or primary key or array of primary keys + * which is used to create the DELETE statement + * @param PropelPDO $con the connection to use + * @return int The number of affected rows (if supported by underlying database driver). This includes CASCADE-related rows + * if supported by native driver or if emulated using Propel. + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function doDelete($values, PropelPDO $con = null) + { + if ($con === null) { + $con = Propel::getConnection(CcFilesPeer::DATABASE_NAME, Propel::CONNECTION_WRITE); + } + + if ($values instanceof Criteria) { + // invalidate the cache for all objects of this type, since we have no + // way of knowing (without running a query) what objects should be invalidated + // from the cache based on this Criteria. + CcFilesPeer::clearInstancePool(); + // rename for clarity + $criteria = clone $values; + } elseif ($values instanceof CcFiles) { // it's a model object + // invalidate the cache for this single object + CcFilesPeer::removeInstanceFromPool($values); + // create criteria based on pk values + $criteria = $values->buildPkeyCriteria(); + } else { // it's a primary key, or an array of pks + $criteria = new Criteria(self::DATABASE_NAME); + $criteria->add(CcFilesPeer::ID, (array) $values, Criteria::IN); + // invalidate the cache for this object(s) + foreach ((array) $values as $singleval) { + CcFilesPeer::removeInstanceFromPool($singleval); + } + } + + // Set the correct dbName + $criteria->setDbName(self::DATABASE_NAME); + + $affectedRows = 0; // initialize var to track total num of affected rows + + try { + // use transaction because $criteria could contain info + // for more than one table or we could emulating ON DELETE CASCADE, etc. + $con->beginTransaction(); + + $affectedRows += BasePeer::doDelete($criteria, $con); + CcFilesPeer::clearRelatedInstancePool(); + $con->commit(); + return $affectedRows; + } catch (PropelException $e) { + $con->rollBack(); + throw $e; + } + } + + /** + * Validates all modified columns of given CcFiles object. + * If parameter $columns is either a single column name or an array of column names + * than only those columns are validated. + * + * NOTICE: This does not apply to primary or foreign keys for now. + * + * @param CcFiles $obj The object to validate. + * @param mixed $cols Column name or array of column names. + * + * @return mixed TRUE if all columns are valid or the error message of the first invalid column. + */ + public static function doValidate(CcFiles $obj, $cols = null) + { + $columns = array(); + + if ($cols) { + $dbMap = Propel::getDatabaseMap(CcFilesPeer::DATABASE_NAME); + $tableMap = $dbMap->getTable(CcFilesPeer::TABLE_NAME); + + if (! is_array($cols)) { + $cols = array($cols); + } + + foreach ($cols as $colName) { + if ($tableMap->containsColumn($colName)) { + $get = 'get' . $tableMap->getColumn($colName)->getPhpName(); + $columns[$colName] = $obj->$get(); + } + } + } else { + + } + + return BasePeer::doValidate(CcFilesPeer::DATABASE_NAME, CcFilesPeer::TABLE_NAME, $columns); + } + + /** + * Retrieve a single object by pkey. + * + * @param int $pk the primary key. + * @param PropelPDO $con the connection to use + * @return CcFiles + */ + public static function retrieveByPK($pk, PropelPDO $con = null) + { + + if (null !== ($obj = CcFilesPeer::getInstanceFromPool((string) $pk))) { + return $obj; + } + + if ($con === null) { + $con = Propel::getConnection(CcFilesPeer::DATABASE_NAME, Propel::CONNECTION_READ); + } + + $criteria = new Criteria(CcFilesPeer::DATABASE_NAME); + $criteria->add(CcFilesPeer::ID, $pk); + + $v = CcFilesPeer::doSelect($criteria, $con); + + return !empty($v) > 0 ? $v[0] : null; + } + + /** + * Retrieve multiple objects by pkey. + * + * @param array $pks List of primary keys + * @param PropelPDO $con the connection to use + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function retrieveByPKs($pks, PropelPDO $con = null) + { + if ($con === null) { + $con = Propel::getConnection(CcFilesPeer::DATABASE_NAME, Propel::CONNECTION_READ); + } + + $objs = null; + if (empty($pks)) { + $objs = array(); + } else { + $criteria = new Criteria(CcFilesPeer::DATABASE_NAME); + $criteria->add(CcFilesPeer::ID, $pks, Criteria::IN); + $objs = CcFilesPeer::doSelect($criteria, $con); + } + return $objs; + } + +} // BaseCcFilesPeer + +// This is the static code needed to register the TableMap for this table with the main Propel class. +// +BaseCcFilesPeer::buildTableMap(); + diff --git a/application/models/campcaster/om/BaseCcFilesQuery.php b/application/models/campcaster/om/BaseCcFilesQuery.php new file mode 100644 index 000000000..03136febe --- /dev/null +++ b/application/models/campcaster/om/BaseCcFilesQuery.php @@ -0,0 +1,1733 @@ +setModelAlias($modelAlias); + } + if ($criteria instanceof Criteria) { + $query->mergeWith($criteria); + } + return $query; + } + + /** + * Find object by primary key + * Use instance pooling to avoid a database query if the object exists + * + * $obj = $c->findPk(12, $con); + * + * @param mixed $key Primary key to use for the query + * @param PropelPDO $con an optional connection object + * + * @return CcFiles|array|mixed the result, formatted by the current formatter + */ + public function findPk($key, $con = null) + { + if ((null !== ($obj = CcFilesPeer::getInstanceFromPool((string) $key))) && $this->getFormatter()->isObjectFormatter()) { + // the object is alredy in the instance pool + return $obj; + } else { + // the object has not been requested yet, or the formatter is not an object formatter + $criteria = $this->isKeepQuery() ? clone $this : $this; + $stmt = $criteria + ->filterByPrimaryKey($key) + ->getSelectStatement($con); + return $criteria->getFormatter()->init($criteria)->formatOne($stmt); + } + } + + /** + * Find objects by primary key + * + * $objs = $c->findPks(array(12, 56, 832), $con); + * + * @param array $keys Primary keys to use for the query + * @param PropelPDO $con an optional connection object + * + * @return PropelObjectCollection|array|mixed the list of results, formatted by the current formatter + */ + public function findPks($keys, $con = null) + { + $criteria = $this->isKeepQuery() ? clone $this : $this; + return $this + ->filterByPrimaryKeys($keys) + ->find($con); + } + + /** + * Filter the query by primary key + * + * @param mixed $key Primary key to use for the query + * + * @return CcFilesQuery The current query, for fluid interface + */ + public function filterByPrimaryKey($key) + { + return $this->addUsingAlias(CcFilesPeer::ID, $key, Criteria::EQUAL); + } + + /** + * Filter the query by a list of primary keys + * + * @param array $keys The list of primary key to use for the query + * + * @return CcFilesQuery The current query, for fluid interface + */ + public function filterByPrimaryKeys($keys) + { + return $this->addUsingAlias(CcFilesPeer::ID, $keys, Criteria::IN); + } + + /** + * Filter the query on the id column + * + * @param int|array $dbId The value to use as filter. + * Accepts an associative array('min' => $minValue, 'max' => $maxValue) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcFilesQuery The current query, for fluid interface + */ + public function filterByDbId($dbId = null, $comparison = null) + { + if (is_array($dbId) && null === $comparison) { + $comparison = Criteria::IN; + } + return $this->addUsingAlias(CcFilesPeer::ID, $dbId, $comparison); + } + + /** + * Filter the query on the gunid column + * + * @param string $gunid The value to use as filter. + * Accepts wildcards (* and % trigger a LIKE) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcFilesQuery The current query, for fluid interface + */ + public function filterByGunid($gunid = null, $comparison = null) + { + if (null === $comparison) { + if (is_array($gunid)) { + $comparison = Criteria::IN; + } elseif (preg_match('/[\%\*]/', $gunid)) { + $gunid = str_replace('*', '%', $gunid); + $comparison = Criteria::LIKE; + } + } + return $this->addUsingAlias(CcFilesPeer::GUNID, $gunid, $comparison); + } + + /** + * Filter the query on the name column + * + * @param string $name The value to use as filter. + * Accepts wildcards (* and % trigger a LIKE) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcFilesQuery The current query, for fluid interface + */ + public function filterByName($name = null, $comparison = null) + { + if (null === $comparison) { + if (is_array($name)) { + $comparison = Criteria::IN; + } elseif (preg_match('/[\%\*]/', $name)) { + $name = str_replace('*', '%', $name); + $comparison = Criteria::LIKE; + } + } + return $this->addUsingAlias(CcFilesPeer::NAME, $name, $comparison); + } + + /** + * Filter the query on the mime column + * + * @param string $mime The value to use as filter. + * Accepts wildcards (* and % trigger a LIKE) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcFilesQuery The current query, for fluid interface + */ + public function filterByMime($mime = null, $comparison = null) + { + if (null === $comparison) { + if (is_array($mime)) { + $comparison = Criteria::IN; + } elseif (preg_match('/[\%\*]/', $mime)) { + $mime = str_replace('*', '%', $mime); + $comparison = Criteria::LIKE; + } + } + return $this->addUsingAlias(CcFilesPeer::MIME, $mime, $comparison); + } + + /** + * Filter the query on the ftype column + * + * @param string $ftype The value to use as filter. + * Accepts wildcards (* and % trigger a LIKE) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcFilesQuery The current query, for fluid interface + */ + public function filterByFtype($ftype = null, $comparison = null) + { + if (null === $comparison) { + if (is_array($ftype)) { + $comparison = Criteria::IN; + } elseif (preg_match('/[\%\*]/', $ftype)) { + $ftype = str_replace('*', '%', $ftype); + $comparison = Criteria::LIKE; + } + } + return $this->addUsingAlias(CcFilesPeer::FTYPE, $ftype, $comparison); + } + + /** + * Filter the query on the filepath column + * + * @param string $filepath The value to use as filter. + * Accepts wildcards (* and % trigger a LIKE) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcFilesQuery The current query, for fluid interface + */ + public function filterByfilepath($filepath = null, $comparison = null) + { + if (null === $comparison) { + if (is_array($filepath)) { + $comparison = Criteria::IN; + } elseif (preg_match('/[\%\*]/', $filepath)) { + $filepath = str_replace('*', '%', $filepath); + $comparison = Criteria::LIKE; + } + } + return $this->addUsingAlias(CcFilesPeer::FILEPATH, $filepath, $comparison); + } + + /** + * Filter the query on the state column + * + * @param string $state The value to use as filter. + * Accepts wildcards (* and % trigger a LIKE) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcFilesQuery The current query, for fluid interface + */ + public function filterByState($state = null, $comparison = null) + { + if (null === $comparison) { + if (is_array($state)) { + $comparison = Criteria::IN; + } elseif (preg_match('/[\%\*]/', $state)) { + $state = str_replace('*', '%', $state); + $comparison = Criteria::LIKE; + } + } + return $this->addUsingAlias(CcFilesPeer::STATE, $state, $comparison); + } + + /** + * Filter the query on the currentlyaccessing column + * + * @param int|array $currentlyaccessing The value to use as filter. + * Accepts an associative array('min' => $minValue, 'max' => $maxValue) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcFilesQuery The current query, for fluid interface + */ + public function filterByCurrentlyaccessing($currentlyaccessing = null, $comparison = null) + { + if (is_array($currentlyaccessing)) { + $useMinMax = false; + if (isset($currentlyaccessing['min'])) { + $this->addUsingAlias(CcFilesPeer::CURRENTLYACCESSING, $currentlyaccessing['min'], Criteria::GREATER_EQUAL); + $useMinMax = true; + } + if (isset($currentlyaccessing['max'])) { + $this->addUsingAlias(CcFilesPeer::CURRENTLYACCESSING, $currentlyaccessing['max'], Criteria::LESS_EQUAL); + $useMinMax = true; + } + if ($useMinMax) { + return $this; + } + if (null === $comparison) { + $comparison = Criteria::IN; + } + } + return $this->addUsingAlias(CcFilesPeer::CURRENTLYACCESSING, $currentlyaccessing, $comparison); + } + + /** + * Filter the query on the editedby column + * + * @param int|array $editedby The value to use as filter. + * Accepts an associative array('min' => $minValue, 'max' => $maxValue) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcFilesQuery The current query, for fluid interface + */ + public function filterByEditedby($editedby = null, $comparison = null) + { + if (is_array($editedby)) { + $useMinMax = false; + if (isset($editedby['min'])) { + $this->addUsingAlias(CcFilesPeer::EDITEDBY, $editedby['min'], Criteria::GREATER_EQUAL); + $useMinMax = true; + } + if (isset($editedby['max'])) { + $this->addUsingAlias(CcFilesPeer::EDITEDBY, $editedby['max'], Criteria::LESS_EQUAL); + $useMinMax = true; + } + if ($useMinMax) { + return $this; + } + if (null === $comparison) { + $comparison = Criteria::IN; + } + } + return $this->addUsingAlias(CcFilesPeer::EDITEDBY, $editedby, $comparison); + } + + /** + * Filter the query on the mtime column + * + * @param string|array $mtime The value to use as filter. + * Accepts an associative array('min' => $minValue, 'max' => $maxValue) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcFilesQuery The current query, for fluid interface + */ + public function filterByMtime($mtime = null, $comparison = null) + { + if (is_array($mtime)) { + $useMinMax = false; + if (isset($mtime['min'])) { + $this->addUsingAlias(CcFilesPeer::MTIME, $mtime['min'], Criteria::GREATER_EQUAL); + $useMinMax = true; + } + if (isset($mtime['max'])) { + $this->addUsingAlias(CcFilesPeer::MTIME, $mtime['max'], Criteria::LESS_EQUAL); + $useMinMax = true; + } + if ($useMinMax) { + return $this; + } + if (null === $comparison) { + $comparison = Criteria::IN; + } + } + return $this->addUsingAlias(CcFilesPeer::MTIME, $mtime, $comparison); + } + + /** + * Filter the query on the md5 column + * + * @param string $md5 The value to use as filter. + * Accepts wildcards (* and % trigger a LIKE) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcFilesQuery The current query, for fluid interface + */ + public function filterByMd5($md5 = null, $comparison = null) + { + if (null === $comparison) { + if (is_array($md5)) { + $comparison = Criteria::IN; + } elseif (preg_match('/[\%\*]/', $md5)) { + $md5 = str_replace('*', '%', $md5); + $comparison = Criteria::LIKE; + } + } + return $this->addUsingAlias(CcFilesPeer::MD5, $md5, $comparison); + } + + /** + * Filter the query on the track_title column + * + * @param string $trackTitle The value to use as filter. + * Accepts wildcards (* and % trigger a LIKE) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcFilesQuery The current query, for fluid interface + */ + public function filterByTrackTitle($trackTitle = null, $comparison = null) + { + if (null === $comparison) { + if (is_array($trackTitle)) { + $comparison = Criteria::IN; + } elseif (preg_match('/[\%\*]/', $trackTitle)) { + $trackTitle = str_replace('*', '%', $trackTitle); + $comparison = Criteria::LIKE; + } + } + return $this->addUsingAlias(CcFilesPeer::TRACK_TITLE, $trackTitle, $comparison); + } + + /** + * Filter the query on the artist_name column + * + * @param string $artistName The value to use as filter. + * Accepts wildcards (* and % trigger a LIKE) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcFilesQuery The current query, for fluid interface + */ + public function filterByArtistName($artistName = null, $comparison = null) + { + if (null === $comparison) { + if (is_array($artistName)) { + $comparison = Criteria::IN; + } elseif (preg_match('/[\%\*]/', $artistName)) { + $artistName = str_replace('*', '%', $artistName); + $comparison = Criteria::LIKE; + } + } + return $this->addUsingAlias(CcFilesPeer::ARTIST_NAME, $artistName, $comparison); + } + + /** + * Filter the query on the bit_rate column + * + * @param string $bitRate The value to use as filter. + * Accepts wildcards (* and % trigger a LIKE) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcFilesQuery The current query, for fluid interface + */ + public function filterByBitRate($bitRate = null, $comparison = null) + { + if (null === $comparison) { + if (is_array($bitRate)) { + $comparison = Criteria::IN; + } elseif (preg_match('/[\%\*]/', $bitRate)) { + $bitRate = str_replace('*', '%', $bitRate); + $comparison = Criteria::LIKE; + } + } + return $this->addUsingAlias(CcFilesPeer::BIT_RATE, $bitRate, $comparison); + } + + /** + * Filter the query on the sample_rate column + * + * @param string $sampleRate The value to use as filter. + * Accepts wildcards (* and % trigger a LIKE) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcFilesQuery The current query, for fluid interface + */ + public function filterBySampleRate($sampleRate = null, $comparison = null) + { + if (null === $comparison) { + if (is_array($sampleRate)) { + $comparison = Criteria::IN; + } elseif (preg_match('/[\%\*]/', $sampleRate)) { + $sampleRate = str_replace('*', '%', $sampleRate); + $comparison = Criteria::LIKE; + } + } + return $this->addUsingAlias(CcFilesPeer::SAMPLE_RATE, $sampleRate, $comparison); + } + + /** + * Filter the query on the format column + * + * @param string $format The value to use as filter. + * Accepts wildcards (* and % trigger a LIKE) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcFilesQuery The current query, for fluid interface + */ + public function filterByFormat($format = null, $comparison = null) + { + if (null === $comparison) { + if (is_array($format)) { + $comparison = Criteria::IN; + } elseif (preg_match('/[\%\*]/', $format)) { + $format = str_replace('*', '%', $format); + $comparison = Criteria::LIKE; + } + } + return $this->addUsingAlias(CcFilesPeer::FORMAT, $format, $comparison); + } + + /** + * Filter the query on the length column + * + * @param string|array $dbLength The value to use as filter. + * Accepts an associative array('min' => $minValue, 'max' => $maxValue) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcFilesQuery The current query, for fluid interface + */ + public function filterByDbLength($dbLength = null, $comparison = null) + { + if (is_array($dbLength)) { + $useMinMax = false; + if (isset($dbLength['min'])) { + $this->addUsingAlias(CcFilesPeer::LENGTH, $dbLength['min'], Criteria::GREATER_EQUAL); + $useMinMax = true; + } + if (isset($dbLength['max'])) { + $this->addUsingAlias(CcFilesPeer::LENGTH, $dbLength['max'], Criteria::LESS_EQUAL); + $useMinMax = true; + } + if ($useMinMax) { + return $this; + } + if (null === $comparison) { + $comparison = Criteria::IN; + } + } + return $this->addUsingAlias(CcFilesPeer::LENGTH, $dbLength, $comparison); + } + + /** + * Filter the query on the album_title column + * + * @param string $albumTitle The value to use as filter. + * Accepts wildcards (* and % trigger a LIKE) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcFilesQuery The current query, for fluid interface + */ + public function filterByAlbumTitle($albumTitle = null, $comparison = null) + { + if (null === $comparison) { + if (is_array($albumTitle)) { + $comparison = Criteria::IN; + } elseif (preg_match('/[\%\*]/', $albumTitle)) { + $albumTitle = str_replace('*', '%', $albumTitle); + $comparison = Criteria::LIKE; + } + } + return $this->addUsingAlias(CcFilesPeer::ALBUM_TITLE, $albumTitle, $comparison); + } + + /** + * Filter the query on the genre column + * + * @param string $genre The value to use as filter. + * Accepts wildcards (* and % trigger a LIKE) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcFilesQuery The current query, for fluid interface + */ + public function filterByGenre($genre = null, $comparison = null) + { + if (null === $comparison) { + if (is_array($genre)) { + $comparison = Criteria::IN; + } elseif (preg_match('/[\%\*]/', $genre)) { + $genre = str_replace('*', '%', $genre); + $comparison = Criteria::LIKE; + } + } + return $this->addUsingAlias(CcFilesPeer::GENRE, $genre, $comparison); + } + + /** + * Filter the query on the comments column + * + * @param string $comments The value to use as filter. + * Accepts wildcards (* and % trigger a LIKE) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcFilesQuery The current query, for fluid interface + */ + public function filterByComments($comments = null, $comparison = null) + { + if (null === $comparison) { + if (is_array($comments)) { + $comparison = Criteria::IN; + } elseif (preg_match('/[\%\*]/', $comments)) { + $comments = str_replace('*', '%', $comments); + $comparison = Criteria::LIKE; + } + } + return $this->addUsingAlias(CcFilesPeer::COMMENTS, $comments, $comparison); + } + + /** + * Filter the query on the year column + * + * @param string $year The value to use as filter. + * Accepts wildcards (* and % trigger a LIKE) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcFilesQuery The current query, for fluid interface + */ + public function filterByYear($year = null, $comparison = null) + { + if (null === $comparison) { + if (is_array($year)) { + $comparison = Criteria::IN; + } elseif (preg_match('/[\%\*]/', $year)) { + $year = str_replace('*', '%', $year); + $comparison = Criteria::LIKE; + } + } + return $this->addUsingAlias(CcFilesPeer::YEAR, $year, $comparison); + } + + /** + * Filter the query on the track_number column + * + * @param int|array $trackNumber The value to use as filter. + * Accepts an associative array('min' => $minValue, 'max' => $maxValue) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcFilesQuery The current query, for fluid interface + */ + public function filterByTrackNumber($trackNumber = null, $comparison = null) + { + if (is_array($trackNumber)) { + $useMinMax = false; + if (isset($trackNumber['min'])) { + $this->addUsingAlias(CcFilesPeer::TRACK_NUMBER, $trackNumber['min'], Criteria::GREATER_EQUAL); + $useMinMax = true; + } + if (isset($trackNumber['max'])) { + $this->addUsingAlias(CcFilesPeer::TRACK_NUMBER, $trackNumber['max'], Criteria::LESS_EQUAL); + $useMinMax = true; + } + if ($useMinMax) { + return $this; + } + if (null === $comparison) { + $comparison = Criteria::IN; + } + } + return $this->addUsingAlias(CcFilesPeer::TRACK_NUMBER, $trackNumber, $comparison); + } + + /** + * Filter the query on the channels column + * + * @param int|array $channels The value to use as filter. + * Accepts an associative array('min' => $minValue, 'max' => $maxValue) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcFilesQuery The current query, for fluid interface + */ + public function filterByChannels($channels = null, $comparison = null) + { + if (is_array($channels)) { + $useMinMax = false; + if (isset($channels['min'])) { + $this->addUsingAlias(CcFilesPeer::CHANNELS, $channels['min'], Criteria::GREATER_EQUAL); + $useMinMax = true; + } + if (isset($channels['max'])) { + $this->addUsingAlias(CcFilesPeer::CHANNELS, $channels['max'], Criteria::LESS_EQUAL); + $useMinMax = true; + } + if ($useMinMax) { + return $this; + } + if (null === $comparison) { + $comparison = Criteria::IN; + } + } + return $this->addUsingAlias(CcFilesPeer::CHANNELS, $channels, $comparison); + } + + /** + * Filter the query on the url column + * + * @param string $url The value to use as filter. + * Accepts wildcards (* and % trigger a LIKE) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcFilesQuery The current query, for fluid interface + */ + public function filterByUrl($url = null, $comparison = null) + { + if (null === $comparison) { + if (is_array($url)) { + $comparison = Criteria::IN; + } elseif (preg_match('/[\%\*]/', $url)) { + $url = str_replace('*', '%', $url); + $comparison = Criteria::LIKE; + } + } + return $this->addUsingAlias(CcFilesPeer::URL, $url, $comparison); + } + + /** + * Filter the query on the bpm column + * + * @param string $bpm The value to use as filter. + * Accepts wildcards (* and % trigger a LIKE) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcFilesQuery The current query, for fluid interface + */ + public function filterByBpm($bpm = null, $comparison = null) + { + if (null === $comparison) { + if (is_array($bpm)) { + $comparison = Criteria::IN; + } elseif (preg_match('/[\%\*]/', $bpm)) { + $bpm = str_replace('*', '%', $bpm); + $comparison = Criteria::LIKE; + } + } + return $this->addUsingAlias(CcFilesPeer::BPM, $bpm, $comparison); + } + + /** + * Filter the query on the rating column + * + * @param string $rating The value to use as filter. + * Accepts wildcards (* and % trigger a LIKE) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcFilesQuery The current query, for fluid interface + */ + public function filterByRating($rating = null, $comparison = null) + { + if (null === $comparison) { + if (is_array($rating)) { + $comparison = Criteria::IN; + } elseif (preg_match('/[\%\*]/', $rating)) { + $rating = str_replace('*', '%', $rating); + $comparison = Criteria::LIKE; + } + } + return $this->addUsingAlias(CcFilesPeer::RATING, $rating, $comparison); + } + + /** + * Filter the query on the encoded_by column + * + * @param string $encodedBy The value to use as filter. + * Accepts wildcards (* and % trigger a LIKE) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcFilesQuery The current query, for fluid interface + */ + public function filterByEncodedBy($encodedBy = null, $comparison = null) + { + if (null === $comparison) { + if (is_array($encodedBy)) { + $comparison = Criteria::IN; + } elseif (preg_match('/[\%\*]/', $encodedBy)) { + $encodedBy = str_replace('*', '%', $encodedBy); + $comparison = Criteria::LIKE; + } + } + return $this->addUsingAlias(CcFilesPeer::ENCODED_BY, $encodedBy, $comparison); + } + + /** + * Filter the query on the disc_number column + * + * @param string $discNumber The value to use as filter. + * Accepts wildcards (* and % trigger a LIKE) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcFilesQuery The current query, for fluid interface + */ + public function filterByDiscNumber($discNumber = null, $comparison = null) + { + if (null === $comparison) { + if (is_array($discNumber)) { + $comparison = Criteria::IN; + } elseif (preg_match('/[\%\*]/', $discNumber)) { + $discNumber = str_replace('*', '%', $discNumber); + $comparison = Criteria::LIKE; + } + } + return $this->addUsingAlias(CcFilesPeer::DISC_NUMBER, $discNumber, $comparison); + } + + /** + * Filter the query on the mood column + * + * @param string $mood The value to use as filter. + * Accepts wildcards (* and % trigger a LIKE) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcFilesQuery The current query, for fluid interface + */ + public function filterByMood($mood = null, $comparison = null) + { + if (null === $comparison) { + if (is_array($mood)) { + $comparison = Criteria::IN; + } elseif (preg_match('/[\%\*]/', $mood)) { + $mood = str_replace('*', '%', $mood); + $comparison = Criteria::LIKE; + } + } + return $this->addUsingAlias(CcFilesPeer::MOOD, $mood, $comparison); + } + + /** + * Filter the query on the label column + * + * @param string $label The value to use as filter. + * Accepts wildcards (* and % trigger a LIKE) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcFilesQuery The current query, for fluid interface + */ + public function filterByLabel($label = null, $comparison = null) + { + if (null === $comparison) { + if (is_array($label)) { + $comparison = Criteria::IN; + } elseif (preg_match('/[\%\*]/', $label)) { + $label = str_replace('*', '%', $label); + $comparison = Criteria::LIKE; + } + } + return $this->addUsingAlias(CcFilesPeer::LABEL, $label, $comparison); + } + + /** + * Filter the query on the composer column + * + * @param string $composer The value to use as filter. + * Accepts wildcards (* and % trigger a LIKE) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcFilesQuery The current query, for fluid interface + */ + public function filterByComposer($composer = null, $comparison = null) + { + if (null === $comparison) { + if (is_array($composer)) { + $comparison = Criteria::IN; + } elseif (preg_match('/[\%\*]/', $composer)) { + $composer = str_replace('*', '%', $composer); + $comparison = Criteria::LIKE; + } + } + return $this->addUsingAlias(CcFilesPeer::COMPOSER, $composer, $comparison); + } + + /** + * Filter the query on the encoder column + * + * @param string $encoder The value to use as filter. + * Accepts wildcards (* and % trigger a LIKE) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcFilesQuery The current query, for fluid interface + */ + public function filterByEncoder($encoder = null, $comparison = null) + { + if (null === $comparison) { + if (is_array($encoder)) { + $comparison = Criteria::IN; + } elseif (preg_match('/[\%\*]/', $encoder)) { + $encoder = str_replace('*', '%', $encoder); + $comparison = Criteria::LIKE; + } + } + return $this->addUsingAlias(CcFilesPeer::ENCODER, $encoder, $comparison); + } + + /** + * Filter the query on the checksum column + * + * @param string $checksum The value to use as filter. + * Accepts wildcards (* and % trigger a LIKE) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcFilesQuery The current query, for fluid interface + */ + public function filterByChecksum($checksum = null, $comparison = null) + { + if (null === $comparison) { + if (is_array($checksum)) { + $comparison = Criteria::IN; + } elseif (preg_match('/[\%\*]/', $checksum)) { + $checksum = str_replace('*', '%', $checksum); + $comparison = Criteria::LIKE; + } + } + return $this->addUsingAlias(CcFilesPeer::CHECKSUM, $checksum, $comparison); + } + + /** + * Filter the query on the lyrics column + * + * @param string $lyrics The value to use as filter. + * Accepts wildcards (* and % trigger a LIKE) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcFilesQuery The current query, for fluid interface + */ + public function filterByLyrics($lyrics = null, $comparison = null) + { + if (null === $comparison) { + if (is_array($lyrics)) { + $comparison = Criteria::IN; + } elseif (preg_match('/[\%\*]/', $lyrics)) { + $lyrics = str_replace('*', '%', $lyrics); + $comparison = Criteria::LIKE; + } + } + return $this->addUsingAlias(CcFilesPeer::LYRICS, $lyrics, $comparison); + } + + /** + * Filter the query on the orchestra column + * + * @param string $orchestra The value to use as filter. + * Accepts wildcards (* and % trigger a LIKE) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcFilesQuery The current query, for fluid interface + */ + public function filterByOrchestra($orchestra = null, $comparison = null) + { + if (null === $comparison) { + if (is_array($orchestra)) { + $comparison = Criteria::IN; + } elseif (preg_match('/[\%\*]/', $orchestra)) { + $orchestra = str_replace('*', '%', $orchestra); + $comparison = Criteria::LIKE; + } + } + return $this->addUsingAlias(CcFilesPeer::ORCHESTRA, $orchestra, $comparison); + } + + /** + * Filter the query on the conductor column + * + * @param string $conductor The value to use as filter. + * Accepts wildcards (* and % trigger a LIKE) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcFilesQuery The current query, for fluid interface + */ + public function filterByConductor($conductor = null, $comparison = null) + { + if (null === $comparison) { + if (is_array($conductor)) { + $comparison = Criteria::IN; + } elseif (preg_match('/[\%\*]/', $conductor)) { + $conductor = str_replace('*', '%', $conductor); + $comparison = Criteria::LIKE; + } + } + return $this->addUsingAlias(CcFilesPeer::CONDUCTOR, $conductor, $comparison); + } + + /** + * Filter the query on the lyricist column + * + * @param string $lyricist The value to use as filter. + * Accepts wildcards (* and % trigger a LIKE) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcFilesQuery The current query, for fluid interface + */ + public function filterByLyricist($lyricist = null, $comparison = null) + { + if (null === $comparison) { + if (is_array($lyricist)) { + $comparison = Criteria::IN; + } elseif (preg_match('/[\%\*]/', $lyricist)) { + $lyricist = str_replace('*', '%', $lyricist); + $comparison = Criteria::LIKE; + } + } + return $this->addUsingAlias(CcFilesPeer::LYRICIST, $lyricist, $comparison); + } + + /** + * Filter the query on the original_lyricist column + * + * @param string $originalLyricist The value to use as filter. + * Accepts wildcards (* and % trigger a LIKE) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcFilesQuery The current query, for fluid interface + */ + public function filterByOriginalLyricist($originalLyricist = null, $comparison = null) + { + if (null === $comparison) { + if (is_array($originalLyricist)) { + $comparison = Criteria::IN; + } elseif (preg_match('/[\%\*]/', $originalLyricist)) { + $originalLyricist = str_replace('*', '%', $originalLyricist); + $comparison = Criteria::LIKE; + } + } + return $this->addUsingAlias(CcFilesPeer::ORIGINAL_LYRICIST, $originalLyricist, $comparison); + } + + /** + * Filter the query on the radio_station_name column + * + * @param string $radioStationName The value to use as filter. + * Accepts wildcards (* and % trigger a LIKE) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcFilesQuery The current query, for fluid interface + */ + public function filterByRadioStationName($radioStationName = null, $comparison = null) + { + if (null === $comparison) { + if (is_array($radioStationName)) { + $comparison = Criteria::IN; + } elseif (preg_match('/[\%\*]/', $radioStationName)) { + $radioStationName = str_replace('*', '%', $radioStationName); + $comparison = Criteria::LIKE; + } + } + return $this->addUsingAlias(CcFilesPeer::RADIO_STATION_NAME, $radioStationName, $comparison); + } + + /** + * Filter the query on the info_url column + * + * @param string $infoUrl The value to use as filter. + * Accepts wildcards (* and % trigger a LIKE) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcFilesQuery The current query, for fluid interface + */ + public function filterByInfoUrl($infoUrl = null, $comparison = null) + { + if (null === $comparison) { + if (is_array($infoUrl)) { + $comparison = Criteria::IN; + } elseif (preg_match('/[\%\*]/', $infoUrl)) { + $infoUrl = str_replace('*', '%', $infoUrl); + $comparison = Criteria::LIKE; + } + } + return $this->addUsingAlias(CcFilesPeer::INFO_URL, $infoUrl, $comparison); + } + + /** + * Filter the query on the artist_url column + * + * @param string $artistUrl The value to use as filter. + * Accepts wildcards (* and % trigger a LIKE) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcFilesQuery The current query, for fluid interface + */ + public function filterByArtistUrl($artistUrl = null, $comparison = null) + { + if (null === $comparison) { + if (is_array($artistUrl)) { + $comparison = Criteria::IN; + } elseif (preg_match('/[\%\*]/', $artistUrl)) { + $artistUrl = str_replace('*', '%', $artistUrl); + $comparison = Criteria::LIKE; + } + } + return $this->addUsingAlias(CcFilesPeer::ARTIST_URL, $artistUrl, $comparison); + } + + /** + * Filter the query on the audio_source_url column + * + * @param string $audioSourceUrl The value to use as filter. + * Accepts wildcards (* and % trigger a LIKE) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcFilesQuery The current query, for fluid interface + */ + public function filterByAudioSourceUrl($audioSourceUrl = null, $comparison = null) + { + if (null === $comparison) { + if (is_array($audioSourceUrl)) { + $comparison = Criteria::IN; + } elseif (preg_match('/[\%\*]/', $audioSourceUrl)) { + $audioSourceUrl = str_replace('*', '%', $audioSourceUrl); + $comparison = Criteria::LIKE; + } + } + return $this->addUsingAlias(CcFilesPeer::AUDIO_SOURCE_URL, $audioSourceUrl, $comparison); + } + + /** + * Filter the query on the radio_station_url column + * + * @param string $radioStationUrl The value to use as filter. + * Accepts wildcards (* and % trigger a LIKE) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcFilesQuery The current query, for fluid interface + */ + public function filterByRadioStationUrl($radioStationUrl = null, $comparison = null) + { + if (null === $comparison) { + if (is_array($radioStationUrl)) { + $comparison = Criteria::IN; + } elseif (preg_match('/[\%\*]/', $radioStationUrl)) { + $radioStationUrl = str_replace('*', '%', $radioStationUrl); + $comparison = Criteria::LIKE; + } + } + return $this->addUsingAlias(CcFilesPeer::RADIO_STATION_URL, $radioStationUrl, $comparison); + } + + /** + * Filter the query on the buy_this_url column + * + * @param string $buyThisUrl The value to use as filter. + * Accepts wildcards (* and % trigger a LIKE) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcFilesQuery The current query, for fluid interface + */ + public function filterByBuyThisUrl($buyThisUrl = null, $comparison = null) + { + if (null === $comparison) { + if (is_array($buyThisUrl)) { + $comparison = Criteria::IN; + } elseif (preg_match('/[\%\*]/', $buyThisUrl)) { + $buyThisUrl = str_replace('*', '%', $buyThisUrl); + $comparison = Criteria::LIKE; + } + } + return $this->addUsingAlias(CcFilesPeer::BUY_THIS_URL, $buyThisUrl, $comparison); + } + + /** + * Filter the query on the isrc_number column + * + * @param string $isrcNumber The value to use as filter. + * Accepts wildcards (* and % trigger a LIKE) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcFilesQuery The current query, for fluid interface + */ + public function filterByIsrcNumber($isrcNumber = null, $comparison = null) + { + if (null === $comparison) { + if (is_array($isrcNumber)) { + $comparison = Criteria::IN; + } elseif (preg_match('/[\%\*]/', $isrcNumber)) { + $isrcNumber = str_replace('*', '%', $isrcNumber); + $comparison = Criteria::LIKE; + } + } + return $this->addUsingAlias(CcFilesPeer::ISRC_NUMBER, $isrcNumber, $comparison); + } + + /** + * Filter the query on the catalog_number column + * + * @param string $catalogNumber The value to use as filter. + * Accepts wildcards (* and % trigger a LIKE) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcFilesQuery The current query, for fluid interface + */ + public function filterByCatalogNumber($catalogNumber = null, $comparison = null) + { + if (null === $comparison) { + if (is_array($catalogNumber)) { + $comparison = Criteria::IN; + } elseif (preg_match('/[\%\*]/', $catalogNumber)) { + $catalogNumber = str_replace('*', '%', $catalogNumber); + $comparison = Criteria::LIKE; + } + } + return $this->addUsingAlias(CcFilesPeer::CATALOG_NUMBER, $catalogNumber, $comparison); + } + + /** + * Filter the query on the original_artist column + * + * @param string $originalArtist The value to use as filter. + * Accepts wildcards (* and % trigger a LIKE) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcFilesQuery The current query, for fluid interface + */ + public function filterByOriginalArtist($originalArtist = null, $comparison = null) + { + if (null === $comparison) { + if (is_array($originalArtist)) { + $comparison = Criteria::IN; + } elseif (preg_match('/[\%\*]/', $originalArtist)) { + $originalArtist = str_replace('*', '%', $originalArtist); + $comparison = Criteria::LIKE; + } + } + return $this->addUsingAlias(CcFilesPeer::ORIGINAL_ARTIST, $originalArtist, $comparison); + } + + /** + * Filter the query on the copyright column + * + * @param string $copyright The value to use as filter. + * Accepts wildcards (* and % trigger a LIKE) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcFilesQuery The current query, for fluid interface + */ + public function filterByCopyright($copyright = null, $comparison = null) + { + if (null === $comparison) { + if (is_array($copyright)) { + $comparison = Criteria::IN; + } elseif (preg_match('/[\%\*]/', $copyright)) { + $copyright = str_replace('*', '%', $copyright); + $comparison = Criteria::LIKE; + } + } + return $this->addUsingAlias(CcFilesPeer::COPYRIGHT, $copyright, $comparison); + } + + /** + * Filter the query on the report_datetime column + * + * @param string $reportDatetime The value to use as filter. + * Accepts wildcards (* and % trigger a LIKE) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcFilesQuery The current query, for fluid interface + */ + public function filterByReportDatetime($reportDatetime = null, $comparison = null) + { + if (null === $comparison) { + if (is_array($reportDatetime)) { + $comparison = Criteria::IN; + } elseif (preg_match('/[\%\*]/', $reportDatetime)) { + $reportDatetime = str_replace('*', '%', $reportDatetime); + $comparison = Criteria::LIKE; + } + } + return $this->addUsingAlias(CcFilesPeer::REPORT_DATETIME, $reportDatetime, $comparison); + } + + /** + * Filter the query on the report_location column + * + * @param string $reportLocation The value to use as filter. + * Accepts wildcards (* and % trigger a LIKE) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcFilesQuery The current query, for fluid interface + */ + public function filterByReportLocation($reportLocation = null, $comparison = null) + { + if (null === $comparison) { + if (is_array($reportLocation)) { + $comparison = Criteria::IN; + } elseif (preg_match('/[\%\*]/', $reportLocation)) { + $reportLocation = str_replace('*', '%', $reportLocation); + $comparison = Criteria::LIKE; + } + } + return $this->addUsingAlias(CcFilesPeer::REPORT_LOCATION, $reportLocation, $comparison); + } + + /** + * Filter the query on the report_organization column + * + * @param string $reportOrganization The value to use as filter. + * Accepts wildcards (* and % trigger a LIKE) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcFilesQuery The current query, for fluid interface + */ + public function filterByReportOrganization($reportOrganization = null, $comparison = null) + { + if (null === $comparison) { + if (is_array($reportOrganization)) { + $comparison = Criteria::IN; + } elseif (preg_match('/[\%\*]/', $reportOrganization)) { + $reportOrganization = str_replace('*', '%', $reportOrganization); + $comparison = Criteria::LIKE; + } + } + return $this->addUsingAlias(CcFilesPeer::REPORT_ORGANIZATION, $reportOrganization, $comparison); + } + + /** + * Filter the query on the subject column + * + * @param string $subject The value to use as filter. + * Accepts wildcards (* and % trigger a LIKE) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcFilesQuery The current query, for fluid interface + */ + public function filterBySubject($subject = null, $comparison = null) + { + if (null === $comparison) { + if (is_array($subject)) { + $comparison = Criteria::IN; + } elseif (preg_match('/[\%\*]/', $subject)) { + $subject = str_replace('*', '%', $subject); + $comparison = Criteria::LIKE; + } + } + return $this->addUsingAlias(CcFilesPeer::SUBJECT, $subject, $comparison); + } + + /** + * Filter the query on the contributor column + * + * @param string $contributor The value to use as filter. + * Accepts wildcards (* and % trigger a LIKE) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcFilesQuery The current query, for fluid interface + */ + public function filterByContributor($contributor = null, $comparison = null) + { + if (null === $comparison) { + if (is_array($contributor)) { + $comparison = Criteria::IN; + } elseif (preg_match('/[\%\*]/', $contributor)) { + $contributor = str_replace('*', '%', $contributor); + $comparison = Criteria::LIKE; + } + } + return $this->addUsingAlias(CcFilesPeer::CONTRIBUTOR, $contributor, $comparison); + } + + /** + * Filter the query on the language column + * + * @param string $language The value to use as filter. + * Accepts wildcards (* and % trigger a LIKE) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcFilesQuery The current query, for fluid interface + */ + public function filterByLanguage($language = null, $comparison = null) + { + if (null === $comparison) { + if (is_array($language)) { + $comparison = Criteria::IN; + } elseif (preg_match('/[\%\*]/', $language)) { + $language = str_replace('*', '%', $language); + $comparison = Criteria::LIKE; + } + } + return $this->addUsingAlias(CcFilesPeer::LANGUAGE, $language, $comparison); + } + + /** + * Filter the query by a related CcSubjs object + * + * @param CcSubjs $ccSubjs the related object to use as filter + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcFilesQuery The current query, for fluid interface + */ + public function filterByCcSubjs($ccSubjs, $comparison = null) + { + return $this + ->addUsingAlias(CcFilesPeer::EDITEDBY, $ccSubjs->getId(), $comparison); + } + + /** + * Adds a JOIN clause to the query using the CcSubjs relation + * + * @param string $relationAlias optional alias for the relation + * @param string $joinType Accepted values are null, 'left join', 'right join', 'inner join' + * + * @return CcFilesQuery The current query, for fluid interface + */ + public function joinCcSubjs($relationAlias = '', $joinType = Criteria::LEFT_JOIN) + { + $tableMap = $this->getTableMap(); + $relationMap = $tableMap->getRelation('CcSubjs'); + + // create a ModelJoin object for this join + $join = new ModelJoin(); + $join->setJoinType($joinType); + $join->setRelationMap($relationMap, $this->useAliasInSQL ? $this->getModelAlias() : null, $relationAlias); + if ($previousJoin = $this->getPreviousJoin()) { + $join->setPreviousJoin($previousJoin); + } + + // add the ModelJoin to the current object + if($relationAlias) { + $this->addAlias($relationAlias, $relationMap->getRightTable()->getName()); + $this->addJoinObject($join, $relationAlias); + } else { + $this->addJoinObject($join, 'CcSubjs'); + } + + return $this; + } + + /** + * Use the CcSubjs relation CcSubjs object + * + * @see useQuery() + * + * @param string $relationAlias optional alias for the relation, + * to be used as main alias in the secondary query + * @param string $joinType Accepted values are null, 'left join', 'right join', 'inner join' + * + * @return CcSubjsQuery A secondary query class using the current class as primary query + */ + public function useCcSubjsQuery($relationAlias = '', $joinType = Criteria::LEFT_JOIN) + { + return $this + ->joinCcSubjs($relationAlias, $joinType) + ->useQuery($relationAlias ? $relationAlias : 'CcSubjs', 'CcSubjsQuery'); + } + + /** + * Filter the query by a related CcPlaylistcontents object + * + * @param CcPlaylistcontents $ccPlaylistcontents the related object to use as filter + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcFilesQuery The current query, for fluid interface + */ + public function filterByCcPlaylistcontents($ccPlaylistcontents, $comparison = null) + { + return $this + ->addUsingAlias(CcFilesPeer::ID, $ccPlaylistcontents->getDbFileId(), $comparison); + } + + /** + * Adds a JOIN clause to the query using the CcPlaylistcontents relation + * + * @param string $relationAlias optional alias for the relation + * @param string $joinType Accepted values are null, 'left join', 'right join', 'inner join' + * + * @return CcFilesQuery The current query, for fluid interface + */ + public function joinCcPlaylistcontents($relationAlias = '', $joinType = Criteria::LEFT_JOIN) + { + $tableMap = $this->getTableMap(); + $relationMap = $tableMap->getRelation('CcPlaylistcontents'); + + // create a ModelJoin object for this join + $join = new ModelJoin(); + $join->setJoinType($joinType); + $join->setRelationMap($relationMap, $this->useAliasInSQL ? $this->getModelAlias() : null, $relationAlias); + if ($previousJoin = $this->getPreviousJoin()) { + $join->setPreviousJoin($previousJoin); + } + + // add the ModelJoin to the current object + if($relationAlias) { + $this->addAlias($relationAlias, $relationMap->getRightTable()->getName()); + $this->addJoinObject($join, $relationAlias); + } else { + $this->addJoinObject($join, 'CcPlaylistcontents'); + } + + return $this; + } + + /** + * Use the CcPlaylistcontents relation CcPlaylistcontents object + * + * @see useQuery() + * + * @param string $relationAlias optional alias for the relation, + * to be used as main alias in the secondary query + * @param string $joinType Accepted values are null, 'left join', 'right join', 'inner join' + * + * @return CcPlaylistcontentsQuery A secondary query class using the current class as primary query + */ + public function useCcPlaylistcontentsQuery($relationAlias = '', $joinType = Criteria::LEFT_JOIN) + { + return $this + ->joinCcPlaylistcontents($relationAlias, $joinType) + ->useQuery($relationAlias ? $relationAlias : 'CcPlaylistcontents', 'CcPlaylistcontentsQuery'); + } + + /** + * Exclude object from result + * + * @param CcFiles $ccFiles Object to remove from the list of results + * + * @return CcFilesQuery The current query, for fluid interface + */ + public function prune($ccFiles = null) + { + if ($ccFiles) { + $this->addUsingAlias(CcFilesPeer::ID, $ccFiles->getDbId(), Criteria::NOT_EQUAL); + } + + return $this; + } + +} // BaseCcFilesQuery diff --git a/application/models/campcaster/om/BaseCcPerms.php b/application/models/campcaster/om/BaseCcPerms.php new file mode 100644 index 000000000..a7398920f --- /dev/null +++ b/application/models/campcaster/om/BaseCcPerms.php @@ -0,0 +1,945 @@ +permid; + } + + /** + * Get the [subj] column value. + * + * @return int + */ + public function getSubj() + { + return $this->subj; + } + + /** + * Get the [action] column value. + * + * @return string + */ + public function getAction() + { + return $this->action; + } + + /** + * Get the [obj] column value. + * + * @return int + */ + public function getObj() + { + return $this->obj; + } + + /** + * Get the [type] column value. + * + * @return string + */ + public function getType() + { + return $this->type; + } + + /** + * Set the value of [permid] column. + * + * @param int $v new value + * @return CcPerms The current object (for fluent API support) + */ + public function setPermid($v) + { + if ($v !== null) { + $v = (int) $v; + } + + if ($this->permid !== $v) { + $this->permid = $v; + $this->modifiedColumns[] = CcPermsPeer::PERMID; + } + + return $this; + } // setPermid() + + /** + * Set the value of [subj] column. + * + * @param int $v new value + * @return CcPerms The current object (for fluent API support) + */ + public function setSubj($v) + { + if ($v !== null) { + $v = (int) $v; + } + + if ($this->subj !== $v) { + $this->subj = $v; + $this->modifiedColumns[] = CcPermsPeer::SUBJ; + } + + if ($this->aCcSubjs !== null && $this->aCcSubjs->getId() !== $v) { + $this->aCcSubjs = null; + } + + return $this; + } // setSubj() + + /** + * Set the value of [action] column. + * + * @param string $v new value + * @return CcPerms The current object (for fluent API support) + */ + public function setAction($v) + { + if ($v !== null) { + $v = (string) $v; + } + + if ($this->action !== $v) { + $this->action = $v; + $this->modifiedColumns[] = CcPermsPeer::ACTION; + } + + return $this; + } // setAction() + + /** + * Set the value of [obj] column. + * + * @param int $v new value + * @return CcPerms The current object (for fluent API support) + */ + public function setObj($v) + { + if ($v !== null) { + $v = (int) $v; + } + + if ($this->obj !== $v) { + $this->obj = $v; + $this->modifiedColumns[] = CcPermsPeer::OBJ; + } + + return $this; + } // setObj() + + /** + * Set the value of [type] column. + * + * @param string $v new value + * @return CcPerms The current object (for fluent API support) + */ + public function setType($v) + { + if ($v !== null) { + $v = (string) $v; + } + + if ($this->type !== $v) { + $this->type = $v; + $this->modifiedColumns[] = CcPermsPeer::TYPE; + } + + return $this; + } // setType() + + /** + * Indicates whether the columns in this object are only set to default values. + * + * This method can be used in conjunction with isModified() to indicate whether an object is both + * modified _and_ has some values set which are non-default. + * + * @return boolean Whether the columns in this object are only been set with default values. + */ + public function hasOnlyDefaultValues() + { + // otherwise, everything was equal, so return TRUE + return true; + } // hasOnlyDefaultValues() + + /** + * Hydrates (populates) the object variables with values from the database resultset. + * + * An offset (0-based "start column") is specified so that objects can be hydrated + * with a subset of the columns in the resultset rows. This is needed, for example, + * for results of JOIN queries where the resultset row includes columns from two or + * more tables. + * + * @param array $row The row returned by PDOStatement->fetch(PDO::FETCH_NUM) + * @param int $startcol 0-based offset column which indicates which restultset column to start with. + * @param boolean $rehydrate Whether this object is being re-hydrated from the database. + * @return int next starting column + * @throws PropelException - Any caught Exception will be rewrapped as a PropelException. + */ + public function hydrate($row, $startcol = 0, $rehydrate = false) + { + try { + + $this->permid = ($row[$startcol + 0] !== null) ? (int) $row[$startcol + 0] : null; + $this->subj = ($row[$startcol + 1] !== null) ? (int) $row[$startcol + 1] : null; + $this->action = ($row[$startcol + 2] !== null) ? (string) $row[$startcol + 2] : null; + $this->obj = ($row[$startcol + 3] !== null) ? (int) $row[$startcol + 3] : null; + $this->type = ($row[$startcol + 4] !== null) ? (string) $row[$startcol + 4] : null; + $this->resetModified(); + + $this->setNew(false); + + if ($rehydrate) { + $this->ensureConsistency(); + } + + return $startcol + 5; // 5 = CcPermsPeer::NUM_COLUMNS - CcPermsPeer::NUM_LAZY_LOAD_COLUMNS). + + } catch (Exception $e) { + throw new PropelException("Error populating CcPerms object", $e); + } + } + + /** + * Checks and repairs the internal consistency of the object. + * + * This method is executed after an already-instantiated object is re-hydrated + * from the database. It exists to check any foreign keys to make sure that + * the objects related to the current object are correct based on foreign key. + * + * You can override this method in the stub class, but you should always invoke + * the base method from the overridden method (i.e. parent::ensureConsistency()), + * in case your model changes. + * + * @throws PropelException + */ + public function ensureConsistency() + { + + if ($this->aCcSubjs !== null && $this->subj !== $this->aCcSubjs->getId()) { + $this->aCcSubjs = null; + } + } // ensureConsistency + + /** + * Reloads this object from datastore based on primary key and (optionally) resets all associated objects. + * + * This will only work if the object has been saved and has a valid primary key set. + * + * @param boolean $deep (optional) Whether to also de-associated any related objects. + * @param PropelPDO $con (optional) The PropelPDO connection to use. + * @return void + * @throws PropelException - if this object is deleted, unsaved or doesn't have pk match in db + */ + public function reload($deep = false, PropelPDO $con = null) + { + if ($this->isDeleted()) { + throw new PropelException("Cannot reload a deleted object."); + } + + if ($this->isNew()) { + throw new PropelException("Cannot reload an unsaved object."); + } + + if ($con === null) { + $con = Propel::getConnection(CcPermsPeer::DATABASE_NAME, Propel::CONNECTION_READ); + } + + // We don't need to alter the object instance pool; we're just modifying this instance + // already in the pool. + + $stmt = CcPermsPeer::doSelectStmt($this->buildPkeyCriteria(), $con); + $row = $stmt->fetch(PDO::FETCH_NUM); + $stmt->closeCursor(); + if (!$row) { + throw new PropelException('Cannot find matching row in the database to reload object values.'); + } + $this->hydrate($row, 0, true); // rehydrate + + if ($deep) { // also de-associate any related objects? + + $this->aCcSubjs = null; + } // if (deep) + } + + /** + * Removes this object from datastore and sets delete attribute. + * + * @param PropelPDO $con + * @return void + * @throws PropelException + * @see BaseObject::setDeleted() + * @see BaseObject::isDeleted() + */ + public function delete(PropelPDO $con = null) + { + if ($this->isDeleted()) { + throw new PropelException("This object has already been deleted."); + } + + if ($con === null) { + $con = Propel::getConnection(CcPermsPeer::DATABASE_NAME, Propel::CONNECTION_WRITE); + } + + $con->beginTransaction(); + try { + $ret = $this->preDelete($con); + if ($ret) { + CcPermsQuery::create() + ->filterByPrimaryKey($this->getPrimaryKey()) + ->delete($con); + $this->postDelete($con); + $con->commit(); + $this->setDeleted(true); + } else { + $con->commit(); + } + } catch (PropelException $e) { + $con->rollBack(); + throw $e; + } + } + + /** + * Persists this object to the database. + * + * If the object is new, it inserts it; otherwise an update is performed. + * All modified related objects will also be persisted in the doSave() + * method. This method wraps all precipitate database operations in a + * single transaction. + * + * @param PropelPDO $con + * @return int The number of rows affected by this insert/update and any referring fk objects' save() operations. + * @throws PropelException + * @see doSave() + */ + public function save(PropelPDO $con = null) + { + if ($this->isDeleted()) { + throw new PropelException("You cannot save an object that has been deleted."); + } + + if ($con === null) { + $con = Propel::getConnection(CcPermsPeer::DATABASE_NAME, Propel::CONNECTION_WRITE); + } + + $con->beginTransaction(); + $isInsert = $this->isNew(); + try { + $ret = $this->preSave($con); + if ($isInsert) { + $ret = $ret && $this->preInsert($con); + } else { + $ret = $ret && $this->preUpdate($con); + } + if ($ret) { + $affectedRows = $this->doSave($con); + if ($isInsert) { + $this->postInsert($con); + } else { + $this->postUpdate($con); + } + $this->postSave($con); + CcPermsPeer::addInstanceToPool($this); + } else { + $affectedRows = 0; + } + $con->commit(); + return $affectedRows; + } catch (PropelException $e) { + $con->rollBack(); + throw $e; + } + } + + /** + * Performs the work of inserting or updating the row in the database. + * + * If the object is new, it inserts it; otherwise an update is performed. + * All related objects are also updated in this method. + * + * @param PropelPDO $con + * @return int The number of rows affected by this insert/update and any referring fk objects' save() operations. + * @throws PropelException + * @see save() + */ + protected function doSave(PropelPDO $con) + { + $affectedRows = 0; // initialize var to track total num of affected rows + if (!$this->alreadyInSave) { + $this->alreadyInSave = true; + + // We call the save method on the following object(s) if they + // were passed to this object by their coresponding set + // method. This object relates to these object(s) by a + // foreign key reference. + + if ($this->aCcSubjs !== null) { + if ($this->aCcSubjs->isModified() || $this->aCcSubjs->isNew()) { + $affectedRows += $this->aCcSubjs->save($con); + } + $this->setCcSubjs($this->aCcSubjs); + } + + + // If this object has been modified, then save it to the database. + if ($this->isModified()) { + if ($this->isNew()) { + $criteria = $this->buildCriteria(); + $pk = BasePeer::doInsert($criteria, $con); + $affectedRows += 1; + $this->setNew(false); + } else { + $affectedRows += CcPermsPeer::doUpdate($this, $con); + } + + $this->resetModified(); // [HL] After being saved an object is no longer 'modified' + } + + $this->alreadyInSave = false; + + } + return $affectedRows; + } // doSave() + + /** + * Array of ValidationFailed objects. + * @var array ValidationFailed[] + */ + protected $validationFailures = array(); + + /** + * Gets any ValidationFailed objects that resulted from last call to validate(). + * + * + * @return array ValidationFailed[] + * @see validate() + */ + public function getValidationFailures() + { + return $this->validationFailures; + } + + /** + * Validates the objects modified field values and all objects related to this table. + * + * If $columns is either a column name or an array of column names + * only those columns are validated. + * + * @param mixed $columns Column name or an array of column names. + * @return boolean Whether all columns pass validation. + * @see doValidate() + * @see getValidationFailures() + */ + public function validate($columns = null) + { + $res = $this->doValidate($columns); + if ($res === true) { + $this->validationFailures = array(); + return true; + } else { + $this->validationFailures = $res; + return false; + } + } + + /** + * This function performs the validation work for complex object models. + * + * In addition to checking the current object, all related objects will + * also be validated. If all pass then true is returned; otherwise + * an aggreagated array of ValidationFailed objects will be returned. + * + * @param array $columns Array of column names to validate. + * @return mixed true if all validations pass; array of ValidationFailed objets otherwise. + */ + protected function doValidate($columns = null) + { + if (!$this->alreadyInValidation) { + $this->alreadyInValidation = true; + $retval = null; + + $failureMap = array(); + + + // We call the validate method on the following object(s) if they + // were passed to this object by their coresponding set + // method. This object relates to these object(s) by a + // foreign key reference. + + if ($this->aCcSubjs !== null) { + if (!$this->aCcSubjs->validate($columns)) { + $failureMap = array_merge($failureMap, $this->aCcSubjs->getValidationFailures()); + } + } + + + if (($retval = CcPermsPeer::doValidate($this, $columns)) !== true) { + $failureMap = array_merge($failureMap, $retval); + } + + + + $this->alreadyInValidation = false; + } + + return (!empty($failureMap) ? $failureMap : true); + } + + /** + * Retrieves a field from the object by name passed in as a string. + * + * @param string $name name + * @param string $type The type of fieldname the $name is of: + * one of the class type constants BasePeer::TYPE_PHPNAME, BasePeer::TYPE_STUDLYPHPNAME + * BasePeer::TYPE_COLNAME, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_NUM + * @return mixed Value of field. + */ + public function getByName($name, $type = BasePeer::TYPE_PHPNAME) + { + $pos = CcPermsPeer::translateFieldName($name, $type, BasePeer::TYPE_NUM); + $field = $this->getByPosition($pos); + return $field; + } + + /** + * Retrieves a field from the object by Position as specified in the xml schema. + * Zero-based. + * + * @param int $pos position in xml schema + * @return mixed Value of field at $pos + */ + public function getByPosition($pos) + { + switch($pos) { + case 0: + return $this->getPermid(); + break; + case 1: + return $this->getSubj(); + break; + case 2: + return $this->getAction(); + break; + case 3: + return $this->getObj(); + break; + case 4: + return $this->getType(); + break; + default: + return null; + break; + } // switch() + } + + /** + * Exports the object as an array. + * + * You can specify the key type of the array by passing one of the class + * type constants. + * + * @param string $keyType (optional) One of the class type constants BasePeer::TYPE_PHPNAME, BasePeer::TYPE_STUDLYPHPNAME, + * BasePeer::TYPE_COLNAME, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_NUM. + * Defaults to BasePeer::TYPE_PHPNAME. + * @param boolean $includeLazyLoadColumns (optional) Whether to include lazy loaded columns. Defaults to TRUE. + * @param boolean $includeForeignObjects (optional) Whether to include hydrated related objects. Default to FALSE. + * + * @return array an associative array containing the field names (as keys) and field values + */ + public function toArray($keyType = BasePeer::TYPE_PHPNAME, $includeLazyLoadColumns = true, $includeForeignObjects = false) + { + $keys = CcPermsPeer::getFieldNames($keyType); + $result = array( + $keys[0] => $this->getPermid(), + $keys[1] => $this->getSubj(), + $keys[2] => $this->getAction(), + $keys[3] => $this->getObj(), + $keys[4] => $this->getType(), + ); + if ($includeForeignObjects) { + if (null !== $this->aCcSubjs) { + $result['CcSubjs'] = $this->aCcSubjs->toArray($keyType, $includeLazyLoadColumns, true); + } + } + return $result; + } + + /** + * Sets a field from the object by name passed in as a string. + * + * @param string $name peer name + * @param mixed $value field value + * @param string $type The type of fieldname the $name is of: + * one of the class type constants BasePeer::TYPE_PHPNAME, BasePeer::TYPE_STUDLYPHPNAME + * BasePeer::TYPE_COLNAME, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_NUM + * @return void + */ + public function setByName($name, $value, $type = BasePeer::TYPE_PHPNAME) + { + $pos = CcPermsPeer::translateFieldName($name, $type, BasePeer::TYPE_NUM); + return $this->setByPosition($pos, $value); + } + + /** + * Sets a field from the object by Position as specified in the xml schema. + * Zero-based. + * + * @param int $pos position in xml schema + * @param mixed $value field value + * @return void + */ + public function setByPosition($pos, $value) + { + switch($pos) { + case 0: + $this->setPermid($value); + break; + case 1: + $this->setSubj($value); + break; + case 2: + $this->setAction($value); + break; + case 3: + $this->setObj($value); + break; + case 4: + $this->setType($value); + break; + } // switch() + } + + /** + * Populates the object using an array. + * + * This is particularly useful when populating an object from one of the + * request arrays (e.g. $_POST). This method goes through the column + * names, checking to see whether a matching key exists in populated + * array. If so the setByName() method is called for that column. + * + * You can specify the key type of the array by additionally passing one + * of the class type constants BasePeer::TYPE_PHPNAME, BasePeer::TYPE_STUDLYPHPNAME, + * BasePeer::TYPE_COLNAME, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_NUM. + * The default key type is the column's phpname (e.g. 'AuthorId') + * + * @param array $arr An array to populate the object from. + * @param string $keyType The type of keys the array uses. + * @return void + */ + public function fromArray($arr, $keyType = BasePeer::TYPE_PHPNAME) + { + $keys = CcPermsPeer::getFieldNames($keyType); + + if (array_key_exists($keys[0], $arr)) $this->setPermid($arr[$keys[0]]); + if (array_key_exists($keys[1], $arr)) $this->setSubj($arr[$keys[1]]); + if (array_key_exists($keys[2], $arr)) $this->setAction($arr[$keys[2]]); + if (array_key_exists($keys[3], $arr)) $this->setObj($arr[$keys[3]]); + if (array_key_exists($keys[4], $arr)) $this->setType($arr[$keys[4]]); + } + + /** + * Build a Criteria object containing the values of all modified columns in this object. + * + * @return Criteria The Criteria object containing all modified values. + */ + public function buildCriteria() + { + $criteria = new Criteria(CcPermsPeer::DATABASE_NAME); + + if ($this->isColumnModified(CcPermsPeer::PERMID)) $criteria->add(CcPermsPeer::PERMID, $this->permid); + if ($this->isColumnModified(CcPermsPeer::SUBJ)) $criteria->add(CcPermsPeer::SUBJ, $this->subj); + if ($this->isColumnModified(CcPermsPeer::ACTION)) $criteria->add(CcPermsPeer::ACTION, $this->action); + if ($this->isColumnModified(CcPermsPeer::OBJ)) $criteria->add(CcPermsPeer::OBJ, $this->obj); + if ($this->isColumnModified(CcPermsPeer::TYPE)) $criteria->add(CcPermsPeer::TYPE, $this->type); + + return $criteria; + } + + /** + * Builds a Criteria object containing the primary key for this object. + * + * Unlike buildCriteria() this method includes the primary key values regardless + * of whether or not they have been modified. + * + * @return Criteria The Criteria object containing value(s) for primary key(s). + */ + public function buildPkeyCriteria() + { + $criteria = new Criteria(CcPermsPeer::DATABASE_NAME); + $criteria->add(CcPermsPeer::PERMID, $this->permid); + + return $criteria; + } + + /** + * Returns the primary key for this object (row). + * @return int + */ + public function getPrimaryKey() + { + return $this->getPermid(); + } + + /** + * Generic method to set the primary key (permid column). + * + * @param int $key Primary key. + * @return void + */ + public function setPrimaryKey($key) + { + $this->setPermid($key); + } + + /** + * Returns true if the primary key for this object is null. + * @return boolean + */ + public function isPrimaryKeyNull() + { + return null === $this->getPermid(); + } + + /** + * Sets contents of passed object to values from current object. + * + * If desired, this method can also make copies of all associated (fkey referrers) + * objects. + * + * @param object $copyObj An object of CcPerms (or compatible) type. + * @param boolean $deepCopy Whether to also copy all rows that refer (by fkey) to the current row. + * @throws PropelException + */ + public function copyInto($copyObj, $deepCopy = false) + { + $copyObj->setPermid($this->permid); + $copyObj->setSubj($this->subj); + $copyObj->setAction($this->action); + $copyObj->setObj($this->obj); + $copyObj->setType($this->type); + + $copyObj->setNew(true); + } + + /** + * Makes a copy of this object that will be inserted as a new row in table when saved. + * It creates a new object filling in the simple attributes, but skipping any primary + * keys that are defined for the table. + * + * If desired, this method can also make copies of all associated (fkey referrers) + * objects. + * + * @param boolean $deepCopy Whether to also copy all rows that refer (by fkey) to the current row. + * @return CcPerms Clone of current object. + * @throws PropelException + */ + public function copy($deepCopy = false) + { + // we use get_class(), because this might be a subclass + $clazz = get_class($this); + $copyObj = new $clazz(); + $this->copyInto($copyObj, $deepCopy); + return $copyObj; + } + + /** + * Returns a peer instance associated with this om. + * + * Since Peer classes are not to have any instance attributes, this method returns the + * same instance for all member of this class. The method could therefore + * be static, but this would prevent one from overriding the behavior. + * + * @return CcPermsPeer + */ + public function getPeer() + { + if (self::$peer === null) { + self::$peer = new CcPermsPeer(); + } + return self::$peer; + } + + /** + * Declares an association between this object and a CcSubjs object. + * + * @param CcSubjs $v + * @return CcPerms The current object (for fluent API support) + * @throws PropelException + */ + public function setCcSubjs(CcSubjs $v = null) + { + if ($v === null) { + $this->setSubj(NULL); + } else { + $this->setSubj($v->getId()); + } + + $this->aCcSubjs = $v; + + // Add binding for other direction of this n:n relationship. + // If this object has already been added to the CcSubjs object, it will not be re-added. + if ($v !== null) { + $v->addCcPerms($this); + } + + return $this; + } + + + /** + * Get the associated CcSubjs object + * + * @param PropelPDO Optional Connection object. + * @return CcSubjs The associated CcSubjs object. + * @throws PropelException + */ + public function getCcSubjs(PropelPDO $con = null) + { + if ($this->aCcSubjs === null && ($this->subj !== null)) { + $this->aCcSubjs = CcSubjsQuery::create()->findPk($this->subj, $con); + /* The following can be used additionally to + guarantee the related object contains a reference + to this object. This level of coupling may, however, be + undesirable since it could result in an only partially populated collection + in the referenced object. + $this->aCcSubjs->addCcPermss($this); + */ + } + return $this->aCcSubjs; + } + + /** + * Clears the current object and sets all attributes to their default values + */ + public function clear() + { + $this->permid = null; + $this->subj = null; + $this->action = null; + $this->obj = null; + $this->type = null; + $this->alreadyInSave = false; + $this->alreadyInValidation = false; + $this->clearAllReferences(); + $this->resetModified(); + $this->setNew(true); + $this->setDeleted(false); + } + + /** + * Resets all collections of referencing foreign keys. + * + * This method is a user-space workaround for PHP's inability to garbage collect objects + * with circular references. This is currently necessary when using Propel in certain + * daemon or large-volumne/high-memory operations. + * + * @param boolean $deep Whether to also clear the references on all associated objects. + */ + public function clearAllReferences($deep = false) + { + if ($deep) { + } // if ($deep) + + $this->aCcSubjs = null; + } + + /** + * Catches calls to virtual methods + */ + public function __call($name, $params) + { + if (preg_match('/get(\w+)/', $name, $matches)) { + $virtualColumn = $matches[1]; + if ($this->hasVirtualColumn($virtualColumn)) { + return $this->getVirtualColumn($virtualColumn); + } + // no lcfirst in php<5.3... + $virtualColumn[0] = strtolower($virtualColumn[0]); + if ($this->hasVirtualColumn($virtualColumn)) { + return $this->getVirtualColumn($virtualColumn); + } + } + throw new PropelException('Call to undefined method: ' . $name); + } + +} // BaseCcPerms diff --git a/application/models/campcaster/om/BaseCcPermsPeer.php b/application/models/campcaster/om/BaseCcPermsPeer.php new file mode 100644 index 000000000..2bfdaeb0e --- /dev/null +++ b/application/models/campcaster/om/BaseCcPermsPeer.php @@ -0,0 +1,984 @@ + array ('Permid', 'Subj', 'Action', 'Obj', 'Type', ), + BasePeer::TYPE_STUDLYPHPNAME => array ('permid', 'subj', 'action', 'obj', 'type', ), + BasePeer::TYPE_COLNAME => array (self::PERMID, self::SUBJ, self::ACTION, self::OBJ, self::TYPE, ), + BasePeer::TYPE_RAW_COLNAME => array ('PERMID', 'SUBJ', 'ACTION', 'OBJ', 'TYPE', ), + BasePeer::TYPE_FIELDNAME => array ('permid', 'subj', 'action', 'obj', 'type', ), + BasePeer::TYPE_NUM => array (0, 1, 2, 3, 4, ) + ); + + /** + * holds an array of keys for quick access to the fieldnames array + * + * first dimension keys are the type constants + * e.g. self::$fieldNames[BasePeer::TYPE_PHPNAME]['Id'] = 0 + */ + private static $fieldKeys = array ( + BasePeer::TYPE_PHPNAME => array ('Permid' => 0, 'Subj' => 1, 'Action' => 2, 'Obj' => 3, 'Type' => 4, ), + BasePeer::TYPE_STUDLYPHPNAME => array ('permid' => 0, 'subj' => 1, 'action' => 2, 'obj' => 3, 'type' => 4, ), + BasePeer::TYPE_COLNAME => array (self::PERMID => 0, self::SUBJ => 1, self::ACTION => 2, self::OBJ => 3, self::TYPE => 4, ), + BasePeer::TYPE_RAW_COLNAME => array ('PERMID' => 0, 'SUBJ' => 1, 'ACTION' => 2, 'OBJ' => 3, 'TYPE' => 4, ), + BasePeer::TYPE_FIELDNAME => array ('permid' => 0, 'subj' => 1, 'action' => 2, 'obj' => 3, 'type' => 4, ), + BasePeer::TYPE_NUM => array (0, 1, 2, 3, 4, ) + ); + + /** + * Translates a fieldname to another type + * + * @param string $name field name + * @param string $fromType One of the class type constants BasePeer::TYPE_PHPNAME, BasePeer::TYPE_STUDLYPHPNAME + * BasePeer::TYPE_COLNAME, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_NUM + * @param string $toType One of the class type constants + * @return string translated name of the field. + * @throws PropelException - if the specified name could not be found in the fieldname mappings. + */ + static public function translateFieldName($name, $fromType, $toType) + { + $toNames = self::getFieldNames($toType); + $key = isset(self::$fieldKeys[$fromType][$name]) ? self::$fieldKeys[$fromType][$name] : null; + if ($key === null) { + throw new PropelException("'$name' could not be found in the field names of type '$fromType'. These are: " . print_r(self::$fieldKeys[$fromType], true)); + } + return $toNames[$key]; + } + + /** + * Returns an array of field names. + * + * @param string $type The type of fieldnames to return: + * One of the class type constants BasePeer::TYPE_PHPNAME, BasePeer::TYPE_STUDLYPHPNAME + * BasePeer::TYPE_COLNAME, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_NUM + * @return array A list of field names + */ + + static public function getFieldNames($type = BasePeer::TYPE_PHPNAME) + { + if (!array_key_exists($type, self::$fieldNames)) { + throw new PropelException('Method getFieldNames() expects the parameter $type to be one of the class constants BasePeer::TYPE_PHPNAME, BasePeer::TYPE_STUDLYPHPNAME, BasePeer::TYPE_COLNAME, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_NUM. ' . $type . ' was given.'); + } + return self::$fieldNames[$type]; + } + + /** + * Convenience method which changes table.column to alias.column. + * + * Using this method you can maintain SQL abstraction while using column aliases. + * + * $c->addAlias("alias1", TablePeer::TABLE_NAME); + * $c->addJoin(TablePeer::alias("alias1", TablePeer::PRIMARY_KEY_COLUMN), TablePeer::PRIMARY_KEY_COLUMN); + * + * @param string $alias The alias for the current table. + * @param string $column The column name for current table. (i.e. CcPermsPeer::COLUMN_NAME). + * @return string + */ + public static function alias($alias, $column) + { + return str_replace(CcPermsPeer::TABLE_NAME.'.', $alias.'.', $column); + } + + /** + * Add all the columns needed to create a new object. + * + * Note: any columns that were marked with lazyLoad="true" in the + * XML schema will not be added to the select list and only loaded + * on demand. + * + * @param Criteria $criteria object containing the columns to add. + * @param string $alias optional table alias + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function addSelectColumns(Criteria $criteria, $alias = null) + { + if (null === $alias) { + $criteria->addSelectColumn(CcPermsPeer::PERMID); + $criteria->addSelectColumn(CcPermsPeer::SUBJ); + $criteria->addSelectColumn(CcPermsPeer::ACTION); + $criteria->addSelectColumn(CcPermsPeer::OBJ); + $criteria->addSelectColumn(CcPermsPeer::TYPE); + } else { + $criteria->addSelectColumn($alias . '.PERMID'); + $criteria->addSelectColumn($alias . '.SUBJ'); + $criteria->addSelectColumn($alias . '.ACTION'); + $criteria->addSelectColumn($alias . '.OBJ'); + $criteria->addSelectColumn($alias . '.TYPE'); + } + } + + /** + * Returns the number of rows matching criteria. + * + * @param Criteria $criteria + * @param boolean $distinct Whether to select only distinct columns; deprecated: use Criteria->setDistinct() instead. + * @param PropelPDO $con + * @return int Number of matching rows. + */ + public static function doCount(Criteria $criteria, $distinct = false, PropelPDO $con = null) + { + // we may modify criteria, so copy it first + $criteria = clone $criteria; + + // We need to set the primary table name, since in the case that there are no WHERE columns + // it will be impossible for the BasePeer::createSelectSql() method to determine which + // tables go into the FROM clause. + $criteria->setPrimaryTableName(CcPermsPeer::TABLE_NAME); + + if ($distinct && !in_array(Criteria::DISTINCT, $criteria->getSelectModifiers())) { + $criteria->setDistinct(); + } + + if (!$criteria->hasSelectClause()) { + CcPermsPeer::addSelectColumns($criteria); + } + + $criteria->clearOrderByColumns(); // ORDER BY won't ever affect the count + $criteria->setDbName(self::DATABASE_NAME); // Set the correct dbName + + if ($con === null) { + $con = Propel::getConnection(CcPermsPeer::DATABASE_NAME, Propel::CONNECTION_READ); + } + // BasePeer returns a PDOStatement + $stmt = BasePeer::doCount($criteria, $con); + + if ($row = $stmt->fetch(PDO::FETCH_NUM)) { + $count = (int) $row[0]; + } else { + $count = 0; // no rows returned; we infer that means 0 matches. + } + $stmt->closeCursor(); + return $count; + } + /** + * Method to select one object from the DB. + * + * @param Criteria $criteria object used to create the SELECT statement. + * @param PropelPDO $con + * @return CcPerms + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function doSelectOne(Criteria $criteria, PropelPDO $con = null) + { + $critcopy = clone $criteria; + $critcopy->setLimit(1); + $objects = CcPermsPeer::doSelect($critcopy, $con); + if ($objects) { + return $objects[0]; + } + return null; + } + /** + * Method to do selects. + * + * @param Criteria $criteria The Criteria object used to build the SELECT statement. + * @param PropelPDO $con + * @return array Array of selected Objects + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function doSelect(Criteria $criteria, PropelPDO $con = null) + { + return CcPermsPeer::populateObjects(CcPermsPeer::doSelectStmt($criteria, $con)); + } + /** + * Prepares the Criteria object and uses the parent doSelect() method to execute a PDOStatement. + * + * Use this method directly if you want to work with an executed statement durirectly (for example + * to perform your own object hydration). + * + * @param Criteria $criteria The Criteria object used to build the SELECT statement. + * @param PropelPDO $con The connection to use + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + * @return PDOStatement The executed PDOStatement object. + * @see BasePeer::doSelect() + */ + public static function doSelectStmt(Criteria $criteria, PropelPDO $con = null) + { + if ($con === null) { + $con = Propel::getConnection(CcPermsPeer::DATABASE_NAME, Propel::CONNECTION_READ); + } + + if (!$criteria->hasSelectClause()) { + $criteria = clone $criteria; + CcPermsPeer::addSelectColumns($criteria); + } + + // Set the correct dbName + $criteria->setDbName(self::DATABASE_NAME); + + // BasePeer returns a PDOStatement + return BasePeer::doSelect($criteria, $con); + } + /** + * Adds an object to the instance pool. + * + * Propel keeps cached copies of objects in an instance pool when they are retrieved + * from the database. In some cases -- especially when you override doSelect*() + * methods in your stub classes -- you may need to explicitly add objects + * to the cache in order to ensure that the same objects are always returned by doSelect*() + * and retrieveByPK*() calls. + * + * @param CcPerms $value A CcPerms object. + * @param string $key (optional) key to use for instance map (for performance boost if key was already calculated externally). + */ + public static function addInstanceToPool(CcPerms $obj, $key = null) + { + if (Propel::isInstancePoolingEnabled()) { + if ($key === null) { + $key = (string) $obj->getPermid(); + } // if key === null + self::$instances[$key] = $obj; + } + } + + /** + * Removes an object from the instance pool. + * + * Propel keeps cached copies of objects in an instance pool when they are retrieved + * from the database. In some cases -- especially when you override doDelete + * methods in your stub classes -- you may need to explicitly remove objects + * from the cache in order to prevent returning objects that no longer exist. + * + * @param mixed $value A CcPerms object or a primary key value. + */ + public static function removeInstanceFromPool($value) + { + if (Propel::isInstancePoolingEnabled() && $value !== null) { + if (is_object($value) && $value instanceof CcPerms) { + $key = (string) $value->getPermid(); + } elseif (is_scalar($value)) { + // assume we've been passed a primary key + $key = (string) $value; + } else { + $e = new PropelException("Invalid value passed to removeInstanceFromPool(). Expected primary key or CcPerms object; got " . (is_object($value) ? get_class($value) . ' object.' : var_export($value,true))); + throw $e; + } + + unset(self::$instances[$key]); + } + } // removeInstanceFromPool() + + /** + * Retrieves a string version of the primary key from the DB resultset row that can be used to uniquely identify a row in this table. + * + * For tables with a single-column primary key, that simple pkey value will be returned. For tables with + * a multi-column primary key, a serialize()d version of the primary key will be returned. + * + * @param string $key The key (@see getPrimaryKeyHash()) for this instance. + * @return CcPerms Found object or NULL if 1) no instance exists for specified key or 2) instance pooling has been disabled. + * @see getPrimaryKeyHash() + */ + public static function getInstanceFromPool($key) + { + if (Propel::isInstancePoolingEnabled()) { + if (isset(self::$instances[$key])) { + return self::$instances[$key]; + } + } + return null; // just to be explicit + } + + /** + * Clear the instance pool. + * + * @return void + */ + public static function clearInstancePool() + { + self::$instances = array(); + } + + /** + * Method to invalidate the instance pool of all tables related to cc_perms + * by a foreign key with ON DELETE CASCADE + */ + public static function clearRelatedInstancePool() + { + } + + /** + * Retrieves a string version of the primary key from the DB resultset row that can be used to uniquely identify a row in this table. + * + * For tables with a single-column primary key, that simple pkey value will be returned. For tables with + * a multi-column primary key, a serialize()d version of the primary key will be returned. + * + * @param array $row PropelPDO resultset row. + * @param int $startcol The 0-based offset for reading from the resultset row. + * @return string A string version of PK or NULL if the components of primary key in result array are all null. + */ + public static function getPrimaryKeyHashFromRow($row, $startcol = 0) + { + // If the PK cannot be derived from the row, return NULL. + if ($row[$startcol] === null) { + return null; + } + return (string) $row[$startcol]; + } + + /** + * Retrieves the primary key from the DB resultset row + * For tables with a single-column primary key, that simple pkey value will be returned. For tables with + * a multi-column primary key, an array of the primary key columns will be returned. + * + * @param array $row PropelPDO resultset row. + * @param int $startcol The 0-based offset for reading from the resultset row. + * @return mixed The primary key of the row + */ + public static function getPrimaryKeyFromRow($row, $startcol = 0) + { + return (int) $row[$startcol]; + } + + /** + * The returned array will contain objects of the default type or + * objects that inherit from the default. + * + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function populateObjects(PDOStatement $stmt) + { + $results = array(); + + // set the class once to avoid overhead in the loop + $cls = CcPermsPeer::getOMClass(false); + // populate the object(s) + while ($row = $stmt->fetch(PDO::FETCH_NUM)) { + $key = CcPermsPeer::getPrimaryKeyHashFromRow($row, 0); + if (null !== ($obj = CcPermsPeer::getInstanceFromPool($key))) { + // We no longer rehydrate the object, since this can cause data loss. + // See http://www.propelorm.org/ticket/509 + // $obj->hydrate($row, 0, true); // rehydrate + $results[] = $obj; + } else { + $obj = new $cls(); + $obj->hydrate($row); + $results[] = $obj; + CcPermsPeer::addInstanceToPool($obj, $key); + } // if key exists + } + $stmt->closeCursor(); + return $results; + } + /** + * Populates an object of the default type or an object that inherit from the default. + * + * @param array $row PropelPDO resultset row. + * @param int $startcol The 0-based offset for reading from the resultset row. + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + * @return array (CcPerms object, last column rank) + */ + public static function populateObject($row, $startcol = 0) + { + $key = CcPermsPeer::getPrimaryKeyHashFromRow($row, $startcol); + if (null !== ($obj = CcPermsPeer::getInstanceFromPool($key))) { + // We no longer rehydrate the object, since this can cause data loss. + // See http://www.propelorm.org/ticket/509 + // $obj->hydrate($row, $startcol, true); // rehydrate + $col = $startcol + CcPermsPeer::NUM_COLUMNS; + } else { + $cls = CcPermsPeer::OM_CLASS; + $obj = new $cls(); + $col = $obj->hydrate($row, $startcol); + CcPermsPeer::addInstanceToPool($obj, $key); + } + return array($obj, $col); + } + + /** + * Returns the number of rows matching criteria, joining the related CcSubjs table + * + * @param Criteria $criteria + * @param boolean $distinct Whether to select only distinct columns; deprecated: use Criteria->setDistinct() instead. + * @param PropelPDO $con + * @param String $join_behavior the type of joins to use, defaults to Criteria::LEFT_JOIN + * @return int Number of matching rows. + */ + public static function doCountJoinCcSubjs(Criteria $criteria, $distinct = false, PropelPDO $con = null, $join_behavior = Criteria::LEFT_JOIN) + { + // we're going to modify criteria, so copy it first + $criteria = clone $criteria; + + // We need to set the primary table name, since in the case that there are no WHERE columns + // it will be impossible for the BasePeer::createSelectSql() method to determine which + // tables go into the FROM clause. + $criteria->setPrimaryTableName(CcPermsPeer::TABLE_NAME); + + if ($distinct && !in_array(Criteria::DISTINCT, $criteria->getSelectModifiers())) { + $criteria->setDistinct(); + } + + if (!$criteria->hasSelectClause()) { + CcPermsPeer::addSelectColumns($criteria); + } + + $criteria->clearOrderByColumns(); // ORDER BY won't ever affect the count + + // Set the correct dbName + $criteria->setDbName(self::DATABASE_NAME); + + if ($con === null) { + $con = Propel::getConnection(CcPermsPeer::DATABASE_NAME, Propel::CONNECTION_READ); + } + + $criteria->addJoin(CcPermsPeer::SUBJ, CcSubjsPeer::ID, $join_behavior); + + $stmt = BasePeer::doCount($criteria, $con); + + if ($row = $stmt->fetch(PDO::FETCH_NUM)) { + $count = (int) $row[0]; + } else { + $count = 0; // no rows returned; we infer that means 0 matches. + } + $stmt->closeCursor(); + return $count; + } + + + /** + * Selects a collection of CcPerms objects pre-filled with their CcSubjs objects. + * @param Criteria $criteria + * @param PropelPDO $con + * @param String $join_behavior the type of joins to use, defaults to Criteria::LEFT_JOIN + * @return array Array of CcPerms objects. + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function doSelectJoinCcSubjs(Criteria $criteria, $con = null, $join_behavior = Criteria::LEFT_JOIN) + { + $criteria = clone $criteria; + + // Set the correct dbName if it has not been overridden + if ($criteria->getDbName() == Propel::getDefaultDB()) { + $criteria->setDbName(self::DATABASE_NAME); + } + + CcPermsPeer::addSelectColumns($criteria); + $startcol = (CcPermsPeer::NUM_COLUMNS - CcPermsPeer::NUM_LAZY_LOAD_COLUMNS); + CcSubjsPeer::addSelectColumns($criteria); + + $criteria->addJoin(CcPermsPeer::SUBJ, CcSubjsPeer::ID, $join_behavior); + + $stmt = BasePeer::doSelect($criteria, $con); + $results = array(); + + while ($row = $stmt->fetch(PDO::FETCH_NUM)) { + $key1 = CcPermsPeer::getPrimaryKeyHashFromRow($row, 0); + if (null !== ($obj1 = CcPermsPeer::getInstanceFromPool($key1))) { + // We no longer rehydrate the object, since this can cause data loss. + // See http://www.propelorm.org/ticket/509 + // $obj1->hydrate($row, 0, true); // rehydrate + } else { + + $cls = CcPermsPeer::getOMClass(false); + + $obj1 = new $cls(); + $obj1->hydrate($row); + CcPermsPeer::addInstanceToPool($obj1, $key1); + } // if $obj1 already loaded + + $key2 = CcSubjsPeer::getPrimaryKeyHashFromRow($row, $startcol); + if ($key2 !== null) { + $obj2 = CcSubjsPeer::getInstanceFromPool($key2); + if (!$obj2) { + + $cls = CcSubjsPeer::getOMClass(false); + + $obj2 = new $cls(); + $obj2->hydrate($row, $startcol); + CcSubjsPeer::addInstanceToPool($obj2, $key2); + } // if obj2 already loaded + + // Add the $obj1 (CcPerms) to $obj2 (CcSubjs) + $obj2->addCcPerms($obj1); + + } // if joined row was not null + + $results[] = $obj1; + } + $stmt->closeCursor(); + return $results; + } + + + /** + * Returns the number of rows matching criteria, joining all related tables + * + * @param Criteria $criteria + * @param boolean $distinct Whether to select only distinct columns; deprecated: use Criteria->setDistinct() instead. + * @param PropelPDO $con + * @param String $join_behavior the type of joins to use, defaults to Criteria::LEFT_JOIN + * @return int Number of matching rows. + */ + public static function doCountJoinAll(Criteria $criteria, $distinct = false, PropelPDO $con = null, $join_behavior = Criteria::LEFT_JOIN) + { + // we're going to modify criteria, so copy it first + $criteria = clone $criteria; + + // We need to set the primary table name, since in the case that there are no WHERE columns + // it will be impossible for the BasePeer::createSelectSql() method to determine which + // tables go into the FROM clause. + $criteria->setPrimaryTableName(CcPermsPeer::TABLE_NAME); + + if ($distinct && !in_array(Criteria::DISTINCT, $criteria->getSelectModifiers())) { + $criteria->setDistinct(); + } + + if (!$criteria->hasSelectClause()) { + CcPermsPeer::addSelectColumns($criteria); + } + + $criteria->clearOrderByColumns(); // ORDER BY won't ever affect the count + + // Set the correct dbName + $criteria->setDbName(self::DATABASE_NAME); + + if ($con === null) { + $con = Propel::getConnection(CcPermsPeer::DATABASE_NAME, Propel::CONNECTION_READ); + } + + $criteria->addJoin(CcPermsPeer::SUBJ, CcSubjsPeer::ID, $join_behavior); + + $stmt = BasePeer::doCount($criteria, $con); + + if ($row = $stmt->fetch(PDO::FETCH_NUM)) { + $count = (int) $row[0]; + } else { + $count = 0; // no rows returned; we infer that means 0 matches. + } + $stmt->closeCursor(); + return $count; + } + + /** + * Selects a collection of CcPerms objects pre-filled with all related objects. + * + * @param Criteria $criteria + * @param PropelPDO $con + * @param String $join_behavior the type of joins to use, defaults to Criteria::LEFT_JOIN + * @return array Array of CcPerms objects. + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function doSelectJoinAll(Criteria $criteria, $con = null, $join_behavior = Criteria::LEFT_JOIN) + { + $criteria = clone $criteria; + + // Set the correct dbName if it has not been overridden + if ($criteria->getDbName() == Propel::getDefaultDB()) { + $criteria->setDbName(self::DATABASE_NAME); + } + + CcPermsPeer::addSelectColumns($criteria); + $startcol2 = (CcPermsPeer::NUM_COLUMNS - CcPermsPeer::NUM_LAZY_LOAD_COLUMNS); + + CcSubjsPeer::addSelectColumns($criteria); + $startcol3 = $startcol2 + (CcSubjsPeer::NUM_COLUMNS - CcSubjsPeer::NUM_LAZY_LOAD_COLUMNS); + + $criteria->addJoin(CcPermsPeer::SUBJ, CcSubjsPeer::ID, $join_behavior); + + $stmt = BasePeer::doSelect($criteria, $con); + $results = array(); + + while ($row = $stmt->fetch(PDO::FETCH_NUM)) { + $key1 = CcPermsPeer::getPrimaryKeyHashFromRow($row, 0); + if (null !== ($obj1 = CcPermsPeer::getInstanceFromPool($key1))) { + // We no longer rehydrate the object, since this can cause data loss. + // See http://www.propelorm.org/ticket/509 + // $obj1->hydrate($row, 0, true); // rehydrate + } else { + $cls = CcPermsPeer::getOMClass(false); + + $obj1 = new $cls(); + $obj1->hydrate($row); + CcPermsPeer::addInstanceToPool($obj1, $key1); + } // if obj1 already loaded + + // Add objects for joined CcSubjs rows + + $key2 = CcSubjsPeer::getPrimaryKeyHashFromRow($row, $startcol2); + if ($key2 !== null) { + $obj2 = CcSubjsPeer::getInstanceFromPool($key2); + if (!$obj2) { + + $cls = CcSubjsPeer::getOMClass(false); + + $obj2 = new $cls(); + $obj2->hydrate($row, $startcol2); + CcSubjsPeer::addInstanceToPool($obj2, $key2); + } // if obj2 loaded + + // Add the $obj1 (CcPerms) to the collection in $obj2 (CcSubjs) + $obj2->addCcPerms($obj1); + } // if joined row not null + + $results[] = $obj1; + } + $stmt->closeCursor(); + return $results; + } + + /** + * Returns the TableMap related to this peer. + * This method is not needed for general use but a specific application could have a need. + * @return TableMap + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function getTableMap() + { + return Propel::getDatabaseMap(self::DATABASE_NAME)->getTable(self::TABLE_NAME); + } + + /** + * Add a TableMap instance to the database for this peer class. + */ + public static function buildTableMap() + { + $dbMap = Propel::getDatabaseMap(BaseCcPermsPeer::DATABASE_NAME); + if (!$dbMap->hasTable(BaseCcPermsPeer::TABLE_NAME)) + { + $dbMap->addTableObject(new CcPermsTableMap()); + } + } + + /** + * The class that the Peer will make instances of. + * + * If $withPrefix is true, the returned path + * uses a dot-path notation which is tranalted into a path + * relative to a location on the PHP include_path. + * (e.g. path.to.MyClass -> 'path/to/MyClass.php') + * + * @param boolean $withPrefix Whether or not to return the path with the class name + * @return string path.to.ClassName + */ + public static function getOMClass($withPrefix = true) + { + return $withPrefix ? CcPermsPeer::CLASS_DEFAULT : CcPermsPeer::OM_CLASS; + } + + /** + * Method perform an INSERT on the database, given a CcPerms or Criteria object. + * + * @param mixed $values Criteria or CcPerms object containing data that is used to create the INSERT statement. + * @param PropelPDO $con the PropelPDO connection to use + * @return mixed The new primary key. + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function doInsert($values, PropelPDO $con = null) + { + if ($con === null) { + $con = Propel::getConnection(CcPermsPeer::DATABASE_NAME, Propel::CONNECTION_WRITE); + } + + if ($values instanceof Criteria) { + $criteria = clone $values; // rename for clarity + } else { + $criteria = $values->buildCriteria(); // build Criteria from CcPerms object + } + + + // Set the correct dbName + $criteria->setDbName(self::DATABASE_NAME); + + try { + // use transaction because $criteria could contain info + // for more than one table (I guess, conceivably) + $con->beginTransaction(); + $pk = BasePeer::doInsert($criteria, $con); + $con->commit(); + } catch(PropelException $e) { + $con->rollBack(); + throw $e; + } + + return $pk; + } + + /** + * Method perform an UPDATE on the database, given a CcPerms or Criteria object. + * + * @param mixed $values Criteria or CcPerms object containing data that is used to create the UPDATE statement. + * @param PropelPDO $con The connection to use (specify PropelPDO connection object to exert more control over transactions). + * @return int The number of affected rows (if supported by underlying database driver). + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function doUpdate($values, PropelPDO $con = null) + { + if ($con === null) { + $con = Propel::getConnection(CcPermsPeer::DATABASE_NAME, Propel::CONNECTION_WRITE); + } + + $selectCriteria = new Criteria(self::DATABASE_NAME); + + if ($values instanceof Criteria) { + $criteria = clone $values; // rename for clarity + + $comparison = $criteria->getComparison(CcPermsPeer::PERMID); + $value = $criteria->remove(CcPermsPeer::PERMID); + if ($value) { + $selectCriteria->add(CcPermsPeer::PERMID, $value, $comparison); + } else { + $selectCriteria->setPrimaryTableName(CcPermsPeer::TABLE_NAME); + } + + } else { // $values is CcPerms object + $criteria = $values->buildCriteria(); // gets full criteria + $selectCriteria = $values->buildPkeyCriteria(); // gets criteria w/ primary key(s) + } + + // set the correct dbName + $criteria->setDbName(self::DATABASE_NAME); + + return BasePeer::doUpdate($selectCriteria, $criteria, $con); + } + + /** + * Method to DELETE all rows from the cc_perms table. + * + * @return int The number of affected rows (if supported by underlying database driver). + */ + public static function doDeleteAll($con = null) + { + if ($con === null) { + $con = Propel::getConnection(CcPermsPeer::DATABASE_NAME, Propel::CONNECTION_WRITE); + } + $affectedRows = 0; // initialize var to track total num of affected rows + try { + // use transaction because $criteria could contain info + // for more than one table or we could emulating ON DELETE CASCADE, etc. + $con->beginTransaction(); + $affectedRows += BasePeer::doDeleteAll(CcPermsPeer::TABLE_NAME, $con, CcPermsPeer::DATABASE_NAME); + // Because this db requires some delete cascade/set null emulation, we have to + // clear the cached instance *after* the emulation has happened (since + // instances get re-added by the select statement contained therein). + CcPermsPeer::clearInstancePool(); + CcPermsPeer::clearRelatedInstancePool(); + $con->commit(); + return $affectedRows; + } catch (PropelException $e) { + $con->rollBack(); + throw $e; + } + } + + /** + * Method perform a DELETE on the database, given a CcPerms or Criteria object OR a primary key value. + * + * @param mixed $values Criteria or CcPerms object or primary key or array of primary keys + * which is used to create the DELETE statement + * @param PropelPDO $con the connection to use + * @return int The number of affected rows (if supported by underlying database driver). This includes CASCADE-related rows + * if supported by native driver or if emulated using Propel. + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function doDelete($values, PropelPDO $con = null) + { + if ($con === null) { + $con = Propel::getConnection(CcPermsPeer::DATABASE_NAME, Propel::CONNECTION_WRITE); + } + + if ($values instanceof Criteria) { + // invalidate the cache for all objects of this type, since we have no + // way of knowing (without running a query) what objects should be invalidated + // from the cache based on this Criteria. + CcPermsPeer::clearInstancePool(); + // rename for clarity + $criteria = clone $values; + } elseif ($values instanceof CcPerms) { // it's a model object + // invalidate the cache for this single object + CcPermsPeer::removeInstanceFromPool($values); + // create criteria based on pk values + $criteria = $values->buildPkeyCriteria(); + } else { // it's a primary key, or an array of pks + $criteria = new Criteria(self::DATABASE_NAME); + $criteria->add(CcPermsPeer::PERMID, (array) $values, Criteria::IN); + // invalidate the cache for this object(s) + foreach ((array) $values as $singleval) { + CcPermsPeer::removeInstanceFromPool($singleval); + } + } + + // Set the correct dbName + $criteria->setDbName(self::DATABASE_NAME); + + $affectedRows = 0; // initialize var to track total num of affected rows + + try { + // use transaction because $criteria could contain info + // for more than one table or we could emulating ON DELETE CASCADE, etc. + $con->beginTransaction(); + + $affectedRows += BasePeer::doDelete($criteria, $con); + CcPermsPeer::clearRelatedInstancePool(); + $con->commit(); + return $affectedRows; + } catch (PropelException $e) { + $con->rollBack(); + throw $e; + } + } + + /** + * Validates all modified columns of given CcPerms object. + * If parameter $columns is either a single column name or an array of column names + * than only those columns are validated. + * + * NOTICE: This does not apply to primary or foreign keys for now. + * + * @param CcPerms $obj The object to validate. + * @param mixed $cols Column name or array of column names. + * + * @return mixed TRUE if all columns are valid or the error message of the first invalid column. + */ + public static function doValidate(CcPerms $obj, $cols = null) + { + $columns = array(); + + if ($cols) { + $dbMap = Propel::getDatabaseMap(CcPermsPeer::DATABASE_NAME); + $tableMap = $dbMap->getTable(CcPermsPeer::TABLE_NAME); + + if (! is_array($cols)) { + $cols = array($cols); + } + + foreach ($cols as $colName) { + if ($tableMap->containsColumn($colName)) { + $get = 'get' . $tableMap->getColumn($colName)->getPhpName(); + $columns[$colName] = $obj->$get(); + } + } + } else { + + } + + return BasePeer::doValidate(CcPermsPeer::DATABASE_NAME, CcPermsPeer::TABLE_NAME, $columns); + } + + /** + * Retrieve a single object by pkey. + * + * @param int $pk the primary key. + * @param PropelPDO $con the connection to use + * @return CcPerms + */ + public static function retrieveByPK($pk, PropelPDO $con = null) + { + + if (null !== ($obj = CcPermsPeer::getInstanceFromPool((string) $pk))) { + return $obj; + } + + if ($con === null) { + $con = Propel::getConnection(CcPermsPeer::DATABASE_NAME, Propel::CONNECTION_READ); + } + + $criteria = new Criteria(CcPermsPeer::DATABASE_NAME); + $criteria->add(CcPermsPeer::PERMID, $pk); + + $v = CcPermsPeer::doSelect($criteria, $con); + + return !empty($v) > 0 ? $v[0] : null; + } + + /** + * Retrieve multiple objects by pkey. + * + * @param array $pks List of primary keys + * @param PropelPDO $con the connection to use + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function retrieveByPKs($pks, PropelPDO $con = null) + { + if ($con === null) { + $con = Propel::getConnection(CcPermsPeer::DATABASE_NAME, Propel::CONNECTION_READ); + } + + $objs = null; + if (empty($pks)) { + $objs = array(); + } else { + $criteria = new Criteria(CcPermsPeer::DATABASE_NAME); + $criteria->add(CcPermsPeer::PERMID, $pks, Criteria::IN); + $objs = CcPermsPeer::doSelect($criteria, $con); + } + return $objs; + } + +} // BaseCcPermsPeer + +// This is the static code needed to register the TableMap for this table with the main Propel class. +// +BaseCcPermsPeer::buildTableMap(); + diff --git a/application/models/campcaster/om/BaseCcPermsQuery.php b/application/models/campcaster/om/BaseCcPermsQuery.php new file mode 100644 index 000000000..f78dfb99e --- /dev/null +++ b/application/models/campcaster/om/BaseCcPermsQuery.php @@ -0,0 +1,355 @@ +setModelAlias($modelAlias); + } + if ($criteria instanceof Criteria) { + $query->mergeWith($criteria); + } + return $query; + } + + /** + * Find object by primary key + * Use instance pooling to avoid a database query if the object exists + * + * $obj = $c->findPk(12, $con); + * + * @param mixed $key Primary key to use for the query + * @param PropelPDO $con an optional connection object + * + * @return CcPerms|array|mixed the result, formatted by the current formatter + */ + public function findPk($key, $con = null) + { + if ((null !== ($obj = CcPermsPeer::getInstanceFromPool((string) $key))) && $this->getFormatter()->isObjectFormatter()) { + // the object is alredy in the instance pool + return $obj; + } else { + // the object has not been requested yet, or the formatter is not an object formatter + $criteria = $this->isKeepQuery() ? clone $this : $this; + $stmt = $criteria + ->filterByPrimaryKey($key) + ->getSelectStatement($con); + return $criteria->getFormatter()->init($criteria)->formatOne($stmt); + } + } + + /** + * Find objects by primary key + * + * $objs = $c->findPks(array(12, 56, 832), $con); + * + * @param array $keys Primary keys to use for the query + * @param PropelPDO $con an optional connection object + * + * @return PropelObjectCollection|array|mixed the list of results, formatted by the current formatter + */ + public function findPks($keys, $con = null) + { + $criteria = $this->isKeepQuery() ? clone $this : $this; + return $this + ->filterByPrimaryKeys($keys) + ->find($con); + } + + /** + * Filter the query by primary key + * + * @param mixed $key Primary key to use for the query + * + * @return CcPermsQuery The current query, for fluid interface + */ + public function filterByPrimaryKey($key) + { + return $this->addUsingAlias(CcPermsPeer::PERMID, $key, Criteria::EQUAL); + } + + /** + * Filter the query by a list of primary keys + * + * @param array $keys The list of primary key to use for the query + * + * @return CcPermsQuery The current query, for fluid interface + */ + public function filterByPrimaryKeys($keys) + { + return $this->addUsingAlias(CcPermsPeer::PERMID, $keys, Criteria::IN); + } + + /** + * Filter the query on the permid column + * + * @param int|array $permid The value to use as filter. + * Accepts an associative array('min' => $minValue, 'max' => $maxValue) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcPermsQuery The current query, for fluid interface + */ + public function filterByPermid($permid = null, $comparison = null) + { + if (is_array($permid) && null === $comparison) { + $comparison = Criteria::IN; + } + return $this->addUsingAlias(CcPermsPeer::PERMID, $permid, $comparison); + } + + /** + * Filter the query on the subj column + * + * @param int|array $subj The value to use as filter. + * Accepts an associative array('min' => $minValue, 'max' => $maxValue) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcPermsQuery The current query, for fluid interface + */ + public function filterBySubj($subj = null, $comparison = null) + { + if (is_array($subj)) { + $useMinMax = false; + if (isset($subj['min'])) { + $this->addUsingAlias(CcPermsPeer::SUBJ, $subj['min'], Criteria::GREATER_EQUAL); + $useMinMax = true; + } + if (isset($subj['max'])) { + $this->addUsingAlias(CcPermsPeer::SUBJ, $subj['max'], Criteria::LESS_EQUAL); + $useMinMax = true; + } + if ($useMinMax) { + return $this; + } + if (null === $comparison) { + $comparison = Criteria::IN; + } + } + return $this->addUsingAlias(CcPermsPeer::SUBJ, $subj, $comparison); + } + + /** + * Filter the query on the action column + * + * @param string $action The value to use as filter. + * Accepts wildcards (* and % trigger a LIKE) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcPermsQuery The current query, for fluid interface + */ + public function filterByAction($action = null, $comparison = null) + { + if (null === $comparison) { + if (is_array($action)) { + $comparison = Criteria::IN; + } elseif (preg_match('/[\%\*]/', $action)) { + $action = str_replace('*', '%', $action); + $comparison = Criteria::LIKE; + } + } + return $this->addUsingAlias(CcPermsPeer::ACTION, $action, $comparison); + } + + /** + * Filter the query on the obj column + * + * @param int|array $obj The value to use as filter. + * Accepts an associative array('min' => $minValue, 'max' => $maxValue) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcPermsQuery The current query, for fluid interface + */ + public function filterByObj($obj = null, $comparison = null) + { + if (is_array($obj)) { + $useMinMax = false; + if (isset($obj['min'])) { + $this->addUsingAlias(CcPermsPeer::OBJ, $obj['min'], Criteria::GREATER_EQUAL); + $useMinMax = true; + } + if (isset($obj['max'])) { + $this->addUsingAlias(CcPermsPeer::OBJ, $obj['max'], Criteria::LESS_EQUAL); + $useMinMax = true; + } + if ($useMinMax) { + return $this; + } + if (null === $comparison) { + $comparison = Criteria::IN; + } + } + return $this->addUsingAlias(CcPermsPeer::OBJ, $obj, $comparison); + } + + /** + * Filter the query on the type column + * + * @param string $type The value to use as filter. + * Accepts wildcards (* and % trigger a LIKE) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcPermsQuery The current query, for fluid interface + */ + public function filterByType($type = null, $comparison = null) + { + if (null === $comparison) { + if (is_array($type)) { + $comparison = Criteria::IN; + } elseif (preg_match('/[\%\*]/', $type)) { + $type = str_replace('*', '%', $type); + $comparison = Criteria::LIKE; + } + } + return $this->addUsingAlias(CcPermsPeer::TYPE, $type, $comparison); + } + + /** + * Filter the query by a related CcSubjs object + * + * @param CcSubjs $ccSubjs the related object to use as filter + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcPermsQuery The current query, for fluid interface + */ + public function filterByCcSubjs($ccSubjs, $comparison = null) + { + return $this + ->addUsingAlias(CcPermsPeer::SUBJ, $ccSubjs->getId(), $comparison); + } + + /** + * Adds a JOIN clause to the query using the CcSubjs relation + * + * @param string $relationAlias optional alias for the relation + * @param string $joinType Accepted values are null, 'left join', 'right join', 'inner join' + * + * @return CcPermsQuery The current query, for fluid interface + */ + public function joinCcSubjs($relationAlias = '', $joinType = Criteria::LEFT_JOIN) + { + $tableMap = $this->getTableMap(); + $relationMap = $tableMap->getRelation('CcSubjs'); + + // create a ModelJoin object for this join + $join = new ModelJoin(); + $join->setJoinType($joinType); + $join->setRelationMap($relationMap, $this->useAliasInSQL ? $this->getModelAlias() : null, $relationAlias); + if ($previousJoin = $this->getPreviousJoin()) { + $join->setPreviousJoin($previousJoin); + } + + // add the ModelJoin to the current object + if($relationAlias) { + $this->addAlias($relationAlias, $relationMap->getRightTable()->getName()); + $this->addJoinObject($join, $relationAlias); + } else { + $this->addJoinObject($join, 'CcSubjs'); + } + + return $this; + } + + /** + * Use the CcSubjs relation CcSubjs object + * + * @see useQuery() + * + * @param string $relationAlias optional alias for the relation, + * to be used as main alias in the secondary query + * @param string $joinType Accepted values are null, 'left join', 'right join', 'inner join' + * + * @return CcSubjsQuery A secondary query class using the current class as primary query + */ + public function useCcSubjsQuery($relationAlias = '', $joinType = Criteria::LEFT_JOIN) + { + return $this + ->joinCcSubjs($relationAlias, $joinType) + ->useQuery($relationAlias ? $relationAlias : 'CcSubjs', 'CcSubjsQuery'); + } + + /** + * Exclude object from result + * + * @param CcPerms $ccPerms Object to remove from the list of results + * + * @return CcPermsQuery The current query, for fluid interface + */ + public function prune($ccPerms = null) + { + if ($ccPerms) { + $this->addUsingAlias(CcPermsPeer::PERMID, $ccPerms->getPermid(), Criteria::NOT_EQUAL); + } + + return $this; + } + +} // BaseCcPermsQuery diff --git a/application/models/campcaster/om/BaseCcPlaylist.php b/application/models/campcaster/om/BaseCcPlaylist.php new file mode 100644 index 000000000..295060730 --- /dev/null +++ b/application/models/campcaster/om/BaseCcPlaylist.php @@ -0,0 +1,1365 @@ +name = ''; + $this->state = 'empty'; + $this->currentlyaccessing = 0; + } + + /** + * Initializes internal state of BaseCcPlaylist object. + * @see applyDefaults() + */ + public function __construct() + { + parent::__construct(); + $this->applyDefaultValues(); + } + + /** + * Get the [id] column value. + * + * @return int + */ + public function getDbId() + { + return $this->id; + } + + /** + * Get the [name] column value. + * + * @return string + */ + public function getDbName() + { + return $this->name; + } + + /** + * Get the [state] column value. + * + * @return string + */ + public function getDbState() + { + return $this->state; + } + + /** + * Get the [currentlyaccessing] column value. + * + * @return int + */ + public function getDbCurrentlyaccessing() + { + return $this->currentlyaccessing; + } + + /** + * Get the [editedby] column value. + * + * @return int + */ + public function getDbEditedby() + { + return $this->editedby; + } + + /** + * Get the [optionally formatted] temporal [mtime] column value. + * + * + * @param string $format The date/time format string (either date()-style or strftime()-style). + * If format is NULL, then the raw DateTime object will be returned. + * @return mixed Formatted date/time value as string or DateTime object (if format is NULL), NULL if column is NULL + * @throws PropelException - if unable to parse/validate the date/time value. + */ + public function getDbMtime($format = 'Y-m-d H:i:s') + { + if ($this->mtime === null) { + return null; + } + + + + try { + $dt = new DateTime($this->mtime); + } catch (Exception $x) { + throw new PropelException("Internally stored date/time/timestamp value could not be converted to DateTime: " . var_export($this->mtime, true), $x); + } + + if ($format === null) { + // Because propel.useDateTimeClass is TRUE, we return a DateTime object. + return $dt; + } elseif (strpos($format, '%') !== false) { + return strftime($format, $dt->format('U')); + } else { + return $dt->format($format); + } + } + + /** + * Get the [creator] column value. + * + * @return string + */ + public function getDbCreator() + { + return $this->creator; + } + + /** + * Get the [description] column value. + * + * @return string + */ + public function getDbDescription() + { + return $this->description; + } + + /** + * Set the value of [id] column. + * + * @param int $v new value + * @return CcPlaylist The current object (for fluent API support) + */ + public function setDbId($v) + { + if ($v !== null) { + $v = (int) $v; + } + + if ($this->id !== $v) { + $this->id = $v; + $this->modifiedColumns[] = CcPlaylistPeer::ID; + } + + return $this; + } // setDbId() + + /** + * Set the value of [name] column. + * + * @param string $v new value + * @return CcPlaylist The current object (for fluent API support) + */ + public function setDbName($v) + { + if ($v !== null) { + $v = (string) $v; + } + + if ($this->name !== $v || $this->isNew()) { + $this->name = $v; + $this->modifiedColumns[] = CcPlaylistPeer::NAME; + } + + return $this; + } // setDbName() + + /** + * Set the value of [state] column. + * + * @param string $v new value + * @return CcPlaylist The current object (for fluent API support) + */ + public function setDbState($v) + { + if ($v !== null) { + $v = (string) $v; + } + + if ($this->state !== $v || $this->isNew()) { + $this->state = $v; + $this->modifiedColumns[] = CcPlaylistPeer::STATE; + } + + return $this; + } // setDbState() + + /** + * Set the value of [currentlyaccessing] column. + * + * @param int $v new value + * @return CcPlaylist The current object (for fluent API support) + */ + public function setDbCurrentlyaccessing($v) + { + if ($v !== null) { + $v = (int) $v; + } + + if ($this->currentlyaccessing !== $v || $this->isNew()) { + $this->currentlyaccessing = $v; + $this->modifiedColumns[] = CcPlaylistPeer::CURRENTLYACCESSING; + } + + return $this; + } // setDbCurrentlyaccessing() + + /** + * Set the value of [editedby] column. + * + * @param int $v new value + * @return CcPlaylist The current object (for fluent API support) + */ + public function setDbEditedby($v) + { + if ($v !== null) { + $v = (int) $v; + } + + if ($this->editedby !== $v) { + $this->editedby = $v; + $this->modifiedColumns[] = CcPlaylistPeer::EDITEDBY; + } + + if ($this->aCcSubjs !== null && $this->aCcSubjs->getId() !== $v) { + $this->aCcSubjs = null; + } + + return $this; + } // setDbEditedby() + + /** + * Sets the value of [mtime] column to a normalized version of the date/time value specified. + * + * @param mixed $v string, integer (timestamp), or DateTime value. Empty string will + * be treated as NULL for temporal objects. + * @return CcPlaylist The current object (for fluent API support) + */ + public function setDbMtime($v) + { + // we treat '' as NULL for temporal objects because DateTime('') == DateTime('now') + // -- which is unexpected, to say the least. + if ($v === null || $v === '') { + $dt = null; + } elseif ($v instanceof DateTime) { + $dt = $v; + } else { + // some string/numeric value passed; we normalize that so that we can + // validate it. + try { + if (is_numeric($v)) { // if it's a unix timestamp + $dt = new DateTime('@'.$v, new DateTimeZone('UTC')); + // We have to explicitly specify and then change the time zone because of a + // DateTime bug: http://bugs.php.net/bug.php?id=43003 + $dt->setTimeZone(new DateTimeZone(date_default_timezone_get())); + } else { + $dt = new DateTime($v); + } + } catch (Exception $x) { + throw new PropelException('Error parsing date/time value: ' . var_export($v, true), $x); + } + } + + if ( $this->mtime !== null || $dt !== null ) { + // (nested ifs are a little easier to read in this case) + + $currNorm = ($this->mtime !== null && $tmpDt = new DateTime($this->mtime)) ? $tmpDt->format('Y-m-d\\TH:i:sO') : null; + $newNorm = ($dt !== null) ? $dt->format('Y-m-d\\TH:i:sO') : null; + + if ( ($currNorm !== $newNorm) // normalized values don't match + ) + { + $this->mtime = ($dt ? $dt->format('Y-m-d\\TH:i:sO') : null); + $this->modifiedColumns[] = CcPlaylistPeer::MTIME; + } + } // if either are not null + + return $this; + } // setDbMtime() + + /** + * Set the value of [creator] column. + * + * @param string $v new value + * @return CcPlaylist The current object (for fluent API support) + */ + public function setDbCreator($v) + { + if ($v !== null) { + $v = (string) $v; + } + + if ($this->creator !== $v) { + $this->creator = $v; + $this->modifiedColumns[] = CcPlaylistPeer::CREATOR; + } + + return $this; + } // setDbCreator() + + /** + * Set the value of [description] column. + * + * @param string $v new value + * @return CcPlaylist The current object (for fluent API support) + */ + public function setDbDescription($v) + { + if ($v !== null) { + $v = (string) $v; + } + + if ($this->description !== $v) { + $this->description = $v; + $this->modifiedColumns[] = CcPlaylistPeer::DESCRIPTION; + } + + return $this; + } // setDbDescription() + + /** + * Indicates whether the columns in this object are only set to default values. + * + * This method can be used in conjunction with isModified() to indicate whether an object is both + * modified _and_ has some values set which are non-default. + * + * @return boolean Whether the columns in this object are only been set with default values. + */ + public function hasOnlyDefaultValues() + { + if ($this->name !== '') { + return false; + } + + if ($this->state !== 'empty') { + return false; + } + + if ($this->currentlyaccessing !== 0) { + return false; + } + + // otherwise, everything was equal, so return TRUE + return true; + } // hasOnlyDefaultValues() + + /** + * Hydrates (populates) the object variables with values from the database resultset. + * + * An offset (0-based "start column") is specified so that objects can be hydrated + * with a subset of the columns in the resultset rows. This is needed, for example, + * for results of JOIN queries where the resultset row includes columns from two or + * more tables. + * + * @param array $row The row returned by PDOStatement->fetch(PDO::FETCH_NUM) + * @param int $startcol 0-based offset column which indicates which restultset column to start with. + * @param boolean $rehydrate Whether this object is being re-hydrated from the database. + * @return int next starting column + * @throws PropelException - Any caught Exception will be rewrapped as a PropelException. + */ + public function hydrate($row, $startcol = 0, $rehydrate = false) + { + try { + + $this->id = ($row[$startcol + 0] !== null) ? (int) $row[$startcol + 0] : null; + $this->name = ($row[$startcol + 1] !== null) ? (string) $row[$startcol + 1] : null; + $this->state = ($row[$startcol + 2] !== null) ? (string) $row[$startcol + 2] : null; + $this->currentlyaccessing = ($row[$startcol + 3] !== null) ? (int) $row[$startcol + 3] : null; + $this->editedby = ($row[$startcol + 4] !== null) ? (int) $row[$startcol + 4] : null; + $this->mtime = ($row[$startcol + 5] !== null) ? (string) $row[$startcol + 5] : null; + $this->creator = ($row[$startcol + 6] !== null) ? (string) $row[$startcol + 6] : null; + $this->description = ($row[$startcol + 7] !== null) ? (string) $row[$startcol + 7] : null; + $this->resetModified(); + + $this->setNew(false); + + if ($rehydrate) { + $this->ensureConsistency(); + } + + return $startcol + 8; // 8 = CcPlaylistPeer::NUM_COLUMNS - CcPlaylistPeer::NUM_LAZY_LOAD_COLUMNS). + + } catch (Exception $e) { + throw new PropelException("Error populating CcPlaylist object", $e); + } + } + + /** + * Checks and repairs the internal consistency of the object. + * + * This method is executed after an already-instantiated object is re-hydrated + * from the database. It exists to check any foreign keys to make sure that + * the objects related to the current object are correct based on foreign key. + * + * You can override this method in the stub class, but you should always invoke + * the base method from the overridden method (i.e. parent::ensureConsistency()), + * in case your model changes. + * + * @throws PropelException + */ + public function ensureConsistency() + { + + if ($this->aCcSubjs !== null && $this->editedby !== $this->aCcSubjs->getId()) { + $this->aCcSubjs = null; + } + } // ensureConsistency + + /** + * Reloads this object from datastore based on primary key and (optionally) resets all associated objects. + * + * This will only work if the object has been saved and has a valid primary key set. + * + * @param boolean $deep (optional) Whether to also de-associated any related objects. + * @param PropelPDO $con (optional) The PropelPDO connection to use. + * @return void + * @throws PropelException - if this object is deleted, unsaved or doesn't have pk match in db + */ + public function reload($deep = false, PropelPDO $con = null) + { + if ($this->isDeleted()) { + throw new PropelException("Cannot reload a deleted object."); + } + + if ($this->isNew()) { + throw new PropelException("Cannot reload an unsaved object."); + } + + if ($con === null) { + $con = Propel::getConnection(CcPlaylistPeer::DATABASE_NAME, Propel::CONNECTION_READ); + } + + // We don't need to alter the object instance pool; we're just modifying this instance + // already in the pool. + + $stmt = CcPlaylistPeer::doSelectStmt($this->buildPkeyCriteria(), $con); + $row = $stmt->fetch(PDO::FETCH_NUM); + $stmt->closeCursor(); + if (!$row) { + throw new PropelException('Cannot find matching row in the database to reload object values.'); + } + $this->hydrate($row, 0, true); // rehydrate + + if ($deep) { // also de-associate any related objects? + + $this->aCcSubjs = null; + $this->collCcPlaylistcontentss = null; + + } // if (deep) + } + + /** + * Removes this object from datastore and sets delete attribute. + * + * @param PropelPDO $con + * @return void + * @throws PropelException + * @see BaseObject::setDeleted() + * @see BaseObject::isDeleted() + */ + public function delete(PropelPDO $con = null) + { + if ($this->isDeleted()) { + throw new PropelException("This object has already been deleted."); + } + + if ($con === null) { + $con = Propel::getConnection(CcPlaylistPeer::DATABASE_NAME, Propel::CONNECTION_WRITE); + } + + $con->beginTransaction(); + try { + $ret = $this->preDelete($con); + if ($ret) { + CcPlaylistQuery::create() + ->filterByPrimaryKey($this->getPrimaryKey()) + ->delete($con); + $this->postDelete($con); + $con->commit(); + $this->setDeleted(true); + } else { + $con->commit(); + } + } catch (PropelException $e) { + $con->rollBack(); + throw $e; + } + } + + /** + * Persists this object to the database. + * + * If the object is new, it inserts it; otherwise an update is performed. + * All modified related objects will also be persisted in the doSave() + * method. This method wraps all precipitate database operations in a + * single transaction. + * + * @param PropelPDO $con + * @return int The number of rows affected by this insert/update and any referring fk objects' save() operations. + * @throws PropelException + * @see doSave() + */ + public function save(PropelPDO $con = null) + { + if ($this->isDeleted()) { + throw new PropelException("You cannot save an object that has been deleted."); + } + + if ($con === null) { + $con = Propel::getConnection(CcPlaylistPeer::DATABASE_NAME, Propel::CONNECTION_WRITE); + } + + $con->beginTransaction(); + $isInsert = $this->isNew(); + try { + $ret = $this->preSave($con); + if ($isInsert) { + $ret = $ret && $this->preInsert($con); + } else { + $ret = $ret && $this->preUpdate($con); + } + if ($ret) { + $affectedRows = $this->doSave($con); + if ($isInsert) { + $this->postInsert($con); + } else { + $this->postUpdate($con); + } + $this->postSave($con); + CcPlaylistPeer::addInstanceToPool($this); + } else { + $affectedRows = 0; + } + $con->commit(); + return $affectedRows; + } catch (PropelException $e) { + $con->rollBack(); + throw $e; + } + } + + /** + * Performs the work of inserting or updating the row in the database. + * + * If the object is new, it inserts it; otherwise an update is performed. + * All related objects are also updated in this method. + * + * @param PropelPDO $con + * @return int The number of rows affected by this insert/update and any referring fk objects' save() operations. + * @throws PropelException + * @see save() + */ + protected function doSave(PropelPDO $con) + { + $affectedRows = 0; // initialize var to track total num of affected rows + if (!$this->alreadyInSave) { + $this->alreadyInSave = true; + + // We call the save method on the following object(s) if they + // were passed to this object by their coresponding set + // method. This object relates to these object(s) by a + // foreign key reference. + + if ($this->aCcSubjs !== null) { + if ($this->aCcSubjs->isModified() || $this->aCcSubjs->isNew()) { + $affectedRows += $this->aCcSubjs->save($con); + } + $this->setCcSubjs($this->aCcSubjs); + } + + if ($this->isNew() ) { + $this->modifiedColumns[] = CcPlaylistPeer::ID; + } + + // If this object has been modified, then save it to the database. + if ($this->isModified()) { + if ($this->isNew()) { + $criteria = $this->buildCriteria(); + if ($criteria->keyContainsValue(CcPlaylistPeer::ID) ) { + throw new PropelException('Cannot insert a value for auto-increment primary key ('.CcPlaylistPeer::ID.')'); + } + + $pk = BasePeer::doInsert($criteria, $con); + $affectedRows += 1; + $this->setDbId($pk); //[IMV] update autoincrement primary key + $this->setNew(false); + } else { + $affectedRows += CcPlaylistPeer::doUpdate($this, $con); + } + + $this->resetModified(); // [HL] After being saved an object is no longer 'modified' + } + + if ($this->collCcPlaylistcontentss !== null) { + foreach ($this->collCcPlaylistcontentss as $referrerFK) { + if (!$referrerFK->isDeleted()) { + $affectedRows += $referrerFK->save($con); + } + } + } + + $this->alreadyInSave = false; + + } + return $affectedRows; + } // doSave() + + /** + * Array of ValidationFailed objects. + * @var array ValidationFailed[] + */ + protected $validationFailures = array(); + + /** + * Gets any ValidationFailed objects that resulted from last call to validate(). + * + * + * @return array ValidationFailed[] + * @see validate() + */ + public function getValidationFailures() + { + return $this->validationFailures; + } + + /** + * Validates the objects modified field values and all objects related to this table. + * + * If $columns is either a column name or an array of column names + * only those columns are validated. + * + * @param mixed $columns Column name or an array of column names. + * @return boolean Whether all columns pass validation. + * @see doValidate() + * @see getValidationFailures() + */ + public function validate($columns = null) + { + $res = $this->doValidate($columns); + if ($res === true) { + $this->validationFailures = array(); + return true; + } else { + $this->validationFailures = $res; + return false; + } + } + + /** + * This function performs the validation work for complex object models. + * + * In addition to checking the current object, all related objects will + * also be validated. If all pass then true is returned; otherwise + * an aggreagated array of ValidationFailed objects will be returned. + * + * @param array $columns Array of column names to validate. + * @return mixed true if all validations pass; array of ValidationFailed objets otherwise. + */ + protected function doValidate($columns = null) + { + if (!$this->alreadyInValidation) { + $this->alreadyInValidation = true; + $retval = null; + + $failureMap = array(); + + + // We call the validate method on the following object(s) if they + // were passed to this object by their coresponding set + // method. This object relates to these object(s) by a + // foreign key reference. + + if ($this->aCcSubjs !== null) { + if (!$this->aCcSubjs->validate($columns)) { + $failureMap = array_merge($failureMap, $this->aCcSubjs->getValidationFailures()); + } + } + + + if (($retval = CcPlaylistPeer::doValidate($this, $columns)) !== true) { + $failureMap = array_merge($failureMap, $retval); + } + + + if ($this->collCcPlaylistcontentss !== null) { + foreach ($this->collCcPlaylistcontentss as $referrerFK) { + if (!$referrerFK->validate($columns)) { + $failureMap = array_merge($failureMap, $referrerFK->getValidationFailures()); + } + } + } + + + $this->alreadyInValidation = false; + } + + return (!empty($failureMap) ? $failureMap : true); + } + + /** + * Retrieves a field from the object by name passed in as a string. + * + * @param string $name name + * @param string $type The type of fieldname the $name is of: + * one of the class type constants BasePeer::TYPE_PHPNAME, BasePeer::TYPE_STUDLYPHPNAME + * BasePeer::TYPE_COLNAME, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_NUM + * @return mixed Value of field. + */ + public function getByName($name, $type = BasePeer::TYPE_PHPNAME) + { + $pos = CcPlaylistPeer::translateFieldName($name, $type, BasePeer::TYPE_NUM); + $field = $this->getByPosition($pos); + return $field; + } + + /** + * Retrieves a field from the object by Position as specified in the xml schema. + * Zero-based. + * + * @param int $pos position in xml schema + * @return mixed Value of field at $pos + */ + public function getByPosition($pos) + { + switch($pos) { + case 0: + return $this->getDbId(); + break; + case 1: + return $this->getDbName(); + break; + case 2: + return $this->getDbState(); + break; + case 3: + return $this->getDbCurrentlyaccessing(); + break; + case 4: + return $this->getDbEditedby(); + break; + case 5: + return $this->getDbMtime(); + break; + case 6: + return $this->getDbCreator(); + break; + case 7: + return $this->getDbDescription(); + break; + default: + return null; + break; + } // switch() + } + + /** + * Exports the object as an array. + * + * You can specify the key type of the array by passing one of the class + * type constants. + * + * @param string $keyType (optional) One of the class type constants BasePeer::TYPE_PHPNAME, BasePeer::TYPE_STUDLYPHPNAME, + * BasePeer::TYPE_COLNAME, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_NUM. + * Defaults to BasePeer::TYPE_PHPNAME. + * @param boolean $includeLazyLoadColumns (optional) Whether to include lazy loaded columns. Defaults to TRUE. + * @param boolean $includeForeignObjects (optional) Whether to include hydrated related objects. Default to FALSE. + * + * @return array an associative array containing the field names (as keys) and field values + */ + public function toArray($keyType = BasePeer::TYPE_PHPNAME, $includeLazyLoadColumns = true, $includeForeignObjects = false) + { + $keys = CcPlaylistPeer::getFieldNames($keyType); + $result = array( + $keys[0] => $this->getDbId(), + $keys[1] => $this->getDbName(), + $keys[2] => $this->getDbState(), + $keys[3] => $this->getDbCurrentlyaccessing(), + $keys[4] => $this->getDbEditedby(), + $keys[5] => $this->getDbMtime(), + $keys[6] => $this->getDbCreator(), + $keys[7] => $this->getDbDescription(), + ); + if ($includeForeignObjects) { + if (null !== $this->aCcSubjs) { + $result['CcSubjs'] = $this->aCcSubjs->toArray($keyType, $includeLazyLoadColumns, true); + } + } + return $result; + } + + /** + * Sets a field from the object by name passed in as a string. + * + * @param string $name peer name + * @param mixed $value field value + * @param string $type The type of fieldname the $name is of: + * one of the class type constants BasePeer::TYPE_PHPNAME, BasePeer::TYPE_STUDLYPHPNAME + * BasePeer::TYPE_COLNAME, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_NUM + * @return void + */ + public function setByName($name, $value, $type = BasePeer::TYPE_PHPNAME) + { + $pos = CcPlaylistPeer::translateFieldName($name, $type, BasePeer::TYPE_NUM); + return $this->setByPosition($pos, $value); + } + + /** + * Sets a field from the object by Position as specified in the xml schema. + * Zero-based. + * + * @param int $pos position in xml schema + * @param mixed $value field value + * @return void + */ + public function setByPosition($pos, $value) + { + switch($pos) { + case 0: + $this->setDbId($value); + break; + case 1: + $this->setDbName($value); + break; + case 2: + $this->setDbState($value); + break; + case 3: + $this->setDbCurrentlyaccessing($value); + break; + case 4: + $this->setDbEditedby($value); + break; + case 5: + $this->setDbMtime($value); + break; + case 6: + $this->setDbCreator($value); + break; + case 7: + $this->setDbDescription($value); + break; + } // switch() + } + + /** + * Populates the object using an array. + * + * This is particularly useful when populating an object from one of the + * request arrays (e.g. $_POST). This method goes through the column + * names, checking to see whether a matching key exists in populated + * array. If so the setByName() method is called for that column. + * + * You can specify the key type of the array by additionally passing one + * of the class type constants BasePeer::TYPE_PHPNAME, BasePeer::TYPE_STUDLYPHPNAME, + * BasePeer::TYPE_COLNAME, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_NUM. + * The default key type is the column's phpname (e.g. 'AuthorId') + * + * @param array $arr An array to populate the object from. + * @param string $keyType The type of keys the array uses. + * @return void + */ + public function fromArray($arr, $keyType = BasePeer::TYPE_PHPNAME) + { + $keys = CcPlaylistPeer::getFieldNames($keyType); + + if (array_key_exists($keys[0], $arr)) $this->setDbId($arr[$keys[0]]); + if (array_key_exists($keys[1], $arr)) $this->setDbName($arr[$keys[1]]); + if (array_key_exists($keys[2], $arr)) $this->setDbState($arr[$keys[2]]); + if (array_key_exists($keys[3], $arr)) $this->setDbCurrentlyaccessing($arr[$keys[3]]); + if (array_key_exists($keys[4], $arr)) $this->setDbEditedby($arr[$keys[4]]); + if (array_key_exists($keys[5], $arr)) $this->setDbMtime($arr[$keys[5]]); + if (array_key_exists($keys[6], $arr)) $this->setDbCreator($arr[$keys[6]]); + if (array_key_exists($keys[7], $arr)) $this->setDbDescription($arr[$keys[7]]); + } + + /** + * Build a Criteria object containing the values of all modified columns in this object. + * + * @return Criteria The Criteria object containing all modified values. + */ + public function buildCriteria() + { + $criteria = new Criteria(CcPlaylistPeer::DATABASE_NAME); + + if ($this->isColumnModified(CcPlaylistPeer::ID)) $criteria->add(CcPlaylistPeer::ID, $this->id); + if ($this->isColumnModified(CcPlaylistPeer::NAME)) $criteria->add(CcPlaylistPeer::NAME, $this->name); + if ($this->isColumnModified(CcPlaylistPeer::STATE)) $criteria->add(CcPlaylistPeer::STATE, $this->state); + if ($this->isColumnModified(CcPlaylistPeer::CURRENTLYACCESSING)) $criteria->add(CcPlaylistPeer::CURRENTLYACCESSING, $this->currentlyaccessing); + if ($this->isColumnModified(CcPlaylistPeer::EDITEDBY)) $criteria->add(CcPlaylistPeer::EDITEDBY, $this->editedby); + if ($this->isColumnModified(CcPlaylistPeer::MTIME)) $criteria->add(CcPlaylistPeer::MTIME, $this->mtime); + if ($this->isColumnModified(CcPlaylistPeer::CREATOR)) $criteria->add(CcPlaylistPeer::CREATOR, $this->creator); + if ($this->isColumnModified(CcPlaylistPeer::DESCRIPTION)) $criteria->add(CcPlaylistPeer::DESCRIPTION, $this->description); + + return $criteria; + } + + /** + * Builds a Criteria object containing the primary key for this object. + * + * Unlike buildCriteria() this method includes the primary key values regardless + * of whether or not they have been modified. + * + * @return Criteria The Criteria object containing value(s) for primary key(s). + */ + public function buildPkeyCriteria() + { + $criteria = new Criteria(CcPlaylistPeer::DATABASE_NAME); + $criteria->add(CcPlaylistPeer::ID, $this->id); + + return $criteria; + } + + /** + * Returns the primary key for this object (row). + * @return int + */ + public function getPrimaryKey() + { + return $this->getDbId(); + } + + /** + * Generic method to set the primary key (id column). + * + * @param int $key Primary key. + * @return void + */ + public function setPrimaryKey($key) + { + $this->setDbId($key); + } + + /** + * Returns true if the primary key for this object is null. + * @return boolean + */ + public function isPrimaryKeyNull() + { + return null === $this->getDbId(); + } + + /** + * Sets contents of passed object to values from current object. + * + * If desired, this method can also make copies of all associated (fkey referrers) + * objects. + * + * @param object $copyObj An object of CcPlaylist (or compatible) type. + * @param boolean $deepCopy Whether to also copy all rows that refer (by fkey) to the current row. + * @throws PropelException + */ + public function copyInto($copyObj, $deepCopy = false) + { + $copyObj->setDbName($this->name); + $copyObj->setDbState($this->state); + $copyObj->setDbCurrentlyaccessing($this->currentlyaccessing); + $copyObj->setDbEditedby($this->editedby); + $copyObj->setDbMtime($this->mtime); + $copyObj->setDbCreator($this->creator); + $copyObj->setDbDescription($this->description); + + if ($deepCopy) { + // important: temporarily setNew(false) because this affects the behavior of + // the getter/setter methods for fkey referrer objects. + $copyObj->setNew(false); + + foreach ($this->getCcPlaylistcontentss() as $relObj) { + if ($relObj !== $this) { // ensure that we don't try to copy a reference to ourselves + $copyObj->addCcPlaylistcontents($relObj->copy($deepCopy)); + } + } + + } // if ($deepCopy) + + + $copyObj->setNew(true); + $copyObj->setDbId(NULL); // this is a auto-increment column, so set to default value + } + + /** + * Makes a copy of this object that will be inserted as a new row in table when saved. + * It creates a new object filling in the simple attributes, but skipping any primary + * keys that are defined for the table. + * + * If desired, this method can also make copies of all associated (fkey referrers) + * objects. + * + * @param boolean $deepCopy Whether to also copy all rows that refer (by fkey) to the current row. + * @return CcPlaylist Clone of current object. + * @throws PropelException + */ + public function copy($deepCopy = false) + { + // we use get_class(), because this might be a subclass + $clazz = get_class($this); + $copyObj = new $clazz(); + $this->copyInto($copyObj, $deepCopy); + return $copyObj; + } + + /** + * Returns a peer instance associated with this om. + * + * Since Peer classes are not to have any instance attributes, this method returns the + * same instance for all member of this class. The method could therefore + * be static, but this would prevent one from overriding the behavior. + * + * @return CcPlaylistPeer + */ + public function getPeer() + { + if (self::$peer === null) { + self::$peer = new CcPlaylistPeer(); + } + return self::$peer; + } + + /** + * Declares an association between this object and a CcSubjs object. + * + * @param CcSubjs $v + * @return CcPlaylist The current object (for fluent API support) + * @throws PropelException + */ + public function setCcSubjs(CcSubjs $v = null) + { + if ($v === null) { + $this->setDbEditedby(NULL); + } else { + $this->setDbEditedby($v->getId()); + } + + $this->aCcSubjs = $v; + + // Add binding for other direction of this n:n relationship. + // If this object has already been added to the CcSubjs object, it will not be re-added. + if ($v !== null) { + $v->addCcPlaylist($this); + } + + return $this; + } + + + /** + * Get the associated CcSubjs object + * + * @param PropelPDO Optional Connection object. + * @return CcSubjs The associated CcSubjs object. + * @throws PropelException + */ + public function getCcSubjs(PropelPDO $con = null) + { + if ($this->aCcSubjs === null && ($this->editedby !== null)) { + $this->aCcSubjs = CcSubjsQuery::create()->findPk($this->editedby, $con); + /* The following can be used additionally to + guarantee the related object contains a reference + to this object. This level of coupling may, however, be + undesirable since it could result in an only partially populated collection + in the referenced object. + $this->aCcSubjs->addCcPlaylists($this); + */ + } + return $this->aCcSubjs; + } + + /** + * Clears out the collCcPlaylistcontentss collection + * + * This does not modify the database; however, it will remove any associated objects, causing + * them to be refetched by subsequent calls to accessor method. + * + * @return void + * @see addCcPlaylistcontentss() + */ + public function clearCcPlaylistcontentss() + { + $this->collCcPlaylistcontentss = null; // important to set this to NULL since that means it is uninitialized + } + + /** + * Initializes the collCcPlaylistcontentss collection. + * + * By default this just sets the collCcPlaylistcontentss collection to an empty array (like clearcollCcPlaylistcontentss()); + * however, you may wish to override this method in your stub class to provide setting appropriate + * to your application -- for example, setting the initial array to the values stored in database. + * + * @return void + */ + public function initCcPlaylistcontentss() + { + $this->collCcPlaylistcontentss = new PropelObjectCollection(); + $this->collCcPlaylistcontentss->setModel('CcPlaylistcontents'); + } + + /** + * Gets an array of CcPlaylistcontents objects which contain a foreign key that references this object. + * + * If the $criteria is not null, it is used to always fetch the results from the database. + * Otherwise the results are fetched from the database the first time, then cached. + * Next time the same method is called without $criteria, the cached collection is returned. + * If this CcPlaylist is new, it will return + * an empty collection or the current collection; the criteria is ignored on a new object. + * + * @param Criteria $criteria optional Criteria object to narrow the query + * @param PropelPDO $con optional connection object + * @return PropelCollection|array CcPlaylistcontents[] List of CcPlaylistcontents objects + * @throws PropelException + */ + public function getCcPlaylistcontentss($criteria = null, PropelPDO $con = null) + { + if(null === $this->collCcPlaylistcontentss || null !== $criteria) { + if ($this->isNew() && null === $this->collCcPlaylistcontentss) { + // return empty collection + $this->initCcPlaylistcontentss(); + } else { + $collCcPlaylistcontentss = CcPlaylistcontentsQuery::create(null, $criteria) + ->filterByCcPlaylist($this) + ->find($con); + if (null !== $criteria) { + return $collCcPlaylistcontentss; + } + $this->collCcPlaylistcontentss = $collCcPlaylistcontentss; + } + } + return $this->collCcPlaylistcontentss; + } + + /** + * Returns the number of related CcPlaylistcontents objects. + * + * @param Criteria $criteria + * @param boolean $distinct + * @param PropelPDO $con + * @return int Count of related CcPlaylistcontents objects. + * @throws PropelException + */ + public function countCcPlaylistcontentss(Criteria $criteria = null, $distinct = false, PropelPDO $con = null) + { + if(null === $this->collCcPlaylistcontentss || null !== $criteria) { + if ($this->isNew() && null === $this->collCcPlaylistcontentss) { + return 0; + } else { + $query = CcPlaylistcontentsQuery::create(null, $criteria); + if($distinct) { + $query->distinct(); + } + return $query + ->filterByCcPlaylist($this) + ->count($con); + } + } else { + return count($this->collCcPlaylistcontentss); + } + } + + /** + * Method called to associate a CcPlaylistcontents object to this object + * through the CcPlaylistcontents foreign key attribute. + * + * @param CcPlaylistcontents $l CcPlaylistcontents + * @return void + * @throws PropelException + */ + public function addCcPlaylistcontents(CcPlaylistcontents $l) + { + if ($this->collCcPlaylistcontentss === null) { + $this->initCcPlaylistcontentss(); + } + if (!$this->collCcPlaylistcontentss->contains($l)) { // only add it if the **same** object is not already associated + $this->collCcPlaylistcontentss[]= $l; + $l->setCcPlaylist($this); + } + } + + + /** + * If this collection has already been initialized with + * an identical criteria, it returns the collection. + * Otherwise if this CcPlaylist is new, it will return + * an empty collection; or if this CcPlaylist has previously + * been saved, it will retrieve related CcPlaylistcontentss from storage. + * + * This method is protected by default in order to keep the public + * api reasonable. You can provide public methods for those you + * actually need in CcPlaylist. + * + * @param Criteria $criteria optional Criteria object to narrow the query + * @param PropelPDO $con optional connection object + * @param string $join_behavior optional join type to use (defaults to Criteria::LEFT_JOIN) + * @return PropelCollection|array CcPlaylistcontents[] List of CcPlaylistcontents objects + */ + public function getCcPlaylistcontentssJoinCcFiles($criteria = null, $con = null, $join_behavior = Criteria::LEFT_JOIN) + { + $query = CcPlaylistcontentsQuery::create(null, $criteria); + $query->joinWith('CcFiles', $join_behavior); + + return $this->getCcPlaylistcontentss($query, $con); + } + + /** + * Clears the current object and sets all attributes to their default values + */ + public function clear() + { + $this->id = null; + $this->name = null; + $this->state = null; + $this->currentlyaccessing = null; + $this->editedby = null; + $this->mtime = null; + $this->creator = null; + $this->description = null; + $this->alreadyInSave = false; + $this->alreadyInValidation = false; + $this->clearAllReferences(); + $this->applyDefaultValues(); + $this->resetModified(); + $this->setNew(true); + $this->setDeleted(false); + } + + /** + * Resets all collections of referencing foreign keys. + * + * This method is a user-space workaround for PHP's inability to garbage collect objects + * with circular references. This is currently necessary when using Propel in certain + * daemon or large-volumne/high-memory operations. + * + * @param boolean $deep Whether to also clear the references on all associated objects. + */ + public function clearAllReferences($deep = false) + { + if ($deep) { + if ($this->collCcPlaylistcontentss) { + foreach ((array) $this->collCcPlaylistcontentss as $o) { + $o->clearAllReferences($deep); + } + } + } // if ($deep) + + $this->collCcPlaylistcontentss = null; + $this->aCcSubjs = null; + } + + /** + * Catches calls to virtual methods + */ + public function __call($name, $params) + { + if (preg_match('/get(\w+)/', $name, $matches)) { + $virtualColumn = $matches[1]; + if ($this->hasVirtualColumn($virtualColumn)) { + return $this->getVirtualColumn($virtualColumn); + } + // no lcfirst in php<5.3... + $virtualColumn[0] = strtolower($virtualColumn[0]); + if ($this->hasVirtualColumn($virtualColumn)) { + return $this->getVirtualColumn($virtualColumn); + } + } + throw new PropelException('Call to undefined method: ' . $name); + } + +} // BaseCcPlaylist diff --git a/application/models/campcaster/om/BaseCcPlaylistPeer.php b/application/models/campcaster/om/BaseCcPlaylistPeer.php new file mode 100644 index 000000000..04ff4a612 --- /dev/null +++ b/application/models/campcaster/om/BaseCcPlaylistPeer.php @@ -0,0 +1,1006 @@ + array ('DbId', 'DbName', 'DbState', 'DbCurrentlyaccessing', 'DbEditedby', 'DbMtime', 'DbCreator', 'DbDescription', ), + BasePeer::TYPE_STUDLYPHPNAME => array ('dbId', 'dbName', 'dbState', 'dbCurrentlyaccessing', 'dbEditedby', 'dbMtime', 'dbCreator', 'dbDescription', ), + BasePeer::TYPE_COLNAME => array (self::ID, self::NAME, self::STATE, self::CURRENTLYACCESSING, self::EDITEDBY, self::MTIME, self::CREATOR, self::DESCRIPTION, ), + BasePeer::TYPE_RAW_COLNAME => array ('ID', 'NAME', 'STATE', 'CURRENTLYACCESSING', 'EDITEDBY', 'MTIME', 'CREATOR', 'DESCRIPTION', ), + BasePeer::TYPE_FIELDNAME => array ('id', 'name', 'state', 'currentlyaccessing', 'editedby', 'mtime', 'creator', 'description', ), + BasePeer::TYPE_NUM => array (0, 1, 2, 3, 4, 5, 6, 7, ) + ); + + /** + * holds an array of keys for quick access to the fieldnames array + * + * first dimension keys are the type constants + * e.g. self::$fieldNames[BasePeer::TYPE_PHPNAME]['Id'] = 0 + */ + private static $fieldKeys = array ( + BasePeer::TYPE_PHPNAME => array ('DbId' => 0, 'DbName' => 1, 'DbState' => 2, 'DbCurrentlyaccessing' => 3, 'DbEditedby' => 4, 'DbMtime' => 5, 'DbCreator' => 6, 'DbDescription' => 7, ), + BasePeer::TYPE_STUDLYPHPNAME => array ('dbId' => 0, 'dbName' => 1, 'dbState' => 2, 'dbCurrentlyaccessing' => 3, 'dbEditedby' => 4, 'dbMtime' => 5, 'dbCreator' => 6, 'dbDescription' => 7, ), + BasePeer::TYPE_COLNAME => array (self::ID => 0, self::NAME => 1, self::STATE => 2, self::CURRENTLYACCESSING => 3, self::EDITEDBY => 4, self::MTIME => 5, self::CREATOR => 6, self::DESCRIPTION => 7, ), + BasePeer::TYPE_RAW_COLNAME => array ('ID' => 0, 'NAME' => 1, 'STATE' => 2, 'CURRENTLYACCESSING' => 3, 'EDITEDBY' => 4, 'MTIME' => 5, 'CREATOR' => 6, 'DESCRIPTION' => 7, ), + BasePeer::TYPE_FIELDNAME => array ('id' => 0, 'name' => 1, 'state' => 2, 'currentlyaccessing' => 3, 'editedby' => 4, 'mtime' => 5, 'creator' => 6, 'description' => 7, ), + BasePeer::TYPE_NUM => array (0, 1, 2, 3, 4, 5, 6, 7, ) + ); + + /** + * Translates a fieldname to another type + * + * @param string $name field name + * @param string $fromType One of the class type constants BasePeer::TYPE_PHPNAME, BasePeer::TYPE_STUDLYPHPNAME + * BasePeer::TYPE_COLNAME, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_NUM + * @param string $toType One of the class type constants + * @return string translated name of the field. + * @throws PropelException - if the specified name could not be found in the fieldname mappings. + */ + static public function translateFieldName($name, $fromType, $toType) + { + $toNames = self::getFieldNames($toType); + $key = isset(self::$fieldKeys[$fromType][$name]) ? self::$fieldKeys[$fromType][$name] : null; + if ($key === null) { + throw new PropelException("'$name' could not be found in the field names of type '$fromType'. These are: " . print_r(self::$fieldKeys[$fromType], true)); + } + return $toNames[$key]; + } + + /** + * Returns an array of field names. + * + * @param string $type The type of fieldnames to return: + * One of the class type constants BasePeer::TYPE_PHPNAME, BasePeer::TYPE_STUDLYPHPNAME + * BasePeer::TYPE_COLNAME, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_NUM + * @return array A list of field names + */ + + static public function getFieldNames($type = BasePeer::TYPE_PHPNAME) + { + if (!array_key_exists($type, self::$fieldNames)) { + throw new PropelException('Method getFieldNames() expects the parameter $type to be one of the class constants BasePeer::TYPE_PHPNAME, BasePeer::TYPE_STUDLYPHPNAME, BasePeer::TYPE_COLNAME, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_NUM. ' . $type . ' was given.'); + } + return self::$fieldNames[$type]; + } + + /** + * Convenience method which changes table.column to alias.column. + * + * Using this method you can maintain SQL abstraction while using column aliases. + * + * $c->addAlias("alias1", TablePeer::TABLE_NAME); + * $c->addJoin(TablePeer::alias("alias1", TablePeer::PRIMARY_KEY_COLUMN), TablePeer::PRIMARY_KEY_COLUMN); + * + * @param string $alias The alias for the current table. + * @param string $column The column name for current table. (i.e. CcPlaylistPeer::COLUMN_NAME). + * @return string + */ + public static function alias($alias, $column) + { + return str_replace(CcPlaylistPeer::TABLE_NAME.'.', $alias.'.', $column); + } + + /** + * Add all the columns needed to create a new object. + * + * Note: any columns that were marked with lazyLoad="true" in the + * XML schema will not be added to the select list and only loaded + * on demand. + * + * @param Criteria $criteria object containing the columns to add. + * @param string $alias optional table alias + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function addSelectColumns(Criteria $criteria, $alias = null) + { + if (null === $alias) { + $criteria->addSelectColumn(CcPlaylistPeer::ID); + $criteria->addSelectColumn(CcPlaylistPeer::NAME); + $criteria->addSelectColumn(CcPlaylistPeer::STATE); + $criteria->addSelectColumn(CcPlaylistPeer::CURRENTLYACCESSING); + $criteria->addSelectColumn(CcPlaylistPeer::EDITEDBY); + $criteria->addSelectColumn(CcPlaylistPeer::MTIME); + $criteria->addSelectColumn(CcPlaylistPeer::CREATOR); + $criteria->addSelectColumn(CcPlaylistPeer::DESCRIPTION); + } else { + $criteria->addSelectColumn($alias . '.ID'); + $criteria->addSelectColumn($alias . '.NAME'); + $criteria->addSelectColumn($alias . '.STATE'); + $criteria->addSelectColumn($alias . '.CURRENTLYACCESSING'); + $criteria->addSelectColumn($alias . '.EDITEDBY'); + $criteria->addSelectColumn($alias . '.MTIME'); + $criteria->addSelectColumn($alias . '.CREATOR'); + $criteria->addSelectColumn($alias . '.DESCRIPTION'); + } + } + + /** + * Returns the number of rows matching criteria. + * + * @param Criteria $criteria + * @param boolean $distinct Whether to select only distinct columns; deprecated: use Criteria->setDistinct() instead. + * @param PropelPDO $con + * @return int Number of matching rows. + */ + public static function doCount(Criteria $criteria, $distinct = false, PropelPDO $con = null) + { + // we may modify criteria, so copy it first + $criteria = clone $criteria; + + // We need to set the primary table name, since in the case that there are no WHERE columns + // it will be impossible for the BasePeer::createSelectSql() method to determine which + // tables go into the FROM clause. + $criteria->setPrimaryTableName(CcPlaylistPeer::TABLE_NAME); + + if ($distinct && !in_array(Criteria::DISTINCT, $criteria->getSelectModifiers())) { + $criteria->setDistinct(); + } + + if (!$criteria->hasSelectClause()) { + CcPlaylistPeer::addSelectColumns($criteria); + } + + $criteria->clearOrderByColumns(); // ORDER BY won't ever affect the count + $criteria->setDbName(self::DATABASE_NAME); // Set the correct dbName + + if ($con === null) { + $con = Propel::getConnection(CcPlaylistPeer::DATABASE_NAME, Propel::CONNECTION_READ); + } + // BasePeer returns a PDOStatement + $stmt = BasePeer::doCount($criteria, $con); + + if ($row = $stmt->fetch(PDO::FETCH_NUM)) { + $count = (int) $row[0]; + } else { + $count = 0; // no rows returned; we infer that means 0 matches. + } + $stmt->closeCursor(); + return $count; + } + /** + * Method to select one object from the DB. + * + * @param Criteria $criteria object used to create the SELECT statement. + * @param PropelPDO $con + * @return CcPlaylist + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function doSelectOne(Criteria $criteria, PropelPDO $con = null) + { + $critcopy = clone $criteria; + $critcopy->setLimit(1); + $objects = CcPlaylistPeer::doSelect($critcopy, $con); + if ($objects) { + return $objects[0]; + } + return null; + } + /** + * Method to do selects. + * + * @param Criteria $criteria The Criteria object used to build the SELECT statement. + * @param PropelPDO $con + * @return array Array of selected Objects + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function doSelect(Criteria $criteria, PropelPDO $con = null) + { + return CcPlaylistPeer::populateObjects(CcPlaylistPeer::doSelectStmt($criteria, $con)); + } + /** + * Prepares the Criteria object and uses the parent doSelect() method to execute a PDOStatement. + * + * Use this method directly if you want to work with an executed statement durirectly (for example + * to perform your own object hydration). + * + * @param Criteria $criteria The Criteria object used to build the SELECT statement. + * @param PropelPDO $con The connection to use + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + * @return PDOStatement The executed PDOStatement object. + * @see BasePeer::doSelect() + */ + public static function doSelectStmt(Criteria $criteria, PropelPDO $con = null) + { + if ($con === null) { + $con = Propel::getConnection(CcPlaylistPeer::DATABASE_NAME, Propel::CONNECTION_READ); + } + + if (!$criteria->hasSelectClause()) { + $criteria = clone $criteria; + CcPlaylistPeer::addSelectColumns($criteria); + } + + // Set the correct dbName + $criteria->setDbName(self::DATABASE_NAME); + + // BasePeer returns a PDOStatement + return BasePeer::doSelect($criteria, $con); + } + /** + * Adds an object to the instance pool. + * + * Propel keeps cached copies of objects in an instance pool when they are retrieved + * from the database. In some cases -- especially when you override doSelect*() + * methods in your stub classes -- you may need to explicitly add objects + * to the cache in order to ensure that the same objects are always returned by doSelect*() + * and retrieveByPK*() calls. + * + * @param CcPlaylist $value A CcPlaylist object. + * @param string $key (optional) key to use for instance map (for performance boost if key was already calculated externally). + */ + public static function addInstanceToPool(CcPlaylist $obj, $key = null) + { + if (Propel::isInstancePoolingEnabled()) { + if ($key === null) { + $key = (string) $obj->getDbId(); + } // if key === null + self::$instances[$key] = $obj; + } + } + + /** + * Removes an object from the instance pool. + * + * Propel keeps cached copies of objects in an instance pool when they are retrieved + * from the database. In some cases -- especially when you override doDelete + * methods in your stub classes -- you may need to explicitly remove objects + * from the cache in order to prevent returning objects that no longer exist. + * + * @param mixed $value A CcPlaylist object or a primary key value. + */ + public static function removeInstanceFromPool($value) + { + if (Propel::isInstancePoolingEnabled() && $value !== null) { + if (is_object($value) && $value instanceof CcPlaylist) { + $key = (string) $value->getDbId(); + } elseif (is_scalar($value)) { + // assume we've been passed a primary key + $key = (string) $value; + } else { + $e = new PropelException("Invalid value passed to removeInstanceFromPool(). Expected primary key or CcPlaylist object; got " . (is_object($value) ? get_class($value) . ' object.' : var_export($value,true))); + throw $e; + } + + unset(self::$instances[$key]); + } + } // removeInstanceFromPool() + + /** + * Retrieves a string version of the primary key from the DB resultset row that can be used to uniquely identify a row in this table. + * + * For tables with a single-column primary key, that simple pkey value will be returned. For tables with + * a multi-column primary key, a serialize()d version of the primary key will be returned. + * + * @param string $key The key (@see getPrimaryKeyHash()) for this instance. + * @return CcPlaylist Found object or NULL if 1) no instance exists for specified key or 2) instance pooling has been disabled. + * @see getPrimaryKeyHash() + */ + public static function getInstanceFromPool($key) + { + if (Propel::isInstancePoolingEnabled()) { + if (isset(self::$instances[$key])) { + return self::$instances[$key]; + } + } + return null; // just to be explicit + } + + /** + * Clear the instance pool. + * + * @return void + */ + public static function clearInstancePool() + { + self::$instances = array(); + } + + /** + * Method to invalidate the instance pool of all tables related to cc_playlist + * by a foreign key with ON DELETE CASCADE + */ + public static function clearRelatedInstancePool() + { + // Invalidate objects in CcPlaylistcontentsPeer instance pool, + // since one or more of them may be deleted by ON DELETE CASCADE/SETNULL rule. + CcPlaylistcontentsPeer::clearInstancePool(); + } + + /** + * Retrieves a string version of the primary key from the DB resultset row that can be used to uniquely identify a row in this table. + * + * For tables with a single-column primary key, that simple pkey value will be returned. For tables with + * a multi-column primary key, a serialize()d version of the primary key will be returned. + * + * @param array $row PropelPDO resultset row. + * @param int $startcol The 0-based offset for reading from the resultset row. + * @return string A string version of PK or NULL if the components of primary key in result array are all null. + */ + public static function getPrimaryKeyHashFromRow($row, $startcol = 0) + { + // If the PK cannot be derived from the row, return NULL. + if ($row[$startcol] === null) { + return null; + } + return (string) $row[$startcol]; + } + + /** + * Retrieves the primary key from the DB resultset row + * For tables with a single-column primary key, that simple pkey value will be returned. For tables with + * a multi-column primary key, an array of the primary key columns will be returned. + * + * @param array $row PropelPDO resultset row. + * @param int $startcol The 0-based offset for reading from the resultset row. + * @return mixed The primary key of the row + */ + public static function getPrimaryKeyFromRow($row, $startcol = 0) + { + return (int) $row[$startcol]; + } + + /** + * The returned array will contain objects of the default type or + * objects that inherit from the default. + * + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function populateObjects(PDOStatement $stmt) + { + $results = array(); + + // set the class once to avoid overhead in the loop + $cls = CcPlaylistPeer::getOMClass(false); + // populate the object(s) + while ($row = $stmt->fetch(PDO::FETCH_NUM)) { + $key = CcPlaylistPeer::getPrimaryKeyHashFromRow($row, 0); + if (null !== ($obj = CcPlaylistPeer::getInstanceFromPool($key))) { + // We no longer rehydrate the object, since this can cause data loss. + // See http://www.propelorm.org/ticket/509 + // $obj->hydrate($row, 0, true); // rehydrate + $results[] = $obj; + } else { + $obj = new $cls(); + $obj->hydrate($row); + $results[] = $obj; + CcPlaylistPeer::addInstanceToPool($obj, $key); + } // if key exists + } + $stmt->closeCursor(); + return $results; + } + /** + * Populates an object of the default type or an object that inherit from the default. + * + * @param array $row PropelPDO resultset row. + * @param int $startcol The 0-based offset for reading from the resultset row. + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + * @return array (CcPlaylist object, last column rank) + */ + public static function populateObject($row, $startcol = 0) + { + $key = CcPlaylistPeer::getPrimaryKeyHashFromRow($row, $startcol); + if (null !== ($obj = CcPlaylistPeer::getInstanceFromPool($key))) { + // We no longer rehydrate the object, since this can cause data loss. + // See http://www.propelorm.org/ticket/509 + // $obj->hydrate($row, $startcol, true); // rehydrate + $col = $startcol + CcPlaylistPeer::NUM_COLUMNS; + } else { + $cls = CcPlaylistPeer::OM_CLASS; + $obj = new $cls(); + $col = $obj->hydrate($row, $startcol); + CcPlaylistPeer::addInstanceToPool($obj, $key); + } + return array($obj, $col); + } + + /** + * Returns the number of rows matching criteria, joining the related CcSubjs table + * + * @param Criteria $criteria + * @param boolean $distinct Whether to select only distinct columns; deprecated: use Criteria->setDistinct() instead. + * @param PropelPDO $con + * @param String $join_behavior the type of joins to use, defaults to Criteria::LEFT_JOIN + * @return int Number of matching rows. + */ + public static function doCountJoinCcSubjs(Criteria $criteria, $distinct = false, PropelPDO $con = null, $join_behavior = Criteria::LEFT_JOIN) + { + // we're going to modify criteria, so copy it first + $criteria = clone $criteria; + + // We need to set the primary table name, since in the case that there are no WHERE columns + // it will be impossible for the BasePeer::createSelectSql() method to determine which + // tables go into the FROM clause. + $criteria->setPrimaryTableName(CcPlaylistPeer::TABLE_NAME); + + if ($distinct && !in_array(Criteria::DISTINCT, $criteria->getSelectModifiers())) { + $criteria->setDistinct(); + } + + if (!$criteria->hasSelectClause()) { + CcPlaylistPeer::addSelectColumns($criteria); + } + + $criteria->clearOrderByColumns(); // ORDER BY won't ever affect the count + + // Set the correct dbName + $criteria->setDbName(self::DATABASE_NAME); + + if ($con === null) { + $con = Propel::getConnection(CcPlaylistPeer::DATABASE_NAME, Propel::CONNECTION_READ); + } + + $criteria->addJoin(CcPlaylistPeer::EDITEDBY, CcSubjsPeer::ID, $join_behavior); + + $stmt = BasePeer::doCount($criteria, $con); + + if ($row = $stmt->fetch(PDO::FETCH_NUM)) { + $count = (int) $row[0]; + } else { + $count = 0; // no rows returned; we infer that means 0 matches. + } + $stmt->closeCursor(); + return $count; + } + + + /** + * Selects a collection of CcPlaylist objects pre-filled with their CcSubjs objects. + * @param Criteria $criteria + * @param PropelPDO $con + * @param String $join_behavior the type of joins to use, defaults to Criteria::LEFT_JOIN + * @return array Array of CcPlaylist objects. + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function doSelectJoinCcSubjs(Criteria $criteria, $con = null, $join_behavior = Criteria::LEFT_JOIN) + { + $criteria = clone $criteria; + + // Set the correct dbName if it has not been overridden + if ($criteria->getDbName() == Propel::getDefaultDB()) { + $criteria->setDbName(self::DATABASE_NAME); + } + + CcPlaylistPeer::addSelectColumns($criteria); + $startcol = (CcPlaylistPeer::NUM_COLUMNS - CcPlaylistPeer::NUM_LAZY_LOAD_COLUMNS); + CcSubjsPeer::addSelectColumns($criteria); + + $criteria->addJoin(CcPlaylistPeer::EDITEDBY, CcSubjsPeer::ID, $join_behavior); + + $stmt = BasePeer::doSelect($criteria, $con); + $results = array(); + + while ($row = $stmt->fetch(PDO::FETCH_NUM)) { + $key1 = CcPlaylistPeer::getPrimaryKeyHashFromRow($row, 0); + if (null !== ($obj1 = CcPlaylistPeer::getInstanceFromPool($key1))) { + // We no longer rehydrate the object, since this can cause data loss. + // See http://www.propelorm.org/ticket/509 + // $obj1->hydrate($row, 0, true); // rehydrate + } else { + + $cls = CcPlaylistPeer::getOMClass(false); + + $obj1 = new $cls(); + $obj1->hydrate($row); + CcPlaylistPeer::addInstanceToPool($obj1, $key1); + } // if $obj1 already loaded + + $key2 = CcSubjsPeer::getPrimaryKeyHashFromRow($row, $startcol); + if ($key2 !== null) { + $obj2 = CcSubjsPeer::getInstanceFromPool($key2); + if (!$obj2) { + + $cls = CcSubjsPeer::getOMClass(false); + + $obj2 = new $cls(); + $obj2->hydrate($row, $startcol); + CcSubjsPeer::addInstanceToPool($obj2, $key2); + } // if obj2 already loaded + + // Add the $obj1 (CcPlaylist) to $obj2 (CcSubjs) + $obj2->addCcPlaylist($obj1); + + } // if joined row was not null + + $results[] = $obj1; + } + $stmt->closeCursor(); + return $results; + } + + + /** + * Returns the number of rows matching criteria, joining all related tables + * + * @param Criteria $criteria + * @param boolean $distinct Whether to select only distinct columns; deprecated: use Criteria->setDistinct() instead. + * @param PropelPDO $con + * @param String $join_behavior the type of joins to use, defaults to Criteria::LEFT_JOIN + * @return int Number of matching rows. + */ + public static function doCountJoinAll(Criteria $criteria, $distinct = false, PropelPDO $con = null, $join_behavior = Criteria::LEFT_JOIN) + { + // we're going to modify criteria, so copy it first + $criteria = clone $criteria; + + // We need to set the primary table name, since in the case that there are no WHERE columns + // it will be impossible for the BasePeer::createSelectSql() method to determine which + // tables go into the FROM clause. + $criteria->setPrimaryTableName(CcPlaylistPeer::TABLE_NAME); + + if ($distinct && !in_array(Criteria::DISTINCT, $criteria->getSelectModifiers())) { + $criteria->setDistinct(); + } + + if (!$criteria->hasSelectClause()) { + CcPlaylistPeer::addSelectColumns($criteria); + } + + $criteria->clearOrderByColumns(); // ORDER BY won't ever affect the count + + // Set the correct dbName + $criteria->setDbName(self::DATABASE_NAME); + + if ($con === null) { + $con = Propel::getConnection(CcPlaylistPeer::DATABASE_NAME, Propel::CONNECTION_READ); + } + + $criteria->addJoin(CcPlaylistPeer::EDITEDBY, CcSubjsPeer::ID, $join_behavior); + + $stmt = BasePeer::doCount($criteria, $con); + + if ($row = $stmt->fetch(PDO::FETCH_NUM)) { + $count = (int) $row[0]; + } else { + $count = 0; // no rows returned; we infer that means 0 matches. + } + $stmt->closeCursor(); + return $count; + } + + /** + * Selects a collection of CcPlaylist objects pre-filled with all related objects. + * + * @param Criteria $criteria + * @param PropelPDO $con + * @param String $join_behavior the type of joins to use, defaults to Criteria::LEFT_JOIN + * @return array Array of CcPlaylist objects. + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function doSelectJoinAll(Criteria $criteria, $con = null, $join_behavior = Criteria::LEFT_JOIN) + { + $criteria = clone $criteria; + + // Set the correct dbName if it has not been overridden + if ($criteria->getDbName() == Propel::getDefaultDB()) { + $criteria->setDbName(self::DATABASE_NAME); + } + + CcPlaylistPeer::addSelectColumns($criteria); + $startcol2 = (CcPlaylistPeer::NUM_COLUMNS - CcPlaylistPeer::NUM_LAZY_LOAD_COLUMNS); + + CcSubjsPeer::addSelectColumns($criteria); + $startcol3 = $startcol2 + (CcSubjsPeer::NUM_COLUMNS - CcSubjsPeer::NUM_LAZY_LOAD_COLUMNS); + + $criteria->addJoin(CcPlaylistPeer::EDITEDBY, CcSubjsPeer::ID, $join_behavior); + + $stmt = BasePeer::doSelect($criteria, $con); + $results = array(); + + while ($row = $stmt->fetch(PDO::FETCH_NUM)) { + $key1 = CcPlaylistPeer::getPrimaryKeyHashFromRow($row, 0); + if (null !== ($obj1 = CcPlaylistPeer::getInstanceFromPool($key1))) { + // We no longer rehydrate the object, since this can cause data loss. + // See http://www.propelorm.org/ticket/509 + // $obj1->hydrate($row, 0, true); // rehydrate + } else { + $cls = CcPlaylistPeer::getOMClass(false); + + $obj1 = new $cls(); + $obj1->hydrate($row); + CcPlaylistPeer::addInstanceToPool($obj1, $key1); + } // if obj1 already loaded + + // Add objects for joined CcSubjs rows + + $key2 = CcSubjsPeer::getPrimaryKeyHashFromRow($row, $startcol2); + if ($key2 !== null) { + $obj2 = CcSubjsPeer::getInstanceFromPool($key2); + if (!$obj2) { + + $cls = CcSubjsPeer::getOMClass(false); + + $obj2 = new $cls(); + $obj2->hydrate($row, $startcol2); + CcSubjsPeer::addInstanceToPool($obj2, $key2); + } // if obj2 loaded + + // Add the $obj1 (CcPlaylist) to the collection in $obj2 (CcSubjs) + $obj2->addCcPlaylist($obj1); + } // if joined row not null + + $results[] = $obj1; + } + $stmt->closeCursor(); + return $results; + } + + /** + * Returns the TableMap related to this peer. + * This method is not needed for general use but a specific application could have a need. + * @return TableMap + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function getTableMap() + { + return Propel::getDatabaseMap(self::DATABASE_NAME)->getTable(self::TABLE_NAME); + } + + /** + * Add a TableMap instance to the database for this peer class. + */ + public static function buildTableMap() + { + $dbMap = Propel::getDatabaseMap(BaseCcPlaylistPeer::DATABASE_NAME); + if (!$dbMap->hasTable(BaseCcPlaylistPeer::TABLE_NAME)) + { + $dbMap->addTableObject(new CcPlaylistTableMap()); + } + } + + /** + * The class that the Peer will make instances of. + * + * If $withPrefix is true, the returned path + * uses a dot-path notation which is tranalted into a path + * relative to a location on the PHP include_path. + * (e.g. path.to.MyClass -> 'path/to/MyClass.php') + * + * @param boolean $withPrefix Whether or not to return the path with the class name + * @return string path.to.ClassName + */ + public static function getOMClass($withPrefix = true) + { + return $withPrefix ? CcPlaylistPeer::CLASS_DEFAULT : CcPlaylistPeer::OM_CLASS; + } + + /** + * Method perform an INSERT on the database, given a CcPlaylist or Criteria object. + * + * @param mixed $values Criteria or CcPlaylist object containing data that is used to create the INSERT statement. + * @param PropelPDO $con the PropelPDO connection to use + * @return mixed The new primary key. + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function doInsert($values, PropelPDO $con = null) + { + if ($con === null) { + $con = Propel::getConnection(CcPlaylistPeer::DATABASE_NAME, Propel::CONNECTION_WRITE); + } + + if ($values instanceof Criteria) { + $criteria = clone $values; // rename for clarity + } else { + $criteria = $values->buildCriteria(); // build Criteria from CcPlaylist object + } + + if ($criteria->containsKey(CcPlaylistPeer::ID) && $criteria->keyContainsValue(CcPlaylistPeer::ID) ) { + throw new PropelException('Cannot insert a value for auto-increment primary key ('.CcPlaylistPeer::ID.')'); + } + + + // Set the correct dbName + $criteria->setDbName(self::DATABASE_NAME); + + try { + // use transaction because $criteria could contain info + // for more than one table (I guess, conceivably) + $con->beginTransaction(); + $pk = BasePeer::doInsert($criteria, $con); + $con->commit(); + } catch(PropelException $e) { + $con->rollBack(); + throw $e; + } + + return $pk; + } + + /** + * Method perform an UPDATE on the database, given a CcPlaylist or Criteria object. + * + * @param mixed $values Criteria or CcPlaylist object containing data that is used to create the UPDATE statement. + * @param PropelPDO $con The connection to use (specify PropelPDO connection object to exert more control over transactions). + * @return int The number of affected rows (if supported by underlying database driver). + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function doUpdate($values, PropelPDO $con = null) + { + if ($con === null) { + $con = Propel::getConnection(CcPlaylistPeer::DATABASE_NAME, Propel::CONNECTION_WRITE); + } + + $selectCriteria = new Criteria(self::DATABASE_NAME); + + if ($values instanceof Criteria) { + $criteria = clone $values; // rename for clarity + + $comparison = $criteria->getComparison(CcPlaylistPeer::ID); + $value = $criteria->remove(CcPlaylistPeer::ID); + if ($value) { + $selectCriteria->add(CcPlaylistPeer::ID, $value, $comparison); + } else { + $selectCriteria->setPrimaryTableName(CcPlaylistPeer::TABLE_NAME); + } + + } else { // $values is CcPlaylist object + $criteria = $values->buildCriteria(); // gets full criteria + $selectCriteria = $values->buildPkeyCriteria(); // gets criteria w/ primary key(s) + } + + // set the correct dbName + $criteria->setDbName(self::DATABASE_NAME); + + return BasePeer::doUpdate($selectCriteria, $criteria, $con); + } + + /** + * Method to DELETE all rows from the cc_playlist table. + * + * @return int The number of affected rows (if supported by underlying database driver). + */ + public static function doDeleteAll($con = null) + { + if ($con === null) { + $con = Propel::getConnection(CcPlaylistPeer::DATABASE_NAME, Propel::CONNECTION_WRITE); + } + $affectedRows = 0; // initialize var to track total num of affected rows + try { + // use transaction because $criteria could contain info + // for more than one table or we could emulating ON DELETE CASCADE, etc. + $con->beginTransaction(); + $affectedRows += BasePeer::doDeleteAll(CcPlaylistPeer::TABLE_NAME, $con, CcPlaylistPeer::DATABASE_NAME); + // Because this db requires some delete cascade/set null emulation, we have to + // clear the cached instance *after* the emulation has happened (since + // instances get re-added by the select statement contained therein). + CcPlaylistPeer::clearInstancePool(); + CcPlaylistPeer::clearRelatedInstancePool(); + $con->commit(); + return $affectedRows; + } catch (PropelException $e) { + $con->rollBack(); + throw $e; + } + } + + /** + * Method perform a DELETE on the database, given a CcPlaylist or Criteria object OR a primary key value. + * + * @param mixed $values Criteria or CcPlaylist object or primary key or array of primary keys + * which is used to create the DELETE statement + * @param PropelPDO $con the connection to use + * @return int The number of affected rows (if supported by underlying database driver). This includes CASCADE-related rows + * if supported by native driver or if emulated using Propel. + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function doDelete($values, PropelPDO $con = null) + { + if ($con === null) { + $con = Propel::getConnection(CcPlaylistPeer::DATABASE_NAME, Propel::CONNECTION_WRITE); + } + + if ($values instanceof Criteria) { + // invalidate the cache for all objects of this type, since we have no + // way of knowing (without running a query) what objects should be invalidated + // from the cache based on this Criteria. + CcPlaylistPeer::clearInstancePool(); + // rename for clarity + $criteria = clone $values; + } elseif ($values instanceof CcPlaylist) { // it's a model object + // invalidate the cache for this single object + CcPlaylistPeer::removeInstanceFromPool($values); + // create criteria based on pk values + $criteria = $values->buildPkeyCriteria(); + } else { // it's a primary key, or an array of pks + $criteria = new Criteria(self::DATABASE_NAME); + $criteria->add(CcPlaylistPeer::ID, (array) $values, Criteria::IN); + // invalidate the cache for this object(s) + foreach ((array) $values as $singleval) { + CcPlaylistPeer::removeInstanceFromPool($singleval); + } + } + + // Set the correct dbName + $criteria->setDbName(self::DATABASE_NAME); + + $affectedRows = 0; // initialize var to track total num of affected rows + + try { + // use transaction because $criteria could contain info + // for more than one table or we could emulating ON DELETE CASCADE, etc. + $con->beginTransaction(); + + $affectedRows += BasePeer::doDelete($criteria, $con); + CcPlaylistPeer::clearRelatedInstancePool(); + $con->commit(); + return $affectedRows; + } catch (PropelException $e) { + $con->rollBack(); + throw $e; + } + } + + /** + * Validates all modified columns of given CcPlaylist object. + * If parameter $columns is either a single column name or an array of column names + * than only those columns are validated. + * + * NOTICE: This does not apply to primary or foreign keys for now. + * + * @param CcPlaylist $obj The object to validate. + * @param mixed $cols Column name or array of column names. + * + * @return mixed TRUE if all columns are valid or the error message of the first invalid column. + */ + public static function doValidate(CcPlaylist $obj, $cols = null) + { + $columns = array(); + + if ($cols) { + $dbMap = Propel::getDatabaseMap(CcPlaylistPeer::DATABASE_NAME); + $tableMap = $dbMap->getTable(CcPlaylistPeer::TABLE_NAME); + + if (! is_array($cols)) { + $cols = array($cols); + } + + foreach ($cols as $colName) { + if ($tableMap->containsColumn($colName)) { + $get = 'get' . $tableMap->getColumn($colName)->getPhpName(); + $columns[$colName] = $obj->$get(); + } + } + } else { + + } + + return BasePeer::doValidate(CcPlaylistPeer::DATABASE_NAME, CcPlaylistPeer::TABLE_NAME, $columns); + } + + /** + * Retrieve a single object by pkey. + * + * @param int $pk the primary key. + * @param PropelPDO $con the connection to use + * @return CcPlaylist + */ + public static function retrieveByPK($pk, PropelPDO $con = null) + { + + if (null !== ($obj = CcPlaylistPeer::getInstanceFromPool((string) $pk))) { + return $obj; + } + + if ($con === null) { + $con = Propel::getConnection(CcPlaylistPeer::DATABASE_NAME, Propel::CONNECTION_READ); + } + + $criteria = new Criteria(CcPlaylistPeer::DATABASE_NAME); + $criteria->add(CcPlaylistPeer::ID, $pk); + + $v = CcPlaylistPeer::doSelect($criteria, $con); + + return !empty($v) > 0 ? $v[0] : null; + } + + /** + * Retrieve multiple objects by pkey. + * + * @param array $pks List of primary keys + * @param PropelPDO $con the connection to use + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function retrieveByPKs($pks, PropelPDO $con = null) + { + if ($con === null) { + $con = Propel::getConnection(CcPlaylistPeer::DATABASE_NAME, Propel::CONNECTION_READ); + } + + $objs = null; + if (empty($pks)) { + $objs = array(); + } else { + $criteria = new Criteria(CcPlaylistPeer::DATABASE_NAME); + $criteria->add(CcPlaylistPeer::ID, $pks, Criteria::IN); + $objs = CcPlaylistPeer::doSelect($criteria, $con); + } + return $objs; + } + +} // BaseCcPlaylistPeer + +// This is the static code needed to register the TableMap for this table with the main Propel class. +// +BaseCcPlaylistPeer::buildTableMap(); + diff --git a/application/models/campcaster/om/BaseCcPlaylistQuery.php b/application/models/campcaster/om/BaseCcPlaylistQuery.php new file mode 100644 index 000000000..237be4312 --- /dev/null +++ b/application/models/campcaster/om/BaseCcPlaylistQuery.php @@ -0,0 +1,510 @@ +setModelAlias($modelAlias); + } + if ($criteria instanceof Criteria) { + $query->mergeWith($criteria); + } + return $query; + } + + /** + * Find object by primary key + * Use instance pooling to avoid a database query if the object exists + * + * $obj = $c->findPk(12, $con); + * + * @param mixed $key Primary key to use for the query + * @param PropelPDO $con an optional connection object + * + * @return CcPlaylist|array|mixed the result, formatted by the current formatter + */ + public function findPk($key, $con = null) + { + if ((null !== ($obj = CcPlaylistPeer::getInstanceFromPool((string) $key))) && $this->getFormatter()->isObjectFormatter()) { + // the object is alredy in the instance pool + return $obj; + } else { + // the object has not been requested yet, or the formatter is not an object formatter + $criteria = $this->isKeepQuery() ? clone $this : $this; + $stmt = $criteria + ->filterByPrimaryKey($key) + ->getSelectStatement($con); + return $criteria->getFormatter()->init($criteria)->formatOne($stmt); + } + } + + /** + * Find objects by primary key + * + * $objs = $c->findPks(array(12, 56, 832), $con); + * + * @param array $keys Primary keys to use for the query + * @param PropelPDO $con an optional connection object + * + * @return PropelObjectCollection|array|mixed the list of results, formatted by the current formatter + */ + public function findPks($keys, $con = null) + { + $criteria = $this->isKeepQuery() ? clone $this : $this; + return $this + ->filterByPrimaryKeys($keys) + ->find($con); + } + + /** + * Filter the query by primary key + * + * @param mixed $key Primary key to use for the query + * + * @return CcPlaylistQuery The current query, for fluid interface + */ + public function filterByPrimaryKey($key) + { + return $this->addUsingAlias(CcPlaylistPeer::ID, $key, Criteria::EQUAL); + } + + /** + * Filter the query by a list of primary keys + * + * @param array $keys The list of primary key to use for the query + * + * @return CcPlaylistQuery The current query, for fluid interface + */ + public function filterByPrimaryKeys($keys) + { + return $this->addUsingAlias(CcPlaylistPeer::ID, $keys, Criteria::IN); + } + + /** + * Filter the query on the id column + * + * @param int|array $dbId The value to use as filter. + * Accepts an associative array('min' => $minValue, 'max' => $maxValue) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcPlaylistQuery The current query, for fluid interface + */ + public function filterByDbId($dbId = null, $comparison = null) + { + if (is_array($dbId) && null === $comparison) { + $comparison = Criteria::IN; + } + return $this->addUsingAlias(CcPlaylistPeer::ID, $dbId, $comparison); + } + + /** + * Filter the query on the name column + * + * @param string $dbName The value to use as filter. + * Accepts wildcards (* and % trigger a LIKE) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcPlaylistQuery The current query, for fluid interface + */ + public function filterByDbName($dbName = null, $comparison = null) + { + if (null === $comparison) { + if (is_array($dbName)) { + $comparison = Criteria::IN; + } elseif (preg_match('/[\%\*]/', $dbName)) { + $dbName = str_replace('*', '%', $dbName); + $comparison = Criteria::LIKE; + } + } + return $this->addUsingAlias(CcPlaylistPeer::NAME, $dbName, $comparison); + } + + /** + * Filter the query on the state column + * + * @param string $dbState The value to use as filter. + * Accepts wildcards (* and % trigger a LIKE) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcPlaylistQuery The current query, for fluid interface + */ + public function filterByDbState($dbState = null, $comparison = null) + { + if (null === $comparison) { + if (is_array($dbState)) { + $comparison = Criteria::IN; + } elseif (preg_match('/[\%\*]/', $dbState)) { + $dbState = str_replace('*', '%', $dbState); + $comparison = Criteria::LIKE; + } + } + return $this->addUsingAlias(CcPlaylistPeer::STATE, $dbState, $comparison); + } + + /** + * Filter the query on the currentlyaccessing column + * + * @param int|array $dbCurrentlyaccessing The value to use as filter. + * Accepts an associative array('min' => $minValue, 'max' => $maxValue) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcPlaylistQuery The current query, for fluid interface + */ + public function filterByDbCurrentlyaccessing($dbCurrentlyaccessing = null, $comparison = null) + { + if (is_array($dbCurrentlyaccessing)) { + $useMinMax = false; + if (isset($dbCurrentlyaccessing['min'])) { + $this->addUsingAlias(CcPlaylistPeer::CURRENTLYACCESSING, $dbCurrentlyaccessing['min'], Criteria::GREATER_EQUAL); + $useMinMax = true; + } + if (isset($dbCurrentlyaccessing['max'])) { + $this->addUsingAlias(CcPlaylistPeer::CURRENTLYACCESSING, $dbCurrentlyaccessing['max'], Criteria::LESS_EQUAL); + $useMinMax = true; + } + if ($useMinMax) { + return $this; + } + if (null === $comparison) { + $comparison = Criteria::IN; + } + } + return $this->addUsingAlias(CcPlaylistPeer::CURRENTLYACCESSING, $dbCurrentlyaccessing, $comparison); + } + + /** + * Filter the query on the editedby column + * + * @param int|array $dbEditedby The value to use as filter. + * Accepts an associative array('min' => $minValue, 'max' => $maxValue) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcPlaylistQuery The current query, for fluid interface + */ + public function filterByDbEditedby($dbEditedby = null, $comparison = null) + { + if (is_array($dbEditedby)) { + $useMinMax = false; + if (isset($dbEditedby['min'])) { + $this->addUsingAlias(CcPlaylistPeer::EDITEDBY, $dbEditedby['min'], Criteria::GREATER_EQUAL); + $useMinMax = true; + } + if (isset($dbEditedby['max'])) { + $this->addUsingAlias(CcPlaylistPeer::EDITEDBY, $dbEditedby['max'], Criteria::LESS_EQUAL); + $useMinMax = true; + } + if ($useMinMax) { + return $this; + } + if (null === $comparison) { + $comparison = Criteria::IN; + } + } + return $this->addUsingAlias(CcPlaylistPeer::EDITEDBY, $dbEditedby, $comparison); + } + + /** + * Filter the query on the mtime column + * + * @param string|array $dbMtime The value to use as filter. + * Accepts an associative array('min' => $minValue, 'max' => $maxValue) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcPlaylistQuery The current query, for fluid interface + */ + public function filterByDbMtime($dbMtime = null, $comparison = null) + { + if (is_array($dbMtime)) { + $useMinMax = false; + if (isset($dbMtime['min'])) { + $this->addUsingAlias(CcPlaylistPeer::MTIME, $dbMtime['min'], Criteria::GREATER_EQUAL); + $useMinMax = true; + } + if (isset($dbMtime['max'])) { + $this->addUsingAlias(CcPlaylistPeer::MTIME, $dbMtime['max'], Criteria::LESS_EQUAL); + $useMinMax = true; + } + if ($useMinMax) { + return $this; + } + if (null === $comparison) { + $comparison = Criteria::IN; + } + } + return $this->addUsingAlias(CcPlaylistPeer::MTIME, $dbMtime, $comparison); + } + + /** + * Filter the query on the creator column + * + * @param string $dbCreator The value to use as filter. + * Accepts wildcards (* and % trigger a LIKE) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcPlaylistQuery The current query, for fluid interface + */ + public function filterByDbCreator($dbCreator = null, $comparison = null) + { + if (null === $comparison) { + if (is_array($dbCreator)) { + $comparison = Criteria::IN; + } elseif (preg_match('/[\%\*]/', $dbCreator)) { + $dbCreator = str_replace('*', '%', $dbCreator); + $comparison = Criteria::LIKE; + } + } + return $this->addUsingAlias(CcPlaylistPeer::CREATOR, $dbCreator, $comparison); + } + + /** + * Filter the query on the description column + * + * @param string $dbDescription The value to use as filter. + * Accepts wildcards (* and % trigger a LIKE) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcPlaylistQuery The current query, for fluid interface + */ + public function filterByDbDescription($dbDescription = null, $comparison = null) + { + if (null === $comparison) { + if (is_array($dbDescription)) { + $comparison = Criteria::IN; + } elseif (preg_match('/[\%\*]/', $dbDescription)) { + $dbDescription = str_replace('*', '%', $dbDescription); + $comparison = Criteria::LIKE; + } + } + return $this->addUsingAlias(CcPlaylistPeer::DESCRIPTION, $dbDescription, $comparison); + } + + /** + * Filter the query by a related CcSubjs object + * + * @param CcSubjs $ccSubjs the related object to use as filter + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcPlaylistQuery The current query, for fluid interface + */ + public function filterByCcSubjs($ccSubjs, $comparison = null) + { + return $this + ->addUsingAlias(CcPlaylistPeer::EDITEDBY, $ccSubjs->getId(), $comparison); + } + + /** + * Adds a JOIN clause to the query using the CcSubjs relation + * + * @param string $relationAlias optional alias for the relation + * @param string $joinType Accepted values are null, 'left join', 'right join', 'inner join' + * + * @return CcPlaylistQuery The current query, for fluid interface + */ + public function joinCcSubjs($relationAlias = '', $joinType = Criteria::LEFT_JOIN) + { + $tableMap = $this->getTableMap(); + $relationMap = $tableMap->getRelation('CcSubjs'); + + // create a ModelJoin object for this join + $join = new ModelJoin(); + $join->setJoinType($joinType); + $join->setRelationMap($relationMap, $this->useAliasInSQL ? $this->getModelAlias() : null, $relationAlias); + if ($previousJoin = $this->getPreviousJoin()) { + $join->setPreviousJoin($previousJoin); + } + + // add the ModelJoin to the current object + if($relationAlias) { + $this->addAlias($relationAlias, $relationMap->getRightTable()->getName()); + $this->addJoinObject($join, $relationAlias); + } else { + $this->addJoinObject($join, 'CcSubjs'); + } + + return $this; + } + + /** + * Use the CcSubjs relation CcSubjs object + * + * @see useQuery() + * + * @param string $relationAlias optional alias for the relation, + * to be used as main alias in the secondary query + * @param string $joinType Accepted values are null, 'left join', 'right join', 'inner join' + * + * @return CcSubjsQuery A secondary query class using the current class as primary query + */ + public function useCcSubjsQuery($relationAlias = '', $joinType = Criteria::LEFT_JOIN) + { + return $this + ->joinCcSubjs($relationAlias, $joinType) + ->useQuery($relationAlias ? $relationAlias : 'CcSubjs', 'CcSubjsQuery'); + } + + /** + * Filter the query by a related CcPlaylistcontents object + * + * @param CcPlaylistcontents $ccPlaylistcontents the related object to use as filter + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcPlaylistQuery The current query, for fluid interface + */ + public function filterByCcPlaylistcontents($ccPlaylistcontents, $comparison = null) + { + return $this + ->addUsingAlias(CcPlaylistPeer::ID, $ccPlaylistcontents->getDbPlaylistId(), $comparison); + } + + /** + * Adds a JOIN clause to the query using the CcPlaylistcontents relation + * + * @param string $relationAlias optional alias for the relation + * @param string $joinType Accepted values are null, 'left join', 'right join', 'inner join' + * + * @return CcPlaylistQuery The current query, for fluid interface + */ + public function joinCcPlaylistcontents($relationAlias = '', $joinType = Criteria::LEFT_JOIN) + { + $tableMap = $this->getTableMap(); + $relationMap = $tableMap->getRelation('CcPlaylistcontents'); + + // create a ModelJoin object for this join + $join = new ModelJoin(); + $join->setJoinType($joinType); + $join->setRelationMap($relationMap, $this->useAliasInSQL ? $this->getModelAlias() : null, $relationAlias); + if ($previousJoin = $this->getPreviousJoin()) { + $join->setPreviousJoin($previousJoin); + } + + // add the ModelJoin to the current object + if($relationAlias) { + $this->addAlias($relationAlias, $relationMap->getRightTable()->getName()); + $this->addJoinObject($join, $relationAlias); + } else { + $this->addJoinObject($join, 'CcPlaylistcontents'); + } + + return $this; + } + + /** + * Use the CcPlaylistcontents relation CcPlaylistcontents object + * + * @see useQuery() + * + * @param string $relationAlias optional alias for the relation, + * to be used as main alias in the secondary query + * @param string $joinType Accepted values are null, 'left join', 'right join', 'inner join' + * + * @return CcPlaylistcontentsQuery A secondary query class using the current class as primary query + */ + public function useCcPlaylistcontentsQuery($relationAlias = '', $joinType = Criteria::LEFT_JOIN) + { + return $this + ->joinCcPlaylistcontents($relationAlias, $joinType) + ->useQuery($relationAlias ? $relationAlias : 'CcPlaylistcontents', 'CcPlaylistcontentsQuery'); + } + + /** + * Exclude object from result + * + * @param CcPlaylist $ccPlaylist Object to remove from the list of results + * + * @return CcPlaylistQuery The current query, for fluid interface + */ + public function prune($ccPlaylist = null) + { + if ($ccPlaylist) { + $this->addUsingAlias(CcPlaylistPeer::ID, $ccPlaylist->getDbId(), Criteria::NOT_EQUAL); + } + + return $this; + } + +} // BaseCcPlaylistQuery diff --git a/application/models/campcaster/om/BaseCcPlaylistcontents.php b/application/models/campcaster/om/BaseCcPlaylistcontents.php new file mode 100644 index 000000000..53ff5fe8d --- /dev/null +++ b/application/models/campcaster/om/BaseCcPlaylistcontents.php @@ -0,0 +1,1540 @@ +cliplength = '00:00:00'; + $this->cuein = '00:00:00'; + $this->cueout = '00:00:00'; + $this->fadein = '00:00:00'; + $this->fadeout = '00:00:00'; + } + + /** + * Initializes internal state of BaseCcPlaylistcontents object. + * @see applyDefaults() + */ + public function __construct() + { + parent::__construct(); + $this->applyDefaultValues(); + } + + /** + * Get the [id] column value. + * + * @return int + */ + public function getDbId() + { + return $this->id; + } + + /** + * Get the [playlist_id] column value. + * + * @return int + */ + public function getDbPlaylistId() + { + return $this->playlist_id; + } + + /** + * Get the [file_id] column value. + * + * @return int + */ + public function getDbFileId() + { + return $this->file_id; + } + + /** + * Get the [position] column value. + * + * @return int + */ + public function getDbPosition() + { + return $this->position; + } + + /** + * Get the [optionally formatted] temporal [cliplength] column value. + * + * + * @param string $format The date/time format string (either date()-style or strftime()-style). + * If format is NULL, then the raw DateTime object will be returned. + * @return mixed Formatted date/time value as string or DateTime object (if format is NULL), NULL if column is NULL + * @throws PropelException - if unable to parse/validate the date/time value. + */ + public function getDbCliplength($format = '%X') + { + if ($this->cliplength === null) { + return null; + } + + + + try { + $dt = new DateTime($this->cliplength); + } catch (Exception $x) { + throw new PropelException("Internally stored date/time/timestamp value could not be converted to DateTime: " . var_export($this->cliplength, true), $x); + } + + if ($format === null) { + // Because propel.useDateTimeClass is TRUE, we return a DateTime object. + return $dt; + } elseif (strpos($format, '%') !== false) { + return strftime($format, $dt->format('U')); + } else { + return $dt->format($format); + } + } + + /** + * Get the [optionally formatted] temporal [cuein] column value. + * + * + * @param string $format The date/time format string (either date()-style or strftime()-style). + * If format is NULL, then the raw DateTime object will be returned. + * @return mixed Formatted date/time value as string or DateTime object (if format is NULL), NULL if column is NULL + * @throws PropelException - if unable to parse/validate the date/time value. + */ + public function getDbCuein($format = '%X') + { + if ($this->cuein === null) { + return null; + } + + + + try { + $dt = new DateTime($this->cuein); + } catch (Exception $x) { + throw new PropelException("Internally stored date/time/timestamp value could not be converted to DateTime: " . var_export($this->cuein, true), $x); + } + + if ($format === null) { + // Because propel.useDateTimeClass is TRUE, we return a DateTime object. + return $dt; + } elseif (strpos($format, '%') !== false) { + return strftime($format, $dt->format('U')); + } else { + return $dt->format($format); + } + } + + /** + * Get the [optionally formatted] temporal [cueout] column value. + * + * + * @param string $format The date/time format string (either date()-style or strftime()-style). + * If format is NULL, then the raw DateTime object will be returned. + * @return mixed Formatted date/time value as string or DateTime object (if format is NULL), NULL if column is NULL + * @throws PropelException - if unable to parse/validate the date/time value. + */ + public function getDbCueout($format = '%X') + { + if ($this->cueout === null) { + return null; + } + + + + try { + $dt = new DateTime($this->cueout); + } catch (Exception $x) { + throw new PropelException("Internally stored date/time/timestamp value could not be converted to DateTime: " . var_export($this->cueout, true), $x); + } + + if ($format === null) { + // Because propel.useDateTimeClass is TRUE, we return a DateTime object. + return $dt; + } elseif (strpos($format, '%') !== false) { + return strftime($format, $dt->format('U')); + } else { + return $dt->format($format); + } + } + + /** + * Get the [optionally formatted] temporal [fadein] column value. + * + * + * @param string $format The date/time format string (either date()-style or strftime()-style). + * If format is NULL, then the raw DateTime object will be returned. + * @return mixed Formatted date/time value as string or DateTime object (if format is NULL), NULL if column is NULL + * @throws PropelException - if unable to parse/validate the date/time value. + */ + public function getDbFadein($format = '%X') + { + if ($this->fadein === null) { + return null; + } + + + + try { + $dt = new DateTime($this->fadein); + } catch (Exception $x) { + throw new PropelException("Internally stored date/time/timestamp value could not be converted to DateTime: " . var_export($this->fadein, true), $x); + } + + if ($format === null) { + // Because propel.useDateTimeClass is TRUE, we return a DateTime object. + return $dt; + } elseif (strpos($format, '%') !== false) { + return strftime($format, $dt->format('U')); + } else { + return $dt->format($format); + } + } + + /** + * Get the [optionally formatted] temporal [fadeout] column value. + * + * + * @param string $format The date/time format string (either date()-style or strftime()-style). + * If format is NULL, then the raw DateTime object will be returned. + * @return mixed Formatted date/time value as string or DateTime object (if format is NULL), NULL if column is NULL + * @throws PropelException - if unable to parse/validate the date/time value. + */ + public function getDbFadeout($format = '%X') + { + if ($this->fadeout === null) { + return null; + } + + + + try { + $dt = new DateTime($this->fadeout); + } catch (Exception $x) { + throw new PropelException("Internally stored date/time/timestamp value could not be converted to DateTime: " . var_export($this->fadeout, true), $x); + } + + if ($format === null) { + // Because propel.useDateTimeClass is TRUE, we return a DateTime object. + return $dt; + } elseif (strpos($format, '%') !== false) { + return strftime($format, $dt->format('U')); + } else { + return $dt->format($format); + } + } + + /** + * Set the value of [id] column. + * + * @param int $v new value + * @return CcPlaylistcontents The current object (for fluent API support) + */ + public function setDbId($v) + { + if ($v !== null) { + $v = (int) $v; + } + + if ($this->id !== $v) { + $this->id = $v; + $this->modifiedColumns[] = CcPlaylistcontentsPeer::ID; + } + + return $this; + } // setDbId() + + /** + * Set the value of [playlist_id] column. + * + * @param int $v new value + * @return CcPlaylistcontents The current object (for fluent API support) + */ + public function setDbPlaylistId($v) + { + if ($v !== null) { + $v = (int) $v; + } + + if ($this->playlist_id !== $v) { + $this->playlist_id = $v; + $this->modifiedColumns[] = CcPlaylistcontentsPeer::PLAYLIST_ID; + } + + if ($this->aCcPlaylist !== null && $this->aCcPlaylist->getDbId() !== $v) { + $this->aCcPlaylist = null; + } + + return $this; + } // setDbPlaylistId() + + /** + * Set the value of [file_id] column. + * + * @param int $v new value + * @return CcPlaylistcontents The current object (for fluent API support) + */ + public function setDbFileId($v) + { + if ($v !== null) { + $v = (int) $v; + } + + if ($this->file_id !== $v) { + $this->file_id = $v; + $this->modifiedColumns[] = CcPlaylistcontentsPeer::FILE_ID; + } + + if ($this->aCcFiles !== null && $this->aCcFiles->getDbId() !== $v) { + $this->aCcFiles = null; + } + + return $this; + } // setDbFileId() + + /** + * Set the value of [position] column. + * + * @param int $v new value + * @return CcPlaylistcontents The current object (for fluent API support) + */ + public function setDbPosition($v) + { + if ($v !== null) { + $v = (int) $v; + } + + if ($this->position !== $v) { + $this->position = $v; + $this->modifiedColumns[] = CcPlaylistcontentsPeer::POSITION; + } + + return $this; + } // setDbPosition() + + /** + * Sets the value of [cliplength] column to a normalized version of the date/time value specified. + * + * @param mixed $v string, integer (timestamp), or DateTime value. Empty string will + * be treated as NULL for temporal objects. + * @return CcPlaylistcontents The current object (for fluent API support) + */ + public function setDbCliplength($v) + { + // we treat '' as NULL for temporal objects because DateTime('') == DateTime('now') + // -- which is unexpected, to say the least. + if ($v === null || $v === '') { + $dt = null; + } elseif ($v instanceof DateTime) { + $dt = $v; + } else { + // some string/numeric value passed; we normalize that so that we can + // validate it. + try { + if (is_numeric($v)) { // if it's a unix timestamp + $dt = new DateTime('@'.$v, new DateTimeZone('UTC')); + // We have to explicitly specify and then change the time zone because of a + // DateTime bug: http://bugs.php.net/bug.php?id=43003 + $dt->setTimeZone(new DateTimeZone(date_default_timezone_get())); + } else { + $dt = new DateTime($v); + } + } catch (Exception $x) { + throw new PropelException('Error parsing date/time value: ' . var_export($v, true), $x); + } + } + + if ( $this->cliplength !== null || $dt !== null ) { + // (nested ifs are a little easier to read in this case) + + $currNorm = ($this->cliplength !== null && $tmpDt = new DateTime($this->cliplength)) ? $tmpDt->format('H:i:s') : null; + $newNorm = ($dt !== null) ? $dt->format('H:i:s') : null; + + if ( ($currNorm !== $newNorm) // normalized values don't match + || ($dt->format('H:i:s') === '00:00:00') // or the entered value matches the default + ) + { + $this->cliplength = ($dt ? $dt->format('H:i:s') : null); + $this->modifiedColumns[] = CcPlaylistcontentsPeer::CLIPLENGTH; + } + } // if either are not null + + return $this; + } // setDbCliplength() + + /** + * Sets the value of [cuein] column to a normalized version of the date/time value specified. + * + * @param mixed $v string, integer (timestamp), or DateTime value. Empty string will + * be treated as NULL for temporal objects. + * @return CcPlaylistcontents The current object (for fluent API support) + */ + public function setDbCuein($v) + { + // we treat '' as NULL for temporal objects because DateTime('') == DateTime('now') + // -- which is unexpected, to say the least. + if ($v === null || $v === '') { + $dt = null; + } elseif ($v instanceof DateTime) { + $dt = $v; + } else { + // some string/numeric value passed; we normalize that so that we can + // validate it. + try { + if (is_numeric($v)) { // if it's a unix timestamp + $dt = new DateTime('@'.$v, new DateTimeZone('UTC')); + // We have to explicitly specify and then change the time zone because of a + // DateTime bug: http://bugs.php.net/bug.php?id=43003 + $dt->setTimeZone(new DateTimeZone(date_default_timezone_get())); + } else { + $dt = new DateTime($v); + } + } catch (Exception $x) { + throw new PropelException('Error parsing date/time value: ' . var_export($v, true), $x); + } + } + + if ( $this->cuein !== null || $dt !== null ) { + // (nested ifs are a little easier to read in this case) + + $currNorm = ($this->cuein !== null && $tmpDt = new DateTime($this->cuein)) ? $tmpDt->format('H:i:s') : null; + $newNorm = ($dt !== null) ? $dt->format('H:i:s') : null; + + if ( ($currNorm !== $newNorm) // normalized values don't match + || ($dt->format('H:i:s') === '00:00:00') // or the entered value matches the default + ) + { + $this->cuein = ($dt ? $dt->format('H:i:s') : null); + $this->modifiedColumns[] = CcPlaylistcontentsPeer::CUEIN; + } + } // if either are not null + + return $this; + } // setDbCuein() + + /** + * Sets the value of [cueout] column to a normalized version of the date/time value specified. + * + * @param mixed $v string, integer (timestamp), or DateTime value. Empty string will + * be treated as NULL for temporal objects. + * @return CcPlaylistcontents The current object (for fluent API support) + */ + public function setDbCueout($v) + { + // we treat '' as NULL for temporal objects because DateTime('') == DateTime('now') + // -- which is unexpected, to say the least. + if ($v === null || $v === '') { + $dt = null; + } elseif ($v instanceof DateTime) { + $dt = $v; + } else { + // some string/numeric value passed; we normalize that so that we can + // validate it. + try { + if (is_numeric($v)) { // if it's a unix timestamp + $dt = new DateTime('@'.$v, new DateTimeZone('UTC')); + // We have to explicitly specify and then change the time zone because of a + // DateTime bug: http://bugs.php.net/bug.php?id=43003 + $dt->setTimeZone(new DateTimeZone(date_default_timezone_get())); + } else { + $dt = new DateTime($v); + } + } catch (Exception $x) { + throw new PropelException('Error parsing date/time value: ' . var_export($v, true), $x); + } + } + + if ( $this->cueout !== null || $dt !== null ) { + // (nested ifs are a little easier to read in this case) + + $currNorm = ($this->cueout !== null && $tmpDt = new DateTime($this->cueout)) ? $tmpDt->format('H:i:s') : null; + $newNorm = ($dt !== null) ? $dt->format('H:i:s') : null; + + if ( ($currNorm !== $newNorm) // normalized values don't match + || ($dt->format('H:i:s') === '00:00:00') // or the entered value matches the default + ) + { + $this->cueout = ($dt ? $dt->format('H:i:s') : null); + $this->modifiedColumns[] = CcPlaylistcontentsPeer::CUEOUT; + } + } // if either are not null + + return $this; + } // setDbCueout() + + /** + * Sets the value of [fadein] column to a normalized version of the date/time value specified. + * + * @param mixed $v string, integer (timestamp), or DateTime value. Empty string will + * be treated as NULL for temporal objects. + * @return CcPlaylistcontents The current object (for fluent API support) + */ + public function setDbFadein($v) + { + // we treat '' as NULL for temporal objects because DateTime('') == DateTime('now') + // -- which is unexpected, to say the least. + if ($v === null || $v === '') { + $dt = null; + } elseif ($v instanceof DateTime) { + $dt = $v; + } else { + // some string/numeric value passed; we normalize that so that we can + // validate it. + try { + if (is_numeric($v)) { // if it's a unix timestamp + $dt = new DateTime('@'.$v, new DateTimeZone('UTC')); + // We have to explicitly specify and then change the time zone because of a + // DateTime bug: http://bugs.php.net/bug.php?id=43003 + $dt->setTimeZone(new DateTimeZone(date_default_timezone_get())); + } else { + $dt = new DateTime($v); + } + } catch (Exception $x) { + throw new PropelException('Error parsing date/time value: ' . var_export($v, true), $x); + } + } + + if ( $this->fadein !== null || $dt !== null ) { + // (nested ifs are a little easier to read in this case) + + $currNorm = ($this->fadein !== null && $tmpDt = new DateTime($this->fadein)) ? $tmpDt->format('H:i:s') : null; + $newNorm = ($dt !== null) ? $dt->format('H:i:s') : null; + + if ( ($currNorm !== $newNorm) // normalized values don't match + || ($dt->format('H:i:s') === '00:00:00') // or the entered value matches the default + ) + { + $this->fadein = ($dt ? $dt->format('H:i:s') : null); + $this->modifiedColumns[] = CcPlaylistcontentsPeer::FADEIN; + } + } // if either are not null + + return $this; + } // setDbFadein() + + /** + * Sets the value of [fadeout] column to a normalized version of the date/time value specified. + * + * @param mixed $v string, integer (timestamp), or DateTime value. Empty string will + * be treated as NULL for temporal objects. + * @return CcPlaylistcontents The current object (for fluent API support) + */ + public function setDbFadeout($v) + { + // we treat '' as NULL for temporal objects because DateTime('') == DateTime('now') + // -- which is unexpected, to say the least. + if ($v === null || $v === '') { + $dt = null; + } elseif ($v instanceof DateTime) { + $dt = $v; + } else { + // some string/numeric value passed; we normalize that so that we can + // validate it. + try { + if (is_numeric($v)) { // if it's a unix timestamp + $dt = new DateTime('@'.$v, new DateTimeZone('UTC')); + // We have to explicitly specify and then change the time zone because of a + // DateTime bug: http://bugs.php.net/bug.php?id=43003 + $dt->setTimeZone(new DateTimeZone(date_default_timezone_get())); + } else { + $dt = new DateTime($v); + } + } catch (Exception $x) { + throw new PropelException('Error parsing date/time value: ' . var_export($v, true), $x); + } + } + + if ( $this->fadeout !== null || $dt !== null ) { + // (nested ifs are a little easier to read in this case) + + $currNorm = ($this->fadeout !== null && $tmpDt = new DateTime($this->fadeout)) ? $tmpDt->format('H:i:s') : null; + $newNorm = ($dt !== null) ? $dt->format('H:i:s') : null; + + if ( ($currNorm !== $newNorm) // normalized values don't match + || ($dt->format('H:i:s') === '00:00:00') // or the entered value matches the default + ) + { + $this->fadeout = ($dt ? $dt->format('H:i:s') : null); + $this->modifiedColumns[] = CcPlaylistcontentsPeer::FADEOUT; + } + } // if either are not null + + return $this; + } // setDbFadeout() + + /** + * Indicates whether the columns in this object are only set to default values. + * + * This method can be used in conjunction with isModified() to indicate whether an object is both + * modified _and_ has some values set which are non-default. + * + * @return boolean Whether the columns in this object are only been set with default values. + */ + public function hasOnlyDefaultValues() + { + if ($this->cliplength !== '00:00:00') { + return false; + } + + if ($this->cuein !== '00:00:00') { + return false; + } + + if ($this->cueout !== '00:00:00') { + return false; + } + + if ($this->fadein !== '00:00:00') { + return false; + } + + if ($this->fadeout !== '00:00:00') { + return false; + } + + // otherwise, everything was equal, so return TRUE + return true; + } // hasOnlyDefaultValues() + + /** + * Hydrates (populates) the object variables with values from the database resultset. + * + * An offset (0-based "start column") is specified so that objects can be hydrated + * with a subset of the columns in the resultset rows. This is needed, for example, + * for results of JOIN queries where the resultset row includes columns from two or + * more tables. + * + * @param array $row The row returned by PDOStatement->fetch(PDO::FETCH_NUM) + * @param int $startcol 0-based offset column which indicates which restultset column to start with. + * @param boolean $rehydrate Whether this object is being re-hydrated from the database. + * @return int next starting column + * @throws PropelException - Any caught Exception will be rewrapped as a PropelException. + */ + public function hydrate($row, $startcol = 0, $rehydrate = false) + { + try { + + $this->id = ($row[$startcol + 0] !== null) ? (int) $row[$startcol + 0] : null; + $this->playlist_id = ($row[$startcol + 1] !== null) ? (int) $row[$startcol + 1] : null; + $this->file_id = ($row[$startcol + 2] !== null) ? (int) $row[$startcol + 2] : null; + $this->position = ($row[$startcol + 3] !== null) ? (int) $row[$startcol + 3] : null; + $this->cliplength = ($row[$startcol + 4] !== null) ? (string) $row[$startcol + 4] : null; + $this->cuein = ($row[$startcol + 5] !== null) ? (string) $row[$startcol + 5] : null; + $this->cueout = ($row[$startcol + 6] !== null) ? (string) $row[$startcol + 6] : null; + $this->fadein = ($row[$startcol + 7] !== null) ? (string) $row[$startcol + 7] : null; + $this->fadeout = ($row[$startcol + 8] !== null) ? (string) $row[$startcol + 8] : null; + $this->resetModified(); + + $this->setNew(false); + + if ($rehydrate) { + $this->ensureConsistency(); + } + + return $startcol + 9; // 9 = CcPlaylistcontentsPeer::NUM_COLUMNS - CcPlaylistcontentsPeer::NUM_LAZY_LOAD_COLUMNS). + + } catch (Exception $e) { + throw new PropelException("Error populating CcPlaylistcontents object", $e); + } + } + + /** + * Checks and repairs the internal consistency of the object. + * + * This method is executed after an already-instantiated object is re-hydrated + * from the database. It exists to check any foreign keys to make sure that + * the objects related to the current object are correct based on foreign key. + * + * You can override this method in the stub class, but you should always invoke + * the base method from the overridden method (i.e. parent::ensureConsistency()), + * in case your model changes. + * + * @throws PropelException + */ + public function ensureConsistency() + { + + if ($this->aCcPlaylist !== null && $this->playlist_id !== $this->aCcPlaylist->getDbId()) { + $this->aCcPlaylist = null; + } + if ($this->aCcFiles !== null && $this->file_id !== $this->aCcFiles->getDbId()) { + $this->aCcFiles = null; + } + } // ensureConsistency + + /** + * Reloads this object from datastore based on primary key and (optionally) resets all associated objects. + * + * This will only work if the object has been saved and has a valid primary key set. + * + * @param boolean $deep (optional) Whether to also de-associated any related objects. + * @param PropelPDO $con (optional) The PropelPDO connection to use. + * @return void + * @throws PropelException - if this object is deleted, unsaved or doesn't have pk match in db + */ + public function reload($deep = false, PropelPDO $con = null) + { + if ($this->isDeleted()) { + throw new PropelException("Cannot reload a deleted object."); + } + + if ($this->isNew()) { + throw new PropelException("Cannot reload an unsaved object."); + } + + if ($con === null) { + $con = Propel::getConnection(CcPlaylistcontentsPeer::DATABASE_NAME, Propel::CONNECTION_READ); + } + + // We don't need to alter the object instance pool; we're just modifying this instance + // already in the pool. + + $stmt = CcPlaylistcontentsPeer::doSelectStmt($this->buildPkeyCriteria(), $con); + $row = $stmt->fetch(PDO::FETCH_NUM); + $stmt->closeCursor(); + if (!$row) { + throw new PropelException('Cannot find matching row in the database to reload object values.'); + } + $this->hydrate($row, 0, true); // rehydrate + + if ($deep) { // also de-associate any related objects? + + $this->aCcFiles = null; + $this->aCcPlaylist = null; + } // if (deep) + } + + /** + * Removes this object from datastore and sets delete attribute. + * + * @param PropelPDO $con + * @return void + * @throws PropelException + * @see BaseObject::setDeleted() + * @see BaseObject::isDeleted() + */ + public function delete(PropelPDO $con = null) + { + if ($this->isDeleted()) { + throw new PropelException("This object has already been deleted."); + } + + if ($con === null) { + $con = Propel::getConnection(CcPlaylistcontentsPeer::DATABASE_NAME, Propel::CONNECTION_WRITE); + } + + $con->beginTransaction(); + try { + $ret = $this->preDelete($con); + if ($ret) { + CcPlaylistcontentsQuery::create() + ->filterByPrimaryKey($this->getPrimaryKey()) + ->delete($con); + $this->postDelete($con); + $con->commit(); + $this->setDeleted(true); + } else { + $con->commit(); + } + } catch (PropelException $e) { + $con->rollBack(); + throw $e; + } + } + + /** + * Persists this object to the database. + * + * If the object is new, it inserts it; otherwise an update is performed. + * All modified related objects will also be persisted in the doSave() + * method. This method wraps all precipitate database operations in a + * single transaction. + * + * @param PropelPDO $con + * @return int The number of rows affected by this insert/update and any referring fk objects' save() operations. + * @throws PropelException + * @see doSave() + */ + public function save(PropelPDO $con = null) + { + if ($this->isDeleted()) { + throw new PropelException("You cannot save an object that has been deleted."); + } + + if ($con === null) { + $con = Propel::getConnection(CcPlaylistcontentsPeer::DATABASE_NAME, Propel::CONNECTION_WRITE); + } + + $con->beginTransaction(); + $isInsert = $this->isNew(); + try { + $ret = $this->preSave($con); + if ($isInsert) { + $ret = $ret && $this->preInsert($con); + } else { + $ret = $ret && $this->preUpdate($con); + } + if ($ret) { + $affectedRows = $this->doSave($con); + if ($isInsert) { + $this->postInsert($con); + } else { + $this->postUpdate($con); + } + $this->postSave($con); + CcPlaylistcontentsPeer::addInstanceToPool($this); + } else { + $affectedRows = 0; + } + $con->commit(); + return $affectedRows; + } catch (PropelException $e) { + $con->rollBack(); + throw $e; + } + } + + /** + * Performs the work of inserting or updating the row in the database. + * + * If the object is new, it inserts it; otherwise an update is performed. + * All related objects are also updated in this method. + * + * @param PropelPDO $con + * @return int The number of rows affected by this insert/update and any referring fk objects' save() operations. + * @throws PropelException + * @see save() + */ + protected function doSave(PropelPDO $con) + { + $affectedRows = 0; // initialize var to track total num of affected rows + if (!$this->alreadyInSave) { + $this->alreadyInSave = true; + + // We call the save method on the following object(s) if they + // were passed to this object by their coresponding set + // method. This object relates to these object(s) by a + // foreign key reference. + + if ($this->aCcFiles !== null) { + if ($this->aCcFiles->isModified() || $this->aCcFiles->isNew()) { + $affectedRows += $this->aCcFiles->save($con); + } + $this->setCcFiles($this->aCcFiles); + } + + if ($this->aCcPlaylist !== null) { + if ($this->aCcPlaylist->isModified() || $this->aCcPlaylist->isNew()) { + $affectedRows += $this->aCcPlaylist->save($con); + } + $this->setCcPlaylist($this->aCcPlaylist); + } + + if ($this->isNew() ) { + $this->modifiedColumns[] = CcPlaylistcontentsPeer::ID; + } + + // If this object has been modified, then save it to the database. + if ($this->isModified()) { + if ($this->isNew()) { + $criteria = $this->buildCriteria(); + if ($criteria->keyContainsValue(CcPlaylistcontentsPeer::ID) ) { + throw new PropelException('Cannot insert a value for auto-increment primary key ('.CcPlaylistcontentsPeer::ID.')'); + } + + $pk = BasePeer::doInsert($criteria, $con); + $affectedRows += 1; + $this->setDbId($pk); //[IMV] update autoincrement primary key + $this->setNew(false); + } else { + $affectedRows += CcPlaylistcontentsPeer::doUpdate($this, $con); + } + + $this->resetModified(); // [HL] After being saved an object is no longer 'modified' + } + + $this->alreadyInSave = false; + + } + return $affectedRows; + } // doSave() + + /** + * Array of ValidationFailed objects. + * @var array ValidationFailed[] + */ + protected $validationFailures = array(); + + /** + * Gets any ValidationFailed objects that resulted from last call to validate(). + * + * + * @return array ValidationFailed[] + * @see validate() + */ + public function getValidationFailures() + { + return $this->validationFailures; + } + + /** + * Validates the objects modified field values and all objects related to this table. + * + * If $columns is either a column name or an array of column names + * only those columns are validated. + * + * @param mixed $columns Column name or an array of column names. + * @return boolean Whether all columns pass validation. + * @see doValidate() + * @see getValidationFailures() + */ + public function validate($columns = null) + { + $res = $this->doValidate($columns); + if ($res === true) { + $this->validationFailures = array(); + return true; + } else { + $this->validationFailures = $res; + return false; + } + } + + /** + * This function performs the validation work for complex object models. + * + * In addition to checking the current object, all related objects will + * also be validated. If all pass then true is returned; otherwise + * an aggreagated array of ValidationFailed objects will be returned. + * + * @param array $columns Array of column names to validate. + * @return mixed true if all validations pass; array of ValidationFailed objets otherwise. + */ + protected function doValidate($columns = null) + { + if (!$this->alreadyInValidation) { + $this->alreadyInValidation = true; + $retval = null; + + $failureMap = array(); + + + // We call the validate method on the following object(s) if they + // were passed to this object by their coresponding set + // method. This object relates to these object(s) by a + // foreign key reference. + + if ($this->aCcFiles !== null) { + if (!$this->aCcFiles->validate($columns)) { + $failureMap = array_merge($failureMap, $this->aCcFiles->getValidationFailures()); + } + } + + if ($this->aCcPlaylist !== null) { + if (!$this->aCcPlaylist->validate($columns)) { + $failureMap = array_merge($failureMap, $this->aCcPlaylist->getValidationFailures()); + } + } + + + if (($retval = CcPlaylistcontentsPeer::doValidate($this, $columns)) !== true) { + $failureMap = array_merge($failureMap, $retval); + } + + + + $this->alreadyInValidation = false; + } + + return (!empty($failureMap) ? $failureMap : true); + } + + /** + * Retrieves a field from the object by name passed in as a string. + * + * @param string $name name + * @param string $type The type of fieldname the $name is of: + * one of the class type constants BasePeer::TYPE_PHPNAME, BasePeer::TYPE_STUDLYPHPNAME + * BasePeer::TYPE_COLNAME, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_NUM + * @return mixed Value of field. + */ + public function getByName($name, $type = BasePeer::TYPE_PHPNAME) + { + $pos = CcPlaylistcontentsPeer::translateFieldName($name, $type, BasePeer::TYPE_NUM); + $field = $this->getByPosition($pos); + return $field; + } + + /** + * Retrieves a field from the object by Position as specified in the xml schema. + * Zero-based. + * + * @param int $pos position in xml schema + * @return mixed Value of field at $pos + */ + public function getByPosition($pos) + { + switch($pos) { + case 0: + return $this->getDbId(); + break; + case 1: + return $this->getDbPlaylistId(); + break; + case 2: + return $this->getDbFileId(); + break; + case 3: + return $this->getDbPosition(); + break; + case 4: + return $this->getDbCliplength(); + break; + case 5: + return $this->getDbCuein(); + break; + case 6: + return $this->getDbCueout(); + break; + case 7: + return $this->getDbFadein(); + break; + case 8: + return $this->getDbFadeout(); + break; + default: + return null; + break; + } // switch() + } + + /** + * Exports the object as an array. + * + * You can specify the key type of the array by passing one of the class + * type constants. + * + * @param string $keyType (optional) One of the class type constants BasePeer::TYPE_PHPNAME, BasePeer::TYPE_STUDLYPHPNAME, + * BasePeer::TYPE_COLNAME, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_NUM. + * Defaults to BasePeer::TYPE_PHPNAME. + * @param boolean $includeLazyLoadColumns (optional) Whether to include lazy loaded columns. Defaults to TRUE. + * @param boolean $includeForeignObjects (optional) Whether to include hydrated related objects. Default to FALSE. + * + * @return array an associative array containing the field names (as keys) and field values + */ + public function toArray($keyType = BasePeer::TYPE_PHPNAME, $includeLazyLoadColumns = true, $includeForeignObjects = false) + { + $keys = CcPlaylistcontentsPeer::getFieldNames($keyType); + $result = array( + $keys[0] => $this->getDbId(), + $keys[1] => $this->getDbPlaylistId(), + $keys[2] => $this->getDbFileId(), + $keys[3] => $this->getDbPosition(), + $keys[4] => $this->getDbCliplength(), + $keys[5] => $this->getDbCuein(), + $keys[6] => $this->getDbCueout(), + $keys[7] => $this->getDbFadein(), + $keys[8] => $this->getDbFadeout(), + ); + if ($includeForeignObjects) { + if (null !== $this->aCcFiles) { + $result['CcFiles'] = $this->aCcFiles->toArray($keyType, $includeLazyLoadColumns, true); + } + if (null !== $this->aCcPlaylist) { + $result['CcPlaylist'] = $this->aCcPlaylist->toArray($keyType, $includeLazyLoadColumns, true); + } + } + return $result; + } + + /** + * Sets a field from the object by name passed in as a string. + * + * @param string $name peer name + * @param mixed $value field value + * @param string $type The type of fieldname the $name is of: + * one of the class type constants BasePeer::TYPE_PHPNAME, BasePeer::TYPE_STUDLYPHPNAME + * BasePeer::TYPE_COLNAME, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_NUM + * @return void + */ + public function setByName($name, $value, $type = BasePeer::TYPE_PHPNAME) + { + $pos = CcPlaylistcontentsPeer::translateFieldName($name, $type, BasePeer::TYPE_NUM); + return $this->setByPosition($pos, $value); + } + + /** + * Sets a field from the object by Position as specified in the xml schema. + * Zero-based. + * + * @param int $pos position in xml schema + * @param mixed $value field value + * @return void + */ + public function setByPosition($pos, $value) + { + switch($pos) { + case 0: + $this->setDbId($value); + break; + case 1: + $this->setDbPlaylistId($value); + break; + case 2: + $this->setDbFileId($value); + break; + case 3: + $this->setDbPosition($value); + break; + case 4: + $this->setDbCliplength($value); + break; + case 5: + $this->setDbCuein($value); + break; + case 6: + $this->setDbCueout($value); + break; + case 7: + $this->setDbFadein($value); + break; + case 8: + $this->setDbFadeout($value); + break; + } // switch() + } + + /** + * Populates the object using an array. + * + * This is particularly useful when populating an object from one of the + * request arrays (e.g. $_POST). This method goes through the column + * names, checking to see whether a matching key exists in populated + * array. If so the setByName() method is called for that column. + * + * You can specify the key type of the array by additionally passing one + * of the class type constants BasePeer::TYPE_PHPNAME, BasePeer::TYPE_STUDLYPHPNAME, + * BasePeer::TYPE_COLNAME, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_NUM. + * The default key type is the column's phpname (e.g. 'AuthorId') + * + * @param array $arr An array to populate the object from. + * @param string $keyType The type of keys the array uses. + * @return void + */ + public function fromArray($arr, $keyType = BasePeer::TYPE_PHPNAME) + { + $keys = CcPlaylistcontentsPeer::getFieldNames($keyType); + + if (array_key_exists($keys[0], $arr)) $this->setDbId($arr[$keys[0]]); + if (array_key_exists($keys[1], $arr)) $this->setDbPlaylistId($arr[$keys[1]]); + if (array_key_exists($keys[2], $arr)) $this->setDbFileId($arr[$keys[2]]); + if (array_key_exists($keys[3], $arr)) $this->setDbPosition($arr[$keys[3]]); + if (array_key_exists($keys[4], $arr)) $this->setDbCliplength($arr[$keys[4]]); + if (array_key_exists($keys[5], $arr)) $this->setDbCuein($arr[$keys[5]]); + if (array_key_exists($keys[6], $arr)) $this->setDbCueout($arr[$keys[6]]); + if (array_key_exists($keys[7], $arr)) $this->setDbFadein($arr[$keys[7]]); + if (array_key_exists($keys[8], $arr)) $this->setDbFadeout($arr[$keys[8]]); + } + + /** + * Build a Criteria object containing the values of all modified columns in this object. + * + * @return Criteria The Criteria object containing all modified values. + */ + public function buildCriteria() + { + $criteria = new Criteria(CcPlaylistcontentsPeer::DATABASE_NAME); + + if ($this->isColumnModified(CcPlaylistcontentsPeer::ID)) $criteria->add(CcPlaylistcontentsPeer::ID, $this->id); + if ($this->isColumnModified(CcPlaylistcontentsPeer::PLAYLIST_ID)) $criteria->add(CcPlaylistcontentsPeer::PLAYLIST_ID, $this->playlist_id); + if ($this->isColumnModified(CcPlaylistcontentsPeer::FILE_ID)) $criteria->add(CcPlaylistcontentsPeer::FILE_ID, $this->file_id); + if ($this->isColumnModified(CcPlaylistcontentsPeer::POSITION)) $criteria->add(CcPlaylistcontentsPeer::POSITION, $this->position); + if ($this->isColumnModified(CcPlaylistcontentsPeer::CLIPLENGTH)) $criteria->add(CcPlaylistcontentsPeer::CLIPLENGTH, $this->cliplength); + if ($this->isColumnModified(CcPlaylistcontentsPeer::CUEIN)) $criteria->add(CcPlaylistcontentsPeer::CUEIN, $this->cuein); + if ($this->isColumnModified(CcPlaylistcontentsPeer::CUEOUT)) $criteria->add(CcPlaylistcontentsPeer::CUEOUT, $this->cueout); + if ($this->isColumnModified(CcPlaylistcontentsPeer::FADEIN)) $criteria->add(CcPlaylistcontentsPeer::FADEIN, $this->fadein); + if ($this->isColumnModified(CcPlaylistcontentsPeer::FADEOUT)) $criteria->add(CcPlaylistcontentsPeer::FADEOUT, $this->fadeout); + + return $criteria; + } + + /** + * Builds a Criteria object containing the primary key for this object. + * + * Unlike buildCriteria() this method includes the primary key values regardless + * of whether or not they have been modified. + * + * @return Criteria The Criteria object containing value(s) for primary key(s). + */ + public function buildPkeyCriteria() + { + $criteria = new Criteria(CcPlaylistcontentsPeer::DATABASE_NAME); + $criteria->add(CcPlaylistcontentsPeer::ID, $this->id); + + return $criteria; + } + + /** + * Returns the primary key for this object (row). + * @return int + */ + public function getPrimaryKey() + { + return $this->getDbId(); + } + + /** + * Generic method to set the primary key (id column). + * + * @param int $key Primary key. + * @return void + */ + public function setPrimaryKey($key) + { + $this->setDbId($key); + } + + /** + * Returns true if the primary key for this object is null. + * @return boolean + */ + public function isPrimaryKeyNull() + { + return null === $this->getDbId(); + } + + /** + * Sets contents of passed object to values from current object. + * + * If desired, this method can also make copies of all associated (fkey referrers) + * objects. + * + * @param object $copyObj An object of CcPlaylistcontents (or compatible) type. + * @param boolean $deepCopy Whether to also copy all rows that refer (by fkey) to the current row. + * @throws PropelException + */ + public function copyInto($copyObj, $deepCopy = false) + { + $copyObj->setDbPlaylistId($this->playlist_id); + $copyObj->setDbFileId($this->file_id); + $copyObj->setDbPosition($this->position); + $copyObj->setDbCliplength($this->cliplength); + $copyObj->setDbCuein($this->cuein); + $copyObj->setDbCueout($this->cueout); + $copyObj->setDbFadein($this->fadein); + $copyObj->setDbFadeout($this->fadeout); + + $copyObj->setNew(true); + $copyObj->setDbId(NULL); // this is a auto-increment column, so set to default value + } + + /** + * Makes a copy of this object that will be inserted as a new row in table when saved. + * It creates a new object filling in the simple attributes, but skipping any primary + * keys that are defined for the table. + * + * If desired, this method can also make copies of all associated (fkey referrers) + * objects. + * + * @param boolean $deepCopy Whether to also copy all rows that refer (by fkey) to the current row. + * @return CcPlaylistcontents Clone of current object. + * @throws PropelException + */ + public function copy($deepCopy = false) + { + // we use get_class(), because this might be a subclass + $clazz = get_class($this); + $copyObj = new $clazz(); + $this->copyInto($copyObj, $deepCopy); + return $copyObj; + } + + /** + * Returns a peer instance associated with this om. + * + * Since Peer classes are not to have any instance attributes, this method returns the + * same instance for all member of this class. The method could therefore + * be static, but this would prevent one from overriding the behavior. + * + * @return CcPlaylistcontentsPeer + */ + public function getPeer() + { + if (self::$peer === null) { + self::$peer = new CcPlaylistcontentsPeer(); + } + return self::$peer; + } + + /** + * Declares an association between this object and a CcFiles object. + * + * @param CcFiles $v + * @return CcPlaylistcontents The current object (for fluent API support) + * @throws PropelException + */ + public function setCcFiles(CcFiles $v = null) + { + if ($v === null) { + $this->setDbFileId(NULL); + } else { + $this->setDbFileId($v->getDbId()); + } + + $this->aCcFiles = $v; + + // Add binding for other direction of this n:n relationship. + // If this object has already been added to the CcFiles object, it will not be re-added. + if ($v !== null) { + $v->addCcPlaylistcontents($this); + } + + return $this; + } + + + /** + * Get the associated CcFiles object + * + * @param PropelPDO Optional Connection object. + * @return CcFiles The associated CcFiles object. + * @throws PropelException + */ + public function getCcFiles(PropelPDO $con = null) + { + if ($this->aCcFiles === null && ($this->file_id !== null)) { + $this->aCcFiles = CcFilesQuery::create()->findPk($this->file_id, $con); + /* The following can be used additionally to + guarantee the related object contains a reference + to this object. This level of coupling may, however, be + undesirable since it could result in an only partially populated collection + in the referenced object. + $this->aCcFiles->addCcPlaylistcontentss($this); + */ + } + return $this->aCcFiles; + } + + /** + * Declares an association between this object and a CcPlaylist object. + * + * @param CcPlaylist $v + * @return CcPlaylistcontents The current object (for fluent API support) + * @throws PropelException + */ + public function setCcPlaylist(CcPlaylist $v = null) + { + if ($v === null) { + $this->setDbPlaylistId(NULL); + } else { + $this->setDbPlaylistId($v->getDbId()); + } + + $this->aCcPlaylist = $v; + + // Add binding for other direction of this n:n relationship. + // If this object has already been added to the CcPlaylist object, it will not be re-added. + if ($v !== null) { + $v->addCcPlaylistcontents($this); + } + + return $this; + } + + + /** + * Get the associated CcPlaylist object + * + * @param PropelPDO Optional Connection object. + * @return CcPlaylist The associated CcPlaylist object. + * @throws PropelException + */ + public function getCcPlaylist(PropelPDO $con = null) + { + if ($this->aCcPlaylist === null && ($this->playlist_id !== null)) { + $this->aCcPlaylist = CcPlaylistQuery::create()->findPk($this->playlist_id, $con); + /* The following can be used additionally to + guarantee the related object contains a reference + to this object. This level of coupling may, however, be + undesirable since it could result in an only partially populated collection + in the referenced object. + $this->aCcPlaylist->addCcPlaylistcontentss($this); + */ + } + return $this->aCcPlaylist; + } + + /** + * Clears the current object and sets all attributes to their default values + */ + public function clear() + { + $this->id = null; + $this->playlist_id = null; + $this->file_id = null; + $this->position = null; + $this->cliplength = null; + $this->cuein = null; + $this->cueout = null; + $this->fadein = null; + $this->fadeout = null; + $this->alreadyInSave = false; + $this->alreadyInValidation = false; + $this->clearAllReferences(); + $this->applyDefaultValues(); + $this->resetModified(); + $this->setNew(true); + $this->setDeleted(false); + } + + /** + * Resets all collections of referencing foreign keys. + * + * This method is a user-space workaround for PHP's inability to garbage collect objects + * with circular references. This is currently necessary when using Propel in certain + * daemon or large-volumne/high-memory operations. + * + * @param boolean $deep Whether to also clear the references on all associated objects. + */ + public function clearAllReferences($deep = false) + { + if ($deep) { + } // if ($deep) + + $this->aCcFiles = null; + $this->aCcPlaylist = null; + } + + /** + * Catches calls to virtual methods + */ + public function __call($name, $params) + { + if (preg_match('/get(\w+)/', $name, $matches)) { + $virtualColumn = $matches[1]; + if ($this->hasVirtualColumn($virtualColumn)) { + return $this->getVirtualColumn($virtualColumn); + } + // no lcfirst in php<5.3... + $virtualColumn[0] = strtolower($virtualColumn[0]); + if ($this->hasVirtualColumn($virtualColumn)) { + return $this->getVirtualColumn($virtualColumn); + } + } + throw new PropelException('Call to undefined method: ' . $name); + } + +} // BaseCcPlaylistcontents diff --git a/application/models/campcaster/om/BaseCcPlaylistcontentsPeer.php b/application/models/campcaster/om/BaseCcPlaylistcontentsPeer.php new file mode 100644 index 000000000..1d448bd34 --- /dev/null +++ b/application/models/campcaster/om/BaseCcPlaylistcontentsPeer.php @@ -0,0 +1,1395 @@ + array ('DbId', 'DbPlaylistId', 'DbFileId', 'DbPosition', 'DbCliplength', 'DbCuein', 'DbCueout', 'DbFadein', 'DbFadeout', ), + BasePeer::TYPE_STUDLYPHPNAME => array ('dbId', 'dbPlaylistId', 'dbFileId', 'dbPosition', 'dbCliplength', 'dbCuein', 'dbCueout', 'dbFadein', 'dbFadeout', ), + BasePeer::TYPE_COLNAME => array (self::ID, self::PLAYLIST_ID, self::FILE_ID, self::POSITION, self::CLIPLENGTH, self::CUEIN, self::CUEOUT, self::FADEIN, self::FADEOUT, ), + BasePeer::TYPE_RAW_COLNAME => array ('ID', 'PLAYLIST_ID', 'FILE_ID', 'POSITION', 'CLIPLENGTH', 'CUEIN', 'CUEOUT', 'FADEIN', 'FADEOUT', ), + BasePeer::TYPE_FIELDNAME => array ('id', 'playlist_id', 'file_id', 'position', 'cliplength', 'cuein', 'cueout', 'fadein', 'fadeout', ), + BasePeer::TYPE_NUM => array (0, 1, 2, 3, 4, 5, 6, 7, 8, ) + ); + + /** + * holds an array of keys for quick access to the fieldnames array + * + * first dimension keys are the type constants + * e.g. self::$fieldNames[BasePeer::TYPE_PHPNAME]['Id'] = 0 + */ + private static $fieldKeys = array ( + BasePeer::TYPE_PHPNAME => array ('DbId' => 0, 'DbPlaylistId' => 1, 'DbFileId' => 2, 'DbPosition' => 3, 'DbCliplength' => 4, 'DbCuein' => 5, 'DbCueout' => 6, 'DbFadein' => 7, 'DbFadeout' => 8, ), + BasePeer::TYPE_STUDLYPHPNAME => array ('dbId' => 0, 'dbPlaylistId' => 1, 'dbFileId' => 2, 'dbPosition' => 3, 'dbCliplength' => 4, 'dbCuein' => 5, 'dbCueout' => 6, 'dbFadein' => 7, 'dbFadeout' => 8, ), + BasePeer::TYPE_COLNAME => array (self::ID => 0, self::PLAYLIST_ID => 1, self::FILE_ID => 2, self::POSITION => 3, self::CLIPLENGTH => 4, self::CUEIN => 5, self::CUEOUT => 6, self::FADEIN => 7, self::FADEOUT => 8, ), + BasePeer::TYPE_RAW_COLNAME => array ('ID' => 0, 'PLAYLIST_ID' => 1, 'FILE_ID' => 2, 'POSITION' => 3, 'CLIPLENGTH' => 4, 'CUEIN' => 5, 'CUEOUT' => 6, 'FADEIN' => 7, 'FADEOUT' => 8, ), + BasePeer::TYPE_FIELDNAME => array ('id' => 0, 'playlist_id' => 1, 'file_id' => 2, 'position' => 3, 'cliplength' => 4, 'cuein' => 5, 'cueout' => 6, 'fadein' => 7, 'fadeout' => 8, ), + BasePeer::TYPE_NUM => array (0, 1, 2, 3, 4, 5, 6, 7, 8, ) + ); + + /** + * Translates a fieldname to another type + * + * @param string $name field name + * @param string $fromType One of the class type constants BasePeer::TYPE_PHPNAME, BasePeer::TYPE_STUDLYPHPNAME + * BasePeer::TYPE_COLNAME, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_NUM + * @param string $toType One of the class type constants + * @return string translated name of the field. + * @throws PropelException - if the specified name could not be found in the fieldname mappings. + */ + static public function translateFieldName($name, $fromType, $toType) + { + $toNames = self::getFieldNames($toType); + $key = isset(self::$fieldKeys[$fromType][$name]) ? self::$fieldKeys[$fromType][$name] : null; + if ($key === null) { + throw new PropelException("'$name' could not be found in the field names of type '$fromType'. These are: " . print_r(self::$fieldKeys[$fromType], true)); + } + return $toNames[$key]; + } + + /** + * Returns an array of field names. + * + * @param string $type The type of fieldnames to return: + * One of the class type constants BasePeer::TYPE_PHPNAME, BasePeer::TYPE_STUDLYPHPNAME + * BasePeer::TYPE_COLNAME, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_NUM + * @return array A list of field names + */ + + static public function getFieldNames($type = BasePeer::TYPE_PHPNAME) + { + if (!array_key_exists($type, self::$fieldNames)) { + throw new PropelException('Method getFieldNames() expects the parameter $type to be one of the class constants BasePeer::TYPE_PHPNAME, BasePeer::TYPE_STUDLYPHPNAME, BasePeer::TYPE_COLNAME, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_NUM. ' . $type . ' was given.'); + } + return self::$fieldNames[$type]; + } + + /** + * Convenience method which changes table.column to alias.column. + * + * Using this method you can maintain SQL abstraction while using column aliases. + * + * $c->addAlias("alias1", TablePeer::TABLE_NAME); + * $c->addJoin(TablePeer::alias("alias1", TablePeer::PRIMARY_KEY_COLUMN), TablePeer::PRIMARY_KEY_COLUMN); + * + * @param string $alias The alias for the current table. + * @param string $column The column name for current table. (i.e. CcPlaylistcontentsPeer::COLUMN_NAME). + * @return string + */ + public static function alias($alias, $column) + { + return str_replace(CcPlaylistcontentsPeer::TABLE_NAME.'.', $alias.'.', $column); + } + + /** + * Add all the columns needed to create a new object. + * + * Note: any columns that were marked with lazyLoad="true" in the + * XML schema will not be added to the select list and only loaded + * on demand. + * + * @param Criteria $criteria object containing the columns to add. + * @param string $alias optional table alias + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function addSelectColumns(Criteria $criteria, $alias = null) + { + if (null === $alias) { + $criteria->addSelectColumn(CcPlaylistcontentsPeer::ID); + $criteria->addSelectColumn(CcPlaylistcontentsPeer::PLAYLIST_ID); + $criteria->addSelectColumn(CcPlaylistcontentsPeer::FILE_ID); + $criteria->addSelectColumn(CcPlaylistcontentsPeer::POSITION); + $criteria->addSelectColumn(CcPlaylistcontentsPeer::CLIPLENGTH); + $criteria->addSelectColumn(CcPlaylistcontentsPeer::CUEIN); + $criteria->addSelectColumn(CcPlaylistcontentsPeer::CUEOUT); + $criteria->addSelectColumn(CcPlaylistcontentsPeer::FADEIN); + $criteria->addSelectColumn(CcPlaylistcontentsPeer::FADEOUT); + } else { + $criteria->addSelectColumn($alias . '.ID'); + $criteria->addSelectColumn($alias . '.PLAYLIST_ID'); + $criteria->addSelectColumn($alias . '.FILE_ID'); + $criteria->addSelectColumn($alias . '.POSITION'); + $criteria->addSelectColumn($alias . '.CLIPLENGTH'); + $criteria->addSelectColumn($alias . '.CUEIN'); + $criteria->addSelectColumn($alias . '.CUEOUT'); + $criteria->addSelectColumn($alias . '.FADEIN'); + $criteria->addSelectColumn($alias . '.FADEOUT'); + } + } + + /** + * Returns the number of rows matching criteria. + * + * @param Criteria $criteria + * @param boolean $distinct Whether to select only distinct columns; deprecated: use Criteria->setDistinct() instead. + * @param PropelPDO $con + * @return int Number of matching rows. + */ + public static function doCount(Criteria $criteria, $distinct = false, PropelPDO $con = null) + { + // we may modify criteria, so copy it first + $criteria = clone $criteria; + + // We need to set the primary table name, since in the case that there are no WHERE columns + // it will be impossible for the BasePeer::createSelectSql() method to determine which + // tables go into the FROM clause. + $criteria->setPrimaryTableName(CcPlaylistcontentsPeer::TABLE_NAME); + + if ($distinct && !in_array(Criteria::DISTINCT, $criteria->getSelectModifiers())) { + $criteria->setDistinct(); + } + + if (!$criteria->hasSelectClause()) { + CcPlaylistcontentsPeer::addSelectColumns($criteria); + } + + $criteria->clearOrderByColumns(); // ORDER BY won't ever affect the count + $criteria->setDbName(self::DATABASE_NAME); // Set the correct dbName + + if ($con === null) { + $con = Propel::getConnection(CcPlaylistcontentsPeer::DATABASE_NAME, Propel::CONNECTION_READ); + } + // BasePeer returns a PDOStatement + $stmt = BasePeer::doCount($criteria, $con); + + if ($row = $stmt->fetch(PDO::FETCH_NUM)) { + $count = (int) $row[0]; + } else { + $count = 0; // no rows returned; we infer that means 0 matches. + } + $stmt->closeCursor(); + return $count; + } + /** + * Method to select one object from the DB. + * + * @param Criteria $criteria object used to create the SELECT statement. + * @param PropelPDO $con + * @return CcPlaylistcontents + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function doSelectOne(Criteria $criteria, PropelPDO $con = null) + { + $critcopy = clone $criteria; + $critcopy->setLimit(1); + $objects = CcPlaylistcontentsPeer::doSelect($critcopy, $con); + if ($objects) { + return $objects[0]; + } + return null; + } + /** + * Method to do selects. + * + * @param Criteria $criteria The Criteria object used to build the SELECT statement. + * @param PropelPDO $con + * @return array Array of selected Objects + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function doSelect(Criteria $criteria, PropelPDO $con = null) + { + return CcPlaylistcontentsPeer::populateObjects(CcPlaylistcontentsPeer::doSelectStmt($criteria, $con)); + } + /** + * Prepares the Criteria object and uses the parent doSelect() method to execute a PDOStatement. + * + * Use this method directly if you want to work with an executed statement durirectly (for example + * to perform your own object hydration). + * + * @param Criteria $criteria The Criteria object used to build the SELECT statement. + * @param PropelPDO $con The connection to use + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + * @return PDOStatement The executed PDOStatement object. + * @see BasePeer::doSelect() + */ + public static function doSelectStmt(Criteria $criteria, PropelPDO $con = null) + { + if ($con === null) { + $con = Propel::getConnection(CcPlaylistcontentsPeer::DATABASE_NAME, Propel::CONNECTION_READ); + } + + if (!$criteria->hasSelectClause()) { + $criteria = clone $criteria; + CcPlaylistcontentsPeer::addSelectColumns($criteria); + } + + // Set the correct dbName + $criteria->setDbName(self::DATABASE_NAME); + + // BasePeer returns a PDOStatement + return BasePeer::doSelect($criteria, $con); + } + /** + * Adds an object to the instance pool. + * + * Propel keeps cached copies of objects in an instance pool when they are retrieved + * from the database. In some cases -- especially when you override doSelect*() + * methods in your stub classes -- you may need to explicitly add objects + * to the cache in order to ensure that the same objects are always returned by doSelect*() + * and retrieveByPK*() calls. + * + * @param CcPlaylistcontents $value A CcPlaylistcontents object. + * @param string $key (optional) key to use for instance map (for performance boost if key was already calculated externally). + */ + public static function addInstanceToPool(CcPlaylistcontents $obj, $key = null) + { + if (Propel::isInstancePoolingEnabled()) { + if ($key === null) { + $key = (string) $obj->getDbId(); + } // if key === null + self::$instances[$key] = $obj; + } + } + + /** + * Removes an object from the instance pool. + * + * Propel keeps cached copies of objects in an instance pool when they are retrieved + * from the database. In some cases -- especially when you override doDelete + * methods in your stub classes -- you may need to explicitly remove objects + * from the cache in order to prevent returning objects that no longer exist. + * + * @param mixed $value A CcPlaylistcontents object or a primary key value. + */ + public static function removeInstanceFromPool($value) + { + if (Propel::isInstancePoolingEnabled() && $value !== null) { + if (is_object($value) && $value instanceof CcPlaylistcontents) { + $key = (string) $value->getDbId(); + } elseif (is_scalar($value)) { + // assume we've been passed a primary key + $key = (string) $value; + } else { + $e = new PropelException("Invalid value passed to removeInstanceFromPool(). Expected primary key or CcPlaylistcontents object; got " . (is_object($value) ? get_class($value) . ' object.' : var_export($value,true))); + throw $e; + } + + unset(self::$instances[$key]); + } + } // removeInstanceFromPool() + + /** + * Retrieves a string version of the primary key from the DB resultset row that can be used to uniquely identify a row in this table. + * + * For tables with a single-column primary key, that simple pkey value will be returned. For tables with + * a multi-column primary key, a serialize()d version of the primary key will be returned. + * + * @param string $key The key (@see getPrimaryKeyHash()) for this instance. + * @return CcPlaylistcontents Found object or NULL if 1) no instance exists for specified key or 2) instance pooling has been disabled. + * @see getPrimaryKeyHash() + */ + public static function getInstanceFromPool($key) + { + if (Propel::isInstancePoolingEnabled()) { + if (isset(self::$instances[$key])) { + return self::$instances[$key]; + } + } + return null; // just to be explicit + } + + /** + * Clear the instance pool. + * + * @return void + */ + public static function clearInstancePool() + { + self::$instances = array(); + } + + /** + * Method to invalidate the instance pool of all tables related to cc_playlistcontents + * by a foreign key with ON DELETE CASCADE + */ + public static function clearRelatedInstancePool() + { + } + + /** + * Retrieves a string version of the primary key from the DB resultset row that can be used to uniquely identify a row in this table. + * + * For tables with a single-column primary key, that simple pkey value will be returned. For tables with + * a multi-column primary key, a serialize()d version of the primary key will be returned. + * + * @param array $row PropelPDO resultset row. + * @param int $startcol The 0-based offset for reading from the resultset row. + * @return string A string version of PK or NULL if the components of primary key in result array are all null. + */ + public static function getPrimaryKeyHashFromRow($row, $startcol = 0) + { + // If the PK cannot be derived from the row, return NULL. + if ($row[$startcol] === null) { + return null; + } + return (string) $row[$startcol]; + } + + /** + * Retrieves the primary key from the DB resultset row + * For tables with a single-column primary key, that simple pkey value will be returned. For tables with + * a multi-column primary key, an array of the primary key columns will be returned. + * + * @param array $row PropelPDO resultset row. + * @param int $startcol The 0-based offset for reading from the resultset row. + * @return mixed The primary key of the row + */ + public static function getPrimaryKeyFromRow($row, $startcol = 0) + { + return (int) $row[$startcol]; + } + + /** + * The returned array will contain objects of the default type or + * objects that inherit from the default. + * + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function populateObjects(PDOStatement $stmt) + { + $results = array(); + + // set the class once to avoid overhead in the loop + $cls = CcPlaylistcontentsPeer::getOMClass(false); + // populate the object(s) + while ($row = $stmt->fetch(PDO::FETCH_NUM)) { + $key = CcPlaylistcontentsPeer::getPrimaryKeyHashFromRow($row, 0); + if (null !== ($obj = CcPlaylistcontentsPeer::getInstanceFromPool($key))) { + // We no longer rehydrate the object, since this can cause data loss. + // See http://www.propelorm.org/ticket/509 + // $obj->hydrate($row, 0, true); // rehydrate + $results[] = $obj; + } else { + $obj = new $cls(); + $obj->hydrate($row); + $results[] = $obj; + CcPlaylistcontentsPeer::addInstanceToPool($obj, $key); + } // if key exists + } + $stmt->closeCursor(); + return $results; + } + /** + * Populates an object of the default type or an object that inherit from the default. + * + * @param array $row PropelPDO resultset row. + * @param int $startcol The 0-based offset for reading from the resultset row. + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + * @return array (CcPlaylistcontents object, last column rank) + */ + public static function populateObject($row, $startcol = 0) + { + $key = CcPlaylistcontentsPeer::getPrimaryKeyHashFromRow($row, $startcol); + if (null !== ($obj = CcPlaylistcontentsPeer::getInstanceFromPool($key))) { + // We no longer rehydrate the object, since this can cause data loss. + // See http://www.propelorm.org/ticket/509 + // $obj->hydrate($row, $startcol, true); // rehydrate + $col = $startcol + CcPlaylistcontentsPeer::NUM_COLUMNS; + } else { + $cls = CcPlaylistcontentsPeer::OM_CLASS; + $obj = new $cls(); + $col = $obj->hydrate($row, $startcol); + CcPlaylistcontentsPeer::addInstanceToPool($obj, $key); + } + return array($obj, $col); + } + + /** + * Returns the number of rows matching criteria, joining the related CcFiles table + * + * @param Criteria $criteria + * @param boolean $distinct Whether to select only distinct columns; deprecated: use Criteria->setDistinct() instead. + * @param PropelPDO $con + * @param String $join_behavior the type of joins to use, defaults to Criteria::LEFT_JOIN + * @return int Number of matching rows. + */ + public static function doCountJoinCcFiles(Criteria $criteria, $distinct = false, PropelPDO $con = null, $join_behavior = Criteria::LEFT_JOIN) + { + // we're going to modify criteria, so copy it first + $criteria = clone $criteria; + + // We need to set the primary table name, since in the case that there are no WHERE columns + // it will be impossible for the BasePeer::createSelectSql() method to determine which + // tables go into the FROM clause. + $criteria->setPrimaryTableName(CcPlaylistcontentsPeer::TABLE_NAME); + + if ($distinct && !in_array(Criteria::DISTINCT, $criteria->getSelectModifiers())) { + $criteria->setDistinct(); + } + + if (!$criteria->hasSelectClause()) { + CcPlaylistcontentsPeer::addSelectColumns($criteria); + } + + $criteria->clearOrderByColumns(); // ORDER BY won't ever affect the count + + // Set the correct dbName + $criteria->setDbName(self::DATABASE_NAME); + + if ($con === null) { + $con = Propel::getConnection(CcPlaylistcontentsPeer::DATABASE_NAME, Propel::CONNECTION_READ); + } + + $criteria->addJoin(CcPlaylistcontentsPeer::FILE_ID, CcFilesPeer::ID, $join_behavior); + + $stmt = BasePeer::doCount($criteria, $con); + + if ($row = $stmt->fetch(PDO::FETCH_NUM)) { + $count = (int) $row[0]; + } else { + $count = 0; // no rows returned; we infer that means 0 matches. + } + $stmt->closeCursor(); + return $count; + } + + + /** + * Returns the number of rows matching criteria, joining the related CcPlaylist table + * + * @param Criteria $criteria + * @param boolean $distinct Whether to select only distinct columns; deprecated: use Criteria->setDistinct() instead. + * @param PropelPDO $con + * @param String $join_behavior the type of joins to use, defaults to Criteria::LEFT_JOIN + * @return int Number of matching rows. + */ + public static function doCountJoinCcPlaylist(Criteria $criteria, $distinct = false, PropelPDO $con = null, $join_behavior = Criteria::LEFT_JOIN) + { + // we're going to modify criteria, so copy it first + $criteria = clone $criteria; + + // We need to set the primary table name, since in the case that there are no WHERE columns + // it will be impossible for the BasePeer::createSelectSql() method to determine which + // tables go into the FROM clause. + $criteria->setPrimaryTableName(CcPlaylistcontentsPeer::TABLE_NAME); + + if ($distinct && !in_array(Criteria::DISTINCT, $criteria->getSelectModifiers())) { + $criteria->setDistinct(); + } + + if (!$criteria->hasSelectClause()) { + CcPlaylistcontentsPeer::addSelectColumns($criteria); + } + + $criteria->clearOrderByColumns(); // ORDER BY won't ever affect the count + + // Set the correct dbName + $criteria->setDbName(self::DATABASE_NAME); + + if ($con === null) { + $con = Propel::getConnection(CcPlaylistcontentsPeer::DATABASE_NAME, Propel::CONNECTION_READ); + } + + $criteria->addJoin(CcPlaylistcontentsPeer::PLAYLIST_ID, CcPlaylistPeer::ID, $join_behavior); + + $stmt = BasePeer::doCount($criteria, $con); + + if ($row = $stmt->fetch(PDO::FETCH_NUM)) { + $count = (int) $row[0]; + } else { + $count = 0; // no rows returned; we infer that means 0 matches. + } + $stmt->closeCursor(); + return $count; + } + + + /** + * Selects a collection of CcPlaylistcontents objects pre-filled with their CcFiles objects. + * @param Criteria $criteria + * @param PropelPDO $con + * @param String $join_behavior the type of joins to use, defaults to Criteria::LEFT_JOIN + * @return array Array of CcPlaylistcontents objects. + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function doSelectJoinCcFiles(Criteria $criteria, $con = null, $join_behavior = Criteria::LEFT_JOIN) + { + $criteria = clone $criteria; + + // Set the correct dbName if it has not been overridden + if ($criteria->getDbName() == Propel::getDefaultDB()) { + $criteria->setDbName(self::DATABASE_NAME); + } + + CcPlaylistcontentsPeer::addSelectColumns($criteria); + $startcol = (CcPlaylistcontentsPeer::NUM_COLUMNS - CcPlaylistcontentsPeer::NUM_LAZY_LOAD_COLUMNS); + CcFilesPeer::addSelectColumns($criteria); + + $criteria->addJoin(CcPlaylistcontentsPeer::FILE_ID, CcFilesPeer::ID, $join_behavior); + + $stmt = BasePeer::doSelect($criteria, $con); + $results = array(); + + while ($row = $stmt->fetch(PDO::FETCH_NUM)) { + $key1 = CcPlaylistcontentsPeer::getPrimaryKeyHashFromRow($row, 0); + if (null !== ($obj1 = CcPlaylistcontentsPeer::getInstanceFromPool($key1))) { + // We no longer rehydrate the object, since this can cause data loss. + // See http://www.propelorm.org/ticket/509 + // $obj1->hydrate($row, 0, true); // rehydrate + } else { + + $cls = CcPlaylistcontentsPeer::getOMClass(false); + + $obj1 = new $cls(); + $obj1->hydrate($row); + CcPlaylistcontentsPeer::addInstanceToPool($obj1, $key1); + } // if $obj1 already loaded + + $key2 = CcFilesPeer::getPrimaryKeyHashFromRow($row, $startcol); + if ($key2 !== null) { + $obj2 = CcFilesPeer::getInstanceFromPool($key2); + if (!$obj2) { + + $cls = CcFilesPeer::getOMClass(false); + + $obj2 = new $cls(); + $obj2->hydrate($row, $startcol); + CcFilesPeer::addInstanceToPool($obj2, $key2); + } // if obj2 already loaded + + // Add the $obj1 (CcPlaylistcontents) to $obj2 (CcFiles) + $obj2->addCcPlaylistcontents($obj1); + + } // if joined row was not null + + $results[] = $obj1; + } + $stmt->closeCursor(); + return $results; + } + + + /** + * Selects a collection of CcPlaylistcontents objects pre-filled with their CcPlaylist objects. + * @param Criteria $criteria + * @param PropelPDO $con + * @param String $join_behavior the type of joins to use, defaults to Criteria::LEFT_JOIN + * @return array Array of CcPlaylistcontents objects. + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function doSelectJoinCcPlaylist(Criteria $criteria, $con = null, $join_behavior = Criteria::LEFT_JOIN) + { + $criteria = clone $criteria; + + // Set the correct dbName if it has not been overridden + if ($criteria->getDbName() == Propel::getDefaultDB()) { + $criteria->setDbName(self::DATABASE_NAME); + } + + CcPlaylistcontentsPeer::addSelectColumns($criteria); + $startcol = (CcPlaylistcontentsPeer::NUM_COLUMNS - CcPlaylistcontentsPeer::NUM_LAZY_LOAD_COLUMNS); + CcPlaylistPeer::addSelectColumns($criteria); + + $criteria->addJoin(CcPlaylistcontentsPeer::PLAYLIST_ID, CcPlaylistPeer::ID, $join_behavior); + + $stmt = BasePeer::doSelect($criteria, $con); + $results = array(); + + while ($row = $stmt->fetch(PDO::FETCH_NUM)) { + $key1 = CcPlaylistcontentsPeer::getPrimaryKeyHashFromRow($row, 0); + if (null !== ($obj1 = CcPlaylistcontentsPeer::getInstanceFromPool($key1))) { + // We no longer rehydrate the object, since this can cause data loss. + // See http://www.propelorm.org/ticket/509 + // $obj1->hydrate($row, 0, true); // rehydrate + } else { + + $cls = CcPlaylistcontentsPeer::getOMClass(false); + + $obj1 = new $cls(); + $obj1->hydrate($row); + CcPlaylistcontentsPeer::addInstanceToPool($obj1, $key1); + } // if $obj1 already loaded + + $key2 = CcPlaylistPeer::getPrimaryKeyHashFromRow($row, $startcol); + if ($key2 !== null) { + $obj2 = CcPlaylistPeer::getInstanceFromPool($key2); + if (!$obj2) { + + $cls = CcPlaylistPeer::getOMClass(false); + + $obj2 = new $cls(); + $obj2->hydrate($row, $startcol); + CcPlaylistPeer::addInstanceToPool($obj2, $key2); + } // if obj2 already loaded + + // Add the $obj1 (CcPlaylistcontents) to $obj2 (CcPlaylist) + $obj2->addCcPlaylistcontents($obj1); + + } // if joined row was not null + + $results[] = $obj1; + } + $stmt->closeCursor(); + return $results; + } + + + /** + * Returns the number of rows matching criteria, joining all related tables + * + * @param Criteria $criteria + * @param boolean $distinct Whether to select only distinct columns; deprecated: use Criteria->setDistinct() instead. + * @param PropelPDO $con + * @param String $join_behavior the type of joins to use, defaults to Criteria::LEFT_JOIN + * @return int Number of matching rows. + */ + public static function doCountJoinAll(Criteria $criteria, $distinct = false, PropelPDO $con = null, $join_behavior = Criteria::LEFT_JOIN) + { + // we're going to modify criteria, so copy it first + $criteria = clone $criteria; + + // We need to set the primary table name, since in the case that there are no WHERE columns + // it will be impossible for the BasePeer::createSelectSql() method to determine which + // tables go into the FROM clause. + $criteria->setPrimaryTableName(CcPlaylistcontentsPeer::TABLE_NAME); + + if ($distinct && !in_array(Criteria::DISTINCT, $criteria->getSelectModifiers())) { + $criteria->setDistinct(); + } + + if (!$criteria->hasSelectClause()) { + CcPlaylistcontentsPeer::addSelectColumns($criteria); + } + + $criteria->clearOrderByColumns(); // ORDER BY won't ever affect the count + + // Set the correct dbName + $criteria->setDbName(self::DATABASE_NAME); + + if ($con === null) { + $con = Propel::getConnection(CcPlaylistcontentsPeer::DATABASE_NAME, Propel::CONNECTION_READ); + } + + $criteria->addJoin(CcPlaylistcontentsPeer::FILE_ID, CcFilesPeer::ID, $join_behavior); + + $criteria->addJoin(CcPlaylistcontentsPeer::PLAYLIST_ID, CcPlaylistPeer::ID, $join_behavior); + + $stmt = BasePeer::doCount($criteria, $con); + + if ($row = $stmt->fetch(PDO::FETCH_NUM)) { + $count = (int) $row[0]; + } else { + $count = 0; // no rows returned; we infer that means 0 matches. + } + $stmt->closeCursor(); + return $count; + } + + /** + * Selects a collection of CcPlaylistcontents objects pre-filled with all related objects. + * + * @param Criteria $criteria + * @param PropelPDO $con + * @param String $join_behavior the type of joins to use, defaults to Criteria::LEFT_JOIN + * @return array Array of CcPlaylistcontents objects. + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function doSelectJoinAll(Criteria $criteria, $con = null, $join_behavior = Criteria::LEFT_JOIN) + { + $criteria = clone $criteria; + + // Set the correct dbName if it has not been overridden + if ($criteria->getDbName() == Propel::getDefaultDB()) { + $criteria->setDbName(self::DATABASE_NAME); + } + + CcPlaylistcontentsPeer::addSelectColumns($criteria); + $startcol2 = (CcPlaylistcontentsPeer::NUM_COLUMNS - CcPlaylistcontentsPeer::NUM_LAZY_LOAD_COLUMNS); + + CcFilesPeer::addSelectColumns($criteria); + $startcol3 = $startcol2 + (CcFilesPeer::NUM_COLUMNS - CcFilesPeer::NUM_LAZY_LOAD_COLUMNS); + + CcPlaylistPeer::addSelectColumns($criteria); + $startcol4 = $startcol3 + (CcPlaylistPeer::NUM_COLUMNS - CcPlaylistPeer::NUM_LAZY_LOAD_COLUMNS); + + $criteria->addJoin(CcPlaylistcontentsPeer::FILE_ID, CcFilesPeer::ID, $join_behavior); + + $criteria->addJoin(CcPlaylistcontentsPeer::PLAYLIST_ID, CcPlaylistPeer::ID, $join_behavior); + + $stmt = BasePeer::doSelect($criteria, $con); + $results = array(); + + while ($row = $stmt->fetch(PDO::FETCH_NUM)) { + $key1 = CcPlaylistcontentsPeer::getPrimaryKeyHashFromRow($row, 0); + if (null !== ($obj1 = CcPlaylistcontentsPeer::getInstanceFromPool($key1))) { + // We no longer rehydrate the object, since this can cause data loss. + // See http://www.propelorm.org/ticket/509 + // $obj1->hydrate($row, 0, true); // rehydrate + } else { + $cls = CcPlaylistcontentsPeer::getOMClass(false); + + $obj1 = new $cls(); + $obj1->hydrate($row); + CcPlaylistcontentsPeer::addInstanceToPool($obj1, $key1); + } // if obj1 already loaded + + // Add objects for joined CcFiles rows + + $key2 = CcFilesPeer::getPrimaryKeyHashFromRow($row, $startcol2); + if ($key2 !== null) { + $obj2 = CcFilesPeer::getInstanceFromPool($key2); + if (!$obj2) { + + $cls = CcFilesPeer::getOMClass(false); + + $obj2 = new $cls(); + $obj2->hydrate($row, $startcol2); + CcFilesPeer::addInstanceToPool($obj2, $key2); + } // if obj2 loaded + + // Add the $obj1 (CcPlaylistcontents) to the collection in $obj2 (CcFiles) + $obj2->addCcPlaylistcontents($obj1); + } // if joined row not null + + // Add objects for joined CcPlaylist rows + + $key3 = CcPlaylistPeer::getPrimaryKeyHashFromRow($row, $startcol3); + if ($key3 !== null) { + $obj3 = CcPlaylistPeer::getInstanceFromPool($key3); + if (!$obj3) { + + $cls = CcPlaylistPeer::getOMClass(false); + + $obj3 = new $cls(); + $obj3->hydrate($row, $startcol3); + CcPlaylistPeer::addInstanceToPool($obj3, $key3); + } // if obj3 loaded + + // Add the $obj1 (CcPlaylistcontents) to the collection in $obj3 (CcPlaylist) + $obj3->addCcPlaylistcontents($obj1); + } // if joined row not null + + $results[] = $obj1; + } + $stmt->closeCursor(); + return $results; + } + + + /** + * Returns the number of rows matching criteria, joining the related CcFiles table + * + * @param Criteria $criteria + * @param boolean $distinct Whether to select only distinct columns; deprecated: use Criteria->setDistinct() instead. + * @param PropelPDO $con + * @param String $join_behavior the type of joins to use, defaults to Criteria::LEFT_JOIN + * @return int Number of matching rows. + */ + public static function doCountJoinAllExceptCcFiles(Criteria $criteria, $distinct = false, PropelPDO $con = null, $join_behavior = Criteria::LEFT_JOIN) + { + // we're going to modify criteria, so copy it first + $criteria = clone $criteria; + + // We need to set the primary table name, since in the case that there are no WHERE columns + // it will be impossible for the BasePeer::createSelectSql() method to determine which + // tables go into the FROM clause. + $criteria->setPrimaryTableName(CcPlaylistcontentsPeer::TABLE_NAME); + + if ($distinct && !in_array(Criteria::DISTINCT, $criteria->getSelectModifiers())) { + $criteria->setDistinct(); + } + + if (!$criteria->hasSelectClause()) { + CcPlaylistcontentsPeer::addSelectColumns($criteria); + } + + $criteria->clearOrderByColumns(); // ORDER BY should not affect count + + // Set the correct dbName + $criteria->setDbName(self::DATABASE_NAME); + + if ($con === null) { + $con = Propel::getConnection(CcPlaylistcontentsPeer::DATABASE_NAME, Propel::CONNECTION_READ); + } + + $criteria->addJoin(CcPlaylistcontentsPeer::PLAYLIST_ID, CcPlaylistPeer::ID, $join_behavior); + + $stmt = BasePeer::doCount($criteria, $con); + + if ($row = $stmt->fetch(PDO::FETCH_NUM)) { + $count = (int) $row[0]; + } else { + $count = 0; // no rows returned; we infer that means 0 matches. + } + $stmt->closeCursor(); + return $count; + } + + + /** + * Returns the number of rows matching criteria, joining the related CcPlaylist table + * + * @param Criteria $criteria + * @param boolean $distinct Whether to select only distinct columns; deprecated: use Criteria->setDistinct() instead. + * @param PropelPDO $con + * @param String $join_behavior the type of joins to use, defaults to Criteria::LEFT_JOIN + * @return int Number of matching rows. + */ + public static function doCountJoinAllExceptCcPlaylist(Criteria $criteria, $distinct = false, PropelPDO $con = null, $join_behavior = Criteria::LEFT_JOIN) + { + // we're going to modify criteria, so copy it first + $criteria = clone $criteria; + + // We need to set the primary table name, since in the case that there are no WHERE columns + // it will be impossible for the BasePeer::createSelectSql() method to determine which + // tables go into the FROM clause. + $criteria->setPrimaryTableName(CcPlaylistcontentsPeer::TABLE_NAME); + + if ($distinct && !in_array(Criteria::DISTINCT, $criteria->getSelectModifiers())) { + $criteria->setDistinct(); + } + + if (!$criteria->hasSelectClause()) { + CcPlaylistcontentsPeer::addSelectColumns($criteria); + } + + $criteria->clearOrderByColumns(); // ORDER BY should not affect count + + // Set the correct dbName + $criteria->setDbName(self::DATABASE_NAME); + + if ($con === null) { + $con = Propel::getConnection(CcPlaylistcontentsPeer::DATABASE_NAME, Propel::CONNECTION_READ); + } + + $criteria->addJoin(CcPlaylistcontentsPeer::FILE_ID, CcFilesPeer::ID, $join_behavior); + + $stmt = BasePeer::doCount($criteria, $con); + + if ($row = $stmt->fetch(PDO::FETCH_NUM)) { + $count = (int) $row[0]; + } else { + $count = 0; // no rows returned; we infer that means 0 matches. + } + $stmt->closeCursor(); + return $count; + } + + + /** + * Selects a collection of CcPlaylistcontents objects pre-filled with all related objects except CcFiles. + * + * @param Criteria $criteria + * @param PropelPDO $con + * @param String $join_behavior the type of joins to use, defaults to Criteria::LEFT_JOIN + * @return array Array of CcPlaylistcontents objects. + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function doSelectJoinAllExceptCcFiles(Criteria $criteria, $con = null, $join_behavior = Criteria::LEFT_JOIN) + { + $criteria = clone $criteria; + + // Set the correct dbName if it has not been overridden + // $criteria->getDbName() will return the same object if not set to another value + // so == check is okay and faster + if ($criteria->getDbName() == Propel::getDefaultDB()) { + $criteria->setDbName(self::DATABASE_NAME); + } + + CcPlaylistcontentsPeer::addSelectColumns($criteria); + $startcol2 = (CcPlaylistcontentsPeer::NUM_COLUMNS - CcPlaylistcontentsPeer::NUM_LAZY_LOAD_COLUMNS); + + CcPlaylistPeer::addSelectColumns($criteria); + $startcol3 = $startcol2 + (CcPlaylistPeer::NUM_COLUMNS - CcPlaylistPeer::NUM_LAZY_LOAD_COLUMNS); + + $criteria->addJoin(CcPlaylistcontentsPeer::PLAYLIST_ID, CcPlaylistPeer::ID, $join_behavior); + + + $stmt = BasePeer::doSelect($criteria, $con); + $results = array(); + + while ($row = $stmt->fetch(PDO::FETCH_NUM)) { + $key1 = CcPlaylistcontentsPeer::getPrimaryKeyHashFromRow($row, 0); + if (null !== ($obj1 = CcPlaylistcontentsPeer::getInstanceFromPool($key1))) { + // We no longer rehydrate the object, since this can cause data loss. + // See http://www.propelorm.org/ticket/509 + // $obj1->hydrate($row, 0, true); // rehydrate + } else { + $cls = CcPlaylistcontentsPeer::getOMClass(false); + + $obj1 = new $cls(); + $obj1->hydrate($row); + CcPlaylistcontentsPeer::addInstanceToPool($obj1, $key1); + } // if obj1 already loaded + + // Add objects for joined CcPlaylist rows + + $key2 = CcPlaylistPeer::getPrimaryKeyHashFromRow($row, $startcol2); + if ($key2 !== null) { + $obj2 = CcPlaylistPeer::getInstanceFromPool($key2); + if (!$obj2) { + + $cls = CcPlaylistPeer::getOMClass(false); + + $obj2 = new $cls(); + $obj2->hydrate($row, $startcol2); + CcPlaylistPeer::addInstanceToPool($obj2, $key2); + } // if $obj2 already loaded + + // Add the $obj1 (CcPlaylistcontents) to the collection in $obj2 (CcPlaylist) + $obj2->addCcPlaylistcontents($obj1); + + } // if joined row is not null + + $results[] = $obj1; + } + $stmt->closeCursor(); + return $results; + } + + + /** + * Selects a collection of CcPlaylistcontents objects pre-filled with all related objects except CcPlaylist. + * + * @param Criteria $criteria + * @param PropelPDO $con + * @param String $join_behavior the type of joins to use, defaults to Criteria::LEFT_JOIN + * @return array Array of CcPlaylistcontents objects. + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function doSelectJoinAllExceptCcPlaylist(Criteria $criteria, $con = null, $join_behavior = Criteria::LEFT_JOIN) + { + $criteria = clone $criteria; + + // Set the correct dbName if it has not been overridden + // $criteria->getDbName() will return the same object if not set to another value + // so == check is okay and faster + if ($criteria->getDbName() == Propel::getDefaultDB()) { + $criteria->setDbName(self::DATABASE_NAME); + } + + CcPlaylistcontentsPeer::addSelectColumns($criteria); + $startcol2 = (CcPlaylistcontentsPeer::NUM_COLUMNS - CcPlaylistcontentsPeer::NUM_LAZY_LOAD_COLUMNS); + + CcFilesPeer::addSelectColumns($criteria); + $startcol3 = $startcol2 + (CcFilesPeer::NUM_COLUMNS - CcFilesPeer::NUM_LAZY_LOAD_COLUMNS); + + $criteria->addJoin(CcPlaylistcontentsPeer::FILE_ID, CcFilesPeer::ID, $join_behavior); + + + $stmt = BasePeer::doSelect($criteria, $con); + $results = array(); + + while ($row = $stmt->fetch(PDO::FETCH_NUM)) { + $key1 = CcPlaylistcontentsPeer::getPrimaryKeyHashFromRow($row, 0); + if (null !== ($obj1 = CcPlaylistcontentsPeer::getInstanceFromPool($key1))) { + // We no longer rehydrate the object, since this can cause data loss. + // See http://www.propelorm.org/ticket/509 + // $obj1->hydrate($row, 0, true); // rehydrate + } else { + $cls = CcPlaylistcontentsPeer::getOMClass(false); + + $obj1 = new $cls(); + $obj1->hydrate($row); + CcPlaylistcontentsPeer::addInstanceToPool($obj1, $key1); + } // if obj1 already loaded + + // Add objects for joined CcFiles rows + + $key2 = CcFilesPeer::getPrimaryKeyHashFromRow($row, $startcol2); + if ($key2 !== null) { + $obj2 = CcFilesPeer::getInstanceFromPool($key2); + if (!$obj2) { + + $cls = CcFilesPeer::getOMClass(false); + + $obj2 = new $cls(); + $obj2->hydrate($row, $startcol2); + CcFilesPeer::addInstanceToPool($obj2, $key2); + } // if $obj2 already loaded + + // Add the $obj1 (CcPlaylistcontents) to the collection in $obj2 (CcFiles) + $obj2->addCcPlaylistcontents($obj1); + + } // if joined row is not null + + $results[] = $obj1; + } + $stmt->closeCursor(); + return $results; + } + + /** + * Returns the TableMap related to this peer. + * This method is not needed for general use but a specific application could have a need. + * @return TableMap + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function getTableMap() + { + return Propel::getDatabaseMap(self::DATABASE_NAME)->getTable(self::TABLE_NAME); + } + + /** + * Add a TableMap instance to the database for this peer class. + */ + public static function buildTableMap() + { + $dbMap = Propel::getDatabaseMap(BaseCcPlaylistcontentsPeer::DATABASE_NAME); + if (!$dbMap->hasTable(BaseCcPlaylistcontentsPeer::TABLE_NAME)) + { + $dbMap->addTableObject(new CcPlaylistcontentsTableMap()); + } + } + + /** + * The class that the Peer will make instances of. + * + * If $withPrefix is true, the returned path + * uses a dot-path notation which is tranalted into a path + * relative to a location on the PHP include_path. + * (e.g. path.to.MyClass -> 'path/to/MyClass.php') + * + * @param boolean $withPrefix Whether or not to return the path with the class name + * @return string path.to.ClassName + */ + public static function getOMClass($withPrefix = true) + { + return $withPrefix ? CcPlaylistcontentsPeer::CLASS_DEFAULT : CcPlaylistcontentsPeer::OM_CLASS; + } + + /** + * Method perform an INSERT on the database, given a CcPlaylistcontents or Criteria object. + * + * @param mixed $values Criteria or CcPlaylistcontents object containing data that is used to create the INSERT statement. + * @param PropelPDO $con the PropelPDO connection to use + * @return mixed The new primary key. + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function doInsert($values, PropelPDO $con = null) + { + if ($con === null) { + $con = Propel::getConnection(CcPlaylistcontentsPeer::DATABASE_NAME, Propel::CONNECTION_WRITE); + } + + if ($values instanceof Criteria) { + $criteria = clone $values; // rename for clarity + } else { + $criteria = $values->buildCriteria(); // build Criteria from CcPlaylistcontents object + } + + if ($criteria->containsKey(CcPlaylistcontentsPeer::ID) && $criteria->keyContainsValue(CcPlaylistcontentsPeer::ID) ) { + throw new PropelException('Cannot insert a value for auto-increment primary key ('.CcPlaylistcontentsPeer::ID.')'); + } + + + // Set the correct dbName + $criteria->setDbName(self::DATABASE_NAME); + + try { + // use transaction because $criteria could contain info + // for more than one table (I guess, conceivably) + $con->beginTransaction(); + $pk = BasePeer::doInsert($criteria, $con); + $con->commit(); + } catch(PropelException $e) { + $con->rollBack(); + throw $e; + } + + return $pk; + } + + /** + * Method perform an UPDATE on the database, given a CcPlaylistcontents or Criteria object. + * + * @param mixed $values Criteria or CcPlaylistcontents object containing data that is used to create the UPDATE statement. + * @param PropelPDO $con The connection to use (specify PropelPDO connection object to exert more control over transactions). + * @return int The number of affected rows (if supported by underlying database driver). + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function doUpdate($values, PropelPDO $con = null) + { + if ($con === null) { + $con = Propel::getConnection(CcPlaylistcontentsPeer::DATABASE_NAME, Propel::CONNECTION_WRITE); + } + + $selectCriteria = new Criteria(self::DATABASE_NAME); + + if ($values instanceof Criteria) { + $criteria = clone $values; // rename for clarity + + $comparison = $criteria->getComparison(CcPlaylistcontentsPeer::ID); + $value = $criteria->remove(CcPlaylistcontentsPeer::ID); + if ($value) { + $selectCriteria->add(CcPlaylistcontentsPeer::ID, $value, $comparison); + } else { + $selectCriteria->setPrimaryTableName(CcPlaylistcontentsPeer::TABLE_NAME); + } + + } else { // $values is CcPlaylistcontents object + $criteria = $values->buildCriteria(); // gets full criteria + $selectCriteria = $values->buildPkeyCriteria(); // gets criteria w/ primary key(s) + } + + // set the correct dbName + $criteria->setDbName(self::DATABASE_NAME); + + return BasePeer::doUpdate($selectCriteria, $criteria, $con); + } + + /** + * Method to DELETE all rows from the cc_playlistcontents table. + * + * @return int The number of affected rows (if supported by underlying database driver). + */ + public static function doDeleteAll($con = null) + { + if ($con === null) { + $con = Propel::getConnection(CcPlaylistcontentsPeer::DATABASE_NAME, Propel::CONNECTION_WRITE); + } + $affectedRows = 0; // initialize var to track total num of affected rows + try { + // use transaction because $criteria could contain info + // for more than one table or we could emulating ON DELETE CASCADE, etc. + $con->beginTransaction(); + $affectedRows += BasePeer::doDeleteAll(CcPlaylistcontentsPeer::TABLE_NAME, $con, CcPlaylistcontentsPeer::DATABASE_NAME); + // Because this db requires some delete cascade/set null emulation, we have to + // clear the cached instance *after* the emulation has happened (since + // instances get re-added by the select statement contained therein). + CcPlaylistcontentsPeer::clearInstancePool(); + CcPlaylistcontentsPeer::clearRelatedInstancePool(); + $con->commit(); + return $affectedRows; + } catch (PropelException $e) { + $con->rollBack(); + throw $e; + } + } + + /** + * Method perform a DELETE on the database, given a CcPlaylistcontents or Criteria object OR a primary key value. + * + * @param mixed $values Criteria or CcPlaylistcontents object or primary key or array of primary keys + * which is used to create the DELETE statement + * @param PropelPDO $con the connection to use + * @return int The number of affected rows (if supported by underlying database driver). This includes CASCADE-related rows + * if supported by native driver or if emulated using Propel. + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function doDelete($values, PropelPDO $con = null) + { + if ($con === null) { + $con = Propel::getConnection(CcPlaylistcontentsPeer::DATABASE_NAME, Propel::CONNECTION_WRITE); + } + + if ($values instanceof Criteria) { + // invalidate the cache for all objects of this type, since we have no + // way of knowing (without running a query) what objects should be invalidated + // from the cache based on this Criteria. + CcPlaylistcontentsPeer::clearInstancePool(); + // rename for clarity + $criteria = clone $values; + } elseif ($values instanceof CcPlaylistcontents) { // it's a model object + // invalidate the cache for this single object + CcPlaylistcontentsPeer::removeInstanceFromPool($values); + // create criteria based on pk values + $criteria = $values->buildPkeyCriteria(); + } else { // it's a primary key, or an array of pks + $criteria = new Criteria(self::DATABASE_NAME); + $criteria->add(CcPlaylistcontentsPeer::ID, (array) $values, Criteria::IN); + // invalidate the cache for this object(s) + foreach ((array) $values as $singleval) { + CcPlaylistcontentsPeer::removeInstanceFromPool($singleval); + } + } + + // Set the correct dbName + $criteria->setDbName(self::DATABASE_NAME); + + $affectedRows = 0; // initialize var to track total num of affected rows + + try { + // use transaction because $criteria could contain info + // for more than one table or we could emulating ON DELETE CASCADE, etc. + $con->beginTransaction(); + + $affectedRows += BasePeer::doDelete($criteria, $con); + CcPlaylistcontentsPeer::clearRelatedInstancePool(); + $con->commit(); + return $affectedRows; + } catch (PropelException $e) { + $con->rollBack(); + throw $e; + } + } + + /** + * Validates all modified columns of given CcPlaylistcontents object. + * If parameter $columns is either a single column name or an array of column names + * than only those columns are validated. + * + * NOTICE: This does not apply to primary or foreign keys for now. + * + * @param CcPlaylistcontents $obj The object to validate. + * @param mixed $cols Column name or array of column names. + * + * @return mixed TRUE if all columns are valid or the error message of the first invalid column. + */ + public static function doValidate(CcPlaylistcontents $obj, $cols = null) + { + $columns = array(); + + if ($cols) { + $dbMap = Propel::getDatabaseMap(CcPlaylistcontentsPeer::DATABASE_NAME); + $tableMap = $dbMap->getTable(CcPlaylistcontentsPeer::TABLE_NAME); + + if (! is_array($cols)) { + $cols = array($cols); + } + + foreach ($cols as $colName) { + if ($tableMap->containsColumn($colName)) { + $get = 'get' . $tableMap->getColumn($colName)->getPhpName(); + $columns[$colName] = $obj->$get(); + } + } + } else { + + } + + return BasePeer::doValidate(CcPlaylistcontentsPeer::DATABASE_NAME, CcPlaylistcontentsPeer::TABLE_NAME, $columns); + } + + /** + * Retrieve a single object by pkey. + * + * @param int $pk the primary key. + * @param PropelPDO $con the connection to use + * @return CcPlaylistcontents + */ + public static function retrieveByPK($pk, PropelPDO $con = null) + { + + if (null !== ($obj = CcPlaylistcontentsPeer::getInstanceFromPool((string) $pk))) { + return $obj; + } + + if ($con === null) { + $con = Propel::getConnection(CcPlaylistcontentsPeer::DATABASE_NAME, Propel::CONNECTION_READ); + } + + $criteria = new Criteria(CcPlaylistcontentsPeer::DATABASE_NAME); + $criteria->add(CcPlaylistcontentsPeer::ID, $pk); + + $v = CcPlaylistcontentsPeer::doSelect($criteria, $con); + + return !empty($v) > 0 ? $v[0] : null; + } + + /** + * Retrieve multiple objects by pkey. + * + * @param array $pks List of primary keys + * @param PropelPDO $con the connection to use + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function retrieveByPKs($pks, PropelPDO $con = null) + { + if ($con === null) { + $con = Propel::getConnection(CcPlaylistcontentsPeer::DATABASE_NAME, Propel::CONNECTION_READ); + } + + $objs = null; + if (empty($pks)) { + $objs = array(); + } else { + $criteria = new Criteria(CcPlaylistcontentsPeer::DATABASE_NAME); + $criteria->add(CcPlaylistcontentsPeer::ID, $pks, Criteria::IN); + $objs = CcPlaylistcontentsPeer::doSelect($criteria, $con); + } + return $objs; + } + +} // BaseCcPlaylistcontentsPeer + +// This is the static code needed to register the TableMap for this table with the main Propel class. +// +BaseCcPlaylistcontentsPeer::buildTableMap(); + diff --git a/application/models/campcaster/om/BaseCcPlaylistcontentsQuery.php b/application/models/campcaster/om/BaseCcPlaylistcontentsQuery.php new file mode 100644 index 000000000..183fa9a4f --- /dev/null +++ b/application/models/campcaster/om/BaseCcPlaylistcontentsQuery.php @@ -0,0 +1,581 @@ +setModelAlias($modelAlias); + } + if ($criteria instanceof Criteria) { + $query->mergeWith($criteria); + } + return $query; + } + + /** + * Find object by primary key + * Use instance pooling to avoid a database query if the object exists + * + * $obj = $c->findPk(12, $con); + * + * @param mixed $key Primary key to use for the query + * @param PropelPDO $con an optional connection object + * + * @return CcPlaylistcontents|array|mixed the result, formatted by the current formatter + */ + public function findPk($key, $con = null) + { + if ((null !== ($obj = CcPlaylistcontentsPeer::getInstanceFromPool((string) $key))) && $this->getFormatter()->isObjectFormatter()) { + // the object is alredy in the instance pool + return $obj; + } else { + // the object has not been requested yet, or the formatter is not an object formatter + $criteria = $this->isKeepQuery() ? clone $this : $this; + $stmt = $criteria + ->filterByPrimaryKey($key) + ->getSelectStatement($con); + return $criteria->getFormatter()->init($criteria)->formatOne($stmt); + } + } + + /** + * Find objects by primary key + * + * $objs = $c->findPks(array(12, 56, 832), $con); + * + * @param array $keys Primary keys to use for the query + * @param PropelPDO $con an optional connection object + * + * @return PropelObjectCollection|array|mixed the list of results, formatted by the current formatter + */ + public function findPks($keys, $con = null) + { + $criteria = $this->isKeepQuery() ? clone $this : $this; + return $this + ->filterByPrimaryKeys($keys) + ->find($con); + } + + /** + * Filter the query by primary key + * + * @param mixed $key Primary key to use for the query + * + * @return CcPlaylistcontentsQuery The current query, for fluid interface + */ + public function filterByPrimaryKey($key) + { + return $this->addUsingAlias(CcPlaylistcontentsPeer::ID, $key, Criteria::EQUAL); + } + + /** + * Filter the query by a list of primary keys + * + * @param array $keys The list of primary key to use for the query + * + * @return CcPlaylistcontentsQuery The current query, for fluid interface + */ + public function filterByPrimaryKeys($keys) + { + return $this->addUsingAlias(CcPlaylistcontentsPeer::ID, $keys, Criteria::IN); + } + + /** + * Filter the query on the id column + * + * @param int|array $dbId The value to use as filter. + * Accepts an associative array('min' => $minValue, 'max' => $maxValue) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcPlaylistcontentsQuery The current query, for fluid interface + */ + public function filterByDbId($dbId = null, $comparison = null) + { + if (is_array($dbId) && null === $comparison) { + $comparison = Criteria::IN; + } + return $this->addUsingAlias(CcPlaylistcontentsPeer::ID, $dbId, $comparison); + } + + /** + * Filter the query on the playlist_id column + * + * @param int|array $dbPlaylistId The value to use as filter. + * Accepts an associative array('min' => $minValue, 'max' => $maxValue) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcPlaylistcontentsQuery The current query, for fluid interface + */ + public function filterByDbPlaylistId($dbPlaylistId = null, $comparison = null) + { + if (is_array($dbPlaylistId)) { + $useMinMax = false; + if (isset($dbPlaylistId['min'])) { + $this->addUsingAlias(CcPlaylistcontentsPeer::PLAYLIST_ID, $dbPlaylistId['min'], Criteria::GREATER_EQUAL); + $useMinMax = true; + } + if (isset($dbPlaylistId['max'])) { + $this->addUsingAlias(CcPlaylistcontentsPeer::PLAYLIST_ID, $dbPlaylistId['max'], Criteria::LESS_EQUAL); + $useMinMax = true; + } + if ($useMinMax) { + return $this; + } + if (null === $comparison) { + $comparison = Criteria::IN; + } + } + return $this->addUsingAlias(CcPlaylistcontentsPeer::PLAYLIST_ID, $dbPlaylistId, $comparison); + } + + /** + * Filter the query on the file_id column + * + * @param int|array $dbFileId The value to use as filter. + * Accepts an associative array('min' => $minValue, 'max' => $maxValue) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcPlaylistcontentsQuery The current query, for fluid interface + */ + public function filterByDbFileId($dbFileId = null, $comparison = null) + { + if (is_array($dbFileId)) { + $useMinMax = false; + if (isset($dbFileId['min'])) { + $this->addUsingAlias(CcPlaylistcontentsPeer::FILE_ID, $dbFileId['min'], Criteria::GREATER_EQUAL); + $useMinMax = true; + } + if (isset($dbFileId['max'])) { + $this->addUsingAlias(CcPlaylistcontentsPeer::FILE_ID, $dbFileId['max'], Criteria::LESS_EQUAL); + $useMinMax = true; + } + if ($useMinMax) { + return $this; + } + if (null === $comparison) { + $comparison = Criteria::IN; + } + } + return $this->addUsingAlias(CcPlaylistcontentsPeer::FILE_ID, $dbFileId, $comparison); + } + + /** + * Filter the query on the position column + * + * @param int|array $dbPosition The value to use as filter. + * Accepts an associative array('min' => $minValue, 'max' => $maxValue) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcPlaylistcontentsQuery The current query, for fluid interface + */ + public function filterByDbPosition($dbPosition = null, $comparison = null) + { + if (is_array($dbPosition)) { + $useMinMax = false; + if (isset($dbPosition['min'])) { + $this->addUsingAlias(CcPlaylistcontentsPeer::POSITION, $dbPosition['min'], Criteria::GREATER_EQUAL); + $useMinMax = true; + } + if (isset($dbPosition['max'])) { + $this->addUsingAlias(CcPlaylistcontentsPeer::POSITION, $dbPosition['max'], Criteria::LESS_EQUAL); + $useMinMax = true; + } + if ($useMinMax) { + return $this; + } + if (null === $comparison) { + $comparison = Criteria::IN; + } + } + return $this->addUsingAlias(CcPlaylistcontentsPeer::POSITION, $dbPosition, $comparison); + } + + /** + * Filter the query on the cliplength column + * + * @param string|array $dbCliplength The value to use as filter. + * Accepts an associative array('min' => $minValue, 'max' => $maxValue) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcPlaylistcontentsQuery The current query, for fluid interface + */ + public function filterByDbCliplength($dbCliplength = null, $comparison = null) + { + if (is_array($dbCliplength)) { + $useMinMax = false; + if (isset($dbCliplength['min'])) { + $this->addUsingAlias(CcPlaylistcontentsPeer::CLIPLENGTH, $dbCliplength['min'], Criteria::GREATER_EQUAL); + $useMinMax = true; + } + if (isset($dbCliplength['max'])) { + $this->addUsingAlias(CcPlaylistcontentsPeer::CLIPLENGTH, $dbCliplength['max'], Criteria::LESS_EQUAL); + $useMinMax = true; + } + if ($useMinMax) { + return $this; + } + if (null === $comparison) { + $comparison = Criteria::IN; + } + } + return $this->addUsingAlias(CcPlaylistcontentsPeer::CLIPLENGTH, $dbCliplength, $comparison); + } + + /** + * Filter the query on the cuein column + * + * @param string|array $dbCuein The value to use as filter. + * Accepts an associative array('min' => $minValue, 'max' => $maxValue) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcPlaylistcontentsQuery The current query, for fluid interface + */ + public function filterByDbCuein($dbCuein = null, $comparison = null) + { + if (is_array($dbCuein)) { + $useMinMax = false; + if (isset($dbCuein['min'])) { + $this->addUsingAlias(CcPlaylistcontentsPeer::CUEIN, $dbCuein['min'], Criteria::GREATER_EQUAL); + $useMinMax = true; + } + if (isset($dbCuein['max'])) { + $this->addUsingAlias(CcPlaylistcontentsPeer::CUEIN, $dbCuein['max'], Criteria::LESS_EQUAL); + $useMinMax = true; + } + if ($useMinMax) { + return $this; + } + if (null === $comparison) { + $comparison = Criteria::IN; + } + } + return $this->addUsingAlias(CcPlaylistcontentsPeer::CUEIN, $dbCuein, $comparison); + } + + /** + * Filter the query on the cueout column + * + * @param string|array $dbCueout The value to use as filter. + * Accepts an associative array('min' => $minValue, 'max' => $maxValue) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcPlaylistcontentsQuery The current query, for fluid interface + */ + public function filterByDbCueout($dbCueout = null, $comparison = null) + { + if (is_array($dbCueout)) { + $useMinMax = false; + if (isset($dbCueout['min'])) { + $this->addUsingAlias(CcPlaylistcontentsPeer::CUEOUT, $dbCueout['min'], Criteria::GREATER_EQUAL); + $useMinMax = true; + } + if (isset($dbCueout['max'])) { + $this->addUsingAlias(CcPlaylistcontentsPeer::CUEOUT, $dbCueout['max'], Criteria::LESS_EQUAL); + $useMinMax = true; + } + if ($useMinMax) { + return $this; + } + if (null === $comparison) { + $comparison = Criteria::IN; + } + } + return $this->addUsingAlias(CcPlaylistcontentsPeer::CUEOUT, $dbCueout, $comparison); + } + + /** + * Filter the query on the fadein column + * + * @param string|array $dbFadein The value to use as filter. + * Accepts an associative array('min' => $minValue, 'max' => $maxValue) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcPlaylistcontentsQuery The current query, for fluid interface + */ + public function filterByDbFadein($dbFadein = null, $comparison = null) + { + if (is_array($dbFadein)) { + $useMinMax = false; + if (isset($dbFadein['min'])) { + $this->addUsingAlias(CcPlaylistcontentsPeer::FADEIN, $dbFadein['min'], Criteria::GREATER_EQUAL); + $useMinMax = true; + } + if (isset($dbFadein['max'])) { + $this->addUsingAlias(CcPlaylistcontentsPeer::FADEIN, $dbFadein['max'], Criteria::LESS_EQUAL); + $useMinMax = true; + } + if ($useMinMax) { + return $this; + } + if (null === $comparison) { + $comparison = Criteria::IN; + } + } + return $this->addUsingAlias(CcPlaylistcontentsPeer::FADEIN, $dbFadein, $comparison); + } + + /** + * Filter the query on the fadeout column + * + * @param string|array $dbFadeout The value to use as filter. + * Accepts an associative array('min' => $minValue, 'max' => $maxValue) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcPlaylistcontentsQuery The current query, for fluid interface + */ + public function filterByDbFadeout($dbFadeout = null, $comparison = null) + { + if (is_array($dbFadeout)) { + $useMinMax = false; + if (isset($dbFadeout['min'])) { + $this->addUsingAlias(CcPlaylistcontentsPeer::FADEOUT, $dbFadeout['min'], Criteria::GREATER_EQUAL); + $useMinMax = true; + } + if (isset($dbFadeout['max'])) { + $this->addUsingAlias(CcPlaylistcontentsPeer::FADEOUT, $dbFadeout['max'], Criteria::LESS_EQUAL); + $useMinMax = true; + } + if ($useMinMax) { + return $this; + } + if (null === $comparison) { + $comparison = Criteria::IN; + } + } + return $this->addUsingAlias(CcPlaylistcontentsPeer::FADEOUT, $dbFadeout, $comparison); + } + + /** + * Filter the query by a related CcFiles object + * + * @param CcFiles $ccFiles the related object to use as filter + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcPlaylistcontentsQuery The current query, for fluid interface + */ + public function filterByCcFiles($ccFiles, $comparison = null) + { + return $this + ->addUsingAlias(CcPlaylistcontentsPeer::FILE_ID, $ccFiles->getDbId(), $comparison); + } + + /** + * Adds a JOIN clause to the query using the CcFiles relation + * + * @param string $relationAlias optional alias for the relation + * @param string $joinType Accepted values are null, 'left join', 'right join', 'inner join' + * + * @return CcPlaylistcontentsQuery The current query, for fluid interface + */ + public function joinCcFiles($relationAlias = '', $joinType = Criteria::LEFT_JOIN) + { + $tableMap = $this->getTableMap(); + $relationMap = $tableMap->getRelation('CcFiles'); + + // create a ModelJoin object for this join + $join = new ModelJoin(); + $join->setJoinType($joinType); + $join->setRelationMap($relationMap, $this->useAliasInSQL ? $this->getModelAlias() : null, $relationAlias); + if ($previousJoin = $this->getPreviousJoin()) { + $join->setPreviousJoin($previousJoin); + } + + // add the ModelJoin to the current object + if($relationAlias) { + $this->addAlias($relationAlias, $relationMap->getRightTable()->getName()); + $this->addJoinObject($join, $relationAlias); + } else { + $this->addJoinObject($join, 'CcFiles'); + } + + return $this; + } + + /** + * Use the CcFiles relation CcFiles object + * + * @see useQuery() + * + * @param string $relationAlias optional alias for the relation, + * to be used as main alias in the secondary query + * @param string $joinType Accepted values are null, 'left join', 'right join', 'inner join' + * + * @return CcFilesQuery A secondary query class using the current class as primary query + */ + public function useCcFilesQuery($relationAlias = '', $joinType = Criteria::LEFT_JOIN) + { + return $this + ->joinCcFiles($relationAlias, $joinType) + ->useQuery($relationAlias ? $relationAlias : 'CcFiles', 'CcFilesQuery'); + } + + /** + * Filter the query by a related CcPlaylist object + * + * @param CcPlaylist $ccPlaylist the related object to use as filter + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcPlaylistcontentsQuery The current query, for fluid interface + */ + public function filterByCcPlaylist($ccPlaylist, $comparison = null) + { + return $this + ->addUsingAlias(CcPlaylistcontentsPeer::PLAYLIST_ID, $ccPlaylist->getDbId(), $comparison); + } + + /** + * Adds a JOIN clause to the query using the CcPlaylist relation + * + * @param string $relationAlias optional alias for the relation + * @param string $joinType Accepted values are null, 'left join', 'right join', 'inner join' + * + * @return CcPlaylistcontentsQuery The current query, for fluid interface + */ + public function joinCcPlaylist($relationAlias = '', $joinType = Criteria::LEFT_JOIN) + { + $tableMap = $this->getTableMap(); + $relationMap = $tableMap->getRelation('CcPlaylist'); + + // create a ModelJoin object for this join + $join = new ModelJoin(); + $join->setJoinType($joinType); + $join->setRelationMap($relationMap, $this->useAliasInSQL ? $this->getModelAlias() : null, $relationAlias); + if ($previousJoin = $this->getPreviousJoin()) { + $join->setPreviousJoin($previousJoin); + } + + // add the ModelJoin to the current object + if($relationAlias) { + $this->addAlias($relationAlias, $relationMap->getRightTable()->getName()); + $this->addJoinObject($join, $relationAlias); + } else { + $this->addJoinObject($join, 'CcPlaylist'); + } + + return $this; + } + + /** + * Use the CcPlaylist relation CcPlaylist object + * + * @see useQuery() + * + * @param string $relationAlias optional alias for the relation, + * to be used as main alias in the secondary query + * @param string $joinType Accepted values are null, 'left join', 'right join', 'inner join' + * + * @return CcPlaylistQuery A secondary query class using the current class as primary query + */ + public function useCcPlaylistQuery($relationAlias = '', $joinType = Criteria::LEFT_JOIN) + { + return $this + ->joinCcPlaylist($relationAlias, $joinType) + ->useQuery($relationAlias ? $relationAlias : 'CcPlaylist', 'CcPlaylistQuery'); + } + + /** + * Exclude object from result + * + * @param CcPlaylistcontents $ccPlaylistcontents Object to remove from the list of results + * + * @return CcPlaylistcontentsQuery The current query, for fluid interface + */ + public function prune($ccPlaylistcontents = null) + { + if ($ccPlaylistcontents) { + $this->addUsingAlias(CcPlaylistcontentsPeer::ID, $ccPlaylistcontents->getDbId(), Criteria::NOT_EQUAL); + } + + return $this; + } + +} // BaseCcPlaylistcontentsQuery diff --git a/application/models/campcaster/om/BaseCcPref.php b/application/models/campcaster/om/BaseCcPref.php new file mode 100644 index 000000000..d1f07c596 --- /dev/null +++ b/application/models/campcaster/om/BaseCcPref.php @@ -0,0 +1,905 @@ +id; + } + + /** + * Get the [subjid] column value. + * + * @return int + */ + public function getSubjid() + { + return $this->subjid; + } + + /** + * Get the [keystr] column value. + * + * @return string + */ + public function getKeystr() + { + return $this->keystr; + } + + /** + * Get the [valstr] column value. + * + * @return string + */ + public function getValstr() + { + return $this->valstr; + } + + /** + * Set the value of [id] column. + * + * @param int $v new value + * @return CcPref The current object (for fluent API support) + */ + public function setId($v) + { + if ($v !== null) { + $v = (int) $v; + } + + if ($this->id !== $v) { + $this->id = $v; + $this->modifiedColumns[] = CcPrefPeer::ID; + } + + return $this; + } // setId() + + /** + * Set the value of [subjid] column. + * + * @param int $v new value + * @return CcPref The current object (for fluent API support) + */ + public function setSubjid($v) + { + if ($v !== null) { + $v = (int) $v; + } + + if ($this->subjid !== $v) { + $this->subjid = $v; + $this->modifiedColumns[] = CcPrefPeer::SUBJID; + } + + if ($this->aCcSubjs !== null && $this->aCcSubjs->getId() !== $v) { + $this->aCcSubjs = null; + } + + return $this; + } // setSubjid() + + /** + * Set the value of [keystr] column. + * + * @param string $v new value + * @return CcPref The current object (for fluent API support) + */ + public function setKeystr($v) + { + if ($v !== null) { + $v = (string) $v; + } + + if ($this->keystr !== $v) { + $this->keystr = $v; + $this->modifiedColumns[] = CcPrefPeer::KEYSTR; + } + + return $this; + } // setKeystr() + + /** + * Set the value of [valstr] column. + * + * @param string $v new value + * @return CcPref The current object (for fluent API support) + */ + public function setValstr($v) + { + if ($v !== null) { + $v = (string) $v; + } + + if ($this->valstr !== $v) { + $this->valstr = $v; + $this->modifiedColumns[] = CcPrefPeer::VALSTR; + } + + return $this; + } // setValstr() + + /** + * Indicates whether the columns in this object are only set to default values. + * + * This method can be used in conjunction with isModified() to indicate whether an object is both + * modified _and_ has some values set which are non-default. + * + * @return boolean Whether the columns in this object are only been set with default values. + */ + public function hasOnlyDefaultValues() + { + // otherwise, everything was equal, so return TRUE + return true; + } // hasOnlyDefaultValues() + + /** + * Hydrates (populates) the object variables with values from the database resultset. + * + * An offset (0-based "start column") is specified so that objects can be hydrated + * with a subset of the columns in the resultset rows. This is needed, for example, + * for results of JOIN queries where the resultset row includes columns from two or + * more tables. + * + * @param array $row The row returned by PDOStatement->fetch(PDO::FETCH_NUM) + * @param int $startcol 0-based offset column which indicates which restultset column to start with. + * @param boolean $rehydrate Whether this object is being re-hydrated from the database. + * @return int next starting column + * @throws PropelException - Any caught Exception will be rewrapped as a PropelException. + */ + public function hydrate($row, $startcol = 0, $rehydrate = false) + { + try { + + $this->id = ($row[$startcol + 0] !== null) ? (int) $row[$startcol + 0] : null; + $this->subjid = ($row[$startcol + 1] !== null) ? (int) $row[$startcol + 1] : null; + $this->keystr = ($row[$startcol + 2] !== null) ? (string) $row[$startcol + 2] : null; + $this->valstr = ($row[$startcol + 3] !== null) ? (string) $row[$startcol + 3] : null; + $this->resetModified(); + + $this->setNew(false); + + if ($rehydrate) { + $this->ensureConsistency(); + } + + return $startcol + 4; // 4 = CcPrefPeer::NUM_COLUMNS - CcPrefPeer::NUM_LAZY_LOAD_COLUMNS). + + } catch (Exception $e) { + throw new PropelException("Error populating CcPref object", $e); + } + } + + /** + * Checks and repairs the internal consistency of the object. + * + * This method is executed after an already-instantiated object is re-hydrated + * from the database. It exists to check any foreign keys to make sure that + * the objects related to the current object are correct based on foreign key. + * + * You can override this method in the stub class, but you should always invoke + * the base method from the overridden method (i.e. parent::ensureConsistency()), + * in case your model changes. + * + * @throws PropelException + */ + public function ensureConsistency() + { + + if ($this->aCcSubjs !== null && $this->subjid !== $this->aCcSubjs->getId()) { + $this->aCcSubjs = null; + } + } // ensureConsistency + + /** + * Reloads this object from datastore based on primary key and (optionally) resets all associated objects. + * + * This will only work if the object has been saved and has a valid primary key set. + * + * @param boolean $deep (optional) Whether to also de-associated any related objects. + * @param PropelPDO $con (optional) The PropelPDO connection to use. + * @return void + * @throws PropelException - if this object is deleted, unsaved or doesn't have pk match in db + */ + public function reload($deep = false, PropelPDO $con = null) + { + if ($this->isDeleted()) { + throw new PropelException("Cannot reload a deleted object."); + } + + if ($this->isNew()) { + throw new PropelException("Cannot reload an unsaved object."); + } + + if ($con === null) { + $con = Propel::getConnection(CcPrefPeer::DATABASE_NAME, Propel::CONNECTION_READ); + } + + // We don't need to alter the object instance pool; we're just modifying this instance + // already in the pool. + + $stmt = CcPrefPeer::doSelectStmt($this->buildPkeyCriteria(), $con); + $row = $stmt->fetch(PDO::FETCH_NUM); + $stmt->closeCursor(); + if (!$row) { + throw new PropelException('Cannot find matching row in the database to reload object values.'); + } + $this->hydrate($row, 0, true); // rehydrate + + if ($deep) { // also de-associate any related objects? + + $this->aCcSubjs = null; + } // if (deep) + } + + /** + * Removes this object from datastore and sets delete attribute. + * + * @param PropelPDO $con + * @return void + * @throws PropelException + * @see BaseObject::setDeleted() + * @see BaseObject::isDeleted() + */ + public function delete(PropelPDO $con = null) + { + if ($this->isDeleted()) { + throw new PropelException("This object has already been deleted."); + } + + if ($con === null) { + $con = Propel::getConnection(CcPrefPeer::DATABASE_NAME, Propel::CONNECTION_WRITE); + } + + $con->beginTransaction(); + try { + $ret = $this->preDelete($con); + if ($ret) { + CcPrefQuery::create() + ->filterByPrimaryKey($this->getPrimaryKey()) + ->delete($con); + $this->postDelete($con); + $con->commit(); + $this->setDeleted(true); + } else { + $con->commit(); + } + } catch (PropelException $e) { + $con->rollBack(); + throw $e; + } + } + + /** + * Persists this object to the database. + * + * If the object is new, it inserts it; otherwise an update is performed. + * All modified related objects will also be persisted in the doSave() + * method. This method wraps all precipitate database operations in a + * single transaction. + * + * @param PropelPDO $con + * @return int The number of rows affected by this insert/update and any referring fk objects' save() operations. + * @throws PropelException + * @see doSave() + */ + public function save(PropelPDO $con = null) + { + if ($this->isDeleted()) { + throw new PropelException("You cannot save an object that has been deleted."); + } + + if ($con === null) { + $con = Propel::getConnection(CcPrefPeer::DATABASE_NAME, Propel::CONNECTION_WRITE); + } + + $con->beginTransaction(); + $isInsert = $this->isNew(); + try { + $ret = $this->preSave($con); + if ($isInsert) { + $ret = $ret && $this->preInsert($con); + } else { + $ret = $ret && $this->preUpdate($con); + } + if ($ret) { + $affectedRows = $this->doSave($con); + if ($isInsert) { + $this->postInsert($con); + } else { + $this->postUpdate($con); + } + $this->postSave($con); + CcPrefPeer::addInstanceToPool($this); + } else { + $affectedRows = 0; + } + $con->commit(); + return $affectedRows; + } catch (PropelException $e) { + $con->rollBack(); + throw $e; + } + } + + /** + * Performs the work of inserting or updating the row in the database. + * + * If the object is new, it inserts it; otherwise an update is performed. + * All related objects are also updated in this method. + * + * @param PropelPDO $con + * @return int The number of rows affected by this insert/update and any referring fk objects' save() operations. + * @throws PropelException + * @see save() + */ + protected function doSave(PropelPDO $con) + { + $affectedRows = 0; // initialize var to track total num of affected rows + if (!$this->alreadyInSave) { + $this->alreadyInSave = true; + + // We call the save method on the following object(s) if they + // were passed to this object by their coresponding set + // method. This object relates to these object(s) by a + // foreign key reference. + + if ($this->aCcSubjs !== null) { + if ($this->aCcSubjs->isModified() || $this->aCcSubjs->isNew()) { + $affectedRows += $this->aCcSubjs->save($con); + } + $this->setCcSubjs($this->aCcSubjs); + } + + if ($this->isNew() ) { + $this->modifiedColumns[] = CcPrefPeer::ID; + } + + // If this object has been modified, then save it to the database. + if ($this->isModified()) { + if ($this->isNew()) { + $criteria = $this->buildCriteria(); + if ($criteria->keyContainsValue(CcPrefPeer::ID) ) { + throw new PropelException('Cannot insert a value for auto-increment primary key ('.CcPrefPeer::ID.')'); + } + + $pk = BasePeer::doInsert($criteria, $con); + $affectedRows += 1; + $this->setId($pk); //[IMV] update autoincrement primary key + $this->setNew(false); + } else { + $affectedRows += CcPrefPeer::doUpdate($this, $con); + } + + $this->resetModified(); // [HL] After being saved an object is no longer 'modified' + } + + $this->alreadyInSave = false; + + } + return $affectedRows; + } // doSave() + + /** + * Array of ValidationFailed objects. + * @var array ValidationFailed[] + */ + protected $validationFailures = array(); + + /** + * Gets any ValidationFailed objects that resulted from last call to validate(). + * + * + * @return array ValidationFailed[] + * @see validate() + */ + public function getValidationFailures() + { + return $this->validationFailures; + } + + /** + * Validates the objects modified field values and all objects related to this table. + * + * If $columns is either a column name or an array of column names + * only those columns are validated. + * + * @param mixed $columns Column name or an array of column names. + * @return boolean Whether all columns pass validation. + * @see doValidate() + * @see getValidationFailures() + */ + public function validate($columns = null) + { + $res = $this->doValidate($columns); + if ($res === true) { + $this->validationFailures = array(); + return true; + } else { + $this->validationFailures = $res; + return false; + } + } + + /** + * This function performs the validation work for complex object models. + * + * In addition to checking the current object, all related objects will + * also be validated. If all pass then true is returned; otherwise + * an aggreagated array of ValidationFailed objects will be returned. + * + * @param array $columns Array of column names to validate. + * @return mixed true if all validations pass; array of ValidationFailed objets otherwise. + */ + protected function doValidate($columns = null) + { + if (!$this->alreadyInValidation) { + $this->alreadyInValidation = true; + $retval = null; + + $failureMap = array(); + + + // We call the validate method on the following object(s) if they + // were passed to this object by their coresponding set + // method. This object relates to these object(s) by a + // foreign key reference. + + if ($this->aCcSubjs !== null) { + if (!$this->aCcSubjs->validate($columns)) { + $failureMap = array_merge($failureMap, $this->aCcSubjs->getValidationFailures()); + } + } + + + if (($retval = CcPrefPeer::doValidate($this, $columns)) !== true) { + $failureMap = array_merge($failureMap, $retval); + } + + + + $this->alreadyInValidation = false; + } + + return (!empty($failureMap) ? $failureMap : true); + } + + /** + * Retrieves a field from the object by name passed in as a string. + * + * @param string $name name + * @param string $type The type of fieldname the $name is of: + * one of the class type constants BasePeer::TYPE_PHPNAME, BasePeer::TYPE_STUDLYPHPNAME + * BasePeer::TYPE_COLNAME, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_NUM + * @return mixed Value of field. + */ + public function getByName($name, $type = BasePeer::TYPE_PHPNAME) + { + $pos = CcPrefPeer::translateFieldName($name, $type, BasePeer::TYPE_NUM); + $field = $this->getByPosition($pos); + return $field; + } + + /** + * Retrieves a field from the object by Position as specified in the xml schema. + * Zero-based. + * + * @param int $pos position in xml schema + * @return mixed Value of field at $pos + */ + public function getByPosition($pos) + { + switch($pos) { + case 0: + return $this->getId(); + break; + case 1: + return $this->getSubjid(); + break; + case 2: + return $this->getKeystr(); + break; + case 3: + return $this->getValstr(); + break; + default: + return null; + break; + } // switch() + } + + /** + * Exports the object as an array. + * + * You can specify the key type of the array by passing one of the class + * type constants. + * + * @param string $keyType (optional) One of the class type constants BasePeer::TYPE_PHPNAME, BasePeer::TYPE_STUDLYPHPNAME, + * BasePeer::TYPE_COLNAME, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_NUM. + * Defaults to BasePeer::TYPE_PHPNAME. + * @param boolean $includeLazyLoadColumns (optional) Whether to include lazy loaded columns. Defaults to TRUE. + * @param boolean $includeForeignObjects (optional) Whether to include hydrated related objects. Default to FALSE. + * + * @return array an associative array containing the field names (as keys) and field values + */ + public function toArray($keyType = BasePeer::TYPE_PHPNAME, $includeLazyLoadColumns = true, $includeForeignObjects = false) + { + $keys = CcPrefPeer::getFieldNames($keyType); + $result = array( + $keys[0] => $this->getId(), + $keys[1] => $this->getSubjid(), + $keys[2] => $this->getKeystr(), + $keys[3] => $this->getValstr(), + ); + if ($includeForeignObjects) { + if (null !== $this->aCcSubjs) { + $result['CcSubjs'] = $this->aCcSubjs->toArray($keyType, $includeLazyLoadColumns, true); + } + } + return $result; + } + + /** + * Sets a field from the object by name passed in as a string. + * + * @param string $name peer name + * @param mixed $value field value + * @param string $type The type of fieldname the $name is of: + * one of the class type constants BasePeer::TYPE_PHPNAME, BasePeer::TYPE_STUDLYPHPNAME + * BasePeer::TYPE_COLNAME, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_NUM + * @return void + */ + public function setByName($name, $value, $type = BasePeer::TYPE_PHPNAME) + { + $pos = CcPrefPeer::translateFieldName($name, $type, BasePeer::TYPE_NUM); + return $this->setByPosition($pos, $value); + } + + /** + * Sets a field from the object by Position as specified in the xml schema. + * Zero-based. + * + * @param int $pos position in xml schema + * @param mixed $value field value + * @return void + */ + public function setByPosition($pos, $value) + { + switch($pos) { + case 0: + $this->setId($value); + break; + case 1: + $this->setSubjid($value); + break; + case 2: + $this->setKeystr($value); + break; + case 3: + $this->setValstr($value); + break; + } // switch() + } + + /** + * Populates the object using an array. + * + * This is particularly useful when populating an object from one of the + * request arrays (e.g. $_POST). This method goes through the column + * names, checking to see whether a matching key exists in populated + * array. If so the setByName() method is called for that column. + * + * You can specify the key type of the array by additionally passing one + * of the class type constants BasePeer::TYPE_PHPNAME, BasePeer::TYPE_STUDLYPHPNAME, + * BasePeer::TYPE_COLNAME, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_NUM. + * The default key type is the column's phpname (e.g. 'AuthorId') + * + * @param array $arr An array to populate the object from. + * @param string $keyType The type of keys the array uses. + * @return void + */ + public function fromArray($arr, $keyType = BasePeer::TYPE_PHPNAME) + { + $keys = CcPrefPeer::getFieldNames($keyType); + + if (array_key_exists($keys[0], $arr)) $this->setId($arr[$keys[0]]); + if (array_key_exists($keys[1], $arr)) $this->setSubjid($arr[$keys[1]]); + if (array_key_exists($keys[2], $arr)) $this->setKeystr($arr[$keys[2]]); + if (array_key_exists($keys[3], $arr)) $this->setValstr($arr[$keys[3]]); + } + + /** + * Build a Criteria object containing the values of all modified columns in this object. + * + * @return Criteria The Criteria object containing all modified values. + */ + public function buildCriteria() + { + $criteria = new Criteria(CcPrefPeer::DATABASE_NAME); + + if ($this->isColumnModified(CcPrefPeer::ID)) $criteria->add(CcPrefPeer::ID, $this->id); + if ($this->isColumnModified(CcPrefPeer::SUBJID)) $criteria->add(CcPrefPeer::SUBJID, $this->subjid); + if ($this->isColumnModified(CcPrefPeer::KEYSTR)) $criteria->add(CcPrefPeer::KEYSTR, $this->keystr); + if ($this->isColumnModified(CcPrefPeer::VALSTR)) $criteria->add(CcPrefPeer::VALSTR, $this->valstr); + + return $criteria; + } + + /** + * Builds a Criteria object containing the primary key for this object. + * + * Unlike buildCriteria() this method includes the primary key values regardless + * of whether or not they have been modified. + * + * @return Criteria The Criteria object containing value(s) for primary key(s). + */ + public function buildPkeyCriteria() + { + $criteria = new Criteria(CcPrefPeer::DATABASE_NAME); + $criteria->add(CcPrefPeer::ID, $this->id); + + return $criteria; + } + + /** + * Returns the primary key for this object (row). + * @return int + */ + public function getPrimaryKey() + { + return $this->getId(); + } + + /** + * Generic method to set the primary key (id column). + * + * @param int $key Primary key. + * @return void + */ + public function setPrimaryKey($key) + { + $this->setId($key); + } + + /** + * Returns true if the primary key for this object is null. + * @return boolean + */ + public function isPrimaryKeyNull() + { + return null === $this->getId(); + } + + /** + * Sets contents of passed object to values from current object. + * + * If desired, this method can also make copies of all associated (fkey referrers) + * objects. + * + * @param object $copyObj An object of CcPref (or compatible) type. + * @param boolean $deepCopy Whether to also copy all rows that refer (by fkey) to the current row. + * @throws PropelException + */ + public function copyInto($copyObj, $deepCopy = false) + { + $copyObj->setSubjid($this->subjid); + $copyObj->setKeystr($this->keystr); + $copyObj->setValstr($this->valstr); + + $copyObj->setNew(true); + $copyObj->setId(NULL); // this is a auto-increment column, so set to default value + } + + /** + * Makes a copy of this object that will be inserted as a new row in table when saved. + * It creates a new object filling in the simple attributes, but skipping any primary + * keys that are defined for the table. + * + * If desired, this method can also make copies of all associated (fkey referrers) + * objects. + * + * @param boolean $deepCopy Whether to also copy all rows that refer (by fkey) to the current row. + * @return CcPref Clone of current object. + * @throws PropelException + */ + public function copy($deepCopy = false) + { + // we use get_class(), because this might be a subclass + $clazz = get_class($this); + $copyObj = new $clazz(); + $this->copyInto($copyObj, $deepCopy); + return $copyObj; + } + + /** + * Returns a peer instance associated with this om. + * + * Since Peer classes are not to have any instance attributes, this method returns the + * same instance for all member of this class. The method could therefore + * be static, but this would prevent one from overriding the behavior. + * + * @return CcPrefPeer + */ + public function getPeer() + { + if (self::$peer === null) { + self::$peer = new CcPrefPeer(); + } + return self::$peer; + } + + /** + * Declares an association between this object and a CcSubjs object. + * + * @param CcSubjs $v + * @return CcPref The current object (for fluent API support) + * @throws PropelException + */ + public function setCcSubjs(CcSubjs $v = null) + { + if ($v === null) { + $this->setSubjid(NULL); + } else { + $this->setSubjid($v->getId()); + } + + $this->aCcSubjs = $v; + + // Add binding for other direction of this n:n relationship. + // If this object has already been added to the CcSubjs object, it will not be re-added. + if ($v !== null) { + $v->addCcPref($this); + } + + return $this; + } + + + /** + * Get the associated CcSubjs object + * + * @param PropelPDO Optional Connection object. + * @return CcSubjs The associated CcSubjs object. + * @throws PropelException + */ + public function getCcSubjs(PropelPDO $con = null) + { + if ($this->aCcSubjs === null && ($this->subjid !== null)) { + $this->aCcSubjs = CcSubjsQuery::create()->findPk($this->subjid, $con); + /* The following can be used additionally to + guarantee the related object contains a reference + to this object. This level of coupling may, however, be + undesirable since it could result in an only partially populated collection + in the referenced object. + $this->aCcSubjs->addCcPrefs($this); + */ + } + return $this->aCcSubjs; + } + + /** + * Clears the current object and sets all attributes to their default values + */ + public function clear() + { + $this->id = null; + $this->subjid = null; + $this->keystr = null; + $this->valstr = null; + $this->alreadyInSave = false; + $this->alreadyInValidation = false; + $this->clearAllReferences(); + $this->resetModified(); + $this->setNew(true); + $this->setDeleted(false); + } + + /** + * Resets all collections of referencing foreign keys. + * + * This method is a user-space workaround for PHP's inability to garbage collect objects + * with circular references. This is currently necessary when using Propel in certain + * daemon or large-volumne/high-memory operations. + * + * @param boolean $deep Whether to also clear the references on all associated objects. + */ + public function clearAllReferences($deep = false) + { + if ($deep) { + } // if ($deep) + + $this->aCcSubjs = null; + } + + /** + * Catches calls to virtual methods + */ + public function __call($name, $params) + { + if (preg_match('/get(\w+)/', $name, $matches)) { + $virtualColumn = $matches[1]; + if ($this->hasVirtualColumn($virtualColumn)) { + return $this->getVirtualColumn($virtualColumn); + } + // no lcfirst in php<5.3... + $virtualColumn[0] = strtolower($virtualColumn[0]); + if ($this->hasVirtualColumn($virtualColumn)) { + return $this->getVirtualColumn($virtualColumn); + } + } + throw new PropelException('Call to undefined method: ' . $name); + } + +} // BaseCcPref diff --git a/application/models/campcaster/om/BaseCcPrefPeer.php b/application/models/campcaster/om/BaseCcPrefPeer.php new file mode 100644 index 000000000..8356fed9a --- /dev/null +++ b/application/models/campcaster/om/BaseCcPrefPeer.php @@ -0,0 +1,983 @@ + array ('Id', 'Subjid', 'Keystr', 'Valstr', ), + BasePeer::TYPE_STUDLYPHPNAME => array ('id', 'subjid', 'keystr', 'valstr', ), + BasePeer::TYPE_COLNAME => array (self::ID, self::SUBJID, self::KEYSTR, self::VALSTR, ), + BasePeer::TYPE_RAW_COLNAME => array ('ID', 'SUBJID', 'KEYSTR', 'VALSTR', ), + BasePeer::TYPE_FIELDNAME => array ('id', 'subjid', 'keystr', 'valstr', ), + BasePeer::TYPE_NUM => array (0, 1, 2, 3, ) + ); + + /** + * holds an array of keys for quick access to the fieldnames array + * + * first dimension keys are the type constants + * e.g. self::$fieldNames[BasePeer::TYPE_PHPNAME]['Id'] = 0 + */ + private static $fieldKeys = array ( + BasePeer::TYPE_PHPNAME => array ('Id' => 0, 'Subjid' => 1, 'Keystr' => 2, 'Valstr' => 3, ), + BasePeer::TYPE_STUDLYPHPNAME => array ('id' => 0, 'subjid' => 1, 'keystr' => 2, 'valstr' => 3, ), + BasePeer::TYPE_COLNAME => array (self::ID => 0, self::SUBJID => 1, self::KEYSTR => 2, self::VALSTR => 3, ), + BasePeer::TYPE_RAW_COLNAME => array ('ID' => 0, 'SUBJID' => 1, 'KEYSTR' => 2, 'VALSTR' => 3, ), + BasePeer::TYPE_FIELDNAME => array ('id' => 0, 'subjid' => 1, 'keystr' => 2, 'valstr' => 3, ), + BasePeer::TYPE_NUM => array (0, 1, 2, 3, ) + ); + + /** + * Translates a fieldname to another type + * + * @param string $name field name + * @param string $fromType One of the class type constants BasePeer::TYPE_PHPNAME, BasePeer::TYPE_STUDLYPHPNAME + * BasePeer::TYPE_COLNAME, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_NUM + * @param string $toType One of the class type constants + * @return string translated name of the field. + * @throws PropelException - if the specified name could not be found in the fieldname mappings. + */ + static public function translateFieldName($name, $fromType, $toType) + { + $toNames = self::getFieldNames($toType); + $key = isset(self::$fieldKeys[$fromType][$name]) ? self::$fieldKeys[$fromType][$name] : null; + if ($key === null) { + throw new PropelException("'$name' could not be found in the field names of type '$fromType'. These are: " . print_r(self::$fieldKeys[$fromType], true)); + } + return $toNames[$key]; + } + + /** + * Returns an array of field names. + * + * @param string $type The type of fieldnames to return: + * One of the class type constants BasePeer::TYPE_PHPNAME, BasePeer::TYPE_STUDLYPHPNAME + * BasePeer::TYPE_COLNAME, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_NUM + * @return array A list of field names + */ + + static public function getFieldNames($type = BasePeer::TYPE_PHPNAME) + { + if (!array_key_exists($type, self::$fieldNames)) { + throw new PropelException('Method getFieldNames() expects the parameter $type to be one of the class constants BasePeer::TYPE_PHPNAME, BasePeer::TYPE_STUDLYPHPNAME, BasePeer::TYPE_COLNAME, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_NUM. ' . $type . ' was given.'); + } + return self::$fieldNames[$type]; + } + + /** + * Convenience method which changes table.column to alias.column. + * + * Using this method you can maintain SQL abstraction while using column aliases. + * + * $c->addAlias("alias1", TablePeer::TABLE_NAME); + * $c->addJoin(TablePeer::alias("alias1", TablePeer::PRIMARY_KEY_COLUMN), TablePeer::PRIMARY_KEY_COLUMN); + * + * @param string $alias The alias for the current table. + * @param string $column The column name for current table. (i.e. CcPrefPeer::COLUMN_NAME). + * @return string + */ + public static function alias($alias, $column) + { + return str_replace(CcPrefPeer::TABLE_NAME.'.', $alias.'.', $column); + } + + /** + * Add all the columns needed to create a new object. + * + * Note: any columns that were marked with lazyLoad="true" in the + * XML schema will not be added to the select list and only loaded + * on demand. + * + * @param Criteria $criteria object containing the columns to add. + * @param string $alias optional table alias + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function addSelectColumns(Criteria $criteria, $alias = null) + { + if (null === $alias) { + $criteria->addSelectColumn(CcPrefPeer::ID); + $criteria->addSelectColumn(CcPrefPeer::SUBJID); + $criteria->addSelectColumn(CcPrefPeer::KEYSTR); + $criteria->addSelectColumn(CcPrefPeer::VALSTR); + } else { + $criteria->addSelectColumn($alias . '.ID'); + $criteria->addSelectColumn($alias . '.SUBJID'); + $criteria->addSelectColumn($alias . '.KEYSTR'); + $criteria->addSelectColumn($alias . '.VALSTR'); + } + } + + /** + * Returns the number of rows matching criteria. + * + * @param Criteria $criteria + * @param boolean $distinct Whether to select only distinct columns; deprecated: use Criteria->setDistinct() instead. + * @param PropelPDO $con + * @return int Number of matching rows. + */ + public static function doCount(Criteria $criteria, $distinct = false, PropelPDO $con = null) + { + // we may modify criteria, so copy it first + $criteria = clone $criteria; + + // We need to set the primary table name, since in the case that there are no WHERE columns + // it will be impossible for the BasePeer::createSelectSql() method to determine which + // tables go into the FROM clause. + $criteria->setPrimaryTableName(CcPrefPeer::TABLE_NAME); + + if ($distinct && !in_array(Criteria::DISTINCT, $criteria->getSelectModifiers())) { + $criteria->setDistinct(); + } + + if (!$criteria->hasSelectClause()) { + CcPrefPeer::addSelectColumns($criteria); + } + + $criteria->clearOrderByColumns(); // ORDER BY won't ever affect the count + $criteria->setDbName(self::DATABASE_NAME); // Set the correct dbName + + if ($con === null) { + $con = Propel::getConnection(CcPrefPeer::DATABASE_NAME, Propel::CONNECTION_READ); + } + // BasePeer returns a PDOStatement + $stmt = BasePeer::doCount($criteria, $con); + + if ($row = $stmt->fetch(PDO::FETCH_NUM)) { + $count = (int) $row[0]; + } else { + $count = 0; // no rows returned; we infer that means 0 matches. + } + $stmt->closeCursor(); + return $count; + } + /** + * Method to select one object from the DB. + * + * @param Criteria $criteria object used to create the SELECT statement. + * @param PropelPDO $con + * @return CcPref + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function doSelectOne(Criteria $criteria, PropelPDO $con = null) + { + $critcopy = clone $criteria; + $critcopy->setLimit(1); + $objects = CcPrefPeer::doSelect($critcopy, $con); + if ($objects) { + return $objects[0]; + } + return null; + } + /** + * Method to do selects. + * + * @param Criteria $criteria The Criteria object used to build the SELECT statement. + * @param PropelPDO $con + * @return array Array of selected Objects + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function doSelect(Criteria $criteria, PropelPDO $con = null) + { + return CcPrefPeer::populateObjects(CcPrefPeer::doSelectStmt($criteria, $con)); + } + /** + * Prepares the Criteria object and uses the parent doSelect() method to execute a PDOStatement. + * + * Use this method directly if you want to work with an executed statement durirectly (for example + * to perform your own object hydration). + * + * @param Criteria $criteria The Criteria object used to build the SELECT statement. + * @param PropelPDO $con The connection to use + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + * @return PDOStatement The executed PDOStatement object. + * @see BasePeer::doSelect() + */ + public static function doSelectStmt(Criteria $criteria, PropelPDO $con = null) + { + if ($con === null) { + $con = Propel::getConnection(CcPrefPeer::DATABASE_NAME, Propel::CONNECTION_READ); + } + + if (!$criteria->hasSelectClause()) { + $criteria = clone $criteria; + CcPrefPeer::addSelectColumns($criteria); + } + + // Set the correct dbName + $criteria->setDbName(self::DATABASE_NAME); + + // BasePeer returns a PDOStatement + return BasePeer::doSelect($criteria, $con); + } + /** + * Adds an object to the instance pool. + * + * Propel keeps cached copies of objects in an instance pool when they are retrieved + * from the database. In some cases -- especially when you override doSelect*() + * methods in your stub classes -- you may need to explicitly add objects + * to the cache in order to ensure that the same objects are always returned by doSelect*() + * and retrieveByPK*() calls. + * + * @param CcPref $value A CcPref object. + * @param string $key (optional) key to use for instance map (for performance boost if key was already calculated externally). + */ + public static function addInstanceToPool(CcPref $obj, $key = null) + { + if (Propel::isInstancePoolingEnabled()) { + if ($key === null) { + $key = (string) $obj->getId(); + } // if key === null + self::$instances[$key] = $obj; + } + } + + /** + * Removes an object from the instance pool. + * + * Propel keeps cached copies of objects in an instance pool when they are retrieved + * from the database. In some cases -- especially when you override doDelete + * methods in your stub classes -- you may need to explicitly remove objects + * from the cache in order to prevent returning objects that no longer exist. + * + * @param mixed $value A CcPref object or a primary key value. + */ + public static function removeInstanceFromPool($value) + { + if (Propel::isInstancePoolingEnabled() && $value !== null) { + if (is_object($value) && $value instanceof CcPref) { + $key = (string) $value->getId(); + } elseif (is_scalar($value)) { + // assume we've been passed a primary key + $key = (string) $value; + } else { + $e = new PropelException("Invalid value passed to removeInstanceFromPool(). Expected primary key or CcPref object; got " . (is_object($value) ? get_class($value) . ' object.' : var_export($value,true))); + throw $e; + } + + unset(self::$instances[$key]); + } + } // removeInstanceFromPool() + + /** + * Retrieves a string version of the primary key from the DB resultset row that can be used to uniquely identify a row in this table. + * + * For tables with a single-column primary key, that simple pkey value will be returned. For tables with + * a multi-column primary key, a serialize()d version of the primary key will be returned. + * + * @param string $key The key (@see getPrimaryKeyHash()) for this instance. + * @return CcPref Found object or NULL if 1) no instance exists for specified key or 2) instance pooling has been disabled. + * @see getPrimaryKeyHash() + */ + public static function getInstanceFromPool($key) + { + if (Propel::isInstancePoolingEnabled()) { + if (isset(self::$instances[$key])) { + return self::$instances[$key]; + } + } + return null; // just to be explicit + } + + /** + * Clear the instance pool. + * + * @return void + */ + public static function clearInstancePool() + { + self::$instances = array(); + } + + /** + * Method to invalidate the instance pool of all tables related to cc_pref + * by a foreign key with ON DELETE CASCADE + */ + public static function clearRelatedInstancePool() + { + } + + /** + * Retrieves a string version of the primary key from the DB resultset row that can be used to uniquely identify a row in this table. + * + * For tables with a single-column primary key, that simple pkey value will be returned. For tables with + * a multi-column primary key, a serialize()d version of the primary key will be returned. + * + * @param array $row PropelPDO resultset row. + * @param int $startcol The 0-based offset for reading from the resultset row. + * @return string A string version of PK or NULL if the components of primary key in result array are all null. + */ + public static function getPrimaryKeyHashFromRow($row, $startcol = 0) + { + // If the PK cannot be derived from the row, return NULL. + if ($row[$startcol] === null) { + return null; + } + return (string) $row[$startcol]; + } + + /** + * Retrieves the primary key from the DB resultset row + * For tables with a single-column primary key, that simple pkey value will be returned. For tables with + * a multi-column primary key, an array of the primary key columns will be returned. + * + * @param array $row PropelPDO resultset row. + * @param int $startcol The 0-based offset for reading from the resultset row. + * @return mixed The primary key of the row + */ + public static function getPrimaryKeyFromRow($row, $startcol = 0) + { + return (int) $row[$startcol]; + } + + /** + * The returned array will contain objects of the default type or + * objects that inherit from the default. + * + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function populateObjects(PDOStatement $stmt) + { + $results = array(); + + // set the class once to avoid overhead in the loop + $cls = CcPrefPeer::getOMClass(false); + // populate the object(s) + while ($row = $stmt->fetch(PDO::FETCH_NUM)) { + $key = CcPrefPeer::getPrimaryKeyHashFromRow($row, 0); + if (null !== ($obj = CcPrefPeer::getInstanceFromPool($key))) { + // We no longer rehydrate the object, since this can cause data loss. + // See http://www.propelorm.org/ticket/509 + // $obj->hydrate($row, 0, true); // rehydrate + $results[] = $obj; + } else { + $obj = new $cls(); + $obj->hydrate($row); + $results[] = $obj; + CcPrefPeer::addInstanceToPool($obj, $key); + } // if key exists + } + $stmt->closeCursor(); + return $results; + } + /** + * Populates an object of the default type or an object that inherit from the default. + * + * @param array $row PropelPDO resultset row. + * @param int $startcol The 0-based offset for reading from the resultset row. + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + * @return array (CcPref object, last column rank) + */ + public static function populateObject($row, $startcol = 0) + { + $key = CcPrefPeer::getPrimaryKeyHashFromRow($row, $startcol); + if (null !== ($obj = CcPrefPeer::getInstanceFromPool($key))) { + // We no longer rehydrate the object, since this can cause data loss. + // See http://www.propelorm.org/ticket/509 + // $obj->hydrate($row, $startcol, true); // rehydrate + $col = $startcol + CcPrefPeer::NUM_COLUMNS; + } else { + $cls = CcPrefPeer::OM_CLASS; + $obj = new $cls(); + $col = $obj->hydrate($row, $startcol); + CcPrefPeer::addInstanceToPool($obj, $key); + } + return array($obj, $col); + } + + /** + * Returns the number of rows matching criteria, joining the related CcSubjs table + * + * @param Criteria $criteria + * @param boolean $distinct Whether to select only distinct columns; deprecated: use Criteria->setDistinct() instead. + * @param PropelPDO $con + * @param String $join_behavior the type of joins to use, defaults to Criteria::LEFT_JOIN + * @return int Number of matching rows. + */ + public static function doCountJoinCcSubjs(Criteria $criteria, $distinct = false, PropelPDO $con = null, $join_behavior = Criteria::LEFT_JOIN) + { + // we're going to modify criteria, so copy it first + $criteria = clone $criteria; + + // We need to set the primary table name, since in the case that there are no WHERE columns + // it will be impossible for the BasePeer::createSelectSql() method to determine which + // tables go into the FROM clause. + $criteria->setPrimaryTableName(CcPrefPeer::TABLE_NAME); + + if ($distinct && !in_array(Criteria::DISTINCT, $criteria->getSelectModifiers())) { + $criteria->setDistinct(); + } + + if (!$criteria->hasSelectClause()) { + CcPrefPeer::addSelectColumns($criteria); + } + + $criteria->clearOrderByColumns(); // ORDER BY won't ever affect the count + + // Set the correct dbName + $criteria->setDbName(self::DATABASE_NAME); + + if ($con === null) { + $con = Propel::getConnection(CcPrefPeer::DATABASE_NAME, Propel::CONNECTION_READ); + } + + $criteria->addJoin(CcPrefPeer::SUBJID, CcSubjsPeer::ID, $join_behavior); + + $stmt = BasePeer::doCount($criteria, $con); + + if ($row = $stmt->fetch(PDO::FETCH_NUM)) { + $count = (int) $row[0]; + } else { + $count = 0; // no rows returned; we infer that means 0 matches. + } + $stmt->closeCursor(); + return $count; + } + + + /** + * Selects a collection of CcPref objects pre-filled with their CcSubjs objects. + * @param Criteria $criteria + * @param PropelPDO $con + * @param String $join_behavior the type of joins to use, defaults to Criteria::LEFT_JOIN + * @return array Array of CcPref objects. + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function doSelectJoinCcSubjs(Criteria $criteria, $con = null, $join_behavior = Criteria::LEFT_JOIN) + { + $criteria = clone $criteria; + + // Set the correct dbName if it has not been overridden + if ($criteria->getDbName() == Propel::getDefaultDB()) { + $criteria->setDbName(self::DATABASE_NAME); + } + + CcPrefPeer::addSelectColumns($criteria); + $startcol = (CcPrefPeer::NUM_COLUMNS - CcPrefPeer::NUM_LAZY_LOAD_COLUMNS); + CcSubjsPeer::addSelectColumns($criteria); + + $criteria->addJoin(CcPrefPeer::SUBJID, CcSubjsPeer::ID, $join_behavior); + + $stmt = BasePeer::doSelect($criteria, $con); + $results = array(); + + while ($row = $stmt->fetch(PDO::FETCH_NUM)) { + $key1 = CcPrefPeer::getPrimaryKeyHashFromRow($row, 0); + if (null !== ($obj1 = CcPrefPeer::getInstanceFromPool($key1))) { + // We no longer rehydrate the object, since this can cause data loss. + // See http://www.propelorm.org/ticket/509 + // $obj1->hydrate($row, 0, true); // rehydrate + } else { + + $cls = CcPrefPeer::getOMClass(false); + + $obj1 = new $cls(); + $obj1->hydrate($row); + CcPrefPeer::addInstanceToPool($obj1, $key1); + } // if $obj1 already loaded + + $key2 = CcSubjsPeer::getPrimaryKeyHashFromRow($row, $startcol); + if ($key2 !== null) { + $obj2 = CcSubjsPeer::getInstanceFromPool($key2); + if (!$obj2) { + + $cls = CcSubjsPeer::getOMClass(false); + + $obj2 = new $cls(); + $obj2->hydrate($row, $startcol); + CcSubjsPeer::addInstanceToPool($obj2, $key2); + } // if obj2 already loaded + + // Add the $obj1 (CcPref) to $obj2 (CcSubjs) + $obj2->addCcPref($obj1); + + } // if joined row was not null + + $results[] = $obj1; + } + $stmt->closeCursor(); + return $results; + } + + + /** + * Returns the number of rows matching criteria, joining all related tables + * + * @param Criteria $criteria + * @param boolean $distinct Whether to select only distinct columns; deprecated: use Criteria->setDistinct() instead. + * @param PropelPDO $con + * @param String $join_behavior the type of joins to use, defaults to Criteria::LEFT_JOIN + * @return int Number of matching rows. + */ + public static function doCountJoinAll(Criteria $criteria, $distinct = false, PropelPDO $con = null, $join_behavior = Criteria::LEFT_JOIN) + { + // we're going to modify criteria, so copy it first + $criteria = clone $criteria; + + // We need to set the primary table name, since in the case that there are no WHERE columns + // it will be impossible for the BasePeer::createSelectSql() method to determine which + // tables go into the FROM clause. + $criteria->setPrimaryTableName(CcPrefPeer::TABLE_NAME); + + if ($distinct && !in_array(Criteria::DISTINCT, $criteria->getSelectModifiers())) { + $criteria->setDistinct(); + } + + if (!$criteria->hasSelectClause()) { + CcPrefPeer::addSelectColumns($criteria); + } + + $criteria->clearOrderByColumns(); // ORDER BY won't ever affect the count + + // Set the correct dbName + $criteria->setDbName(self::DATABASE_NAME); + + if ($con === null) { + $con = Propel::getConnection(CcPrefPeer::DATABASE_NAME, Propel::CONNECTION_READ); + } + + $criteria->addJoin(CcPrefPeer::SUBJID, CcSubjsPeer::ID, $join_behavior); + + $stmt = BasePeer::doCount($criteria, $con); + + if ($row = $stmt->fetch(PDO::FETCH_NUM)) { + $count = (int) $row[0]; + } else { + $count = 0; // no rows returned; we infer that means 0 matches. + } + $stmt->closeCursor(); + return $count; + } + + /** + * Selects a collection of CcPref objects pre-filled with all related objects. + * + * @param Criteria $criteria + * @param PropelPDO $con + * @param String $join_behavior the type of joins to use, defaults to Criteria::LEFT_JOIN + * @return array Array of CcPref objects. + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function doSelectJoinAll(Criteria $criteria, $con = null, $join_behavior = Criteria::LEFT_JOIN) + { + $criteria = clone $criteria; + + // Set the correct dbName if it has not been overridden + if ($criteria->getDbName() == Propel::getDefaultDB()) { + $criteria->setDbName(self::DATABASE_NAME); + } + + CcPrefPeer::addSelectColumns($criteria); + $startcol2 = (CcPrefPeer::NUM_COLUMNS - CcPrefPeer::NUM_LAZY_LOAD_COLUMNS); + + CcSubjsPeer::addSelectColumns($criteria); + $startcol3 = $startcol2 + (CcSubjsPeer::NUM_COLUMNS - CcSubjsPeer::NUM_LAZY_LOAD_COLUMNS); + + $criteria->addJoin(CcPrefPeer::SUBJID, CcSubjsPeer::ID, $join_behavior); + + $stmt = BasePeer::doSelect($criteria, $con); + $results = array(); + + while ($row = $stmt->fetch(PDO::FETCH_NUM)) { + $key1 = CcPrefPeer::getPrimaryKeyHashFromRow($row, 0); + if (null !== ($obj1 = CcPrefPeer::getInstanceFromPool($key1))) { + // We no longer rehydrate the object, since this can cause data loss. + // See http://www.propelorm.org/ticket/509 + // $obj1->hydrate($row, 0, true); // rehydrate + } else { + $cls = CcPrefPeer::getOMClass(false); + + $obj1 = new $cls(); + $obj1->hydrate($row); + CcPrefPeer::addInstanceToPool($obj1, $key1); + } // if obj1 already loaded + + // Add objects for joined CcSubjs rows + + $key2 = CcSubjsPeer::getPrimaryKeyHashFromRow($row, $startcol2); + if ($key2 !== null) { + $obj2 = CcSubjsPeer::getInstanceFromPool($key2); + if (!$obj2) { + + $cls = CcSubjsPeer::getOMClass(false); + + $obj2 = new $cls(); + $obj2->hydrate($row, $startcol2); + CcSubjsPeer::addInstanceToPool($obj2, $key2); + } // if obj2 loaded + + // Add the $obj1 (CcPref) to the collection in $obj2 (CcSubjs) + $obj2->addCcPref($obj1); + } // if joined row not null + + $results[] = $obj1; + } + $stmt->closeCursor(); + return $results; + } + + /** + * Returns the TableMap related to this peer. + * This method is not needed for general use but a specific application could have a need. + * @return TableMap + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function getTableMap() + { + return Propel::getDatabaseMap(self::DATABASE_NAME)->getTable(self::TABLE_NAME); + } + + /** + * Add a TableMap instance to the database for this peer class. + */ + public static function buildTableMap() + { + $dbMap = Propel::getDatabaseMap(BaseCcPrefPeer::DATABASE_NAME); + if (!$dbMap->hasTable(BaseCcPrefPeer::TABLE_NAME)) + { + $dbMap->addTableObject(new CcPrefTableMap()); + } + } + + /** + * The class that the Peer will make instances of. + * + * If $withPrefix is true, the returned path + * uses a dot-path notation which is tranalted into a path + * relative to a location on the PHP include_path. + * (e.g. path.to.MyClass -> 'path/to/MyClass.php') + * + * @param boolean $withPrefix Whether or not to return the path with the class name + * @return string path.to.ClassName + */ + public static function getOMClass($withPrefix = true) + { + return $withPrefix ? CcPrefPeer::CLASS_DEFAULT : CcPrefPeer::OM_CLASS; + } + + /** + * Method perform an INSERT on the database, given a CcPref or Criteria object. + * + * @param mixed $values Criteria or CcPref object containing data that is used to create the INSERT statement. + * @param PropelPDO $con the PropelPDO connection to use + * @return mixed The new primary key. + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function doInsert($values, PropelPDO $con = null) + { + if ($con === null) { + $con = Propel::getConnection(CcPrefPeer::DATABASE_NAME, Propel::CONNECTION_WRITE); + } + + if ($values instanceof Criteria) { + $criteria = clone $values; // rename for clarity + } else { + $criteria = $values->buildCriteria(); // build Criteria from CcPref object + } + + if ($criteria->containsKey(CcPrefPeer::ID) && $criteria->keyContainsValue(CcPrefPeer::ID) ) { + throw new PropelException('Cannot insert a value for auto-increment primary key ('.CcPrefPeer::ID.')'); + } + + + // Set the correct dbName + $criteria->setDbName(self::DATABASE_NAME); + + try { + // use transaction because $criteria could contain info + // for more than one table (I guess, conceivably) + $con->beginTransaction(); + $pk = BasePeer::doInsert($criteria, $con); + $con->commit(); + } catch(PropelException $e) { + $con->rollBack(); + throw $e; + } + + return $pk; + } + + /** + * Method perform an UPDATE on the database, given a CcPref or Criteria object. + * + * @param mixed $values Criteria or CcPref object containing data that is used to create the UPDATE statement. + * @param PropelPDO $con The connection to use (specify PropelPDO connection object to exert more control over transactions). + * @return int The number of affected rows (if supported by underlying database driver). + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function doUpdate($values, PropelPDO $con = null) + { + if ($con === null) { + $con = Propel::getConnection(CcPrefPeer::DATABASE_NAME, Propel::CONNECTION_WRITE); + } + + $selectCriteria = new Criteria(self::DATABASE_NAME); + + if ($values instanceof Criteria) { + $criteria = clone $values; // rename for clarity + + $comparison = $criteria->getComparison(CcPrefPeer::ID); + $value = $criteria->remove(CcPrefPeer::ID); + if ($value) { + $selectCriteria->add(CcPrefPeer::ID, $value, $comparison); + } else { + $selectCriteria->setPrimaryTableName(CcPrefPeer::TABLE_NAME); + } + + } else { // $values is CcPref object + $criteria = $values->buildCriteria(); // gets full criteria + $selectCriteria = $values->buildPkeyCriteria(); // gets criteria w/ primary key(s) + } + + // set the correct dbName + $criteria->setDbName(self::DATABASE_NAME); + + return BasePeer::doUpdate($selectCriteria, $criteria, $con); + } + + /** + * Method to DELETE all rows from the cc_pref table. + * + * @return int The number of affected rows (if supported by underlying database driver). + */ + public static function doDeleteAll($con = null) + { + if ($con === null) { + $con = Propel::getConnection(CcPrefPeer::DATABASE_NAME, Propel::CONNECTION_WRITE); + } + $affectedRows = 0; // initialize var to track total num of affected rows + try { + // use transaction because $criteria could contain info + // for more than one table or we could emulating ON DELETE CASCADE, etc. + $con->beginTransaction(); + $affectedRows += BasePeer::doDeleteAll(CcPrefPeer::TABLE_NAME, $con, CcPrefPeer::DATABASE_NAME); + // Because this db requires some delete cascade/set null emulation, we have to + // clear the cached instance *after* the emulation has happened (since + // instances get re-added by the select statement contained therein). + CcPrefPeer::clearInstancePool(); + CcPrefPeer::clearRelatedInstancePool(); + $con->commit(); + return $affectedRows; + } catch (PropelException $e) { + $con->rollBack(); + throw $e; + } + } + + /** + * Method perform a DELETE on the database, given a CcPref or Criteria object OR a primary key value. + * + * @param mixed $values Criteria or CcPref object or primary key or array of primary keys + * which is used to create the DELETE statement + * @param PropelPDO $con the connection to use + * @return int The number of affected rows (if supported by underlying database driver). This includes CASCADE-related rows + * if supported by native driver or if emulated using Propel. + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function doDelete($values, PropelPDO $con = null) + { + if ($con === null) { + $con = Propel::getConnection(CcPrefPeer::DATABASE_NAME, Propel::CONNECTION_WRITE); + } + + if ($values instanceof Criteria) { + // invalidate the cache for all objects of this type, since we have no + // way of knowing (without running a query) what objects should be invalidated + // from the cache based on this Criteria. + CcPrefPeer::clearInstancePool(); + // rename for clarity + $criteria = clone $values; + } elseif ($values instanceof CcPref) { // it's a model object + // invalidate the cache for this single object + CcPrefPeer::removeInstanceFromPool($values); + // create criteria based on pk values + $criteria = $values->buildPkeyCriteria(); + } else { // it's a primary key, or an array of pks + $criteria = new Criteria(self::DATABASE_NAME); + $criteria->add(CcPrefPeer::ID, (array) $values, Criteria::IN); + // invalidate the cache for this object(s) + foreach ((array) $values as $singleval) { + CcPrefPeer::removeInstanceFromPool($singleval); + } + } + + // Set the correct dbName + $criteria->setDbName(self::DATABASE_NAME); + + $affectedRows = 0; // initialize var to track total num of affected rows + + try { + // use transaction because $criteria could contain info + // for more than one table or we could emulating ON DELETE CASCADE, etc. + $con->beginTransaction(); + + $affectedRows += BasePeer::doDelete($criteria, $con); + CcPrefPeer::clearRelatedInstancePool(); + $con->commit(); + return $affectedRows; + } catch (PropelException $e) { + $con->rollBack(); + throw $e; + } + } + + /** + * Validates all modified columns of given CcPref object. + * If parameter $columns is either a single column name or an array of column names + * than only those columns are validated. + * + * NOTICE: This does not apply to primary or foreign keys for now. + * + * @param CcPref $obj The object to validate. + * @param mixed $cols Column name or array of column names. + * + * @return mixed TRUE if all columns are valid or the error message of the first invalid column. + */ + public static function doValidate(CcPref $obj, $cols = null) + { + $columns = array(); + + if ($cols) { + $dbMap = Propel::getDatabaseMap(CcPrefPeer::DATABASE_NAME); + $tableMap = $dbMap->getTable(CcPrefPeer::TABLE_NAME); + + if (! is_array($cols)) { + $cols = array($cols); + } + + foreach ($cols as $colName) { + if ($tableMap->containsColumn($colName)) { + $get = 'get' . $tableMap->getColumn($colName)->getPhpName(); + $columns[$colName] = $obj->$get(); + } + } + } else { + + } + + return BasePeer::doValidate(CcPrefPeer::DATABASE_NAME, CcPrefPeer::TABLE_NAME, $columns); + } + + /** + * Retrieve a single object by pkey. + * + * @param int $pk the primary key. + * @param PropelPDO $con the connection to use + * @return CcPref + */ + public static function retrieveByPK($pk, PropelPDO $con = null) + { + + if (null !== ($obj = CcPrefPeer::getInstanceFromPool((string) $pk))) { + return $obj; + } + + if ($con === null) { + $con = Propel::getConnection(CcPrefPeer::DATABASE_NAME, Propel::CONNECTION_READ); + } + + $criteria = new Criteria(CcPrefPeer::DATABASE_NAME); + $criteria->add(CcPrefPeer::ID, $pk); + + $v = CcPrefPeer::doSelect($criteria, $con); + + return !empty($v) > 0 ? $v[0] : null; + } + + /** + * Retrieve multiple objects by pkey. + * + * @param array $pks List of primary keys + * @param PropelPDO $con the connection to use + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function retrieveByPKs($pks, PropelPDO $con = null) + { + if ($con === null) { + $con = Propel::getConnection(CcPrefPeer::DATABASE_NAME, Propel::CONNECTION_READ); + } + + $objs = null; + if (empty($pks)) { + $objs = array(); + } else { + $criteria = new Criteria(CcPrefPeer::DATABASE_NAME); + $criteria->add(CcPrefPeer::ID, $pks, Criteria::IN); + $objs = CcPrefPeer::doSelect($criteria, $con); + } + return $objs; + } + +} // BaseCcPrefPeer + +// This is the static code needed to register the TableMap for this table with the main Propel class. +// +BaseCcPrefPeer::buildTableMap(); + diff --git a/application/models/campcaster/om/BaseCcPrefQuery.php b/application/models/campcaster/om/BaseCcPrefQuery.php new file mode 100644 index 000000000..e85c52a36 --- /dev/null +++ b/application/models/campcaster/om/BaseCcPrefQuery.php @@ -0,0 +1,320 @@ +setModelAlias($modelAlias); + } + if ($criteria instanceof Criteria) { + $query->mergeWith($criteria); + } + return $query; + } + + /** + * Find object by primary key + * Use instance pooling to avoid a database query if the object exists + * + * $obj = $c->findPk(12, $con); + * + * @param mixed $key Primary key to use for the query + * @param PropelPDO $con an optional connection object + * + * @return CcPref|array|mixed the result, formatted by the current formatter + */ + public function findPk($key, $con = null) + { + if ((null !== ($obj = CcPrefPeer::getInstanceFromPool((string) $key))) && $this->getFormatter()->isObjectFormatter()) { + // the object is alredy in the instance pool + return $obj; + } else { + // the object has not been requested yet, or the formatter is not an object formatter + $criteria = $this->isKeepQuery() ? clone $this : $this; + $stmt = $criteria + ->filterByPrimaryKey($key) + ->getSelectStatement($con); + return $criteria->getFormatter()->init($criteria)->formatOne($stmt); + } + } + + /** + * Find objects by primary key + * + * $objs = $c->findPks(array(12, 56, 832), $con); + * + * @param array $keys Primary keys to use for the query + * @param PropelPDO $con an optional connection object + * + * @return PropelObjectCollection|array|mixed the list of results, formatted by the current formatter + */ + public function findPks($keys, $con = null) + { + $criteria = $this->isKeepQuery() ? clone $this : $this; + return $this + ->filterByPrimaryKeys($keys) + ->find($con); + } + + /** + * Filter the query by primary key + * + * @param mixed $key Primary key to use for the query + * + * @return CcPrefQuery The current query, for fluid interface + */ + public function filterByPrimaryKey($key) + { + return $this->addUsingAlias(CcPrefPeer::ID, $key, Criteria::EQUAL); + } + + /** + * Filter the query by a list of primary keys + * + * @param array $keys The list of primary key to use for the query + * + * @return CcPrefQuery The current query, for fluid interface + */ + public function filterByPrimaryKeys($keys) + { + return $this->addUsingAlias(CcPrefPeer::ID, $keys, Criteria::IN); + } + + /** + * Filter the query on the id column + * + * @param int|array $id The value to use as filter. + * Accepts an associative array('min' => $minValue, 'max' => $maxValue) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcPrefQuery The current query, for fluid interface + */ + public function filterById($id = null, $comparison = null) + { + if (is_array($id) && null === $comparison) { + $comparison = Criteria::IN; + } + return $this->addUsingAlias(CcPrefPeer::ID, $id, $comparison); + } + + /** + * Filter the query on the subjid column + * + * @param int|array $subjid The value to use as filter. + * Accepts an associative array('min' => $minValue, 'max' => $maxValue) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcPrefQuery The current query, for fluid interface + */ + public function filterBySubjid($subjid = null, $comparison = null) + { + if (is_array($subjid)) { + $useMinMax = false; + if (isset($subjid['min'])) { + $this->addUsingAlias(CcPrefPeer::SUBJID, $subjid['min'], Criteria::GREATER_EQUAL); + $useMinMax = true; + } + if (isset($subjid['max'])) { + $this->addUsingAlias(CcPrefPeer::SUBJID, $subjid['max'], Criteria::LESS_EQUAL); + $useMinMax = true; + } + if ($useMinMax) { + return $this; + } + if (null === $comparison) { + $comparison = Criteria::IN; + } + } + return $this->addUsingAlias(CcPrefPeer::SUBJID, $subjid, $comparison); + } + + /** + * Filter the query on the keystr column + * + * @param string $keystr The value to use as filter. + * Accepts wildcards (* and % trigger a LIKE) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcPrefQuery The current query, for fluid interface + */ + public function filterByKeystr($keystr = null, $comparison = null) + { + if (null === $comparison) { + if (is_array($keystr)) { + $comparison = Criteria::IN; + } elseif (preg_match('/[\%\*]/', $keystr)) { + $keystr = str_replace('*', '%', $keystr); + $comparison = Criteria::LIKE; + } + } + return $this->addUsingAlias(CcPrefPeer::KEYSTR, $keystr, $comparison); + } + + /** + * Filter the query on the valstr column + * + * @param string $valstr The value to use as filter. + * Accepts wildcards (* and % trigger a LIKE) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcPrefQuery The current query, for fluid interface + */ + public function filterByValstr($valstr = null, $comparison = null) + { + if (null === $comparison) { + if (is_array($valstr)) { + $comparison = Criteria::IN; + } elseif (preg_match('/[\%\*]/', $valstr)) { + $valstr = str_replace('*', '%', $valstr); + $comparison = Criteria::LIKE; + } + } + return $this->addUsingAlias(CcPrefPeer::VALSTR, $valstr, $comparison); + } + + /** + * Filter the query by a related CcSubjs object + * + * @param CcSubjs $ccSubjs the related object to use as filter + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcPrefQuery The current query, for fluid interface + */ + public function filterByCcSubjs($ccSubjs, $comparison = null) + { + return $this + ->addUsingAlias(CcPrefPeer::SUBJID, $ccSubjs->getId(), $comparison); + } + + /** + * Adds a JOIN clause to the query using the CcSubjs relation + * + * @param string $relationAlias optional alias for the relation + * @param string $joinType Accepted values are null, 'left join', 'right join', 'inner join' + * + * @return CcPrefQuery The current query, for fluid interface + */ + public function joinCcSubjs($relationAlias = '', $joinType = Criteria::LEFT_JOIN) + { + $tableMap = $this->getTableMap(); + $relationMap = $tableMap->getRelation('CcSubjs'); + + // create a ModelJoin object for this join + $join = new ModelJoin(); + $join->setJoinType($joinType); + $join->setRelationMap($relationMap, $this->useAliasInSQL ? $this->getModelAlias() : null, $relationAlias); + if ($previousJoin = $this->getPreviousJoin()) { + $join->setPreviousJoin($previousJoin); + } + + // add the ModelJoin to the current object + if($relationAlias) { + $this->addAlias($relationAlias, $relationMap->getRightTable()->getName()); + $this->addJoinObject($join, $relationAlias); + } else { + $this->addJoinObject($join, 'CcSubjs'); + } + + return $this; + } + + /** + * Use the CcSubjs relation CcSubjs object + * + * @see useQuery() + * + * @param string $relationAlias optional alias for the relation, + * to be used as main alias in the secondary query + * @param string $joinType Accepted values are null, 'left join', 'right join', 'inner join' + * + * @return CcSubjsQuery A secondary query class using the current class as primary query + */ + public function useCcSubjsQuery($relationAlias = '', $joinType = Criteria::LEFT_JOIN) + { + return $this + ->joinCcSubjs($relationAlias, $joinType) + ->useQuery($relationAlias ? $relationAlias : 'CcSubjs', 'CcSubjsQuery'); + } + + /** + * Exclude object from result + * + * @param CcPref $ccPref Object to remove from the list of results + * + * @return CcPrefQuery The current query, for fluid interface + */ + public function prune($ccPref = null) + { + if ($ccPref) { + $this->addUsingAlias(CcPrefPeer::ID, $ccPref->getId(), Criteria::NOT_EQUAL); + } + + return $this; + } + +} // BaseCcPrefQuery diff --git a/application/models/campcaster/om/BaseCcSchedule.php b/application/models/campcaster/om/BaseCcSchedule.php new file mode 100644 index 000000000..39bad3761 --- /dev/null +++ b/application/models/campcaster/om/BaseCcSchedule.php @@ -0,0 +1,1560 @@ +clip_length = '00:00:00'; + $this->fade_in = '00:00:00'; + $this->fade_out = '00:00:00'; + $this->cue_in = '00:00:00'; + $this->cue_out = '00:00:00'; + } + + /** + * Initializes internal state of BaseCcSchedule object. + * @see applyDefaults() + */ + public function __construct() + { + parent::__construct(); + $this->applyDefaultValues(); + } + + /** + * Get the [id] column value. + * + * @return string + */ + public function getId() + { + return $this->id; + } + + /** + * Get the [playlist_id] column value. + * + * @return int + */ + public function getPlaylistId() + { + return $this->playlist_id; + } + + /** + * Get the [optionally formatted] temporal [starts] column value. + * + * + * @param string $format The date/time format string (either date()-style or strftime()-style). + * If format is NULL, then the raw DateTime object will be returned. + * @return mixed Formatted date/time value as string or DateTime object (if format is NULL), NULL if column is NULL + * @throws PropelException - if unable to parse/validate the date/time value. + */ + public function getStarts($format = 'Y-m-d H:i:s') + { + if ($this->starts === null) { + return null; + } + + + + try { + $dt = new DateTime($this->starts); + } catch (Exception $x) { + throw new PropelException("Internally stored date/time/timestamp value could not be converted to DateTime: " . var_export($this->starts, true), $x); + } + + if ($format === null) { + // Because propel.useDateTimeClass is TRUE, we return a DateTime object. + return $dt; + } elseif (strpos($format, '%') !== false) { + return strftime($format, $dt->format('U')); + } else { + return $dt->format($format); + } + } + + /** + * Get the [optionally formatted] temporal [ends] column value. + * + * + * @param string $format The date/time format string (either date()-style or strftime()-style). + * If format is NULL, then the raw DateTime object will be returned. + * @return mixed Formatted date/time value as string or DateTime object (if format is NULL), NULL if column is NULL + * @throws PropelException - if unable to parse/validate the date/time value. + */ + public function getEnds($format = 'Y-m-d H:i:s') + { + if ($this->ends === null) { + return null; + } + + + + try { + $dt = new DateTime($this->ends); + } catch (Exception $x) { + throw new PropelException("Internally stored date/time/timestamp value could not be converted to DateTime: " . var_export($this->ends, true), $x); + } + + if ($format === null) { + // Because propel.useDateTimeClass is TRUE, we return a DateTime object. + return $dt; + } elseif (strpos($format, '%') !== false) { + return strftime($format, $dt->format('U')); + } else { + return $dt->format($format); + } + } + + /** + * Get the [group_id] column value. + * + * @return int + */ + public function getGroupId() + { + return $this->group_id; + } + + /** + * Get the [file_id] column value. + * + * @return int + */ + public function getFileId() + { + return $this->file_id; + } + + /** + * Get the [optionally formatted] temporal [clip_length] column value. + * + * + * @param string $format The date/time format string (either date()-style or strftime()-style). + * If format is NULL, then the raw DateTime object will be returned. + * @return mixed Formatted date/time value as string or DateTime object (if format is NULL), NULL if column is NULL + * @throws PropelException - if unable to parse/validate the date/time value. + */ + public function getClipLength($format = '%X') + { + if ($this->clip_length === null) { + return null; + } + + + + try { + $dt = new DateTime($this->clip_length); + } catch (Exception $x) { + throw new PropelException("Internally stored date/time/timestamp value could not be converted to DateTime: " . var_export($this->clip_length, true), $x); + } + + if ($format === null) { + // Because propel.useDateTimeClass is TRUE, we return a DateTime object. + return $dt; + } elseif (strpos($format, '%') !== false) { + return strftime($format, $dt->format('U')); + } else { + return $dt->format($format); + } + } + + /** + * Get the [optionally formatted] temporal [fade_in] column value. + * + * + * @param string $format The date/time format string (either date()-style or strftime()-style). + * If format is NULL, then the raw DateTime object will be returned. + * @return mixed Formatted date/time value as string or DateTime object (if format is NULL), NULL if column is NULL + * @throws PropelException - if unable to parse/validate the date/time value. + */ + public function getFadeIn($format = '%X') + { + if ($this->fade_in === null) { + return null; + } + + + + try { + $dt = new DateTime($this->fade_in); + } catch (Exception $x) { + throw new PropelException("Internally stored date/time/timestamp value could not be converted to DateTime: " . var_export($this->fade_in, true), $x); + } + + if ($format === null) { + // Because propel.useDateTimeClass is TRUE, we return a DateTime object. + return $dt; + } elseif (strpos($format, '%') !== false) { + return strftime($format, $dt->format('U')); + } else { + return $dt->format($format); + } + } + + /** + * Get the [optionally formatted] temporal [fade_out] column value. + * + * + * @param string $format The date/time format string (either date()-style or strftime()-style). + * If format is NULL, then the raw DateTime object will be returned. + * @return mixed Formatted date/time value as string or DateTime object (if format is NULL), NULL if column is NULL + * @throws PropelException - if unable to parse/validate the date/time value. + */ + public function getFadeOut($format = '%X') + { + if ($this->fade_out === null) { + return null; + } + + + + try { + $dt = new DateTime($this->fade_out); + } catch (Exception $x) { + throw new PropelException("Internally stored date/time/timestamp value could not be converted to DateTime: " . var_export($this->fade_out, true), $x); + } + + if ($format === null) { + // Because propel.useDateTimeClass is TRUE, we return a DateTime object. + return $dt; + } elseif (strpos($format, '%') !== false) { + return strftime($format, $dt->format('U')); + } else { + return $dt->format($format); + } + } + + /** + * Get the [optionally formatted] temporal [cue_in] column value. + * + * + * @param string $format The date/time format string (either date()-style or strftime()-style). + * If format is NULL, then the raw DateTime object will be returned. + * @return mixed Formatted date/time value as string or DateTime object (if format is NULL), NULL if column is NULL + * @throws PropelException - if unable to parse/validate the date/time value. + */ + public function getCueIn($format = '%X') + { + if ($this->cue_in === null) { + return null; + } + + + + try { + $dt = new DateTime($this->cue_in); + } catch (Exception $x) { + throw new PropelException("Internally stored date/time/timestamp value could not be converted to DateTime: " . var_export($this->cue_in, true), $x); + } + + if ($format === null) { + // Because propel.useDateTimeClass is TRUE, we return a DateTime object. + return $dt; + } elseif (strpos($format, '%') !== false) { + return strftime($format, $dt->format('U')); + } else { + return $dt->format($format); + } + } + + /** + * Get the [optionally formatted] temporal [cue_out] column value. + * + * + * @param string $format The date/time format string (either date()-style or strftime()-style). + * If format is NULL, then the raw DateTime object will be returned. + * @return mixed Formatted date/time value as string or DateTime object (if format is NULL), NULL if column is NULL + * @throws PropelException - if unable to parse/validate the date/time value. + */ + public function getCueOut($format = '%X') + { + if ($this->cue_out === null) { + return null; + } + + + + try { + $dt = new DateTime($this->cue_out); + } catch (Exception $x) { + throw new PropelException("Internally stored date/time/timestamp value could not be converted to DateTime: " . var_export($this->cue_out, true), $x); + } + + if ($format === null) { + // Because propel.useDateTimeClass is TRUE, we return a DateTime object. + return $dt; + } elseif (strpos($format, '%') !== false) { + return strftime($format, $dt->format('U')); + } else { + return $dt->format($format); + } + } + + /** + * Set the value of [id] column. + * + * @param string $v new value + * @return CcSchedule The current object (for fluent API support) + */ + public function setId($v) + { + if ($v !== null) { + $v = (string) $v; + } + + if ($this->id !== $v) { + $this->id = $v; + $this->modifiedColumns[] = CcSchedulePeer::ID; + } + + return $this; + } // setId() + + /** + * Set the value of [playlist_id] column. + * + * @param int $v new value + * @return CcSchedule The current object (for fluent API support) + */ + public function setPlaylistId($v) + { + if ($v !== null) { + $v = (int) $v; + } + + if ($this->playlist_id !== $v) { + $this->playlist_id = $v; + $this->modifiedColumns[] = CcSchedulePeer::PLAYLIST_ID; + } + + return $this; + } // setPlaylistId() + + /** + * Sets the value of [starts] column to a normalized version of the date/time value specified. + * + * @param mixed $v string, integer (timestamp), or DateTime value. Empty string will + * be treated as NULL for temporal objects. + * @return CcSchedule The current object (for fluent API support) + */ + public function setStarts($v) + { + // we treat '' as NULL for temporal objects because DateTime('') == DateTime('now') + // -- which is unexpected, to say the least. + if ($v === null || $v === '') { + $dt = null; + } elseif ($v instanceof DateTime) { + $dt = $v; + } else { + // some string/numeric value passed; we normalize that so that we can + // validate it. + try { + if (is_numeric($v)) { // if it's a unix timestamp + $dt = new DateTime('@'.$v, new DateTimeZone('UTC')); + // We have to explicitly specify and then change the time zone because of a + // DateTime bug: http://bugs.php.net/bug.php?id=43003 + $dt->setTimeZone(new DateTimeZone(date_default_timezone_get())); + } else { + $dt = new DateTime($v); + } + } catch (Exception $x) { + throw new PropelException('Error parsing date/time value: ' . var_export($v, true), $x); + } + } + + if ( $this->starts !== null || $dt !== null ) { + // (nested ifs are a little easier to read in this case) + + $currNorm = ($this->starts !== null && $tmpDt = new DateTime($this->starts)) ? $tmpDt->format('Y-m-d\\TH:i:sO') : null; + $newNorm = ($dt !== null) ? $dt->format('Y-m-d\\TH:i:sO') : null; + + if ( ($currNorm !== $newNorm) // normalized values don't match + ) + { + $this->starts = ($dt ? $dt->format('Y-m-d\\TH:i:sO') : null); + $this->modifiedColumns[] = CcSchedulePeer::STARTS; + } + } // if either are not null + + return $this; + } // setStarts() + + /** + * Sets the value of [ends] column to a normalized version of the date/time value specified. + * + * @param mixed $v string, integer (timestamp), or DateTime value. Empty string will + * be treated as NULL for temporal objects. + * @return CcSchedule The current object (for fluent API support) + */ + public function setEnds($v) + { + // we treat '' as NULL for temporal objects because DateTime('') == DateTime('now') + // -- which is unexpected, to say the least. + if ($v === null || $v === '') { + $dt = null; + } elseif ($v instanceof DateTime) { + $dt = $v; + } else { + // some string/numeric value passed; we normalize that so that we can + // validate it. + try { + if (is_numeric($v)) { // if it's a unix timestamp + $dt = new DateTime('@'.$v, new DateTimeZone('UTC')); + // We have to explicitly specify and then change the time zone because of a + // DateTime bug: http://bugs.php.net/bug.php?id=43003 + $dt->setTimeZone(new DateTimeZone(date_default_timezone_get())); + } else { + $dt = new DateTime($v); + } + } catch (Exception $x) { + throw new PropelException('Error parsing date/time value: ' . var_export($v, true), $x); + } + } + + if ( $this->ends !== null || $dt !== null ) { + // (nested ifs are a little easier to read in this case) + + $currNorm = ($this->ends !== null && $tmpDt = new DateTime($this->ends)) ? $tmpDt->format('Y-m-d\\TH:i:sO') : null; + $newNorm = ($dt !== null) ? $dt->format('Y-m-d\\TH:i:sO') : null; + + if ( ($currNorm !== $newNorm) // normalized values don't match + ) + { + $this->ends = ($dt ? $dt->format('Y-m-d\\TH:i:sO') : null); + $this->modifiedColumns[] = CcSchedulePeer::ENDS; + } + } // if either are not null + + return $this; + } // setEnds() + + /** + * Set the value of [group_id] column. + * + * @param int $v new value + * @return CcSchedule The current object (for fluent API support) + */ + public function setGroupId($v) + { + if ($v !== null) { + $v = (int) $v; + } + + if ($this->group_id !== $v) { + $this->group_id = $v; + $this->modifiedColumns[] = CcSchedulePeer::GROUP_ID; + } + + return $this; + } // setGroupId() + + /** + * Set the value of [file_id] column. + * + * @param int $v new value + * @return CcSchedule The current object (for fluent API support) + */ + public function setFileId($v) + { + if ($v !== null) { + $v = (int) $v; + } + + if ($this->file_id !== $v) { + $this->file_id = $v; + $this->modifiedColumns[] = CcSchedulePeer::FILE_ID; + } + + return $this; + } // setFileId() + + /** + * Sets the value of [clip_length] column to a normalized version of the date/time value specified. + * + * @param mixed $v string, integer (timestamp), or DateTime value. Empty string will + * be treated as NULL for temporal objects. + * @return CcSchedule The current object (for fluent API support) + */ + public function setClipLength($v) + { + // we treat '' as NULL for temporal objects because DateTime('') == DateTime('now') + // -- which is unexpected, to say the least. + if ($v === null || $v === '') { + $dt = null; + } elseif ($v instanceof DateTime) { + $dt = $v; + } else { + // some string/numeric value passed; we normalize that so that we can + // validate it. + try { + if (is_numeric($v)) { // if it's a unix timestamp + $dt = new DateTime('@'.$v, new DateTimeZone('UTC')); + // We have to explicitly specify and then change the time zone because of a + // DateTime bug: http://bugs.php.net/bug.php?id=43003 + $dt->setTimeZone(new DateTimeZone(date_default_timezone_get())); + } else { + $dt = new DateTime($v); + } + } catch (Exception $x) { + throw new PropelException('Error parsing date/time value: ' . var_export($v, true), $x); + } + } + + if ( $this->clip_length !== null || $dt !== null ) { + // (nested ifs are a little easier to read in this case) + + $currNorm = ($this->clip_length !== null && $tmpDt = new DateTime($this->clip_length)) ? $tmpDt->format('H:i:s') : null; + $newNorm = ($dt !== null) ? $dt->format('H:i:s') : null; + + if ( ($currNorm !== $newNorm) // normalized values don't match + || ($dt->format('H:i:s') === '00:00:00') // or the entered value matches the default + ) + { + $this->clip_length = ($dt ? $dt->format('H:i:s') : null); + $this->modifiedColumns[] = CcSchedulePeer::CLIP_LENGTH; + } + } // if either are not null + + return $this; + } // setClipLength() + + /** + * Sets the value of [fade_in] column to a normalized version of the date/time value specified. + * + * @param mixed $v string, integer (timestamp), or DateTime value. Empty string will + * be treated as NULL for temporal objects. + * @return CcSchedule The current object (for fluent API support) + */ + public function setFadeIn($v) + { + // we treat '' as NULL for temporal objects because DateTime('') == DateTime('now') + // -- which is unexpected, to say the least. + if ($v === null || $v === '') { + $dt = null; + } elseif ($v instanceof DateTime) { + $dt = $v; + } else { + // some string/numeric value passed; we normalize that so that we can + // validate it. + try { + if (is_numeric($v)) { // if it's a unix timestamp + $dt = new DateTime('@'.$v, new DateTimeZone('UTC')); + // We have to explicitly specify and then change the time zone because of a + // DateTime bug: http://bugs.php.net/bug.php?id=43003 + $dt->setTimeZone(new DateTimeZone(date_default_timezone_get())); + } else { + $dt = new DateTime($v); + } + } catch (Exception $x) { + throw new PropelException('Error parsing date/time value: ' . var_export($v, true), $x); + } + } + + if ( $this->fade_in !== null || $dt !== null ) { + // (nested ifs are a little easier to read in this case) + + $currNorm = ($this->fade_in !== null && $tmpDt = new DateTime($this->fade_in)) ? $tmpDt->format('H:i:s') : null; + $newNorm = ($dt !== null) ? $dt->format('H:i:s') : null; + + if ( ($currNorm !== $newNorm) // normalized values don't match + || ($dt->format('H:i:s') === '00:00:00') // or the entered value matches the default + ) + { + $this->fade_in = ($dt ? $dt->format('H:i:s') : null); + $this->modifiedColumns[] = CcSchedulePeer::FADE_IN; + } + } // if either are not null + + return $this; + } // setFadeIn() + + /** + * Sets the value of [fade_out] column to a normalized version of the date/time value specified. + * + * @param mixed $v string, integer (timestamp), or DateTime value. Empty string will + * be treated as NULL for temporal objects. + * @return CcSchedule The current object (for fluent API support) + */ + public function setFadeOut($v) + { + // we treat '' as NULL for temporal objects because DateTime('') == DateTime('now') + // -- which is unexpected, to say the least. + if ($v === null || $v === '') { + $dt = null; + } elseif ($v instanceof DateTime) { + $dt = $v; + } else { + // some string/numeric value passed; we normalize that so that we can + // validate it. + try { + if (is_numeric($v)) { // if it's a unix timestamp + $dt = new DateTime('@'.$v, new DateTimeZone('UTC')); + // We have to explicitly specify and then change the time zone because of a + // DateTime bug: http://bugs.php.net/bug.php?id=43003 + $dt->setTimeZone(new DateTimeZone(date_default_timezone_get())); + } else { + $dt = new DateTime($v); + } + } catch (Exception $x) { + throw new PropelException('Error parsing date/time value: ' . var_export($v, true), $x); + } + } + + if ( $this->fade_out !== null || $dt !== null ) { + // (nested ifs are a little easier to read in this case) + + $currNorm = ($this->fade_out !== null && $tmpDt = new DateTime($this->fade_out)) ? $tmpDt->format('H:i:s') : null; + $newNorm = ($dt !== null) ? $dt->format('H:i:s') : null; + + if ( ($currNorm !== $newNorm) // normalized values don't match + || ($dt->format('H:i:s') === '00:00:00') // or the entered value matches the default + ) + { + $this->fade_out = ($dt ? $dt->format('H:i:s') : null); + $this->modifiedColumns[] = CcSchedulePeer::FADE_OUT; + } + } // if either are not null + + return $this; + } // setFadeOut() + + /** + * Sets the value of [cue_in] column to a normalized version of the date/time value specified. + * + * @param mixed $v string, integer (timestamp), or DateTime value. Empty string will + * be treated as NULL for temporal objects. + * @return CcSchedule The current object (for fluent API support) + */ + public function setCueIn($v) + { + // we treat '' as NULL for temporal objects because DateTime('') == DateTime('now') + // -- which is unexpected, to say the least. + if ($v === null || $v === '') { + $dt = null; + } elseif ($v instanceof DateTime) { + $dt = $v; + } else { + // some string/numeric value passed; we normalize that so that we can + // validate it. + try { + if (is_numeric($v)) { // if it's a unix timestamp + $dt = new DateTime('@'.$v, new DateTimeZone('UTC')); + // We have to explicitly specify and then change the time zone because of a + // DateTime bug: http://bugs.php.net/bug.php?id=43003 + $dt->setTimeZone(new DateTimeZone(date_default_timezone_get())); + } else { + $dt = new DateTime($v); + } + } catch (Exception $x) { + throw new PropelException('Error parsing date/time value: ' . var_export($v, true), $x); + } + } + + if ( $this->cue_in !== null || $dt !== null ) { + // (nested ifs are a little easier to read in this case) + + $currNorm = ($this->cue_in !== null && $tmpDt = new DateTime($this->cue_in)) ? $tmpDt->format('H:i:s') : null; + $newNorm = ($dt !== null) ? $dt->format('H:i:s') : null; + + if ( ($currNorm !== $newNorm) // normalized values don't match + || ($dt->format('H:i:s') === '00:00:00') // or the entered value matches the default + ) + { + $this->cue_in = ($dt ? $dt->format('H:i:s') : null); + $this->modifiedColumns[] = CcSchedulePeer::CUE_IN; + } + } // if either are not null + + return $this; + } // setCueIn() + + /** + * Sets the value of [cue_out] column to a normalized version of the date/time value specified. + * + * @param mixed $v string, integer (timestamp), or DateTime value. Empty string will + * be treated as NULL for temporal objects. + * @return CcSchedule The current object (for fluent API support) + */ + public function setCueOut($v) + { + // we treat '' as NULL for temporal objects because DateTime('') == DateTime('now') + // -- which is unexpected, to say the least. + if ($v === null || $v === '') { + $dt = null; + } elseif ($v instanceof DateTime) { + $dt = $v; + } else { + // some string/numeric value passed; we normalize that so that we can + // validate it. + try { + if (is_numeric($v)) { // if it's a unix timestamp + $dt = new DateTime('@'.$v, new DateTimeZone('UTC')); + // We have to explicitly specify and then change the time zone because of a + // DateTime bug: http://bugs.php.net/bug.php?id=43003 + $dt->setTimeZone(new DateTimeZone(date_default_timezone_get())); + } else { + $dt = new DateTime($v); + } + } catch (Exception $x) { + throw new PropelException('Error parsing date/time value: ' . var_export($v, true), $x); + } + } + + if ( $this->cue_out !== null || $dt !== null ) { + // (nested ifs are a little easier to read in this case) + + $currNorm = ($this->cue_out !== null && $tmpDt = new DateTime($this->cue_out)) ? $tmpDt->format('H:i:s') : null; + $newNorm = ($dt !== null) ? $dt->format('H:i:s') : null; + + if ( ($currNorm !== $newNorm) // normalized values don't match + || ($dt->format('H:i:s') === '00:00:00') // or the entered value matches the default + ) + { + $this->cue_out = ($dt ? $dt->format('H:i:s') : null); + $this->modifiedColumns[] = CcSchedulePeer::CUE_OUT; + } + } // if either are not null + + return $this; + } // setCueOut() + + /** + * Indicates whether the columns in this object are only set to default values. + * + * This method can be used in conjunction with isModified() to indicate whether an object is both + * modified _and_ has some values set which are non-default. + * + * @return boolean Whether the columns in this object are only been set with default values. + */ + public function hasOnlyDefaultValues() + { + if ($this->clip_length !== '00:00:00') { + return false; + } + + if ($this->fade_in !== '00:00:00') { + return false; + } + + if ($this->fade_out !== '00:00:00') { + return false; + } + + if ($this->cue_in !== '00:00:00') { + return false; + } + + if ($this->cue_out !== '00:00:00') { + return false; + } + + // otherwise, everything was equal, so return TRUE + return true; + } // hasOnlyDefaultValues() + + /** + * Hydrates (populates) the object variables with values from the database resultset. + * + * An offset (0-based "start column") is specified so that objects can be hydrated + * with a subset of the columns in the resultset rows. This is needed, for example, + * for results of JOIN queries where the resultset row includes columns from two or + * more tables. + * + * @param array $row The row returned by PDOStatement->fetch(PDO::FETCH_NUM) + * @param int $startcol 0-based offset column which indicates which restultset column to start with. + * @param boolean $rehydrate Whether this object is being re-hydrated from the database. + * @return int next starting column + * @throws PropelException - Any caught Exception will be rewrapped as a PropelException. + */ + public function hydrate($row, $startcol = 0, $rehydrate = false) + { + try { + + $this->id = ($row[$startcol + 0] !== null) ? (string) $row[$startcol + 0] : null; + $this->playlist_id = ($row[$startcol + 1] !== null) ? (int) $row[$startcol + 1] : null; + $this->starts = ($row[$startcol + 2] !== null) ? (string) $row[$startcol + 2] : null; + $this->ends = ($row[$startcol + 3] !== null) ? (string) $row[$startcol + 3] : null; + $this->group_id = ($row[$startcol + 4] !== null) ? (int) $row[$startcol + 4] : null; + $this->file_id = ($row[$startcol + 5] !== null) ? (int) $row[$startcol + 5] : null; + $this->clip_length = ($row[$startcol + 6] !== null) ? (string) $row[$startcol + 6] : null; + $this->fade_in = ($row[$startcol + 7] !== null) ? (string) $row[$startcol + 7] : null; + $this->fade_out = ($row[$startcol + 8] !== null) ? (string) $row[$startcol + 8] : null; + $this->cue_in = ($row[$startcol + 9] !== null) ? (string) $row[$startcol + 9] : null; + $this->cue_out = ($row[$startcol + 10] !== null) ? (string) $row[$startcol + 10] : null; + $this->resetModified(); + + $this->setNew(false); + + if ($rehydrate) { + $this->ensureConsistency(); + } + + return $startcol + 11; // 11 = CcSchedulePeer::NUM_COLUMNS - CcSchedulePeer::NUM_LAZY_LOAD_COLUMNS). + + } catch (Exception $e) { + throw new PropelException("Error populating CcSchedule object", $e); + } + } + + /** + * Checks and repairs the internal consistency of the object. + * + * This method is executed after an already-instantiated object is re-hydrated + * from the database. It exists to check any foreign keys to make sure that + * the objects related to the current object are correct based on foreign key. + * + * You can override this method in the stub class, but you should always invoke + * the base method from the overridden method (i.e. parent::ensureConsistency()), + * in case your model changes. + * + * @throws PropelException + */ + public function ensureConsistency() + { + + } // ensureConsistency + + /** + * Reloads this object from datastore based on primary key and (optionally) resets all associated objects. + * + * This will only work if the object has been saved and has a valid primary key set. + * + * @param boolean $deep (optional) Whether to also de-associated any related objects. + * @param PropelPDO $con (optional) The PropelPDO connection to use. + * @return void + * @throws PropelException - if this object is deleted, unsaved or doesn't have pk match in db + */ + public function reload($deep = false, PropelPDO $con = null) + { + if ($this->isDeleted()) { + throw new PropelException("Cannot reload a deleted object."); + } + + if ($this->isNew()) { + throw new PropelException("Cannot reload an unsaved object."); + } + + if ($con === null) { + $con = Propel::getConnection(CcSchedulePeer::DATABASE_NAME, Propel::CONNECTION_READ); + } + + // We don't need to alter the object instance pool; we're just modifying this instance + // already in the pool. + + $stmt = CcSchedulePeer::doSelectStmt($this->buildPkeyCriteria(), $con); + $row = $stmt->fetch(PDO::FETCH_NUM); + $stmt->closeCursor(); + if (!$row) { + throw new PropelException('Cannot find matching row in the database to reload object values.'); + } + $this->hydrate($row, 0, true); // rehydrate + + if ($deep) { // also de-associate any related objects? + + } // if (deep) + } + + /** + * Removes this object from datastore and sets delete attribute. + * + * @param PropelPDO $con + * @return void + * @throws PropelException + * @see BaseObject::setDeleted() + * @see BaseObject::isDeleted() + */ + public function delete(PropelPDO $con = null) + { + if ($this->isDeleted()) { + throw new PropelException("This object has already been deleted."); + } + + if ($con === null) { + $con = Propel::getConnection(CcSchedulePeer::DATABASE_NAME, Propel::CONNECTION_WRITE); + } + + $con->beginTransaction(); + try { + $ret = $this->preDelete($con); + if ($ret) { + CcScheduleQuery::create() + ->filterByPrimaryKey($this->getPrimaryKey()) + ->delete($con); + $this->postDelete($con); + $con->commit(); + $this->setDeleted(true); + } else { + $con->commit(); + } + } catch (PropelException $e) { + $con->rollBack(); + throw $e; + } + } + + /** + * Persists this object to the database. + * + * If the object is new, it inserts it; otherwise an update is performed. + * All modified related objects will also be persisted in the doSave() + * method. This method wraps all precipitate database operations in a + * single transaction. + * + * @param PropelPDO $con + * @return int The number of rows affected by this insert/update and any referring fk objects' save() operations. + * @throws PropelException + * @see doSave() + */ + public function save(PropelPDO $con = null) + { + if ($this->isDeleted()) { + throw new PropelException("You cannot save an object that has been deleted."); + } + + if ($con === null) { + $con = Propel::getConnection(CcSchedulePeer::DATABASE_NAME, Propel::CONNECTION_WRITE); + } + + $con->beginTransaction(); + $isInsert = $this->isNew(); + try { + $ret = $this->preSave($con); + if ($isInsert) { + $ret = $ret && $this->preInsert($con); + } else { + $ret = $ret && $this->preUpdate($con); + } + if ($ret) { + $affectedRows = $this->doSave($con); + if ($isInsert) { + $this->postInsert($con); + } else { + $this->postUpdate($con); + } + $this->postSave($con); + CcSchedulePeer::addInstanceToPool($this); + } else { + $affectedRows = 0; + } + $con->commit(); + return $affectedRows; + } catch (PropelException $e) { + $con->rollBack(); + throw $e; + } + } + + /** + * Performs the work of inserting or updating the row in the database. + * + * If the object is new, it inserts it; otherwise an update is performed. + * All related objects are also updated in this method. + * + * @param PropelPDO $con + * @return int The number of rows affected by this insert/update and any referring fk objects' save() operations. + * @throws PropelException + * @see save() + */ + protected function doSave(PropelPDO $con) + { + $affectedRows = 0; // initialize var to track total num of affected rows + if (!$this->alreadyInSave) { + $this->alreadyInSave = true; + + + // If this object has been modified, then save it to the database. + if ($this->isModified()) { + if ($this->isNew()) { + $criteria = $this->buildCriteria(); + $pk = BasePeer::doInsert($criteria, $con); + $affectedRows = 1; + $this->setNew(false); + } else { + $affectedRows = CcSchedulePeer::doUpdate($this, $con); + } + + $this->resetModified(); // [HL] After being saved an object is no longer 'modified' + } + + $this->alreadyInSave = false; + + } + return $affectedRows; + } // doSave() + + /** + * Array of ValidationFailed objects. + * @var array ValidationFailed[] + */ + protected $validationFailures = array(); + + /** + * Gets any ValidationFailed objects that resulted from last call to validate(). + * + * + * @return array ValidationFailed[] + * @see validate() + */ + public function getValidationFailures() + { + return $this->validationFailures; + } + + /** + * Validates the objects modified field values and all objects related to this table. + * + * If $columns is either a column name or an array of column names + * only those columns are validated. + * + * @param mixed $columns Column name or an array of column names. + * @return boolean Whether all columns pass validation. + * @see doValidate() + * @see getValidationFailures() + */ + public function validate($columns = null) + { + $res = $this->doValidate($columns); + if ($res === true) { + $this->validationFailures = array(); + return true; + } else { + $this->validationFailures = $res; + return false; + } + } + + /** + * This function performs the validation work for complex object models. + * + * In addition to checking the current object, all related objects will + * also be validated. If all pass then true is returned; otherwise + * an aggreagated array of ValidationFailed objects will be returned. + * + * @param array $columns Array of column names to validate. + * @return mixed true if all validations pass; array of ValidationFailed objets otherwise. + */ + protected function doValidate($columns = null) + { + if (!$this->alreadyInValidation) { + $this->alreadyInValidation = true; + $retval = null; + + $failureMap = array(); + + + if (($retval = CcSchedulePeer::doValidate($this, $columns)) !== true) { + $failureMap = array_merge($failureMap, $retval); + } + + + + $this->alreadyInValidation = false; + } + + return (!empty($failureMap) ? $failureMap : true); + } + + /** + * Retrieves a field from the object by name passed in as a string. + * + * @param string $name name + * @param string $type The type of fieldname the $name is of: + * one of the class type constants BasePeer::TYPE_PHPNAME, BasePeer::TYPE_STUDLYPHPNAME + * BasePeer::TYPE_COLNAME, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_NUM + * @return mixed Value of field. + */ + public function getByName($name, $type = BasePeer::TYPE_PHPNAME) + { + $pos = CcSchedulePeer::translateFieldName($name, $type, BasePeer::TYPE_NUM); + $field = $this->getByPosition($pos); + return $field; + } + + /** + * Retrieves a field from the object by Position as specified in the xml schema. + * Zero-based. + * + * @param int $pos position in xml schema + * @return mixed Value of field at $pos + */ + public function getByPosition($pos) + { + switch($pos) { + case 0: + return $this->getId(); + break; + case 1: + return $this->getPlaylistId(); + break; + case 2: + return $this->getStarts(); + break; + case 3: + return $this->getEnds(); + break; + case 4: + return $this->getGroupId(); + break; + case 5: + return $this->getFileId(); + break; + case 6: + return $this->getClipLength(); + break; + case 7: + return $this->getFadeIn(); + break; + case 8: + return $this->getFadeOut(); + break; + case 9: + return $this->getCueIn(); + break; + case 10: + return $this->getCueOut(); + break; + default: + return null; + break; + } // switch() + } + + /** + * Exports the object as an array. + * + * You can specify the key type of the array by passing one of the class + * type constants. + * + * @param string $keyType (optional) One of the class type constants BasePeer::TYPE_PHPNAME, BasePeer::TYPE_STUDLYPHPNAME, + * BasePeer::TYPE_COLNAME, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_NUM. + * Defaults to BasePeer::TYPE_PHPNAME. + * @param boolean $includeLazyLoadColumns (optional) Whether to include lazy loaded columns. Defaults to TRUE. + * + * @return array an associative array containing the field names (as keys) and field values + */ + public function toArray($keyType = BasePeer::TYPE_PHPNAME, $includeLazyLoadColumns = true) + { + $keys = CcSchedulePeer::getFieldNames($keyType); + $result = array( + $keys[0] => $this->getId(), + $keys[1] => $this->getPlaylistId(), + $keys[2] => $this->getStarts(), + $keys[3] => $this->getEnds(), + $keys[4] => $this->getGroupId(), + $keys[5] => $this->getFileId(), + $keys[6] => $this->getClipLength(), + $keys[7] => $this->getFadeIn(), + $keys[8] => $this->getFadeOut(), + $keys[9] => $this->getCueIn(), + $keys[10] => $this->getCueOut(), + ); + return $result; + } + + /** + * Sets a field from the object by name passed in as a string. + * + * @param string $name peer name + * @param mixed $value field value + * @param string $type The type of fieldname the $name is of: + * one of the class type constants BasePeer::TYPE_PHPNAME, BasePeer::TYPE_STUDLYPHPNAME + * BasePeer::TYPE_COLNAME, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_NUM + * @return void + */ + public function setByName($name, $value, $type = BasePeer::TYPE_PHPNAME) + { + $pos = CcSchedulePeer::translateFieldName($name, $type, BasePeer::TYPE_NUM); + return $this->setByPosition($pos, $value); + } + + /** + * Sets a field from the object by Position as specified in the xml schema. + * Zero-based. + * + * @param int $pos position in xml schema + * @param mixed $value field value + * @return void + */ + public function setByPosition($pos, $value) + { + switch($pos) { + case 0: + $this->setId($value); + break; + case 1: + $this->setPlaylistId($value); + break; + case 2: + $this->setStarts($value); + break; + case 3: + $this->setEnds($value); + break; + case 4: + $this->setGroupId($value); + break; + case 5: + $this->setFileId($value); + break; + case 6: + $this->setClipLength($value); + break; + case 7: + $this->setFadeIn($value); + break; + case 8: + $this->setFadeOut($value); + break; + case 9: + $this->setCueIn($value); + break; + case 10: + $this->setCueOut($value); + break; + } // switch() + } + + /** + * Populates the object using an array. + * + * This is particularly useful when populating an object from one of the + * request arrays (e.g. $_POST). This method goes through the column + * names, checking to see whether a matching key exists in populated + * array. If so the setByName() method is called for that column. + * + * You can specify the key type of the array by additionally passing one + * of the class type constants BasePeer::TYPE_PHPNAME, BasePeer::TYPE_STUDLYPHPNAME, + * BasePeer::TYPE_COLNAME, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_NUM. + * The default key type is the column's phpname (e.g. 'AuthorId') + * + * @param array $arr An array to populate the object from. + * @param string $keyType The type of keys the array uses. + * @return void + */ + public function fromArray($arr, $keyType = BasePeer::TYPE_PHPNAME) + { + $keys = CcSchedulePeer::getFieldNames($keyType); + + if (array_key_exists($keys[0], $arr)) $this->setId($arr[$keys[0]]); + if (array_key_exists($keys[1], $arr)) $this->setPlaylistId($arr[$keys[1]]); + if (array_key_exists($keys[2], $arr)) $this->setStarts($arr[$keys[2]]); + if (array_key_exists($keys[3], $arr)) $this->setEnds($arr[$keys[3]]); + if (array_key_exists($keys[4], $arr)) $this->setGroupId($arr[$keys[4]]); + if (array_key_exists($keys[5], $arr)) $this->setFileId($arr[$keys[5]]); + if (array_key_exists($keys[6], $arr)) $this->setClipLength($arr[$keys[6]]); + if (array_key_exists($keys[7], $arr)) $this->setFadeIn($arr[$keys[7]]); + if (array_key_exists($keys[8], $arr)) $this->setFadeOut($arr[$keys[8]]); + if (array_key_exists($keys[9], $arr)) $this->setCueIn($arr[$keys[9]]); + if (array_key_exists($keys[10], $arr)) $this->setCueOut($arr[$keys[10]]); + } + + /** + * Build a Criteria object containing the values of all modified columns in this object. + * + * @return Criteria The Criteria object containing all modified values. + */ + public function buildCriteria() + { + $criteria = new Criteria(CcSchedulePeer::DATABASE_NAME); + + if ($this->isColumnModified(CcSchedulePeer::ID)) $criteria->add(CcSchedulePeer::ID, $this->id); + if ($this->isColumnModified(CcSchedulePeer::PLAYLIST_ID)) $criteria->add(CcSchedulePeer::PLAYLIST_ID, $this->playlist_id); + if ($this->isColumnModified(CcSchedulePeer::STARTS)) $criteria->add(CcSchedulePeer::STARTS, $this->starts); + if ($this->isColumnModified(CcSchedulePeer::ENDS)) $criteria->add(CcSchedulePeer::ENDS, $this->ends); + if ($this->isColumnModified(CcSchedulePeer::GROUP_ID)) $criteria->add(CcSchedulePeer::GROUP_ID, $this->group_id); + if ($this->isColumnModified(CcSchedulePeer::FILE_ID)) $criteria->add(CcSchedulePeer::FILE_ID, $this->file_id); + if ($this->isColumnModified(CcSchedulePeer::CLIP_LENGTH)) $criteria->add(CcSchedulePeer::CLIP_LENGTH, $this->clip_length); + if ($this->isColumnModified(CcSchedulePeer::FADE_IN)) $criteria->add(CcSchedulePeer::FADE_IN, $this->fade_in); + if ($this->isColumnModified(CcSchedulePeer::FADE_OUT)) $criteria->add(CcSchedulePeer::FADE_OUT, $this->fade_out); + if ($this->isColumnModified(CcSchedulePeer::CUE_IN)) $criteria->add(CcSchedulePeer::CUE_IN, $this->cue_in); + if ($this->isColumnModified(CcSchedulePeer::CUE_OUT)) $criteria->add(CcSchedulePeer::CUE_OUT, $this->cue_out); + + return $criteria; + } + + /** + * Builds a Criteria object containing the primary key for this object. + * + * Unlike buildCriteria() this method includes the primary key values regardless + * of whether or not they have been modified. + * + * @return Criteria The Criteria object containing value(s) for primary key(s). + */ + public function buildPkeyCriteria() + { + $criteria = new Criteria(CcSchedulePeer::DATABASE_NAME); + $criteria->add(CcSchedulePeer::ID, $this->id); + + return $criteria; + } + + /** + * Returns the primary key for this object (row). + * @return string + */ + public function getPrimaryKey() + { + return $this->getId(); + } + + /** + * Generic method to set the primary key (id column). + * + * @param string $key Primary key. + * @return void + */ + public function setPrimaryKey($key) + { + $this->setId($key); + } + + /** + * Returns true if the primary key for this object is null. + * @return boolean + */ + public function isPrimaryKeyNull() + { + return null === $this->getId(); + } + + /** + * Sets contents of passed object to values from current object. + * + * If desired, this method can also make copies of all associated (fkey referrers) + * objects. + * + * @param object $copyObj An object of CcSchedule (or compatible) type. + * @param boolean $deepCopy Whether to also copy all rows that refer (by fkey) to the current row. + * @throws PropelException + */ + public function copyInto($copyObj, $deepCopy = false) + { + $copyObj->setId($this->id); + $copyObj->setPlaylistId($this->playlist_id); + $copyObj->setStarts($this->starts); + $copyObj->setEnds($this->ends); + $copyObj->setGroupId($this->group_id); + $copyObj->setFileId($this->file_id); + $copyObj->setClipLength($this->clip_length); + $copyObj->setFadeIn($this->fade_in); + $copyObj->setFadeOut($this->fade_out); + $copyObj->setCueIn($this->cue_in); + $copyObj->setCueOut($this->cue_out); + + $copyObj->setNew(true); + } + + /** + * Makes a copy of this object that will be inserted as a new row in table when saved. + * It creates a new object filling in the simple attributes, but skipping any primary + * keys that are defined for the table. + * + * If desired, this method can also make copies of all associated (fkey referrers) + * objects. + * + * @param boolean $deepCopy Whether to also copy all rows that refer (by fkey) to the current row. + * @return CcSchedule Clone of current object. + * @throws PropelException + */ + public function copy($deepCopy = false) + { + // we use get_class(), because this might be a subclass + $clazz = get_class($this); + $copyObj = new $clazz(); + $this->copyInto($copyObj, $deepCopy); + return $copyObj; + } + + /** + * Returns a peer instance associated with this om. + * + * Since Peer classes are not to have any instance attributes, this method returns the + * same instance for all member of this class. The method could therefore + * be static, but this would prevent one from overriding the behavior. + * + * @return CcSchedulePeer + */ + public function getPeer() + { + if (self::$peer === null) { + self::$peer = new CcSchedulePeer(); + } + return self::$peer; + } + + /** + * Clears the current object and sets all attributes to their default values + */ + public function clear() + { + $this->id = null; + $this->playlist_id = null; + $this->starts = null; + $this->ends = null; + $this->group_id = null; + $this->file_id = null; + $this->clip_length = null; + $this->fade_in = null; + $this->fade_out = null; + $this->cue_in = null; + $this->cue_out = null; + $this->alreadyInSave = false; + $this->alreadyInValidation = false; + $this->clearAllReferences(); + $this->applyDefaultValues(); + $this->resetModified(); + $this->setNew(true); + $this->setDeleted(false); + } + + /** + * Resets all collections of referencing foreign keys. + * + * This method is a user-space workaround for PHP's inability to garbage collect objects + * with circular references. This is currently necessary when using Propel in certain + * daemon or large-volumne/high-memory operations. + * + * @param boolean $deep Whether to also clear the references on all associated objects. + */ + public function clearAllReferences($deep = false) + { + if ($deep) { + } // if ($deep) + + } + + /** + * Catches calls to virtual methods + */ + public function __call($name, $params) + { + if (preg_match('/get(\w+)/', $name, $matches)) { + $virtualColumn = $matches[1]; + if ($this->hasVirtualColumn($virtualColumn)) { + return $this->getVirtualColumn($virtualColumn); + } + // no lcfirst in php<5.3... + $virtualColumn[0] = strtolower($virtualColumn[0]); + if ($this->hasVirtualColumn($virtualColumn)) { + return $this->getVirtualColumn($virtualColumn); + } + } + throw new PropelException('Call to undefined method: ' . $name); + } + +} // BaseCcSchedule diff --git a/application/models/campcaster/om/BaseCcSchedulePeer.php b/application/models/campcaster/om/BaseCcSchedulePeer.php new file mode 100644 index 000000000..c62a304c7 --- /dev/null +++ b/application/models/campcaster/om/BaseCcSchedulePeer.php @@ -0,0 +1,780 @@ + array ('Id', 'PlaylistId', 'Starts', 'Ends', 'GroupId', 'FileId', 'ClipLength', 'FadeIn', 'FadeOut', 'CueIn', 'CueOut', ), + BasePeer::TYPE_STUDLYPHPNAME => array ('id', 'playlistId', 'starts', 'ends', 'groupId', 'fileId', 'clipLength', 'fadeIn', 'fadeOut', 'cueIn', 'cueOut', ), + BasePeer::TYPE_COLNAME => array (self::ID, self::PLAYLIST_ID, self::STARTS, self::ENDS, self::GROUP_ID, self::FILE_ID, self::CLIP_LENGTH, self::FADE_IN, self::FADE_OUT, self::CUE_IN, self::CUE_OUT, ), + BasePeer::TYPE_RAW_COLNAME => array ('ID', 'PLAYLIST_ID', 'STARTS', 'ENDS', 'GROUP_ID', 'FILE_ID', 'CLIP_LENGTH', 'FADE_IN', 'FADE_OUT', 'CUE_IN', 'CUE_OUT', ), + BasePeer::TYPE_FIELDNAME => array ('id', 'playlist_id', 'starts', 'ends', 'group_id', 'file_id', 'clip_length', 'fade_in', 'fade_out', 'cue_in', 'cue_out', ), + BasePeer::TYPE_NUM => array (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, ) + ); + + /** + * holds an array of keys for quick access to the fieldnames array + * + * first dimension keys are the type constants + * e.g. self::$fieldNames[BasePeer::TYPE_PHPNAME]['Id'] = 0 + */ + private static $fieldKeys = array ( + BasePeer::TYPE_PHPNAME => array ('Id' => 0, 'PlaylistId' => 1, 'Starts' => 2, 'Ends' => 3, 'GroupId' => 4, 'FileId' => 5, 'ClipLength' => 6, 'FadeIn' => 7, 'FadeOut' => 8, 'CueIn' => 9, 'CueOut' => 10, ), + BasePeer::TYPE_STUDLYPHPNAME => array ('id' => 0, 'playlistId' => 1, 'starts' => 2, 'ends' => 3, 'groupId' => 4, 'fileId' => 5, 'clipLength' => 6, 'fadeIn' => 7, 'fadeOut' => 8, 'cueIn' => 9, 'cueOut' => 10, ), + BasePeer::TYPE_COLNAME => array (self::ID => 0, self::PLAYLIST_ID => 1, self::STARTS => 2, self::ENDS => 3, self::GROUP_ID => 4, self::FILE_ID => 5, self::CLIP_LENGTH => 6, self::FADE_IN => 7, self::FADE_OUT => 8, self::CUE_IN => 9, self::CUE_OUT => 10, ), + BasePeer::TYPE_RAW_COLNAME => array ('ID' => 0, 'PLAYLIST_ID' => 1, 'STARTS' => 2, 'ENDS' => 3, 'GROUP_ID' => 4, 'FILE_ID' => 5, 'CLIP_LENGTH' => 6, 'FADE_IN' => 7, 'FADE_OUT' => 8, 'CUE_IN' => 9, 'CUE_OUT' => 10, ), + BasePeer::TYPE_FIELDNAME => array ('id' => 0, 'playlist_id' => 1, 'starts' => 2, 'ends' => 3, 'group_id' => 4, 'file_id' => 5, 'clip_length' => 6, 'fade_in' => 7, 'fade_out' => 8, 'cue_in' => 9, 'cue_out' => 10, ), + BasePeer::TYPE_NUM => array (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, ) + ); + + /** + * Translates a fieldname to another type + * + * @param string $name field name + * @param string $fromType One of the class type constants BasePeer::TYPE_PHPNAME, BasePeer::TYPE_STUDLYPHPNAME + * BasePeer::TYPE_COLNAME, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_NUM + * @param string $toType One of the class type constants + * @return string translated name of the field. + * @throws PropelException - if the specified name could not be found in the fieldname mappings. + */ + static public function translateFieldName($name, $fromType, $toType) + { + $toNames = self::getFieldNames($toType); + $key = isset(self::$fieldKeys[$fromType][$name]) ? self::$fieldKeys[$fromType][$name] : null; + if ($key === null) { + throw new PropelException("'$name' could not be found in the field names of type '$fromType'. These are: " . print_r(self::$fieldKeys[$fromType], true)); + } + return $toNames[$key]; + } + + /** + * Returns an array of field names. + * + * @param string $type The type of fieldnames to return: + * One of the class type constants BasePeer::TYPE_PHPNAME, BasePeer::TYPE_STUDLYPHPNAME + * BasePeer::TYPE_COLNAME, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_NUM + * @return array A list of field names + */ + + static public function getFieldNames($type = BasePeer::TYPE_PHPNAME) + { + if (!array_key_exists($type, self::$fieldNames)) { + throw new PropelException('Method getFieldNames() expects the parameter $type to be one of the class constants BasePeer::TYPE_PHPNAME, BasePeer::TYPE_STUDLYPHPNAME, BasePeer::TYPE_COLNAME, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_NUM. ' . $type . ' was given.'); + } + return self::$fieldNames[$type]; + } + + /** + * Convenience method which changes table.column to alias.column. + * + * Using this method you can maintain SQL abstraction while using column aliases. + * + * $c->addAlias("alias1", TablePeer::TABLE_NAME); + * $c->addJoin(TablePeer::alias("alias1", TablePeer::PRIMARY_KEY_COLUMN), TablePeer::PRIMARY_KEY_COLUMN); + * + * @param string $alias The alias for the current table. + * @param string $column The column name for current table. (i.e. CcSchedulePeer::COLUMN_NAME). + * @return string + */ + public static function alias($alias, $column) + { + return str_replace(CcSchedulePeer::TABLE_NAME.'.', $alias.'.', $column); + } + + /** + * Add all the columns needed to create a new object. + * + * Note: any columns that were marked with lazyLoad="true" in the + * XML schema will not be added to the select list and only loaded + * on demand. + * + * @param Criteria $criteria object containing the columns to add. + * @param string $alias optional table alias + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function addSelectColumns(Criteria $criteria, $alias = null) + { + if (null === $alias) { + $criteria->addSelectColumn(CcSchedulePeer::ID); + $criteria->addSelectColumn(CcSchedulePeer::PLAYLIST_ID); + $criteria->addSelectColumn(CcSchedulePeer::STARTS); + $criteria->addSelectColumn(CcSchedulePeer::ENDS); + $criteria->addSelectColumn(CcSchedulePeer::GROUP_ID); + $criteria->addSelectColumn(CcSchedulePeer::FILE_ID); + $criteria->addSelectColumn(CcSchedulePeer::CLIP_LENGTH); + $criteria->addSelectColumn(CcSchedulePeer::FADE_IN); + $criteria->addSelectColumn(CcSchedulePeer::FADE_OUT); + $criteria->addSelectColumn(CcSchedulePeer::CUE_IN); + $criteria->addSelectColumn(CcSchedulePeer::CUE_OUT); + } else { + $criteria->addSelectColumn($alias . '.ID'); + $criteria->addSelectColumn($alias . '.PLAYLIST_ID'); + $criteria->addSelectColumn($alias . '.STARTS'); + $criteria->addSelectColumn($alias . '.ENDS'); + $criteria->addSelectColumn($alias . '.GROUP_ID'); + $criteria->addSelectColumn($alias . '.FILE_ID'); + $criteria->addSelectColumn($alias . '.CLIP_LENGTH'); + $criteria->addSelectColumn($alias . '.FADE_IN'); + $criteria->addSelectColumn($alias . '.FADE_OUT'); + $criteria->addSelectColumn($alias . '.CUE_IN'); + $criteria->addSelectColumn($alias . '.CUE_OUT'); + } + } + + /** + * Returns the number of rows matching criteria. + * + * @param Criteria $criteria + * @param boolean $distinct Whether to select only distinct columns; deprecated: use Criteria->setDistinct() instead. + * @param PropelPDO $con + * @return int Number of matching rows. + */ + public static function doCount(Criteria $criteria, $distinct = false, PropelPDO $con = null) + { + // we may modify criteria, so copy it first + $criteria = clone $criteria; + + // We need to set the primary table name, since in the case that there are no WHERE columns + // it will be impossible for the BasePeer::createSelectSql() method to determine which + // tables go into the FROM clause. + $criteria->setPrimaryTableName(CcSchedulePeer::TABLE_NAME); + + if ($distinct && !in_array(Criteria::DISTINCT, $criteria->getSelectModifiers())) { + $criteria->setDistinct(); + } + + if (!$criteria->hasSelectClause()) { + CcSchedulePeer::addSelectColumns($criteria); + } + + $criteria->clearOrderByColumns(); // ORDER BY won't ever affect the count + $criteria->setDbName(self::DATABASE_NAME); // Set the correct dbName + + if ($con === null) { + $con = Propel::getConnection(CcSchedulePeer::DATABASE_NAME, Propel::CONNECTION_READ); + } + // BasePeer returns a PDOStatement + $stmt = BasePeer::doCount($criteria, $con); + + if ($row = $stmt->fetch(PDO::FETCH_NUM)) { + $count = (int) $row[0]; + } else { + $count = 0; // no rows returned; we infer that means 0 matches. + } + $stmt->closeCursor(); + return $count; + } + /** + * Method to select one object from the DB. + * + * @param Criteria $criteria object used to create the SELECT statement. + * @param PropelPDO $con + * @return CcSchedule + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function doSelectOne(Criteria $criteria, PropelPDO $con = null) + { + $critcopy = clone $criteria; + $critcopy->setLimit(1); + $objects = CcSchedulePeer::doSelect($critcopy, $con); + if ($objects) { + return $objects[0]; + } + return null; + } + /** + * Method to do selects. + * + * @param Criteria $criteria The Criteria object used to build the SELECT statement. + * @param PropelPDO $con + * @return array Array of selected Objects + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function doSelect(Criteria $criteria, PropelPDO $con = null) + { + return CcSchedulePeer::populateObjects(CcSchedulePeer::doSelectStmt($criteria, $con)); + } + /** + * Prepares the Criteria object and uses the parent doSelect() method to execute a PDOStatement. + * + * Use this method directly if you want to work with an executed statement durirectly (for example + * to perform your own object hydration). + * + * @param Criteria $criteria The Criteria object used to build the SELECT statement. + * @param PropelPDO $con The connection to use + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + * @return PDOStatement The executed PDOStatement object. + * @see BasePeer::doSelect() + */ + public static function doSelectStmt(Criteria $criteria, PropelPDO $con = null) + { + if ($con === null) { + $con = Propel::getConnection(CcSchedulePeer::DATABASE_NAME, Propel::CONNECTION_READ); + } + + if (!$criteria->hasSelectClause()) { + $criteria = clone $criteria; + CcSchedulePeer::addSelectColumns($criteria); + } + + // Set the correct dbName + $criteria->setDbName(self::DATABASE_NAME); + + // BasePeer returns a PDOStatement + return BasePeer::doSelect($criteria, $con); + } + /** + * Adds an object to the instance pool. + * + * Propel keeps cached copies of objects in an instance pool when they are retrieved + * from the database. In some cases -- especially when you override doSelect*() + * methods in your stub classes -- you may need to explicitly add objects + * to the cache in order to ensure that the same objects are always returned by doSelect*() + * and retrieveByPK*() calls. + * + * @param CcSchedule $value A CcSchedule object. + * @param string $key (optional) key to use for instance map (for performance boost if key was already calculated externally). + */ + public static function addInstanceToPool(CcSchedule $obj, $key = null) + { + if (Propel::isInstancePoolingEnabled()) { + if ($key === null) { + $key = (string) $obj->getId(); + } // if key === null + self::$instances[$key] = $obj; + } + } + + /** + * Removes an object from the instance pool. + * + * Propel keeps cached copies of objects in an instance pool when they are retrieved + * from the database. In some cases -- especially when you override doDelete + * methods in your stub classes -- you may need to explicitly remove objects + * from the cache in order to prevent returning objects that no longer exist. + * + * @param mixed $value A CcSchedule object or a primary key value. + */ + public static function removeInstanceFromPool($value) + { + if (Propel::isInstancePoolingEnabled() && $value !== null) { + if (is_object($value) && $value instanceof CcSchedule) { + $key = (string) $value->getId(); + } elseif (is_scalar($value)) { + // assume we've been passed a primary key + $key = (string) $value; + } else { + $e = new PropelException("Invalid value passed to removeInstanceFromPool(). Expected primary key or CcSchedule object; got " . (is_object($value) ? get_class($value) . ' object.' : var_export($value,true))); + throw $e; + } + + unset(self::$instances[$key]); + } + } // removeInstanceFromPool() + + /** + * Retrieves a string version of the primary key from the DB resultset row that can be used to uniquely identify a row in this table. + * + * For tables with a single-column primary key, that simple pkey value will be returned. For tables with + * a multi-column primary key, a serialize()d version of the primary key will be returned. + * + * @param string $key The key (@see getPrimaryKeyHash()) for this instance. + * @return CcSchedule Found object or NULL if 1) no instance exists for specified key or 2) instance pooling has been disabled. + * @see getPrimaryKeyHash() + */ + public static function getInstanceFromPool($key) + { + if (Propel::isInstancePoolingEnabled()) { + if (isset(self::$instances[$key])) { + return self::$instances[$key]; + } + } + return null; // just to be explicit + } + + /** + * Clear the instance pool. + * + * @return void + */ + public static function clearInstancePool() + { + self::$instances = array(); + } + + /** + * Method to invalidate the instance pool of all tables related to cc_schedule + * by a foreign key with ON DELETE CASCADE + */ + public static function clearRelatedInstancePool() + { + } + + /** + * Retrieves a string version of the primary key from the DB resultset row that can be used to uniquely identify a row in this table. + * + * For tables with a single-column primary key, that simple pkey value will be returned. For tables with + * a multi-column primary key, a serialize()d version of the primary key will be returned. + * + * @param array $row PropelPDO resultset row. + * @param int $startcol The 0-based offset for reading from the resultset row. + * @return string A string version of PK or NULL if the components of primary key in result array are all null. + */ + public static function getPrimaryKeyHashFromRow($row, $startcol = 0) + { + // If the PK cannot be derived from the row, return NULL. + if ($row[$startcol] === null) { + return null; + } + return (string) $row[$startcol]; + } + + /** + * Retrieves the primary key from the DB resultset row + * For tables with a single-column primary key, that simple pkey value will be returned. For tables with + * a multi-column primary key, an array of the primary key columns will be returned. + * + * @param array $row PropelPDO resultset row. + * @param int $startcol The 0-based offset for reading from the resultset row. + * @return mixed The primary key of the row + */ + public static function getPrimaryKeyFromRow($row, $startcol = 0) + { + return (string) $row[$startcol]; + } + + /** + * The returned array will contain objects of the default type or + * objects that inherit from the default. + * + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function populateObjects(PDOStatement $stmt) + { + $results = array(); + + // set the class once to avoid overhead in the loop + $cls = CcSchedulePeer::getOMClass(false); + // populate the object(s) + while ($row = $stmt->fetch(PDO::FETCH_NUM)) { + $key = CcSchedulePeer::getPrimaryKeyHashFromRow($row, 0); + if (null !== ($obj = CcSchedulePeer::getInstanceFromPool($key))) { + // We no longer rehydrate the object, since this can cause data loss. + // See http://www.propelorm.org/ticket/509 + // $obj->hydrate($row, 0, true); // rehydrate + $results[] = $obj; + } else { + $obj = new $cls(); + $obj->hydrate($row); + $results[] = $obj; + CcSchedulePeer::addInstanceToPool($obj, $key); + } // if key exists + } + $stmt->closeCursor(); + return $results; + } + /** + * Populates an object of the default type or an object that inherit from the default. + * + * @param array $row PropelPDO resultset row. + * @param int $startcol The 0-based offset for reading from the resultset row. + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + * @return array (CcSchedule object, last column rank) + */ + public static function populateObject($row, $startcol = 0) + { + $key = CcSchedulePeer::getPrimaryKeyHashFromRow($row, $startcol); + if (null !== ($obj = CcSchedulePeer::getInstanceFromPool($key))) { + // We no longer rehydrate the object, since this can cause data loss. + // See http://www.propelorm.org/ticket/509 + // $obj->hydrate($row, $startcol, true); // rehydrate + $col = $startcol + CcSchedulePeer::NUM_COLUMNS; + } else { + $cls = CcSchedulePeer::OM_CLASS; + $obj = new $cls(); + $col = $obj->hydrate($row, $startcol); + CcSchedulePeer::addInstanceToPool($obj, $key); + } + return array($obj, $col); + } + /** + * Returns the TableMap related to this peer. + * This method is not needed for general use but a specific application could have a need. + * @return TableMap + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function getTableMap() + { + return Propel::getDatabaseMap(self::DATABASE_NAME)->getTable(self::TABLE_NAME); + } + + /** + * Add a TableMap instance to the database for this peer class. + */ + public static function buildTableMap() + { + $dbMap = Propel::getDatabaseMap(BaseCcSchedulePeer::DATABASE_NAME); + if (!$dbMap->hasTable(BaseCcSchedulePeer::TABLE_NAME)) + { + $dbMap->addTableObject(new CcScheduleTableMap()); + } + } + + /** + * The class that the Peer will make instances of. + * + * If $withPrefix is true, the returned path + * uses a dot-path notation which is tranalted into a path + * relative to a location on the PHP include_path. + * (e.g. path.to.MyClass -> 'path/to/MyClass.php') + * + * @param boolean $withPrefix Whether or not to return the path with the class name + * @return string path.to.ClassName + */ + public static function getOMClass($withPrefix = true) + { + return $withPrefix ? CcSchedulePeer::CLASS_DEFAULT : CcSchedulePeer::OM_CLASS; + } + + /** + * Method perform an INSERT on the database, given a CcSchedule or Criteria object. + * + * @param mixed $values Criteria or CcSchedule object containing data that is used to create the INSERT statement. + * @param PropelPDO $con the PropelPDO connection to use + * @return mixed The new primary key. + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function doInsert($values, PropelPDO $con = null) + { + if ($con === null) { + $con = Propel::getConnection(CcSchedulePeer::DATABASE_NAME, Propel::CONNECTION_WRITE); + } + + if ($values instanceof Criteria) { + $criteria = clone $values; // rename for clarity + } else { + $criteria = $values->buildCriteria(); // build Criteria from CcSchedule object + } + + + // Set the correct dbName + $criteria->setDbName(self::DATABASE_NAME); + + try { + // use transaction because $criteria could contain info + // for more than one table (I guess, conceivably) + $con->beginTransaction(); + $pk = BasePeer::doInsert($criteria, $con); + $con->commit(); + } catch(PropelException $e) { + $con->rollBack(); + throw $e; + } + + return $pk; + } + + /** + * Method perform an UPDATE on the database, given a CcSchedule or Criteria object. + * + * @param mixed $values Criteria or CcSchedule object containing data that is used to create the UPDATE statement. + * @param PropelPDO $con The connection to use (specify PropelPDO connection object to exert more control over transactions). + * @return int The number of affected rows (if supported by underlying database driver). + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function doUpdate($values, PropelPDO $con = null) + { + if ($con === null) { + $con = Propel::getConnection(CcSchedulePeer::DATABASE_NAME, Propel::CONNECTION_WRITE); + } + + $selectCriteria = new Criteria(self::DATABASE_NAME); + + if ($values instanceof Criteria) { + $criteria = clone $values; // rename for clarity + + $comparison = $criteria->getComparison(CcSchedulePeer::ID); + $value = $criteria->remove(CcSchedulePeer::ID); + if ($value) { + $selectCriteria->add(CcSchedulePeer::ID, $value, $comparison); + } else { + $selectCriteria->setPrimaryTableName(CcSchedulePeer::TABLE_NAME); + } + + } else { // $values is CcSchedule object + $criteria = $values->buildCriteria(); // gets full criteria + $selectCriteria = $values->buildPkeyCriteria(); // gets criteria w/ primary key(s) + } + + // set the correct dbName + $criteria->setDbName(self::DATABASE_NAME); + + return BasePeer::doUpdate($selectCriteria, $criteria, $con); + } + + /** + * Method to DELETE all rows from the cc_schedule table. + * + * @return int The number of affected rows (if supported by underlying database driver). + */ + public static function doDeleteAll($con = null) + { + if ($con === null) { + $con = Propel::getConnection(CcSchedulePeer::DATABASE_NAME, Propel::CONNECTION_WRITE); + } + $affectedRows = 0; // initialize var to track total num of affected rows + try { + // use transaction because $criteria could contain info + // for more than one table or we could emulating ON DELETE CASCADE, etc. + $con->beginTransaction(); + $affectedRows += BasePeer::doDeleteAll(CcSchedulePeer::TABLE_NAME, $con, CcSchedulePeer::DATABASE_NAME); + // Because this db requires some delete cascade/set null emulation, we have to + // clear the cached instance *after* the emulation has happened (since + // instances get re-added by the select statement contained therein). + CcSchedulePeer::clearInstancePool(); + CcSchedulePeer::clearRelatedInstancePool(); + $con->commit(); + return $affectedRows; + } catch (PropelException $e) { + $con->rollBack(); + throw $e; + } + } + + /** + * Method perform a DELETE on the database, given a CcSchedule or Criteria object OR a primary key value. + * + * @param mixed $values Criteria or CcSchedule object or primary key or array of primary keys + * which is used to create the DELETE statement + * @param PropelPDO $con the connection to use + * @return int The number of affected rows (if supported by underlying database driver). This includes CASCADE-related rows + * if supported by native driver or if emulated using Propel. + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function doDelete($values, PropelPDO $con = null) + { + if ($con === null) { + $con = Propel::getConnection(CcSchedulePeer::DATABASE_NAME, Propel::CONNECTION_WRITE); + } + + if ($values instanceof Criteria) { + // invalidate the cache for all objects of this type, since we have no + // way of knowing (without running a query) what objects should be invalidated + // from the cache based on this Criteria. + CcSchedulePeer::clearInstancePool(); + // rename for clarity + $criteria = clone $values; + } elseif ($values instanceof CcSchedule) { // it's a model object + // invalidate the cache for this single object + CcSchedulePeer::removeInstanceFromPool($values); + // create criteria based on pk values + $criteria = $values->buildPkeyCriteria(); + } else { // it's a primary key, or an array of pks + $criteria = new Criteria(self::DATABASE_NAME); + $criteria->add(CcSchedulePeer::ID, (array) $values, Criteria::IN); + // invalidate the cache for this object(s) + foreach ((array) $values as $singleval) { + CcSchedulePeer::removeInstanceFromPool($singleval); + } + } + + // Set the correct dbName + $criteria->setDbName(self::DATABASE_NAME); + + $affectedRows = 0; // initialize var to track total num of affected rows + + try { + // use transaction because $criteria could contain info + // for more than one table or we could emulating ON DELETE CASCADE, etc. + $con->beginTransaction(); + + $affectedRows += BasePeer::doDelete($criteria, $con); + CcSchedulePeer::clearRelatedInstancePool(); + $con->commit(); + return $affectedRows; + } catch (PropelException $e) { + $con->rollBack(); + throw $e; + } + } + + /** + * Validates all modified columns of given CcSchedule object. + * If parameter $columns is either a single column name or an array of column names + * than only those columns are validated. + * + * NOTICE: This does not apply to primary or foreign keys for now. + * + * @param CcSchedule $obj The object to validate. + * @param mixed $cols Column name or array of column names. + * + * @return mixed TRUE if all columns are valid or the error message of the first invalid column. + */ + public static function doValidate(CcSchedule $obj, $cols = null) + { + $columns = array(); + + if ($cols) { + $dbMap = Propel::getDatabaseMap(CcSchedulePeer::DATABASE_NAME); + $tableMap = $dbMap->getTable(CcSchedulePeer::TABLE_NAME); + + if (! is_array($cols)) { + $cols = array($cols); + } + + foreach ($cols as $colName) { + if ($tableMap->containsColumn($colName)) { + $get = 'get' . $tableMap->getColumn($colName)->getPhpName(); + $columns[$colName] = $obj->$get(); + } + } + } else { + + } + + return BasePeer::doValidate(CcSchedulePeer::DATABASE_NAME, CcSchedulePeer::TABLE_NAME, $columns); + } + + /** + * Retrieve a single object by pkey. + * + * @param string $pk the primary key. + * @param PropelPDO $con the connection to use + * @return CcSchedule + */ + public static function retrieveByPK($pk, PropelPDO $con = null) + { + + if (null !== ($obj = CcSchedulePeer::getInstanceFromPool((string) $pk))) { + return $obj; + } + + if ($con === null) { + $con = Propel::getConnection(CcSchedulePeer::DATABASE_NAME, Propel::CONNECTION_READ); + } + + $criteria = new Criteria(CcSchedulePeer::DATABASE_NAME); + $criteria->add(CcSchedulePeer::ID, $pk); + + $v = CcSchedulePeer::doSelect($criteria, $con); + + return !empty($v) > 0 ? $v[0] : null; + } + + /** + * Retrieve multiple objects by pkey. + * + * @param array $pks List of primary keys + * @param PropelPDO $con the connection to use + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function retrieveByPKs($pks, PropelPDO $con = null) + { + if ($con === null) { + $con = Propel::getConnection(CcSchedulePeer::DATABASE_NAME, Propel::CONNECTION_READ); + } + + $objs = null; + if (empty($pks)) { + $objs = array(); + } else { + $criteria = new Criteria(CcSchedulePeer::DATABASE_NAME); + $criteria->add(CcSchedulePeer::ID, $pks, Criteria::IN); + $objs = CcSchedulePeer::doSelect($criteria, $con); + } + return $objs; + } + +} // BaseCcSchedulePeer + +// This is the static code needed to register the TableMap for this table with the main Propel class. +// +BaseCcSchedulePeer::buildTableMap(); + diff --git a/application/models/campcaster/om/BaseCcScheduleQuery.php b/application/models/campcaster/om/BaseCcScheduleQuery.php new file mode 100644 index 000000000..22435bf77 --- /dev/null +++ b/application/models/campcaster/om/BaseCcScheduleQuery.php @@ -0,0 +1,515 @@ +setModelAlias($modelAlias); + } + if ($criteria instanceof Criteria) { + $query->mergeWith($criteria); + } + return $query; + } + + /** + * Find object by primary key + * Use instance pooling to avoid a database query if the object exists + * + * $obj = $c->findPk(12, $con); + * + * @param mixed $key Primary key to use for the query + * @param PropelPDO $con an optional connection object + * + * @return CcSchedule|array|mixed the result, formatted by the current formatter + */ + public function findPk($key, $con = null) + { + if ((null !== ($obj = CcSchedulePeer::getInstanceFromPool((string) $key))) && $this->getFormatter()->isObjectFormatter()) { + // the object is alredy in the instance pool + return $obj; + } else { + // the object has not been requested yet, or the formatter is not an object formatter + $criteria = $this->isKeepQuery() ? clone $this : $this; + $stmt = $criteria + ->filterByPrimaryKey($key) + ->getSelectStatement($con); + return $criteria->getFormatter()->init($criteria)->formatOne($stmt); + } + } + + /** + * Find objects by primary key + * + * $objs = $c->findPks(array(12, 56, 832), $con); + * + * @param array $keys Primary keys to use for the query + * @param PropelPDO $con an optional connection object + * + * @return PropelObjectCollection|array|mixed the list of results, formatted by the current formatter + */ + public function findPks($keys, $con = null) + { + $criteria = $this->isKeepQuery() ? clone $this : $this; + return $this + ->filterByPrimaryKeys($keys) + ->find($con); + } + + /** + * Filter the query by primary key + * + * @param mixed $key Primary key to use for the query + * + * @return CcScheduleQuery The current query, for fluid interface + */ + public function filterByPrimaryKey($key) + { + return $this->addUsingAlias(CcSchedulePeer::ID, $key, Criteria::EQUAL); + } + + /** + * Filter the query by a list of primary keys + * + * @param array $keys The list of primary key to use for the query + * + * @return CcScheduleQuery The current query, for fluid interface + */ + public function filterByPrimaryKeys($keys) + { + return $this->addUsingAlias(CcSchedulePeer::ID, $keys, Criteria::IN); + } + + /** + * Filter the query on the id column + * + * @param string|array $id The value to use as filter. + * Accepts an associative array('min' => $minValue, 'max' => $maxValue) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcScheduleQuery The current query, for fluid interface + */ + public function filterById($id = null, $comparison = null) + { + if (is_array($id) && null === $comparison) { + $comparison = Criteria::IN; + } + return $this->addUsingAlias(CcSchedulePeer::ID, $id, $comparison); + } + + /** + * Filter the query on the playlist_id column + * + * @param int|array $playlistId The value to use as filter. + * Accepts an associative array('min' => $minValue, 'max' => $maxValue) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcScheduleQuery The current query, for fluid interface + */ + public function filterByPlaylistId($playlistId = null, $comparison = null) + { + if (is_array($playlistId)) { + $useMinMax = false; + if (isset($playlistId['min'])) { + $this->addUsingAlias(CcSchedulePeer::PLAYLIST_ID, $playlistId['min'], Criteria::GREATER_EQUAL); + $useMinMax = true; + } + if (isset($playlistId['max'])) { + $this->addUsingAlias(CcSchedulePeer::PLAYLIST_ID, $playlistId['max'], Criteria::LESS_EQUAL); + $useMinMax = true; + } + if ($useMinMax) { + return $this; + } + if (null === $comparison) { + $comparison = Criteria::IN; + } + } + return $this->addUsingAlias(CcSchedulePeer::PLAYLIST_ID, $playlistId, $comparison); + } + + /** + * Filter the query on the starts column + * + * @param string|array $starts The value to use as filter. + * Accepts an associative array('min' => $minValue, 'max' => $maxValue) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcScheduleQuery The current query, for fluid interface + */ + public function filterByStarts($starts = null, $comparison = null) + { + if (is_array($starts)) { + $useMinMax = false; + if (isset($starts['min'])) { + $this->addUsingAlias(CcSchedulePeer::STARTS, $starts['min'], Criteria::GREATER_EQUAL); + $useMinMax = true; + } + if (isset($starts['max'])) { + $this->addUsingAlias(CcSchedulePeer::STARTS, $starts['max'], Criteria::LESS_EQUAL); + $useMinMax = true; + } + if ($useMinMax) { + return $this; + } + if (null === $comparison) { + $comparison = Criteria::IN; + } + } + return $this->addUsingAlias(CcSchedulePeer::STARTS, $starts, $comparison); + } + + /** + * Filter the query on the ends column + * + * @param string|array $ends The value to use as filter. + * Accepts an associative array('min' => $minValue, 'max' => $maxValue) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcScheduleQuery The current query, for fluid interface + */ + public function filterByEnds($ends = null, $comparison = null) + { + if (is_array($ends)) { + $useMinMax = false; + if (isset($ends['min'])) { + $this->addUsingAlias(CcSchedulePeer::ENDS, $ends['min'], Criteria::GREATER_EQUAL); + $useMinMax = true; + } + if (isset($ends['max'])) { + $this->addUsingAlias(CcSchedulePeer::ENDS, $ends['max'], Criteria::LESS_EQUAL); + $useMinMax = true; + } + if ($useMinMax) { + return $this; + } + if (null === $comparison) { + $comparison = Criteria::IN; + } + } + return $this->addUsingAlias(CcSchedulePeer::ENDS, $ends, $comparison); + } + + /** + * Filter the query on the group_id column + * + * @param int|array $groupId The value to use as filter. + * Accepts an associative array('min' => $minValue, 'max' => $maxValue) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcScheduleQuery The current query, for fluid interface + */ + public function filterByGroupId($groupId = null, $comparison = null) + { + if (is_array($groupId)) { + $useMinMax = false; + if (isset($groupId['min'])) { + $this->addUsingAlias(CcSchedulePeer::GROUP_ID, $groupId['min'], Criteria::GREATER_EQUAL); + $useMinMax = true; + } + if (isset($groupId['max'])) { + $this->addUsingAlias(CcSchedulePeer::GROUP_ID, $groupId['max'], Criteria::LESS_EQUAL); + $useMinMax = true; + } + if ($useMinMax) { + return $this; + } + if (null === $comparison) { + $comparison = Criteria::IN; + } + } + return $this->addUsingAlias(CcSchedulePeer::GROUP_ID, $groupId, $comparison); + } + + /** + * Filter the query on the file_id column + * + * @param int|array $fileId The value to use as filter. + * Accepts an associative array('min' => $minValue, 'max' => $maxValue) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcScheduleQuery The current query, for fluid interface + */ + public function filterByFileId($fileId = null, $comparison = null) + { + if (is_array($fileId)) { + $useMinMax = false; + if (isset($fileId['min'])) { + $this->addUsingAlias(CcSchedulePeer::FILE_ID, $fileId['min'], Criteria::GREATER_EQUAL); + $useMinMax = true; + } + if (isset($fileId['max'])) { + $this->addUsingAlias(CcSchedulePeer::FILE_ID, $fileId['max'], Criteria::LESS_EQUAL); + $useMinMax = true; + } + if ($useMinMax) { + return $this; + } + if (null === $comparison) { + $comparison = Criteria::IN; + } + } + return $this->addUsingAlias(CcSchedulePeer::FILE_ID, $fileId, $comparison); + } + + /** + * Filter the query on the clip_length column + * + * @param string|array $clipLength The value to use as filter. + * Accepts an associative array('min' => $minValue, 'max' => $maxValue) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcScheduleQuery The current query, for fluid interface + */ + public function filterByClipLength($clipLength = null, $comparison = null) + { + if (is_array($clipLength)) { + $useMinMax = false; + if (isset($clipLength['min'])) { + $this->addUsingAlias(CcSchedulePeer::CLIP_LENGTH, $clipLength['min'], Criteria::GREATER_EQUAL); + $useMinMax = true; + } + if (isset($clipLength['max'])) { + $this->addUsingAlias(CcSchedulePeer::CLIP_LENGTH, $clipLength['max'], Criteria::LESS_EQUAL); + $useMinMax = true; + } + if ($useMinMax) { + return $this; + } + if (null === $comparison) { + $comparison = Criteria::IN; + } + } + return $this->addUsingAlias(CcSchedulePeer::CLIP_LENGTH, $clipLength, $comparison); + } + + /** + * Filter the query on the fade_in column + * + * @param string|array $fadeIn The value to use as filter. + * Accepts an associative array('min' => $minValue, 'max' => $maxValue) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcScheduleQuery The current query, for fluid interface + */ + public function filterByFadeIn($fadeIn = null, $comparison = null) + { + if (is_array($fadeIn)) { + $useMinMax = false; + if (isset($fadeIn['min'])) { + $this->addUsingAlias(CcSchedulePeer::FADE_IN, $fadeIn['min'], Criteria::GREATER_EQUAL); + $useMinMax = true; + } + if (isset($fadeIn['max'])) { + $this->addUsingAlias(CcSchedulePeer::FADE_IN, $fadeIn['max'], Criteria::LESS_EQUAL); + $useMinMax = true; + } + if ($useMinMax) { + return $this; + } + if (null === $comparison) { + $comparison = Criteria::IN; + } + } + return $this->addUsingAlias(CcSchedulePeer::FADE_IN, $fadeIn, $comparison); + } + + /** + * Filter the query on the fade_out column + * + * @param string|array $fadeOut The value to use as filter. + * Accepts an associative array('min' => $minValue, 'max' => $maxValue) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcScheduleQuery The current query, for fluid interface + */ + public function filterByFadeOut($fadeOut = null, $comparison = null) + { + if (is_array($fadeOut)) { + $useMinMax = false; + if (isset($fadeOut['min'])) { + $this->addUsingAlias(CcSchedulePeer::FADE_OUT, $fadeOut['min'], Criteria::GREATER_EQUAL); + $useMinMax = true; + } + if (isset($fadeOut['max'])) { + $this->addUsingAlias(CcSchedulePeer::FADE_OUT, $fadeOut['max'], Criteria::LESS_EQUAL); + $useMinMax = true; + } + if ($useMinMax) { + return $this; + } + if (null === $comparison) { + $comparison = Criteria::IN; + } + } + return $this->addUsingAlias(CcSchedulePeer::FADE_OUT, $fadeOut, $comparison); + } + + /** + * Filter the query on the cue_in column + * + * @param string|array $cueIn The value to use as filter. + * Accepts an associative array('min' => $minValue, 'max' => $maxValue) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcScheduleQuery The current query, for fluid interface + */ + public function filterByCueIn($cueIn = null, $comparison = null) + { + if (is_array($cueIn)) { + $useMinMax = false; + if (isset($cueIn['min'])) { + $this->addUsingAlias(CcSchedulePeer::CUE_IN, $cueIn['min'], Criteria::GREATER_EQUAL); + $useMinMax = true; + } + if (isset($cueIn['max'])) { + $this->addUsingAlias(CcSchedulePeer::CUE_IN, $cueIn['max'], Criteria::LESS_EQUAL); + $useMinMax = true; + } + if ($useMinMax) { + return $this; + } + if (null === $comparison) { + $comparison = Criteria::IN; + } + } + return $this->addUsingAlias(CcSchedulePeer::CUE_IN, $cueIn, $comparison); + } + + /** + * Filter the query on the cue_out column + * + * @param string|array $cueOut The value to use as filter. + * Accepts an associative array('min' => $minValue, 'max' => $maxValue) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcScheduleQuery The current query, for fluid interface + */ + public function filterByCueOut($cueOut = null, $comparison = null) + { + if (is_array($cueOut)) { + $useMinMax = false; + if (isset($cueOut['min'])) { + $this->addUsingAlias(CcSchedulePeer::CUE_OUT, $cueOut['min'], Criteria::GREATER_EQUAL); + $useMinMax = true; + } + if (isset($cueOut['max'])) { + $this->addUsingAlias(CcSchedulePeer::CUE_OUT, $cueOut['max'], Criteria::LESS_EQUAL); + $useMinMax = true; + } + if ($useMinMax) { + return $this; + } + if (null === $comparison) { + $comparison = Criteria::IN; + } + } + return $this->addUsingAlias(CcSchedulePeer::CUE_OUT, $cueOut, $comparison); + } + + /** + * Exclude object from result + * + * @param CcSchedule $ccSchedule Object to remove from the list of results + * + * @return CcScheduleQuery The current query, for fluid interface + */ + public function prune($ccSchedule = null) + { + if ($ccSchedule) { + $this->addUsingAlias(CcSchedulePeer::ID, $ccSchedule->getId(), Criteria::NOT_EQUAL); + } + + return $this; + } + +} // BaseCcScheduleQuery diff --git a/application/models/campcaster/om/BaseCcSess.php b/application/models/campcaster/om/BaseCcSess.php new file mode 100644 index 000000000..a68aa8e12 --- /dev/null +++ b/application/models/campcaster/om/BaseCcSess.php @@ -0,0 +1,949 @@ +sessid; + } + + /** + * Get the [userid] column value. + * + * @return int + */ + public function getUserid() + { + return $this->userid; + } + + /** + * Get the [login] column value. + * + * @return string + */ + public function getLogin() + { + return $this->login; + } + + /** + * Get the [optionally formatted] temporal [ts] column value. + * + * + * @param string $format The date/time format string (either date()-style or strftime()-style). + * If format is NULL, then the raw DateTime object will be returned. + * @return mixed Formatted date/time value as string or DateTime object (if format is NULL), NULL if column is NULL + * @throws PropelException - if unable to parse/validate the date/time value. + */ + public function getTs($format = 'Y-m-d H:i:s') + { + if ($this->ts === null) { + return null; + } + + + + try { + $dt = new DateTime($this->ts); + } catch (Exception $x) { + throw new PropelException("Internally stored date/time/timestamp value could not be converted to DateTime: " . var_export($this->ts, true), $x); + } + + if ($format === null) { + // Because propel.useDateTimeClass is TRUE, we return a DateTime object. + return $dt; + } elseif (strpos($format, '%') !== false) { + return strftime($format, $dt->format('U')); + } else { + return $dt->format($format); + } + } + + /** + * Set the value of [sessid] column. + * + * @param string $v new value + * @return CcSess The current object (for fluent API support) + */ + public function setSessid($v) + { + if ($v !== null) { + $v = (string) $v; + } + + if ($this->sessid !== $v) { + $this->sessid = $v; + $this->modifiedColumns[] = CcSessPeer::SESSID; + } + + return $this; + } // setSessid() + + /** + * Set the value of [userid] column. + * + * @param int $v new value + * @return CcSess The current object (for fluent API support) + */ + public function setUserid($v) + { + if ($v !== null) { + $v = (int) $v; + } + + if ($this->userid !== $v) { + $this->userid = $v; + $this->modifiedColumns[] = CcSessPeer::USERID; + } + + if ($this->aCcSubjs !== null && $this->aCcSubjs->getId() !== $v) { + $this->aCcSubjs = null; + } + + return $this; + } // setUserid() + + /** + * Set the value of [login] column. + * + * @param string $v new value + * @return CcSess The current object (for fluent API support) + */ + public function setLogin($v) + { + if ($v !== null) { + $v = (string) $v; + } + + if ($this->login !== $v) { + $this->login = $v; + $this->modifiedColumns[] = CcSessPeer::LOGIN; + } + + return $this; + } // setLogin() + + /** + * Sets the value of [ts] column to a normalized version of the date/time value specified. + * + * @param mixed $v string, integer (timestamp), or DateTime value. Empty string will + * be treated as NULL for temporal objects. + * @return CcSess The current object (for fluent API support) + */ + public function setTs($v) + { + // we treat '' as NULL for temporal objects because DateTime('') == DateTime('now') + // -- which is unexpected, to say the least. + if ($v === null || $v === '') { + $dt = null; + } elseif ($v instanceof DateTime) { + $dt = $v; + } else { + // some string/numeric value passed; we normalize that so that we can + // validate it. + try { + if (is_numeric($v)) { // if it's a unix timestamp + $dt = new DateTime('@'.$v, new DateTimeZone('UTC')); + // We have to explicitly specify and then change the time zone because of a + // DateTime bug: http://bugs.php.net/bug.php?id=43003 + $dt->setTimeZone(new DateTimeZone(date_default_timezone_get())); + } else { + $dt = new DateTime($v); + } + } catch (Exception $x) { + throw new PropelException('Error parsing date/time value: ' . var_export($v, true), $x); + } + } + + if ( $this->ts !== null || $dt !== null ) { + // (nested ifs are a little easier to read in this case) + + $currNorm = ($this->ts !== null && $tmpDt = new DateTime($this->ts)) ? $tmpDt->format('Y-m-d\\TH:i:sO') : null; + $newNorm = ($dt !== null) ? $dt->format('Y-m-d\\TH:i:sO') : null; + + if ( ($currNorm !== $newNorm) // normalized values don't match + ) + { + $this->ts = ($dt ? $dt->format('Y-m-d\\TH:i:sO') : null); + $this->modifiedColumns[] = CcSessPeer::TS; + } + } // if either are not null + + return $this; + } // setTs() + + /** + * Indicates whether the columns in this object are only set to default values. + * + * This method can be used in conjunction with isModified() to indicate whether an object is both + * modified _and_ has some values set which are non-default. + * + * @return boolean Whether the columns in this object are only been set with default values. + */ + public function hasOnlyDefaultValues() + { + // otherwise, everything was equal, so return TRUE + return true; + } // hasOnlyDefaultValues() + + /** + * Hydrates (populates) the object variables with values from the database resultset. + * + * An offset (0-based "start column") is specified so that objects can be hydrated + * with a subset of the columns in the resultset rows. This is needed, for example, + * for results of JOIN queries where the resultset row includes columns from two or + * more tables. + * + * @param array $row The row returned by PDOStatement->fetch(PDO::FETCH_NUM) + * @param int $startcol 0-based offset column which indicates which restultset column to start with. + * @param boolean $rehydrate Whether this object is being re-hydrated from the database. + * @return int next starting column + * @throws PropelException - Any caught Exception will be rewrapped as a PropelException. + */ + public function hydrate($row, $startcol = 0, $rehydrate = false) + { + try { + + $this->sessid = ($row[$startcol + 0] !== null) ? (string) $row[$startcol + 0] : null; + $this->userid = ($row[$startcol + 1] !== null) ? (int) $row[$startcol + 1] : null; + $this->login = ($row[$startcol + 2] !== null) ? (string) $row[$startcol + 2] : null; + $this->ts = ($row[$startcol + 3] !== null) ? (string) $row[$startcol + 3] : null; + $this->resetModified(); + + $this->setNew(false); + + if ($rehydrate) { + $this->ensureConsistency(); + } + + return $startcol + 4; // 4 = CcSessPeer::NUM_COLUMNS - CcSessPeer::NUM_LAZY_LOAD_COLUMNS). + + } catch (Exception $e) { + throw new PropelException("Error populating CcSess object", $e); + } + } + + /** + * Checks and repairs the internal consistency of the object. + * + * This method is executed after an already-instantiated object is re-hydrated + * from the database. It exists to check any foreign keys to make sure that + * the objects related to the current object are correct based on foreign key. + * + * You can override this method in the stub class, but you should always invoke + * the base method from the overridden method (i.e. parent::ensureConsistency()), + * in case your model changes. + * + * @throws PropelException + */ + public function ensureConsistency() + { + + if ($this->aCcSubjs !== null && $this->userid !== $this->aCcSubjs->getId()) { + $this->aCcSubjs = null; + } + } // ensureConsistency + + /** + * Reloads this object from datastore based on primary key and (optionally) resets all associated objects. + * + * This will only work if the object has been saved and has a valid primary key set. + * + * @param boolean $deep (optional) Whether to also de-associated any related objects. + * @param PropelPDO $con (optional) The PropelPDO connection to use. + * @return void + * @throws PropelException - if this object is deleted, unsaved or doesn't have pk match in db + */ + public function reload($deep = false, PropelPDO $con = null) + { + if ($this->isDeleted()) { + throw new PropelException("Cannot reload a deleted object."); + } + + if ($this->isNew()) { + throw new PropelException("Cannot reload an unsaved object."); + } + + if ($con === null) { + $con = Propel::getConnection(CcSessPeer::DATABASE_NAME, Propel::CONNECTION_READ); + } + + // We don't need to alter the object instance pool; we're just modifying this instance + // already in the pool. + + $stmt = CcSessPeer::doSelectStmt($this->buildPkeyCriteria(), $con); + $row = $stmt->fetch(PDO::FETCH_NUM); + $stmt->closeCursor(); + if (!$row) { + throw new PropelException('Cannot find matching row in the database to reload object values.'); + } + $this->hydrate($row, 0, true); // rehydrate + + if ($deep) { // also de-associate any related objects? + + $this->aCcSubjs = null; + } // if (deep) + } + + /** + * Removes this object from datastore and sets delete attribute. + * + * @param PropelPDO $con + * @return void + * @throws PropelException + * @see BaseObject::setDeleted() + * @see BaseObject::isDeleted() + */ + public function delete(PropelPDO $con = null) + { + if ($this->isDeleted()) { + throw new PropelException("This object has already been deleted."); + } + + if ($con === null) { + $con = Propel::getConnection(CcSessPeer::DATABASE_NAME, Propel::CONNECTION_WRITE); + } + + $con->beginTransaction(); + try { + $ret = $this->preDelete($con); + if ($ret) { + CcSessQuery::create() + ->filterByPrimaryKey($this->getPrimaryKey()) + ->delete($con); + $this->postDelete($con); + $con->commit(); + $this->setDeleted(true); + } else { + $con->commit(); + } + } catch (PropelException $e) { + $con->rollBack(); + throw $e; + } + } + + /** + * Persists this object to the database. + * + * If the object is new, it inserts it; otherwise an update is performed. + * All modified related objects will also be persisted in the doSave() + * method. This method wraps all precipitate database operations in a + * single transaction. + * + * @param PropelPDO $con + * @return int The number of rows affected by this insert/update and any referring fk objects' save() operations. + * @throws PropelException + * @see doSave() + */ + public function save(PropelPDO $con = null) + { + if ($this->isDeleted()) { + throw new PropelException("You cannot save an object that has been deleted."); + } + + if ($con === null) { + $con = Propel::getConnection(CcSessPeer::DATABASE_NAME, Propel::CONNECTION_WRITE); + } + + $con->beginTransaction(); + $isInsert = $this->isNew(); + try { + $ret = $this->preSave($con); + if ($isInsert) { + $ret = $ret && $this->preInsert($con); + } else { + $ret = $ret && $this->preUpdate($con); + } + if ($ret) { + $affectedRows = $this->doSave($con); + if ($isInsert) { + $this->postInsert($con); + } else { + $this->postUpdate($con); + } + $this->postSave($con); + CcSessPeer::addInstanceToPool($this); + } else { + $affectedRows = 0; + } + $con->commit(); + return $affectedRows; + } catch (PropelException $e) { + $con->rollBack(); + throw $e; + } + } + + /** + * Performs the work of inserting or updating the row in the database. + * + * If the object is new, it inserts it; otherwise an update is performed. + * All related objects are also updated in this method. + * + * @param PropelPDO $con + * @return int The number of rows affected by this insert/update and any referring fk objects' save() operations. + * @throws PropelException + * @see save() + */ + protected function doSave(PropelPDO $con) + { + $affectedRows = 0; // initialize var to track total num of affected rows + if (!$this->alreadyInSave) { + $this->alreadyInSave = true; + + // We call the save method on the following object(s) if they + // were passed to this object by their coresponding set + // method. This object relates to these object(s) by a + // foreign key reference. + + if ($this->aCcSubjs !== null) { + if ($this->aCcSubjs->isModified() || $this->aCcSubjs->isNew()) { + $affectedRows += $this->aCcSubjs->save($con); + } + $this->setCcSubjs($this->aCcSubjs); + } + + + // If this object has been modified, then save it to the database. + if ($this->isModified()) { + if ($this->isNew()) { + $criteria = $this->buildCriteria(); + $pk = BasePeer::doInsert($criteria, $con); + $affectedRows += 1; + $this->setNew(false); + } else { + $affectedRows += CcSessPeer::doUpdate($this, $con); + } + + $this->resetModified(); // [HL] After being saved an object is no longer 'modified' + } + + $this->alreadyInSave = false; + + } + return $affectedRows; + } // doSave() + + /** + * Array of ValidationFailed objects. + * @var array ValidationFailed[] + */ + protected $validationFailures = array(); + + /** + * Gets any ValidationFailed objects that resulted from last call to validate(). + * + * + * @return array ValidationFailed[] + * @see validate() + */ + public function getValidationFailures() + { + return $this->validationFailures; + } + + /** + * Validates the objects modified field values and all objects related to this table. + * + * If $columns is either a column name or an array of column names + * only those columns are validated. + * + * @param mixed $columns Column name or an array of column names. + * @return boolean Whether all columns pass validation. + * @see doValidate() + * @see getValidationFailures() + */ + public function validate($columns = null) + { + $res = $this->doValidate($columns); + if ($res === true) { + $this->validationFailures = array(); + return true; + } else { + $this->validationFailures = $res; + return false; + } + } + + /** + * This function performs the validation work for complex object models. + * + * In addition to checking the current object, all related objects will + * also be validated. If all pass then true is returned; otherwise + * an aggreagated array of ValidationFailed objects will be returned. + * + * @param array $columns Array of column names to validate. + * @return mixed true if all validations pass; array of ValidationFailed objets otherwise. + */ + protected function doValidate($columns = null) + { + if (!$this->alreadyInValidation) { + $this->alreadyInValidation = true; + $retval = null; + + $failureMap = array(); + + + // We call the validate method on the following object(s) if they + // were passed to this object by their coresponding set + // method. This object relates to these object(s) by a + // foreign key reference. + + if ($this->aCcSubjs !== null) { + if (!$this->aCcSubjs->validate($columns)) { + $failureMap = array_merge($failureMap, $this->aCcSubjs->getValidationFailures()); + } + } + + + if (($retval = CcSessPeer::doValidate($this, $columns)) !== true) { + $failureMap = array_merge($failureMap, $retval); + } + + + + $this->alreadyInValidation = false; + } + + return (!empty($failureMap) ? $failureMap : true); + } + + /** + * Retrieves a field from the object by name passed in as a string. + * + * @param string $name name + * @param string $type The type of fieldname the $name is of: + * one of the class type constants BasePeer::TYPE_PHPNAME, BasePeer::TYPE_STUDLYPHPNAME + * BasePeer::TYPE_COLNAME, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_NUM + * @return mixed Value of field. + */ + public function getByName($name, $type = BasePeer::TYPE_PHPNAME) + { + $pos = CcSessPeer::translateFieldName($name, $type, BasePeer::TYPE_NUM); + $field = $this->getByPosition($pos); + return $field; + } + + /** + * Retrieves a field from the object by Position as specified in the xml schema. + * Zero-based. + * + * @param int $pos position in xml schema + * @return mixed Value of field at $pos + */ + public function getByPosition($pos) + { + switch($pos) { + case 0: + return $this->getSessid(); + break; + case 1: + return $this->getUserid(); + break; + case 2: + return $this->getLogin(); + break; + case 3: + return $this->getTs(); + break; + default: + return null; + break; + } // switch() + } + + /** + * Exports the object as an array. + * + * You can specify the key type of the array by passing one of the class + * type constants. + * + * @param string $keyType (optional) One of the class type constants BasePeer::TYPE_PHPNAME, BasePeer::TYPE_STUDLYPHPNAME, + * BasePeer::TYPE_COLNAME, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_NUM. + * Defaults to BasePeer::TYPE_PHPNAME. + * @param boolean $includeLazyLoadColumns (optional) Whether to include lazy loaded columns. Defaults to TRUE. + * @param boolean $includeForeignObjects (optional) Whether to include hydrated related objects. Default to FALSE. + * + * @return array an associative array containing the field names (as keys) and field values + */ + public function toArray($keyType = BasePeer::TYPE_PHPNAME, $includeLazyLoadColumns = true, $includeForeignObjects = false) + { + $keys = CcSessPeer::getFieldNames($keyType); + $result = array( + $keys[0] => $this->getSessid(), + $keys[1] => $this->getUserid(), + $keys[2] => $this->getLogin(), + $keys[3] => $this->getTs(), + ); + if ($includeForeignObjects) { + if (null !== $this->aCcSubjs) { + $result['CcSubjs'] = $this->aCcSubjs->toArray($keyType, $includeLazyLoadColumns, true); + } + } + return $result; + } + + /** + * Sets a field from the object by name passed in as a string. + * + * @param string $name peer name + * @param mixed $value field value + * @param string $type The type of fieldname the $name is of: + * one of the class type constants BasePeer::TYPE_PHPNAME, BasePeer::TYPE_STUDLYPHPNAME + * BasePeer::TYPE_COLNAME, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_NUM + * @return void + */ + public function setByName($name, $value, $type = BasePeer::TYPE_PHPNAME) + { + $pos = CcSessPeer::translateFieldName($name, $type, BasePeer::TYPE_NUM); + return $this->setByPosition($pos, $value); + } + + /** + * Sets a field from the object by Position as specified in the xml schema. + * Zero-based. + * + * @param int $pos position in xml schema + * @param mixed $value field value + * @return void + */ + public function setByPosition($pos, $value) + { + switch($pos) { + case 0: + $this->setSessid($value); + break; + case 1: + $this->setUserid($value); + break; + case 2: + $this->setLogin($value); + break; + case 3: + $this->setTs($value); + break; + } // switch() + } + + /** + * Populates the object using an array. + * + * This is particularly useful when populating an object from one of the + * request arrays (e.g. $_POST). This method goes through the column + * names, checking to see whether a matching key exists in populated + * array. If so the setByName() method is called for that column. + * + * You can specify the key type of the array by additionally passing one + * of the class type constants BasePeer::TYPE_PHPNAME, BasePeer::TYPE_STUDLYPHPNAME, + * BasePeer::TYPE_COLNAME, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_NUM. + * The default key type is the column's phpname (e.g. 'AuthorId') + * + * @param array $arr An array to populate the object from. + * @param string $keyType The type of keys the array uses. + * @return void + */ + public function fromArray($arr, $keyType = BasePeer::TYPE_PHPNAME) + { + $keys = CcSessPeer::getFieldNames($keyType); + + if (array_key_exists($keys[0], $arr)) $this->setSessid($arr[$keys[0]]); + if (array_key_exists($keys[1], $arr)) $this->setUserid($arr[$keys[1]]); + if (array_key_exists($keys[2], $arr)) $this->setLogin($arr[$keys[2]]); + if (array_key_exists($keys[3], $arr)) $this->setTs($arr[$keys[3]]); + } + + /** + * Build a Criteria object containing the values of all modified columns in this object. + * + * @return Criteria The Criteria object containing all modified values. + */ + public function buildCriteria() + { + $criteria = new Criteria(CcSessPeer::DATABASE_NAME); + + if ($this->isColumnModified(CcSessPeer::SESSID)) $criteria->add(CcSessPeer::SESSID, $this->sessid); + if ($this->isColumnModified(CcSessPeer::USERID)) $criteria->add(CcSessPeer::USERID, $this->userid); + if ($this->isColumnModified(CcSessPeer::LOGIN)) $criteria->add(CcSessPeer::LOGIN, $this->login); + if ($this->isColumnModified(CcSessPeer::TS)) $criteria->add(CcSessPeer::TS, $this->ts); + + return $criteria; + } + + /** + * Builds a Criteria object containing the primary key for this object. + * + * Unlike buildCriteria() this method includes the primary key values regardless + * of whether or not they have been modified. + * + * @return Criteria The Criteria object containing value(s) for primary key(s). + */ + public function buildPkeyCriteria() + { + $criteria = new Criteria(CcSessPeer::DATABASE_NAME); + $criteria->add(CcSessPeer::SESSID, $this->sessid); + + return $criteria; + } + + /** + * Returns the primary key for this object (row). + * @return string + */ + public function getPrimaryKey() + { + return $this->getSessid(); + } + + /** + * Generic method to set the primary key (sessid column). + * + * @param string $key Primary key. + * @return void + */ + public function setPrimaryKey($key) + { + $this->setSessid($key); + } + + /** + * Returns true if the primary key for this object is null. + * @return boolean + */ + public function isPrimaryKeyNull() + { + return null === $this->getSessid(); + } + + /** + * Sets contents of passed object to values from current object. + * + * If desired, this method can also make copies of all associated (fkey referrers) + * objects. + * + * @param object $copyObj An object of CcSess (or compatible) type. + * @param boolean $deepCopy Whether to also copy all rows that refer (by fkey) to the current row. + * @throws PropelException + */ + public function copyInto($copyObj, $deepCopy = false) + { + $copyObj->setSessid($this->sessid); + $copyObj->setUserid($this->userid); + $copyObj->setLogin($this->login); + $copyObj->setTs($this->ts); + + $copyObj->setNew(true); + } + + /** + * Makes a copy of this object that will be inserted as a new row in table when saved. + * It creates a new object filling in the simple attributes, but skipping any primary + * keys that are defined for the table. + * + * If desired, this method can also make copies of all associated (fkey referrers) + * objects. + * + * @param boolean $deepCopy Whether to also copy all rows that refer (by fkey) to the current row. + * @return CcSess Clone of current object. + * @throws PropelException + */ + public function copy($deepCopy = false) + { + // we use get_class(), because this might be a subclass + $clazz = get_class($this); + $copyObj = new $clazz(); + $this->copyInto($copyObj, $deepCopy); + return $copyObj; + } + + /** + * Returns a peer instance associated with this om. + * + * Since Peer classes are not to have any instance attributes, this method returns the + * same instance for all member of this class. The method could therefore + * be static, but this would prevent one from overriding the behavior. + * + * @return CcSessPeer + */ + public function getPeer() + { + if (self::$peer === null) { + self::$peer = new CcSessPeer(); + } + return self::$peer; + } + + /** + * Declares an association between this object and a CcSubjs object. + * + * @param CcSubjs $v + * @return CcSess The current object (for fluent API support) + * @throws PropelException + */ + public function setCcSubjs(CcSubjs $v = null) + { + if ($v === null) { + $this->setUserid(NULL); + } else { + $this->setUserid($v->getId()); + } + + $this->aCcSubjs = $v; + + // Add binding for other direction of this n:n relationship. + // If this object has already been added to the CcSubjs object, it will not be re-added. + if ($v !== null) { + $v->addCcSess($this); + } + + return $this; + } + + + /** + * Get the associated CcSubjs object + * + * @param PropelPDO Optional Connection object. + * @return CcSubjs The associated CcSubjs object. + * @throws PropelException + */ + public function getCcSubjs(PropelPDO $con = null) + { + if ($this->aCcSubjs === null && ($this->userid !== null)) { + $this->aCcSubjs = CcSubjsQuery::create()->findPk($this->userid, $con); + /* The following can be used additionally to + guarantee the related object contains a reference + to this object. This level of coupling may, however, be + undesirable since it could result in an only partially populated collection + in the referenced object. + $this->aCcSubjs->addCcSesss($this); + */ + } + return $this->aCcSubjs; + } + + /** + * Clears the current object and sets all attributes to their default values + */ + public function clear() + { + $this->sessid = null; + $this->userid = null; + $this->login = null; + $this->ts = null; + $this->alreadyInSave = false; + $this->alreadyInValidation = false; + $this->clearAllReferences(); + $this->resetModified(); + $this->setNew(true); + $this->setDeleted(false); + } + + /** + * Resets all collections of referencing foreign keys. + * + * This method is a user-space workaround for PHP's inability to garbage collect objects + * with circular references. This is currently necessary when using Propel in certain + * daemon or large-volumne/high-memory operations. + * + * @param boolean $deep Whether to also clear the references on all associated objects. + */ + public function clearAllReferences($deep = false) + { + if ($deep) { + } // if ($deep) + + $this->aCcSubjs = null; + } + + /** + * Catches calls to virtual methods + */ + public function __call($name, $params) + { + if (preg_match('/get(\w+)/', $name, $matches)) { + $virtualColumn = $matches[1]; + if ($this->hasVirtualColumn($virtualColumn)) { + return $this->getVirtualColumn($virtualColumn); + } + // no lcfirst in php<5.3... + $virtualColumn[0] = strtolower($virtualColumn[0]); + if ($this->hasVirtualColumn($virtualColumn)) { + return $this->getVirtualColumn($virtualColumn); + } + } + throw new PropelException('Call to undefined method: ' . $name); + } + +} // BaseCcSess diff --git a/application/models/campcaster/om/BaseCcSessPeer.php b/application/models/campcaster/om/BaseCcSessPeer.php new file mode 100644 index 000000000..0ca095e21 --- /dev/null +++ b/application/models/campcaster/om/BaseCcSessPeer.php @@ -0,0 +1,979 @@ + array ('Sessid', 'Userid', 'Login', 'Ts', ), + BasePeer::TYPE_STUDLYPHPNAME => array ('sessid', 'userid', 'login', 'ts', ), + BasePeer::TYPE_COLNAME => array (self::SESSID, self::USERID, self::LOGIN, self::TS, ), + BasePeer::TYPE_RAW_COLNAME => array ('SESSID', 'USERID', 'LOGIN', 'TS', ), + BasePeer::TYPE_FIELDNAME => array ('sessid', 'userid', 'login', 'ts', ), + BasePeer::TYPE_NUM => array (0, 1, 2, 3, ) + ); + + /** + * holds an array of keys for quick access to the fieldnames array + * + * first dimension keys are the type constants + * e.g. self::$fieldNames[BasePeer::TYPE_PHPNAME]['Id'] = 0 + */ + private static $fieldKeys = array ( + BasePeer::TYPE_PHPNAME => array ('Sessid' => 0, 'Userid' => 1, 'Login' => 2, 'Ts' => 3, ), + BasePeer::TYPE_STUDLYPHPNAME => array ('sessid' => 0, 'userid' => 1, 'login' => 2, 'ts' => 3, ), + BasePeer::TYPE_COLNAME => array (self::SESSID => 0, self::USERID => 1, self::LOGIN => 2, self::TS => 3, ), + BasePeer::TYPE_RAW_COLNAME => array ('SESSID' => 0, 'USERID' => 1, 'LOGIN' => 2, 'TS' => 3, ), + BasePeer::TYPE_FIELDNAME => array ('sessid' => 0, 'userid' => 1, 'login' => 2, 'ts' => 3, ), + BasePeer::TYPE_NUM => array (0, 1, 2, 3, ) + ); + + /** + * Translates a fieldname to another type + * + * @param string $name field name + * @param string $fromType One of the class type constants BasePeer::TYPE_PHPNAME, BasePeer::TYPE_STUDLYPHPNAME + * BasePeer::TYPE_COLNAME, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_NUM + * @param string $toType One of the class type constants + * @return string translated name of the field. + * @throws PropelException - if the specified name could not be found in the fieldname mappings. + */ + static public function translateFieldName($name, $fromType, $toType) + { + $toNames = self::getFieldNames($toType); + $key = isset(self::$fieldKeys[$fromType][$name]) ? self::$fieldKeys[$fromType][$name] : null; + if ($key === null) { + throw new PropelException("'$name' could not be found in the field names of type '$fromType'. These are: " . print_r(self::$fieldKeys[$fromType], true)); + } + return $toNames[$key]; + } + + /** + * Returns an array of field names. + * + * @param string $type The type of fieldnames to return: + * One of the class type constants BasePeer::TYPE_PHPNAME, BasePeer::TYPE_STUDLYPHPNAME + * BasePeer::TYPE_COLNAME, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_NUM + * @return array A list of field names + */ + + static public function getFieldNames($type = BasePeer::TYPE_PHPNAME) + { + if (!array_key_exists($type, self::$fieldNames)) { + throw new PropelException('Method getFieldNames() expects the parameter $type to be one of the class constants BasePeer::TYPE_PHPNAME, BasePeer::TYPE_STUDLYPHPNAME, BasePeer::TYPE_COLNAME, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_NUM. ' . $type . ' was given.'); + } + return self::$fieldNames[$type]; + } + + /** + * Convenience method which changes table.column to alias.column. + * + * Using this method you can maintain SQL abstraction while using column aliases. + * + * $c->addAlias("alias1", TablePeer::TABLE_NAME); + * $c->addJoin(TablePeer::alias("alias1", TablePeer::PRIMARY_KEY_COLUMN), TablePeer::PRIMARY_KEY_COLUMN); + * + * @param string $alias The alias for the current table. + * @param string $column The column name for current table. (i.e. CcSessPeer::COLUMN_NAME). + * @return string + */ + public static function alias($alias, $column) + { + return str_replace(CcSessPeer::TABLE_NAME.'.', $alias.'.', $column); + } + + /** + * Add all the columns needed to create a new object. + * + * Note: any columns that were marked with lazyLoad="true" in the + * XML schema will not be added to the select list and only loaded + * on demand. + * + * @param Criteria $criteria object containing the columns to add. + * @param string $alias optional table alias + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function addSelectColumns(Criteria $criteria, $alias = null) + { + if (null === $alias) { + $criteria->addSelectColumn(CcSessPeer::SESSID); + $criteria->addSelectColumn(CcSessPeer::USERID); + $criteria->addSelectColumn(CcSessPeer::LOGIN); + $criteria->addSelectColumn(CcSessPeer::TS); + } else { + $criteria->addSelectColumn($alias . '.SESSID'); + $criteria->addSelectColumn($alias . '.USERID'); + $criteria->addSelectColumn($alias . '.LOGIN'); + $criteria->addSelectColumn($alias . '.TS'); + } + } + + /** + * Returns the number of rows matching criteria. + * + * @param Criteria $criteria + * @param boolean $distinct Whether to select only distinct columns; deprecated: use Criteria->setDistinct() instead. + * @param PropelPDO $con + * @return int Number of matching rows. + */ + public static function doCount(Criteria $criteria, $distinct = false, PropelPDO $con = null) + { + // we may modify criteria, so copy it first + $criteria = clone $criteria; + + // We need to set the primary table name, since in the case that there are no WHERE columns + // it will be impossible for the BasePeer::createSelectSql() method to determine which + // tables go into the FROM clause. + $criteria->setPrimaryTableName(CcSessPeer::TABLE_NAME); + + if ($distinct && !in_array(Criteria::DISTINCT, $criteria->getSelectModifiers())) { + $criteria->setDistinct(); + } + + if (!$criteria->hasSelectClause()) { + CcSessPeer::addSelectColumns($criteria); + } + + $criteria->clearOrderByColumns(); // ORDER BY won't ever affect the count + $criteria->setDbName(self::DATABASE_NAME); // Set the correct dbName + + if ($con === null) { + $con = Propel::getConnection(CcSessPeer::DATABASE_NAME, Propel::CONNECTION_READ); + } + // BasePeer returns a PDOStatement + $stmt = BasePeer::doCount($criteria, $con); + + if ($row = $stmt->fetch(PDO::FETCH_NUM)) { + $count = (int) $row[0]; + } else { + $count = 0; // no rows returned; we infer that means 0 matches. + } + $stmt->closeCursor(); + return $count; + } + /** + * Method to select one object from the DB. + * + * @param Criteria $criteria object used to create the SELECT statement. + * @param PropelPDO $con + * @return CcSess + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function doSelectOne(Criteria $criteria, PropelPDO $con = null) + { + $critcopy = clone $criteria; + $critcopy->setLimit(1); + $objects = CcSessPeer::doSelect($critcopy, $con); + if ($objects) { + return $objects[0]; + } + return null; + } + /** + * Method to do selects. + * + * @param Criteria $criteria The Criteria object used to build the SELECT statement. + * @param PropelPDO $con + * @return array Array of selected Objects + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function doSelect(Criteria $criteria, PropelPDO $con = null) + { + return CcSessPeer::populateObjects(CcSessPeer::doSelectStmt($criteria, $con)); + } + /** + * Prepares the Criteria object and uses the parent doSelect() method to execute a PDOStatement. + * + * Use this method directly if you want to work with an executed statement durirectly (for example + * to perform your own object hydration). + * + * @param Criteria $criteria The Criteria object used to build the SELECT statement. + * @param PropelPDO $con The connection to use + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + * @return PDOStatement The executed PDOStatement object. + * @see BasePeer::doSelect() + */ + public static function doSelectStmt(Criteria $criteria, PropelPDO $con = null) + { + if ($con === null) { + $con = Propel::getConnection(CcSessPeer::DATABASE_NAME, Propel::CONNECTION_READ); + } + + if (!$criteria->hasSelectClause()) { + $criteria = clone $criteria; + CcSessPeer::addSelectColumns($criteria); + } + + // Set the correct dbName + $criteria->setDbName(self::DATABASE_NAME); + + // BasePeer returns a PDOStatement + return BasePeer::doSelect($criteria, $con); + } + /** + * Adds an object to the instance pool. + * + * Propel keeps cached copies of objects in an instance pool when they are retrieved + * from the database. In some cases -- especially when you override doSelect*() + * methods in your stub classes -- you may need to explicitly add objects + * to the cache in order to ensure that the same objects are always returned by doSelect*() + * and retrieveByPK*() calls. + * + * @param CcSess $value A CcSess object. + * @param string $key (optional) key to use for instance map (for performance boost if key was already calculated externally). + */ + public static function addInstanceToPool(CcSess $obj, $key = null) + { + if (Propel::isInstancePoolingEnabled()) { + if ($key === null) { + $key = (string) $obj->getSessid(); + } // if key === null + self::$instances[$key] = $obj; + } + } + + /** + * Removes an object from the instance pool. + * + * Propel keeps cached copies of objects in an instance pool when they are retrieved + * from the database. In some cases -- especially when you override doDelete + * methods in your stub classes -- you may need to explicitly remove objects + * from the cache in order to prevent returning objects that no longer exist. + * + * @param mixed $value A CcSess object or a primary key value. + */ + public static function removeInstanceFromPool($value) + { + if (Propel::isInstancePoolingEnabled() && $value !== null) { + if (is_object($value) && $value instanceof CcSess) { + $key = (string) $value->getSessid(); + } elseif (is_scalar($value)) { + // assume we've been passed a primary key + $key = (string) $value; + } else { + $e = new PropelException("Invalid value passed to removeInstanceFromPool(). Expected primary key or CcSess object; got " . (is_object($value) ? get_class($value) . ' object.' : var_export($value,true))); + throw $e; + } + + unset(self::$instances[$key]); + } + } // removeInstanceFromPool() + + /** + * Retrieves a string version of the primary key from the DB resultset row that can be used to uniquely identify a row in this table. + * + * For tables with a single-column primary key, that simple pkey value will be returned. For tables with + * a multi-column primary key, a serialize()d version of the primary key will be returned. + * + * @param string $key The key (@see getPrimaryKeyHash()) for this instance. + * @return CcSess Found object or NULL if 1) no instance exists for specified key or 2) instance pooling has been disabled. + * @see getPrimaryKeyHash() + */ + public static function getInstanceFromPool($key) + { + if (Propel::isInstancePoolingEnabled()) { + if (isset(self::$instances[$key])) { + return self::$instances[$key]; + } + } + return null; // just to be explicit + } + + /** + * Clear the instance pool. + * + * @return void + */ + public static function clearInstancePool() + { + self::$instances = array(); + } + + /** + * Method to invalidate the instance pool of all tables related to cc_sess + * by a foreign key with ON DELETE CASCADE + */ + public static function clearRelatedInstancePool() + { + } + + /** + * Retrieves a string version of the primary key from the DB resultset row that can be used to uniquely identify a row in this table. + * + * For tables with a single-column primary key, that simple pkey value will be returned. For tables with + * a multi-column primary key, a serialize()d version of the primary key will be returned. + * + * @param array $row PropelPDO resultset row. + * @param int $startcol The 0-based offset for reading from the resultset row. + * @return string A string version of PK or NULL if the components of primary key in result array are all null. + */ + public static function getPrimaryKeyHashFromRow($row, $startcol = 0) + { + // If the PK cannot be derived from the row, return NULL. + if ($row[$startcol] === null) { + return null; + } + return (string) $row[$startcol]; + } + + /** + * Retrieves the primary key from the DB resultset row + * For tables with a single-column primary key, that simple pkey value will be returned. For tables with + * a multi-column primary key, an array of the primary key columns will be returned. + * + * @param array $row PropelPDO resultset row. + * @param int $startcol The 0-based offset for reading from the resultset row. + * @return mixed The primary key of the row + */ + public static function getPrimaryKeyFromRow($row, $startcol = 0) + { + return (string) $row[$startcol]; + } + + /** + * The returned array will contain objects of the default type or + * objects that inherit from the default. + * + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function populateObjects(PDOStatement $stmt) + { + $results = array(); + + // set the class once to avoid overhead in the loop + $cls = CcSessPeer::getOMClass(false); + // populate the object(s) + while ($row = $stmt->fetch(PDO::FETCH_NUM)) { + $key = CcSessPeer::getPrimaryKeyHashFromRow($row, 0); + if (null !== ($obj = CcSessPeer::getInstanceFromPool($key))) { + // We no longer rehydrate the object, since this can cause data loss. + // See http://www.propelorm.org/ticket/509 + // $obj->hydrate($row, 0, true); // rehydrate + $results[] = $obj; + } else { + $obj = new $cls(); + $obj->hydrate($row); + $results[] = $obj; + CcSessPeer::addInstanceToPool($obj, $key); + } // if key exists + } + $stmt->closeCursor(); + return $results; + } + /** + * Populates an object of the default type or an object that inherit from the default. + * + * @param array $row PropelPDO resultset row. + * @param int $startcol The 0-based offset for reading from the resultset row. + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + * @return array (CcSess object, last column rank) + */ + public static function populateObject($row, $startcol = 0) + { + $key = CcSessPeer::getPrimaryKeyHashFromRow($row, $startcol); + if (null !== ($obj = CcSessPeer::getInstanceFromPool($key))) { + // We no longer rehydrate the object, since this can cause data loss. + // See http://www.propelorm.org/ticket/509 + // $obj->hydrate($row, $startcol, true); // rehydrate + $col = $startcol + CcSessPeer::NUM_COLUMNS; + } else { + $cls = CcSessPeer::OM_CLASS; + $obj = new $cls(); + $col = $obj->hydrate($row, $startcol); + CcSessPeer::addInstanceToPool($obj, $key); + } + return array($obj, $col); + } + + /** + * Returns the number of rows matching criteria, joining the related CcSubjs table + * + * @param Criteria $criteria + * @param boolean $distinct Whether to select only distinct columns; deprecated: use Criteria->setDistinct() instead. + * @param PropelPDO $con + * @param String $join_behavior the type of joins to use, defaults to Criteria::LEFT_JOIN + * @return int Number of matching rows. + */ + public static function doCountJoinCcSubjs(Criteria $criteria, $distinct = false, PropelPDO $con = null, $join_behavior = Criteria::LEFT_JOIN) + { + // we're going to modify criteria, so copy it first + $criteria = clone $criteria; + + // We need to set the primary table name, since in the case that there are no WHERE columns + // it will be impossible for the BasePeer::createSelectSql() method to determine which + // tables go into the FROM clause. + $criteria->setPrimaryTableName(CcSessPeer::TABLE_NAME); + + if ($distinct && !in_array(Criteria::DISTINCT, $criteria->getSelectModifiers())) { + $criteria->setDistinct(); + } + + if (!$criteria->hasSelectClause()) { + CcSessPeer::addSelectColumns($criteria); + } + + $criteria->clearOrderByColumns(); // ORDER BY won't ever affect the count + + // Set the correct dbName + $criteria->setDbName(self::DATABASE_NAME); + + if ($con === null) { + $con = Propel::getConnection(CcSessPeer::DATABASE_NAME, Propel::CONNECTION_READ); + } + + $criteria->addJoin(CcSessPeer::USERID, CcSubjsPeer::ID, $join_behavior); + + $stmt = BasePeer::doCount($criteria, $con); + + if ($row = $stmt->fetch(PDO::FETCH_NUM)) { + $count = (int) $row[0]; + } else { + $count = 0; // no rows returned; we infer that means 0 matches. + } + $stmt->closeCursor(); + return $count; + } + + + /** + * Selects a collection of CcSess objects pre-filled with their CcSubjs objects. + * @param Criteria $criteria + * @param PropelPDO $con + * @param String $join_behavior the type of joins to use, defaults to Criteria::LEFT_JOIN + * @return array Array of CcSess objects. + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function doSelectJoinCcSubjs(Criteria $criteria, $con = null, $join_behavior = Criteria::LEFT_JOIN) + { + $criteria = clone $criteria; + + // Set the correct dbName if it has not been overridden + if ($criteria->getDbName() == Propel::getDefaultDB()) { + $criteria->setDbName(self::DATABASE_NAME); + } + + CcSessPeer::addSelectColumns($criteria); + $startcol = (CcSessPeer::NUM_COLUMNS - CcSessPeer::NUM_LAZY_LOAD_COLUMNS); + CcSubjsPeer::addSelectColumns($criteria); + + $criteria->addJoin(CcSessPeer::USERID, CcSubjsPeer::ID, $join_behavior); + + $stmt = BasePeer::doSelect($criteria, $con); + $results = array(); + + while ($row = $stmt->fetch(PDO::FETCH_NUM)) { + $key1 = CcSessPeer::getPrimaryKeyHashFromRow($row, 0); + if (null !== ($obj1 = CcSessPeer::getInstanceFromPool($key1))) { + // We no longer rehydrate the object, since this can cause data loss. + // See http://www.propelorm.org/ticket/509 + // $obj1->hydrate($row, 0, true); // rehydrate + } else { + + $cls = CcSessPeer::getOMClass(false); + + $obj1 = new $cls(); + $obj1->hydrate($row); + CcSessPeer::addInstanceToPool($obj1, $key1); + } // if $obj1 already loaded + + $key2 = CcSubjsPeer::getPrimaryKeyHashFromRow($row, $startcol); + if ($key2 !== null) { + $obj2 = CcSubjsPeer::getInstanceFromPool($key2); + if (!$obj2) { + + $cls = CcSubjsPeer::getOMClass(false); + + $obj2 = new $cls(); + $obj2->hydrate($row, $startcol); + CcSubjsPeer::addInstanceToPool($obj2, $key2); + } // if obj2 already loaded + + // Add the $obj1 (CcSess) to $obj2 (CcSubjs) + $obj2->addCcSess($obj1); + + } // if joined row was not null + + $results[] = $obj1; + } + $stmt->closeCursor(); + return $results; + } + + + /** + * Returns the number of rows matching criteria, joining all related tables + * + * @param Criteria $criteria + * @param boolean $distinct Whether to select only distinct columns; deprecated: use Criteria->setDistinct() instead. + * @param PropelPDO $con + * @param String $join_behavior the type of joins to use, defaults to Criteria::LEFT_JOIN + * @return int Number of matching rows. + */ + public static function doCountJoinAll(Criteria $criteria, $distinct = false, PropelPDO $con = null, $join_behavior = Criteria::LEFT_JOIN) + { + // we're going to modify criteria, so copy it first + $criteria = clone $criteria; + + // We need to set the primary table name, since in the case that there are no WHERE columns + // it will be impossible for the BasePeer::createSelectSql() method to determine which + // tables go into the FROM clause. + $criteria->setPrimaryTableName(CcSessPeer::TABLE_NAME); + + if ($distinct && !in_array(Criteria::DISTINCT, $criteria->getSelectModifiers())) { + $criteria->setDistinct(); + } + + if (!$criteria->hasSelectClause()) { + CcSessPeer::addSelectColumns($criteria); + } + + $criteria->clearOrderByColumns(); // ORDER BY won't ever affect the count + + // Set the correct dbName + $criteria->setDbName(self::DATABASE_NAME); + + if ($con === null) { + $con = Propel::getConnection(CcSessPeer::DATABASE_NAME, Propel::CONNECTION_READ); + } + + $criteria->addJoin(CcSessPeer::USERID, CcSubjsPeer::ID, $join_behavior); + + $stmt = BasePeer::doCount($criteria, $con); + + if ($row = $stmt->fetch(PDO::FETCH_NUM)) { + $count = (int) $row[0]; + } else { + $count = 0; // no rows returned; we infer that means 0 matches. + } + $stmt->closeCursor(); + return $count; + } + + /** + * Selects a collection of CcSess objects pre-filled with all related objects. + * + * @param Criteria $criteria + * @param PropelPDO $con + * @param String $join_behavior the type of joins to use, defaults to Criteria::LEFT_JOIN + * @return array Array of CcSess objects. + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function doSelectJoinAll(Criteria $criteria, $con = null, $join_behavior = Criteria::LEFT_JOIN) + { + $criteria = clone $criteria; + + // Set the correct dbName if it has not been overridden + if ($criteria->getDbName() == Propel::getDefaultDB()) { + $criteria->setDbName(self::DATABASE_NAME); + } + + CcSessPeer::addSelectColumns($criteria); + $startcol2 = (CcSessPeer::NUM_COLUMNS - CcSessPeer::NUM_LAZY_LOAD_COLUMNS); + + CcSubjsPeer::addSelectColumns($criteria); + $startcol3 = $startcol2 + (CcSubjsPeer::NUM_COLUMNS - CcSubjsPeer::NUM_LAZY_LOAD_COLUMNS); + + $criteria->addJoin(CcSessPeer::USERID, CcSubjsPeer::ID, $join_behavior); + + $stmt = BasePeer::doSelect($criteria, $con); + $results = array(); + + while ($row = $stmt->fetch(PDO::FETCH_NUM)) { + $key1 = CcSessPeer::getPrimaryKeyHashFromRow($row, 0); + if (null !== ($obj1 = CcSessPeer::getInstanceFromPool($key1))) { + // We no longer rehydrate the object, since this can cause data loss. + // See http://www.propelorm.org/ticket/509 + // $obj1->hydrate($row, 0, true); // rehydrate + } else { + $cls = CcSessPeer::getOMClass(false); + + $obj1 = new $cls(); + $obj1->hydrate($row); + CcSessPeer::addInstanceToPool($obj1, $key1); + } // if obj1 already loaded + + // Add objects for joined CcSubjs rows + + $key2 = CcSubjsPeer::getPrimaryKeyHashFromRow($row, $startcol2); + if ($key2 !== null) { + $obj2 = CcSubjsPeer::getInstanceFromPool($key2); + if (!$obj2) { + + $cls = CcSubjsPeer::getOMClass(false); + + $obj2 = new $cls(); + $obj2->hydrate($row, $startcol2); + CcSubjsPeer::addInstanceToPool($obj2, $key2); + } // if obj2 loaded + + // Add the $obj1 (CcSess) to the collection in $obj2 (CcSubjs) + $obj2->addCcSess($obj1); + } // if joined row not null + + $results[] = $obj1; + } + $stmt->closeCursor(); + return $results; + } + + /** + * Returns the TableMap related to this peer. + * This method is not needed for general use but a specific application could have a need. + * @return TableMap + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function getTableMap() + { + return Propel::getDatabaseMap(self::DATABASE_NAME)->getTable(self::TABLE_NAME); + } + + /** + * Add a TableMap instance to the database for this peer class. + */ + public static function buildTableMap() + { + $dbMap = Propel::getDatabaseMap(BaseCcSessPeer::DATABASE_NAME); + if (!$dbMap->hasTable(BaseCcSessPeer::TABLE_NAME)) + { + $dbMap->addTableObject(new CcSessTableMap()); + } + } + + /** + * The class that the Peer will make instances of. + * + * If $withPrefix is true, the returned path + * uses a dot-path notation which is tranalted into a path + * relative to a location on the PHP include_path. + * (e.g. path.to.MyClass -> 'path/to/MyClass.php') + * + * @param boolean $withPrefix Whether or not to return the path with the class name + * @return string path.to.ClassName + */ + public static function getOMClass($withPrefix = true) + { + return $withPrefix ? CcSessPeer::CLASS_DEFAULT : CcSessPeer::OM_CLASS; + } + + /** + * Method perform an INSERT on the database, given a CcSess or Criteria object. + * + * @param mixed $values Criteria or CcSess object containing data that is used to create the INSERT statement. + * @param PropelPDO $con the PropelPDO connection to use + * @return mixed The new primary key. + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function doInsert($values, PropelPDO $con = null) + { + if ($con === null) { + $con = Propel::getConnection(CcSessPeer::DATABASE_NAME, Propel::CONNECTION_WRITE); + } + + if ($values instanceof Criteria) { + $criteria = clone $values; // rename for clarity + } else { + $criteria = $values->buildCriteria(); // build Criteria from CcSess object + } + + + // Set the correct dbName + $criteria->setDbName(self::DATABASE_NAME); + + try { + // use transaction because $criteria could contain info + // for more than one table (I guess, conceivably) + $con->beginTransaction(); + $pk = BasePeer::doInsert($criteria, $con); + $con->commit(); + } catch(PropelException $e) { + $con->rollBack(); + throw $e; + } + + return $pk; + } + + /** + * Method perform an UPDATE on the database, given a CcSess or Criteria object. + * + * @param mixed $values Criteria or CcSess object containing data that is used to create the UPDATE statement. + * @param PropelPDO $con The connection to use (specify PropelPDO connection object to exert more control over transactions). + * @return int The number of affected rows (if supported by underlying database driver). + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function doUpdate($values, PropelPDO $con = null) + { + if ($con === null) { + $con = Propel::getConnection(CcSessPeer::DATABASE_NAME, Propel::CONNECTION_WRITE); + } + + $selectCriteria = new Criteria(self::DATABASE_NAME); + + if ($values instanceof Criteria) { + $criteria = clone $values; // rename for clarity + + $comparison = $criteria->getComparison(CcSessPeer::SESSID); + $value = $criteria->remove(CcSessPeer::SESSID); + if ($value) { + $selectCriteria->add(CcSessPeer::SESSID, $value, $comparison); + } else { + $selectCriteria->setPrimaryTableName(CcSessPeer::TABLE_NAME); + } + + } else { // $values is CcSess object + $criteria = $values->buildCriteria(); // gets full criteria + $selectCriteria = $values->buildPkeyCriteria(); // gets criteria w/ primary key(s) + } + + // set the correct dbName + $criteria->setDbName(self::DATABASE_NAME); + + return BasePeer::doUpdate($selectCriteria, $criteria, $con); + } + + /** + * Method to DELETE all rows from the cc_sess table. + * + * @return int The number of affected rows (if supported by underlying database driver). + */ + public static function doDeleteAll($con = null) + { + if ($con === null) { + $con = Propel::getConnection(CcSessPeer::DATABASE_NAME, Propel::CONNECTION_WRITE); + } + $affectedRows = 0; // initialize var to track total num of affected rows + try { + // use transaction because $criteria could contain info + // for more than one table or we could emulating ON DELETE CASCADE, etc. + $con->beginTransaction(); + $affectedRows += BasePeer::doDeleteAll(CcSessPeer::TABLE_NAME, $con, CcSessPeer::DATABASE_NAME); + // Because this db requires some delete cascade/set null emulation, we have to + // clear the cached instance *after* the emulation has happened (since + // instances get re-added by the select statement contained therein). + CcSessPeer::clearInstancePool(); + CcSessPeer::clearRelatedInstancePool(); + $con->commit(); + return $affectedRows; + } catch (PropelException $e) { + $con->rollBack(); + throw $e; + } + } + + /** + * Method perform a DELETE on the database, given a CcSess or Criteria object OR a primary key value. + * + * @param mixed $values Criteria or CcSess object or primary key or array of primary keys + * which is used to create the DELETE statement + * @param PropelPDO $con the connection to use + * @return int The number of affected rows (if supported by underlying database driver). This includes CASCADE-related rows + * if supported by native driver or if emulated using Propel. + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function doDelete($values, PropelPDO $con = null) + { + if ($con === null) { + $con = Propel::getConnection(CcSessPeer::DATABASE_NAME, Propel::CONNECTION_WRITE); + } + + if ($values instanceof Criteria) { + // invalidate the cache for all objects of this type, since we have no + // way of knowing (without running a query) what objects should be invalidated + // from the cache based on this Criteria. + CcSessPeer::clearInstancePool(); + // rename for clarity + $criteria = clone $values; + } elseif ($values instanceof CcSess) { // it's a model object + // invalidate the cache for this single object + CcSessPeer::removeInstanceFromPool($values); + // create criteria based on pk values + $criteria = $values->buildPkeyCriteria(); + } else { // it's a primary key, or an array of pks + $criteria = new Criteria(self::DATABASE_NAME); + $criteria->add(CcSessPeer::SESSID, (array) $values, Criteria::IN); + // invalidate the cache for this object(s) + foreach ((array) $values as $singleval) { + CcSessPeer::removeInstanceFromPool($singleval); + } + } + + // Set the correct dbName + $criteria->setDbName(self::DATABASE_NAME); + + $affectedRows = 0; // initialize var to track total num of affected rows + + try { + // use transaction because $criteria could contain info + // for more than one table or we could emulating ON DELETE CASCADE, etc. + $con->beginTransaction(); + + $affectedRows += BasePeer::doDelete($criteria, $con); + CcSessPeer::clearRelatedInstancePool(); + $con->commit(); + return $affectedRows; + } catch (PropelException $e) { + $con->rollBack(); + throw $e; + } + } + + /** + * Validates all modified columns of given CcSess object. + * If parameter $columns is either a single column name or an array of column names + * than only those columns are validated. + * + * NOTICE: This does not apply to primary or foreign keys for now. + * + * @param CcSess $obj The object to validate. + * @param mixed $cols Column name or array of column names. + * + * @return mixed TRUE if all columns are valid or the error message of the first invalid column. + */ + public static function doValidate(CcSess $obj, $cols = null) + { + $columns = array(); + + if ($cols) { + $dbMap = Propel::getDatabaseMap(CcSessPeer::DATABASE_NAME); + $tableMap = $dbMap->getTable(CcSessPeer::TABLE_NAME); + + if (! is_array($cols)) { + $cols = array($cols); + } + + foreach ($cols as $colName) { + if ($tableMap->containsColumn($colName)) { + $get = 'get' . $tableMap->getColumn($colName)->getPhpName(); + $columns[$colName] = $obj->$get(); + } + } + } else { + + } + + return BasePeer::doValidate(CcSessPeer::DATABASE_NAME, CcSessPeer::TABLE_NAME, $columns); + } + + /** + * Retrieve a single object by pkey. + * + * @param string $pk the primary key. + * @param PropelPDO $con the connection to use + * @return CcSess + */ + public static function retrieveByPK($pk, PropelPDO $con = null) + { + + if (null !== ($obj = CcSessPeer::getInstanceFromPool((string) $pk))) { + return $obj; + } + + if ($con === null) { + $con = Propel::getConnection(CcSessPeer::DATABASE_NAME, Propel::CONNECTION_READ); + } + + $criteria = new Criteria(CcSessPeer::DATABASE_NAME); + $criteria->add(CcSessPeer::SESSID, $pk); + + $v = CcSessPeer::doSelect($criteria, $con); + + return !empty($v) > 0 ? $v[0] : null; + } + + /** + * Retrieve multiple objects by pkey. + * + * @param array $pks List of primary keys + * @param PropelPDO $con the connection to use + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function retrieveByPKs($pks, PropelPDO $con = null) + { + if ($con === null) { + $con = Propel::getConnection(CcSessPeer::DATABASE_NAME, Propel::CONNECTION_READ); + } + + $objs = null; + if (empty($pks)) { + $objs = array(); + } else { + $criteria = new Criteria(CcSessPeer::DATABASE_NAME); + $criteria->add(CcSessPeer::SESSID, $pks, Criteria::IN); + $objs = CcSessPeer::doSelect($criteria, $con); + } + return $objs; + } + +} // BaseCcSessPeer + +// This is the static code needed to register the TableMap for this table with the main Propel class. +// +BaseCcSessPeer::buildTableMap(); + diff --git a/application/models/campcaster/om/BaseCcSessQuery.php b/application/models/campcaster/om/BaseCcSessQuery.php new file mode 100644 index 000000000..f2462c695 --- /dev/null +++ b/application/models/campcaster/om/BaseCcSessQuery.php @@ -0,0 +1,334 @@ +setModelAlias($modelAlias); + } + if ($criteria instanceof Criteria) { + $query->mergeWith($criteria); + } + return $query; + } + + /** + * Find object by primary key + * Use instance pooling to avoid a database query if the object exists + * + * $obj = $c->findPk(12, $con); + * + * @param mixed $key Primary key to use for the query + * @param PropelPDO $con an optional connection object + * + * @return CcSess|array|mixed the result, formatted by the current formatter + */ + public function findPk($key, $con = null) + { + if ((null !== ($obj = CcSessPeer::getInstanceFromPool((string) $key))) && $this->getFormatter()->isObjectFormatter()) { + // the object is alredy in the instance pool + return $obj; + } else { + // the object has not been requested yet, or the formatter is not an object formatter + $criteria = $this->isKeepQuery() ? clone $this : $this; + $stmt = $criteria + ->filterByPrimaryKey($key) + ->getSelectStatement($con); + return $criteria->getFormatter()->init($criteria)->formatOne($stmt); + } + } + + /** + * Find objects by primary key + * + * $objs = $c->findPks(array(12, 56, 832), $con); + * + * @param array $keys Primary keys to use for the query + * @param PropelPDO $con an optional connection object + * + * @return PropelObjectCollection|array|mixed the list of results, formatted by the current formatter + */ + public function findPks($keys, $con = null) + { + $criteria = $this->isKeepQuery() ? clone $this : $this; + return $this + ->filterByPrimaryKeys($keys) + ->find($con); + } + + /** + * Filter the query by primary key + * + * @param mixed $key Primary key to use for the query + * + * @return CcSessQuery The current query, for fluid interface + */ + public function filterByPrimaryKey($key) + { + return $this->addUsingAlias(CcSessPeer::SESSID, $key, Criteria::EQUAL); + } + + /** + * Filter the query by a list of primary keys + * + * @param array $keys The list of primary key to use for the query + * + * @return CcSessQuery The current query, for fluid interface + */ + public function filterByPrimaryKeys($keys) + { + return $this->addUsingAlias(CcSessPeer::SESSID, $keys, Criteria::IN); + } + + /** + * Filter the query on the sessid column + * + * @param string $sessid The value to use as filter. + * Accepts wildcards (* and % trigger a LIKE) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcSessQuery The current query, for fluid interface + */ + public function filterBySessid($sessid = null, $comparison = null) + { + if (null === $comparison) { + if (is_array($sessid)) { + $comparison = Criteria::IN; + } elseif (preg_match('/[\%\*]/', $sessid)) { + $sessid = str_replace('*', '%', $sessid); + $comparison = Criteria::LIKE; + } + } + return $this->addUsingAlias(CcSessPeer::SESSID, $sessid, $comparison); + } + + /** + * Filter the query on the userid column + * + * @param int|array $userid The value to use as filter. + * Accepts an associative array('min' => $minValue, 'max' => $maxValue) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcSessQuery The current query, for fluid interface + */ + public function filterByUserid($userid = null, $comparison = null) + { + if (is_array($userid)) { + $useMinMax = false; + if (isset($userid['min'])) { + $this->addUsingAlias(CcSessPeer::USERID, $userid['min'], Criteria::GREATER_EQUAL); + $useMinMax = true; + } + if (isset($userid['max'])) { + $this->addUsingAlias(CcSessPeer::USERID, $userid['max'], Criteria::LESS_EQUAL); + $useMinMax = true; + } + if ($useMinMax) { + return $this; + } + if (null === $comparison) { + $comparison = Criteria::IN; + } + } + return $this->addUsingAlias(CcSessPeer::USERID, $userid, $comparison); + } + + /** + * Filter the query on the login column + * + * @param string $login The value to use as filter. + * Accepts wildcards (* and % trigger a LIKE) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcSessQuery The current query, for fluid interface + */ + public function filterByLogin($login = null, $comparison = null) + { + if (null === $comparison) { + if (is_array($login)) { + $comparison = Criteria::IN; + } elseif (preg_match('/[\%\*]/', $login)) { + $login = str_replace('*', '%', $login); + $comparison = Criteria::LIKE; + } + } + return $this->addUsingAlias(CcSessPeer::LOGIN, $login, $comparison); + } + + /** + * Filter the query on the ts column + * + * @param string|array $ts The value to use as filter. + * Accepts an associative array('min' => $minValue, 'max' => $maxValue) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcSessQuery The current query, for fluid interface + */ + public function filterByTs($ts = null, $comparison = null) + { + if (is_array($ts)) { + $useMinMax = false; + if (isset($ts['min'])) { + $this->addUsingAlias(CcSessPeer::TS, $ts['min'], Criteria::GREATER_EQUAL); + $useMinMax = true; + } + if (isset($ts['max'])) { + $this->addUsingAlias(CcSessPeer::TS, $ts['max'], Criteria::LESS_EQUAL); + $useMinMax = true; + } + if ($useMinMax) { + return $this; + } + if (null === $comparison) { + $comparison = Criteria::IN; + } + } + return $this->addUsingAlias(CcSessPeer::TS, $ts, $comparison); + } + + /** + * Filter the query by a related CcSubjs object + * + * @param CcSubjs $ccSubjs the related object to use as filter + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcSessQuery The current query, for fluid interface + */ + public function filterByCcSubjs($ccSubjs, $comparison = null) + { + return $this + ->addUsingAlias(CcSessPeer::USERID, $ccSubjs->getId(), $comparison); + } + + /** + * Adds a JOIN clause to the query using the CcSubjs relation + * + * @param string $relationAlias optional alias for the relation + * @param string $joinType Accepted values are null, 'left join', 'right join', 'inner join' + * + * @return CcSessQuery The current query, for fluid interface + */ + public function joinCcSubjs($relationAlias = '', $joinType = Criteria::LEFT_JOIN) + { + $tableMap = $this->getTableMap(); + $relationMap = $tableMap->getRelation('CcSubjs'); + + // create a ModelJoin object for this join + $join = new ModelJoin(); + $join->setJoinType($joinType); + $join->setRelationMap($relationMap, $this->useAliasInSQL ? $this->getModelAlias() : null, $relationAlias); + if ($previousJoin = $this->getPreviousJoin()) { + $join->setPreviousJoin($previousJoin); + } + + // add the ModelJoin to the current object + if($relationAlias) { + $this->addAlias($relationAlias, $relationMap->getRightTable()->getName()); + $this->addJoinObject($join, $relationAlias); + } else { + $this->addJoinObject($join, 'CcSubjs'); + } + + return $this; + } + + /** + * Use the CcSubjs relation CcSubjs object + * + * @see useQuery() + * + * @param string $relationAlias optional alias for the relation, + * to be used as main alias in the secondary query + * @param string $joinType Accepted values are null, 'left join', 'right join', 'inner join' + * + * @return CcSubjsQuery A secondary query class using the current class as primary query + */ + public function useCcSubjsQuery($relationAlias = '', $joinType = Criteria::LEFT_JOIN) + { + return $this + ->joinCcSubjs($relationAlias, $joinType) + ->useQuery($relationAlias ? $relationAlias : 'CcSubjs', 'CcSubjsQuery'); + } + + /** + * Exclude object from result + * + * @param CcSess $ccSess Object to remove from the list of results + * + * @return CcSessQuery The current query, for fluid interface + */ + public function prune($ccSess = null) + { + if ($ccSess) { + $this->addUsingAlias(CcSessPeer::SESSID, $ccSess->getSessid(), Criteria::NOT_EQUAL); + } + + return $this; + } + +} // BaseCcSessQuery diff --git a/application/models/campcaster/om/BaseCcShow.php b/application/models/campcaster/om/BaseCcShow.php new file mode 100644 index 000000000..d0df93fc4 --- /dev/null +++ b/application/models/campcaster/om/BaseCcShow.php @@ -0,0 +1,1287 @@ +name = ''; + } + + /** + * Initializes internal state of BaseCcShow object. + * @see applyDefaults() + */ + public function __construct() + { + parent::__construct(); + $this->applyDefaultValues(); + } + + /** + * Get the [id] column value. + * + * @return int + */ + public function getDbId() + { + return $this->id; + } + + /** + * Get the [name] column value. + * + * @return string + */ + public function getDbName() + { + return $this->name; + } + + /** + * Get the [optionally formatted] temporal [first_show] column value. + * + * + * @param string $format The date/time format string (either date()-style or strftime()-style). + * If format is NULL, then the raw DateTime object will be returned. + * @return mixed Formatted date/time value as string or DateTime object (if format is NULL), NULL if column is NULL + * @throws PropelException - if unable to parse/validate the date/time value. + */ + public function getDbFirstShow($format = '%x') + { + if ($this->first_show === null) { + return null; + } + + + + try { + $dt = new DateTime($this->first_show); + } catch (Exception $x) { + throw new PropelException("Internally stored date/time/timestamp value could not be converted to DateTime: " . var_export($this->first_show, true), $x); + } + + if ($format === null) { + // Because propel.useDateTimeClass is TRUE, we return a DateTime object. + return $dt; + } elseif (strpos($format, '%') !== false) { + return strftime($format, $dt->format('U')); + } else { + return $dt->format($format); + } + } + + /** + * Get the [optionally formatted] temporal [last_show] column value. + * + * + * @param string $format The date/time format string (either date()-style or strftime()-style). + * If format is NULL, then the raw DateTime object will be returned. + * @return mixed Formatted date/time value as string or DateTime object (if format is NULL), NULL if column is NULL + * @throws PropelException - if unable to parse/validate the date/time value. + */ + public function getDbLastShow($format = '%x') + { + if ($this->last_show === null) { + return null; + } + + + + try { + $dt = new DateTime($this->last_show); + } catch (Exception $x) { + throw new PropelException("Internally stored date/time/timestamp value could not be converted to DateTime: " . var_export($this->last_show, true), $x); + } + + if ($format === null) { + // Because propel.useDateTimeClass is TRUE, we return a DateTime object. + return $dt; + } elseif (strpos($format, '%') !== false) { + return strftime($format, $dt->format('U')); + } else { + return $dt->format($format); + } + } + + /** + * Get the [optionally formatted] temporal [start_time] column value. + * + * + * @param string $format The date/time format string (either date()-style or strftime()-style). + * If format is NULL, then the raw DateTime object will be returned. + * @return mixed Formatted date/time value as string or DateTime object (if format is NULL), NULL if column is NULL + * @throws PropelException - if unable to parse/validate the date/time value. + */ + public function getDbStartTime($format = '%X') + { + if ($this->start_time === null) { + return null; + } + + + + try { + $dt = new DateTime($this->start_time); + } catch (Exception $x) { + throw new PropelException("Internally stored date/time/timestamp value could not be converted to DateTime: " . var_export($this->start_time, true), $x); + } + + if ($format === null) { + // Because propel.useDateTimeClass is TRUE, we return a DateTime object. + return $dt; + } elseif (strpos($format, '%') !== false) { + return strftime($format, $dt->format('U')); + } else { + return $dt->format($format); + } + } + + /** + * Get the [optionally formatted] temporal [end_time] column value. + * + * + * @param string $format The date/time format string (either date()-style or strftime()-style). + * If format is NULL, then the raw DateTime object will be returned. + * @return mixed Formatted date/time value as string or DateTime object (if format is NULL), NULL if column is NULL + * @throws PropelException - if unable to parse/validate the date/time value. + */ + public function getDbEndTime($format = '%X') + { + if ($this->end_time === null) { + return null; + } + + + + try { + $dt = new DateTime($this->end_time); + } catch (Exception $x) { + throw new PropelException("Internally stored date/time/timestamp value could not be converted to DateTime: " . var_export($this->end_time, true), $x); + } + + if ($format === null) { + // Because propel.useDateTimeClass is TRUE, we return a DateTime object. + return $dt; + } elseif (strpos($format, '%') !== false) { + return strftime($format, $dt->format('U')); + } else { + return $dt->format($format); + } + } + + /** + * Get the [repeats] column value. + * + * @return int + */ + public function getDbRepeats() + { + return $this->repeats; + } + + /** + * Get the [day] column value. + * + * @return int + */ + public function getDbDay() + { + return $this->day; + } + + /** + * Get the [description] column value. + * + * @return string + */ + public function getDbDescription() + { + return $this->description; + } + + /** + * Set the value of [id] column. + * + * @param int $v new value + * @return CcShow The current object (for fluent API support) + */ + public function setDbId($v) + { + if ($v !== null) { + $v = (int) $v; + } + + if ($this->id !== $v) { + $this->id = $v; + $this->modifiedColumns[] = CcShowPeer::ID; + } + + return $this; + } // setDbId() + + /** + * Set the value of [name] column. + * + * @param string $v new value + * @return CcShow The current object (for fluent API support) + */ + public function setDbName($v) + { + if ($v !== null) { + $v = (string) $v; + } + + if ($this->name !== $v || $this->isNew()) { + $this->name = $v; + $this->modifiedColumns[] = CcShowPeer::NAME; + } + + return $this; + } // setDbName() + + /** + * Sets the value of [first_show] column to a normalized version of the date/time value specified. + * + * @param mixed $v string, integer (timestamp), or DateTime value. Empty string will + * be treated as NULL for temporal objects. + * @return CcShow The current object (for fluent API support) + */ + public function setDbFirstShow($v) + { + // we treat '' as NULL for temporal objects because DateTime('') == DateTime('now') + // -- which is unexpected, to say the least. + if ($v === null || $v === '') { + $dt = null; + } elseif ($v instanceof DateTime) { + $dt = $v; + } else { + // some string/numeric value passed; we normalize that so that we can + // validate it. + try { + if (is_numeric($v)) { // if it's a unix timestamp + $dt = new DateTime('@'.$v, new DateTimeZone('UTC')); + // We have to explicitly specify and then change the time zone because of a + // DateTime bug: http://bugs.php.net/bug.php?id=43003 + $dt->setTimeZone(new DateTimeZone(date_default_timezone_get())); + } else { + $dt = new DateTime($v); + } + } catch (Exception $x) { + throw new PropelException('Error parsing date/time value: ' . var_export($v, true), $x); + } + } + + if ( $this->first_show !== null || $dt !== null ) { + // (nested ifs are a little easier to read in this case) + + $currNorm = ($this->first_show !== null && $tmpDt = new DateTime($this->first_show)) ? $tmpDt->format('Y-m-d') : null; + $newNorm = ($dt !== null) ? $dt->format('Y-m-d') : null; + + if ( ($currNorm !== $newNorm) // normalized values don't match + ) + { + $this->first_show = ($dt ? $dt->format('Y-m-d') : null); + $this->modifiedColumns[] = CcShowPeer::FIRST_SHOW; + } + } // if either are not null + + return $this; + } // setDbFirstShow() + + /** + * Sets the value of [last_show] column to a normalized version of the date/time value specified. + * + * @param mixed $v string, integer (timestamp), or DateTime value. Empty string will + * be treated as NULL for temporal objects. + * @return CcShow The current object (for fluent API support) + */ + public function setDbLastShow($v) + { + // we treat '' as NULL for temporal objects because DateTime('') == DateTime('now') + // -- which is unexpected, to say the least. + if ($v === null || $v === '') { + $dt = null; + } elseif ($v instanceof DateTime) { + $dt = $v; + } else { + // some string/numeric value passed; we normalize that so that we can + // validate it. + try { + if (is_numeric($v)) { // if it's a unix timestamp + $dt = new DateTime('@'.$v, new DateTimeZone('UTC')); + // We have to explicitly specify and then change the time zone because of a + // DateTime bug: http://bugs.php.net/bug.php?id=43003 + $dt->setTimeZone(new DateTimeZone(date_default_timezone_get())); + } else { + $dt = new DateTime($v); + } + } catch (Exception $x) { + throw new PropelException('Error parsing date/time value: ' . var_export($v, true), $x); + } + } + + if ( $this->last_show !== null || $dt !== null ) { + // (nested ifs are a little easier to read in this case) + + $currNorm = ($this->last_show !== null && $tmpDt = new DateTime($this->last_show)) ? $tmpDt->format('Y-m-d') : null; + $newNorm = ($dt !== null) ? $dt->format('Y-m-d') : null; + + if ( ($currNorm !== $newNorm) // normalized values don't match + ) + { + $this->last_show = ($dt ? $dt->format('Y-m-d') : null); + $this->modifiedColumns[] = CcShowPeer::LAST_SHOW; + } + } // if either are not null + + return $this; + } // setDbLastShow() + + /** + * Sets the value of [start_time] column to a normalized version of the date/time value specified. + * + * @param mixed $v string, integer (timestamp), or DateTime value. Empty string will + * be treated as NULL for temporal objects. + * @return CcShow The current object (for fluent API support) + */ + public function setDbStartTime($v) + { + // we treat '' as NULL for temporal objects because DateTime('') == DateTime('now') + // -- which is unexpected, to say the least. + if ($v === null || $v === '') { + $dt = null; + } elseif ($v instanceof DateTime) { + $dt = $v; + } else { + // some string/numeric value passed; we normalize that so that we can + // validate it. + try { + if (is_numeric($v)) { // if it's a unix timestamp + $dt = new DateTime('@'.$v, new DateTimeZone('UTC')); + // We have to explicitly specify and then change the time zone because of a + // DateTime bug: http://bugs.php.net/bug.php?id=43003 + $dt->setTimeZone(new DateTimeZone(date_default_timezone_get())); + } else { + $dt = new DateTime($v); + } + } catch (Exception $x) { + throw new PropelException('Error parsing date/time value: ' . var_export($v, true), $x); + } + } + + if ( $this->start_time !== null || $dt !== null ) { + // (nested ifs are a little easier to read in this case) + + $currNorm = ($this->start_time !== null && $tmpDt = new DateTime($this->start_time)) ? $tmpDt->format('H:i:s') : null; + $newNorm = ($dt !== null) ? $dt->format('H:i:s') : null; + + if ( ($currNorm !== $newNorm) // normalized values don't match + ) + { + $this->start_time = ($dt ? $dt->format('H:i:s') : null); + $this->modifiedColumns[] = CcShowPeer::START_TIME; + } + } // if either are not null + + return $this; + } // setDbStartTime() + + /** + * Sets the value of [end_time] column to a normalized version of the date/time value specified. + * + * @param mixed $v string, integer (timestamp), or DateTime value. Empty string will + * be treated as NULL for temporal objects. + * @return CcShow The current object (for fluent API support) + */ + public function setDbEndTime($v) + { + // we treat '' as NULL for temporal objects because DateTime('') == DateTime('now') + // -- which is unexpected, to say the least. + if ($v === null || $v === '') { + $dt = null; + } elseif ($v instanceof DateTime) { + $dt = $v; + } else { + // some string/numeric value passed; we normalize that so that we can + // validate it. + try { + if (is_numeric($v)) { // if it's a unix timestamp + $dt = new DateTime('@'.$v, new DateTimeZone('UTC')); + // We have to explicitly specify and then change the time zone because of a + // DateTime bug: http://bugs.php.net/bug.php?id=43003 + $dt->setTimeZone(new DateTimeZone(date_default_timezone_get())); + } else { + $dt = new DateTime($v); + } + } catch (Exception $x) { + throw new PropelException('Error parsing date/time value: ' . var_export($v, true), $x); + } + } + + if ( $this->end_time !== null || $dt !== null ) { + // (nested ifs are a little easier to read in this case) + + $currNorm = ($this->end_time !== null && $tmpDt = new DateTime($this->end_time)) ? $tmpDt->format('H:i:s') : null; + $newNorm = ($dt !== null) ? $dt->format('H:i:s') : null; + + if ( ($currNorm !== $newNorm) // normalized values don't match + ) + { + $this->end_time = ($dt ? $dt->format('H:i:s') : null); + $this->modifiedColumns[] = CcShowPeer::END_TIME; + } + } // if either are not null + + return $this; + } // setDbEndTime() + + /** + * Set the value of [repeats] column. + * + * @param int $v new value + * @return CcShow The current object (for fluent API support) + */ + public function setDbRepeats($v) + { + if ($v !== null) { + $v = (int) $v; + } + + if ($this->repeats !== $v) { + $this->repeats = $v; + $this->modifiedColumns[] = CcShowPeer::REPEATS; + } + + return $this; + } // setDbRepeats() + + /** + * Set the value of [day] column. + * + * @param int $v new value + * @return CcShow The current object (for fluent API support) + */ + public function setDbDay($v) + { + if ($v !== null) { + $v = (int) $v; + } + + if ($this->day !== $v) { + $this->day = $v; + $this->modifiedColumns[] = CcShowPeer::DAY; + } + + return $this; + } // setDbDay() + + /** + * Set the value of [description] column. + * + * @param string $v new value + * @return CcShow The current object (for fluent API support) + */ + public function setDbDescription($v) + { + if ($v !== null) { + $v = (string) $v; + } + + if ($this->description !== $v) { + $this->description = $v; + $this->modifiedColumns[] = CcShowPeer::DESCRIPTION; + } + + return $this; + } // setDbDescription() + + /** + * Indicates whether the columns in this object are only set to default values. + * + * This method can be used in conjunction with isModified() to indicate whether an object is both + * modified _and_ has some values set which are non-default. + * + * @return boolean Whether the columns in this object are only been set with default values. + */ + public function hasOnlyDefaultValues() + { + if ($this->name !== '') { + return false; + } + + // otherwise, everything was equal, so return TRUE + return true; + } // hasOnlyDefaultValues() + + /** + * Hydrates (populates) the object variables with values from the database resultset. + * + * An offset (0-based "start column") is specified so that objects can be hydrated + * with a subset of the columns in the resultset rows. This is needed, for example, + * for results of JOIN queries where the resultset row includes columns from two or + * more tables. + * + * @param array $row The row returned by PDOStatement->fetch(PDO::FETCH_NUM) + * @param int $startcol 0-based offset column which indicates which restultset column to start with. + * @param boolean $rehydrate Whether this object is being re-hydrated from the database. + * @return int next starting column + * @throws PropelException - Any caught Exception will be rewrapped as a PropelException. + */ + public function hydrate($row, $startcol = 0, $rehydrate = false) + { + try { + + $this->id = ($row[$startcol + 0] !== null) ? (int) $row[$startcol + 0] : null; + $this->name = ($row[$startcol + 1] !== null) ? (string) $row[$startcol + 1] : null; + $this->first_show = ($row[$startcol + 2] !== null) ? (string) $row[$startcol + 2] : null; + $this->last_show = ($row[$startcol + 3] !== null) ? (string) $row[$startcol + 3] : null; + $this->start_time = ($row[$startcol + 4] !== null) ? (string) $row[$startcol + 4] : null; + $this->end_time = ($row[$startcol + 5] !== null) ? (string) $row[$startcol + 5] : null; + $this->repeats = ($row[$startcol + 6] !== null) ? (int) $row[$startcol + 6] : null; + $this->day = ($row[$startcol + 7] !== null) ? (int) $row[$startcol + 7] : null; + $this->description = ($row[$startcol + 8] !== null) ? (string) $row[$startcol + 8] : null; + $this->resetModified(); + + $this->setNew(false); + + if ($rehydrate) { + $this->ensureConsistency(); + } + + return $startcol + 9; // 9 = CcShowPeer::NUM_COLUMNS - CcShowPeer::NUM_LAZY_LOAD_COLUMNS). + + } catch (Exception $e) { + throw new PropelException("Error populating CcShow object", $e); + } + } + + /** + * Checks and repairs the internal consistency of the object. + * + * This method is executed after an already-instantiated object is re-hydrated + * from the database. It exists to check any foreign keys to make sure that + * the objects related to the current object are correct based on foreign key. + * + * You can override this method in the stub class, but you should always invoke + * the base method from the overridden method (i.e. parent::ensureConsistency()), + * in case your model changes. + * + * @throws PropelException + */ + public function ensureConsistency() + { + + } // ensureConsistency + + /** + * Reloads this object from datastore based on primary key and (optionally) resets all associated objects. + * + * This will only work if the object has been saved and has a valid primary key set. + * + * @param boolean $deep (optional) Whether to also de-associated any related objects. + * @param PropelPDO $con (optional) The PropelPDO connection to use. + * @return void + * @throws PropelException - if this object is deleted, unsaved or doesn't have pk match in db + */ + public function reload($deep = false, PropelPDO $con = null) + { + if ($this->isDeleted()) { + throw new PropelException("Cannot reload a deleted object."); + } + + if ($this->isNew()) { + throw new PropelException("Cannot reload an unsaved object."); + } + + if ($con === null) { + $con = Propel::getConnection(CcShowPeer::DATABASE_NAME, Propel::CONNECTION_READ); + } + + // We don't need to alter the object instance pool; we're just modifying this instance + // already in the pool. + + $stmt = CcShowPeer::doSelectStmt($this->buildPkeyCriteria(), $con); + $row = $stmt->fetch(PDO::FETCH_NUM); + $stmt->closeCursor(); + if (!$row) { + throw new PropelException('Cannot find matching row in the database to reload object values.'); + } + $this->hydrate($row, 0, true); // rehydrate + + if ($deep) { // also de-associate any related objects? + + } // if (deep) + } + + /** + * Removes this object from datastore and sets delete attribute. + * + * @param PropelPDO $con + * @return void + * @throws PropelException + * @see BaseObject::setDeleted() + * @see BaseObject::isDeleted() + */ + public function delete(PropelPDO $con = null) + { + if ($this->isDeleted()) { + throw new PropelException("This object has already been deleted."); + } + + if ($con === null) { + $con = Propel::getConnection(CcShowPeer::DATABASE_NAME, Propel::CONNECTION_WRITE); + } + + $con->beginTransaction(); + try { + $ret = $this->preDelete($con); + if ($ret) { + CcShowQuery::create() + ->filterByPrimaryKey($this->getPrimaryKey()) + ->delete($con); + $this->postDelete($con); + $con->commit(); + $this->setDeleted(true); + } else { + $con->commit(); + } + } catch (PropelException $e) { + $con->rollBack(); + throw $e; + } + } + + /** + * Persists this object to the database. + * + * If the object is new, it inserts it; otherwise an update is performed. + * All modified related objects will also be persisted in the doSave() + * method. This method wraps all precipitate database operations in a + * single transaction. + * + * @param PropelPDO $con + * @return int The number of rows affected by this insert/update and any referring fk objects' save() operations. + * @throws PropelException + * @see doSave() + */ + public function save(PropelPDO $con = null) + { + if ($this->isDeleted()) { + throw new PropelException("You cannot save an object that has been deleted."); + } + + if ($con === null) { + $con = Propel::getConnection(CcShowPeer::DATABASE_NAME, Propel::CONNECTION_WRITE); + } + + $con->beginTransaction(); + $isInsert = $this->isNew(); + try { + $ret = $this->preSave($con); + if ($isInsert) { + $ret = $ret && $this->preInsert($con); + } else { + $ret = $ret && $this->preUpdate($con); + } + if ($ret) { + $affectedRows = $this->doSave($con); + if ($isInsert) { + $this->postInsert($con); + } else { + $this->postUpdate($con); + } + $this->postSave($con); + CcShowPeer::addInstanceToPool($this); + } else { + $affectedRows = 0; + } + $con->commit(); + return $affectedRows; + } catch (PropelException $e) { + $con->rollBack(); + throw $e; + } + } + + /** + * Performs the work of inserting or updating the row in the database. + * + * If the object is new, it inserts it; otherwise an update is performed. + * All related objects are also updated in this method. + * + * @param PropelPDO $con + * @return int The number of rows affected by this insert/update and any referring fk objects' save() operations. + * @throws PropelException + * @see save() + */ + protected function doSave(PropelPDO $con) + { + $affectedRows = 0; // initialize var to track total num of affected rows + if (!$this->alreadyInSave) { + $this->alreadyInSave = true; + + if ($this->isNew() ) { + $this->modifiedColumns[] = CcShowPeer::ID; + } + + // If this object has been modified, then save it to the database. + if ($this->isModified()) { + if ($this->isNew()) { + $criteria = $this->buildCriteria(); + if ($criteria->keyContainsValue(CcShowPeer::ID) ) { + throw new PropelException('Cannot insert a value for auto-increment primary key ('.CcShowPeer::ID.')'); + } + + $pk = BasePeer::doInsert($criteria, $con); + $affectedRows = 1; + $this->setDbId($pk); //[IMV] update autoincrement primary key + $this->setNew(false); + } else { + $affectedRows = CcShowPeer::doUpdate($this, $con); + } + + $this->resetModified(); // [HL] After being saved an object is no longer 'modified' + } + + $this->alreadyInSave = false; + + } + return $affectedRows; + } // doSave() + + /** + * Array of ValidationFailed objects. + * @var array ValidationFailed[] + */ + protected $validationFailures = array(); + + /** + * Gets any ValidationFailed objects that resulted from last call to validate(). + * + * + * @return array ValidationFailed[] + * @see validate() + */ + public function getValidationFailures() + { + return $this->validationFailures; + } + + /** + * Validates the objects modified field values and all objects related to this table. + * + * If $columns is either a column name or an array of column names + * only those columns are validated. + * + * @param mixed $columns Column name or an array of column names. + * @return boolean Whether all columns pass validation. + * @see doValidate() + * @see getValidationFailures() + */ + public function validate($columns = null) + { + $res = $this->doValidate($columns); + if ($res === true) { + $this->validationFailures = array(); + return true; + } else { + $this->validationFailures = $res; + return false; + } + } + + /** + * This function performs the validation work for complex object models. + * + * In addition to checking the current object, all related objects will + * also be validated. If all pass then true is returned; otherwise + * an aggreagated array of ValidationFailed objects will be returned. + * + * @param array $columns Array of column names to validate. + * @return mixed true if all validations pass; array of ValidationFailed objets otherwise. + */ + protected function doValidate($columns = null) + { + if (!$this->alreadyInValidation) { + $this->alreadyInValidation = true; + $retval = null; + + $failureMap = array(); + + + if (($retval = CcShowPeer::doValidate($this, $columns)) !== true) { + $failureMap = array_merge($failureMap, $retval); + } + + + + $this->alreadyInValidation = false; + } + + return (!empty($failureMap) ? $failureMap : true); + } + + /** + * Retrieves a field from the object by name passed in as a string. + * + * @param string $name name + * @param string $type The type of fieldname the $name is of: + * one of the class type constants BasePeer::TYPE_PHPNAME, BasePeer::TYPE_STUDLYPHPNAME + * BasePeer::TYPE_COLNAME, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_NUM + * @return mixed Value of field. + */ + public function getByName($name, $type = BasePeer::TYPE_PHPNAME) + { + $pos = CcShowPeer::translateFieldName($name, $type, BasePeer::TYPE_NUM); + $field = $this->getByPosition($pos); + return $field; + } + + /** + * Retrieves a field from the object by Position as specified in the xml schema. + * Zero-based. + * + * @param int $pos position in xml schema + * @return mixed Value of field at $pos + */ + public function getByPosition($pos) + { + switch($pos) { + case 0: + return $this->getDbId(); + break; + case 1: + return $this->getDbName(); + break; + case 2: + return $this->getDbFirstShow(); + break; + case 3: + return $this->getDbLastShow(); + break; + case 4: + return $this->getDbStartTime(); + break; + case 5: + return $this->getDbEndTime(); + break; + case 6: + return $this->getDbRepeats(); + break; + case 7: + return $this->getDbDay(); + break; + case 8: + return $this->getDbDescription(); + break; + default: + return null; + break; + } // switch() + } + + /** + * Exports the object as an array. + * + * You can specify the key type of the array by passing one of the class + * type constants. + * + * @param string $keyType (optional) One of the class type constants BasePeer::TYPE_PHPNAME, BasePeer::TYPE_STUDLYPHPNAME, + * BasePeer::TYPE_COLNAME, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_NUM. + * Defaults to BasePeer::TYPE_PHPNAME. + * @param boolean $includeLazyLoadColumns (optional) Whether to include lazy loaded columns. Defaults to TRUE. + * + * @return array an associative array containing the field names (as keys) and field values + */ + public function toArray($keyType = BasePeer::TYPE_PHPNAME, $includeLazyLoadColumns = true) + { + $keys = CcShowPeer::getFieldNames($keyType); + $result = array( + $keys[0] => $this->getDbId(), + $keys[1] => $this->getDbName(), + $keys[2] => $this->getDbFirstShow(), + $keys[3] => $this->getDbLastShow(), + $keys[4] => $this->getDbStartTime(), + $keys[5] => $this->getDbEndTime(), + $keys[6] => $this->getDbRepeats(), + $keys[7] => $this->getDbDay(), + $keys[8] => $this->getDbDescription(), + ); + return $result; + } + + /** + * Sets a field from the object by name passed in as a string. + * + * @param string $name peer name + * @param mixed $value field value + * @param string $type The type of fieldname the $name is of: + * one of the class type constants BasePeer::TYPE_PHPNAME, BasePeer::TYPE_STUDLYPHPNAME + * BasePeer::TYPE_COLNAME, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_NUM + * @return void + */ + public function setByName($name, $value, $type = BasePeer::TYPE_PHPNAME) + { + $pos = CcShowPeer::translateFieldName($name, $type, BasePeer::TYPE_NUM); + return $this->setByPosition($pos, $value); + } + + /** + * Sets a field from the object by Position as specified in the xml schema. + * Zero-based. + * + * @param int $pos position in xml schema + * @param mixed $value field value + * @return void + */ + public function setByPosition($pos, $value) + { + switch($pos) { + case 0: + $this->setDbId($value); + break; + case 1: + $this->setDbName($value); + break; + case 2: + $this->setDbFirstShow($value); + break; + case 3: + $this->setDbLastShow($value); + break; + case 4: + $this->setDbStartTime($value); + break; + case 5: + $this->setDbEndTime($value); + break; + case 6: + $this->setDbRepeats($value); + break; + case 7: + $this->setDbDay($value); + break; + case 8: + $this->setDbDescription($value); + break; + } // switch() + } + + /** + * Populates the object using an array. + * + * This is particularly useful when populating an object from one of the + * request arrays (e.g. $_POST). This method goes through the column + * names, checking to see whether a matching key exists in populated + * array. If so the setByName() method is called for that column. + * + * You can specify the key type of the array by additionally passing one + * of the class type constants BasePeer::TYPE_PHPNAME, BasePeer::TYPE_STUDLYPHPNAME, + * BasePeer::TYPE_COLNAME, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_NUM. + * The default key type is the column's phpname (e.g. 'AuthorId') + * + * @param array $arr An array to populate the object from. + * @param string $keyType The type of keys the array uses. + * @return void + */ + public function fromArray($arr, $keyType = BasePeer::TYPE_PHPNAME) + { + $keys = CcShowPeer::getFieldNames($keyType); + + if (array_key_exists($keys[0], $arr)) $this->setDbId($arr[$keys[0]]); + if (array_key_exists($keys[1], $arr)) $this->setDbName($arr[$keys[1]]); + if (array_key_exists($keys[2], $arr)) $this->setDbFirstShow($arr[$keys[2]]); + if (array_key_exists($keys[3], $arr)) $this->setDbLastShow($arr[$keys[3]]); + if (array_key_exists($keys[4], $arr)) $this->setDbStartTime($arr[$keys[4]]); + if (array_key_exists($keys[5], $arr)) $this->setDbEndTime($arr[$keys[5]]); + if (array_key_exists($keys[6], $arr)) $this->setDbRepeats($arr[$keys[6]]); + if (array_key_exists($keys[7], $arr)) $this->setDbDay($arr[$keys[7]]); + if (array_key_exists($keys[8], $arr)) $this->setDbDescription($arr[$keys[8]]); + } + + /** + * Build a Criteria object containing the values of all modified columns in this object. + * + * @return Criteria The Criteria object containing all modified values. + */ + public function buildCriteria() + { + $criteria = new Criteria(CcShowPeer::DATABASE_NAME); + + if ($this->isColumnModified(CcShowPeer::ID)) $criteria->add(CcShowPeer::ID, $this->id); + if ($this->isColumnModified(CcShowPeer::NAME)) $criteria->add(CcShowPeer::NAME, $this->name); + if ($this->isColumnModified(CcShowPeer::FIRST_SHOW)) $criteria->add(CcShowPeer::FIRST_SHOW, $this->first_show); + if ($this->isColumnModified(CcShowPeer::LAST_SHOW)) $criteria->add(CcShowPeer::LAST_SHOW, $this->last_show); + if ($this->isColumnModified(CcShowPeer::START_TIME)) $criteria->add(CcShowPeer::START_TIME, $this->start_time); + if ($this->isColumnModified(CcShowPeer::END_TIME)) $criteria->add(CcShowPeer::END_TIME, $this->end_time); + if ($this->isColumnModified(CcShowPeer::REPEATS)) $criteria->add(CcShowPeer::REPEATS, $this->repeats); + if ($this->isColumnModified(CcShowPeer::DAY)) $criteria->add(CcShowPeer::DAY, $this->day); + if ($this->isColumnModified(CcShowPeer::DESCRIPTION)) $criteria->add(CcShowPeer::DESCRIPTION, $this->description); + + return $criteria; + } + + /** + * Builds a Criteria object containing the primary key for this object. + * + * Unlike buildCriteria() this method includes the primary key values regardless + * of whether or not they have been modified. + * + * @return Criteria The Criteria object containing value(s) for primary key(s). + */ + public function buildPkeyCriteria() + { + $criteria = new Criteria(CcShowPeer::DATABASE_NAME); + $criteria->add(CcShowPeer::ID, $this->id); + + return $criteria; + } + + /** + * Returns the primary key for this object (row). + * @return int + */ + public function getPrimaryKey() + { + return $this->getDbId(); + } + + /** + * Generic method to set the primary key (id column). + * + * @param int $key Primary key. + * @return void + */ + public function setPrimaryKey($key) + { + $this->setDbId($key); + } + + /** + * Returns true if the primary key for this object is null. + * @return boolean + */ + public function isPrimaryKeyNull() + { + return null === $this->getDbId(); + } + + /** + * Sets contents of passed object to values from current object. + * + * If desired, this method can also make copies of all associated (fkey referrers) + * objects. + * + * @param object $copyObj An object of CcShow (or compatible) type. + * @param boolean $deepCopy Whether to also copy all rows that refer (by fkey) to the current row. + * @throws PropelException + */ + public function copyInto($copyObj, $deepCopy = false) + { + $copyObj->setDbName($this->name); + $copyObj->setDbFirstShow($this->first_show); + $copyObj->setDbLastShow($this->last_show); + $copyObj->setDbStartTime($this->start_time); + $copyObj->setDbEndTime($this->end_time); + $copyObj->setDbRepeats($this->repeats); + $copyObj->setDbDay($this->day); + $copyObj->setDbDescription($this->description); + + $copyObj->setNew(true); + $copyObj->setDbId(NULL); // this is a auto-increment column, so set to default value + } + + /** + * Makes a copy of this object that will be inserted as a new row in table when saved. + * It creates a new object filling in the simple attributes, but skipping any primary + * keys that are defined for the table. + * + * If desired, this method can also make copies of all associated (fkey referrers) + * objects. + * + * @param boolean $deepCopy Whether to also copy all rows that refer (by fkey) to the current row. + * @return CcShow Clone of current object. + * @throws PropelException + */ + public function copy($deepCopy = false) + { + // we use get_class(), because this might be a subclass + $clazz = get_class($this); + $copyObj = new $clazz(); + $this->copyInto($copyObj, $deepCopy); + return $copyObj; + } + + /** + * Returns a peer instance associated with this om. + * + * Since Peer classes are not to have any instance attributes, this method returns the + * same instance for all member of this class. The method could therefore + * be static, but this would prevent one from overriding the behavior. + * + * @return CcShowPeer + */ + public function getPeer() + { + if (self::$peer === null) { + self::$peer = new CcShowPeer(); + } + return self::$peer; + } + + /** + * Clears the current object and sets all attributes to their default values + */ + public function clear() + { + $this->id = null; + $this->name = null; + $this->first_show = null; + $this->last_show = null; + $this->start_time = null; + $this->end_time = null; + $this->repeats = null; + $this->day = null; + $this->description = null; + $this->alreadyInSave = false; + $this->alreadyInValidation = false; + $this->clearAllReferences(); + $this->applyDefaultValues(); + $this->resetModified(); + $this->setNew(true); + $this->setDeleted(false); + } + + /** + * Resets all collections of referencing foreign keys. + * + * This method is a user-space workaround for PHP's inability to garbage collect objects + * with circular references. This is currently necessary when using Propel in certain + * daemon or large-volumne/high-memory operations. + * + * @param boolean $deep Whether to also clear the references on all associated objects. + */ + public function clearAllReferences($deep = false) + { + if ($deep) { + } // if ($deep) + + } + + /** + * Catches calls to virtual methods + */ + public function __call($name, $params) + { + if (preg_match('/get(\w+)/', $name, $matches)) { + $virtualColumn = $matches[1]; + if ($this->hasVirtualColumn($virtualColumn)) { + return $this->getVirtualColumn($virtualColumn); + } + // no lcfirst in php<5.3... + $virtualColumn[0] = strtolower($virtualColumn[0]); + if ($this->hasVirtualColumn($virtualColumn)) { + return $this->getVirtualColumn($virtualColumn); + } + } + throw new PropelException('Call to undefined method: ' . $name); + } + +} // BaseCcShow diff --git a/application/models/campcaster/om/BaseCcShowPeer.php b/application/models/campcaster/om/BaseCcShowPeer.php new file mode 100644 index 000000000..f407adde1 --- /dev/null +++ b/application/models/campcaster/om/BaseCcShowPeer.php @@ -0,0 +1,774 @@ + array ('DbId', 'DbName', 'DbFirstShow', 'DbLastShow', 'DbStartTime', 'DbEndTime', 'DbRepeats', 'DbDay', 'DbDescription', ), + BasePeer::TYPE_STUDLYPHPNAME => array ('dbId', 'dbName', 'dbFirstShow', 'dbLastShow', 'dbStartTime', 'dbEndTime', 'dbRepeats', 'dbDay', 'dbDescription', ), + BasePeer::TYPE_COLNAME => array (self::ID, self::NAME, self::FIRST_SHOW, self::LAST_SHOW, self::START_TIME, self::END_TIME, self::REPEATS, self::DAY, self::DESCRIPTION, ), + BasePeer::TYPE_RAW_COLNAME => array ('ID', 'NAME', 'FIRST_SHOW', 'LAST_SHOW', 'START_TIME', 'END_TIME', 'REPEATS', 'DAY', 'DESCRIPTION', ), + BasePeer::TYPE_FIELDNAME => array ('id', 'name', 'first_show', 'last_show', 'start_time', 'end_time', 'repeats', 'day', 'description', ), + BasePeer::TYPE_NUM => array (0, 1, 2, 3, 4, 5, 6, 7, 8, ) + ); + + /** + * holds an array of keys for quick access to the fieldnames array + * + * first dimension keys are the type constants + * e.g. self::$fieldNames[BasePeer::TYPE_PHPNAME]['Id'] = 0 + */ + private static $fieldKeys = array ( + BasePeer::TYPE_PHPNAME => array ('DbId' => 0, 'DbName' => 1, 'DbFirstShow' => 2, 'DbLastShow' => 3, 'DbStartTime' => 4, 'DbEndTime' => 5, 'DbRepeats' => 6, 'DbDay' => 7, 'DbDescription' => 8, ), + BasePeer::TYPE_STUDLYPHPNAME => array ('dbId' => 0, 'dbName' => 1, 'dbFirstShow' => 2, 'dbLastShow' => 3, 'dbStartTime' => 4, 'dbEndTime' => 5, 'dbRepeats' => 6, 'dbDay' => 7, 'dbDescription' => 8, ), + BasePeer::TYPE_COLNAME => array (self::ID => 0, self::NAME => 1, self::FIRST_SHOW => 2, self::LAST_SHOW => 3, self::START_TIME => 4, self::END_TIME => 5, self::REPEATS => 6, self::DAY => 7, self::DESCRIPTION => 8, ), + BasePeer::TYPE_RAW_COLNAME => array ('ID' => 0, 'NAME' => 1, 'FIRST_SHOW' => 2, 'LAST_SHOW' => 3, 'START_TIME' => 4, 'END_TIME' => 5, 'REPEATS' => 6, 'DAY' => 7, 'DESCRIPTION' => 8, ), + BasePeer::TYPE_FIELDNAME => array ('id' => 0, 'name' => 1, 'first_show' => 2, 'last_show' => 3, 'start_time' => 4, 'end_time' => 5, 'repeats' => 6, 'day' => 7, 'description' => 8, ), + BasePeer::TYPE_NUM => array (0, 1, 2, 3, 4, 5, 6, 7, 8, ) + ); + + /** + * Translates a fieldname to another type + * + * @param string $name field name + * @param string $fromType One of the class type constants BasePeer::TYPE_PHPNAME, BasePeer::TYPE_STUDLYPHPNAME + * BasePeer::TYPE_COLNAME, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_NUM + * @param string $toType One of the class type constants + * @return string translated name of the field. + * @throws PropelException - if the specified name could not be found in the fieldname mappings. + */ + static public function translateFieldName($name, $fromType, $toType) + { + $toNames = self::getFieldNames($toType); + $key = isset(self::$fieldKeys[$fromType][$name]) ? self::$fieldKeys[$fromType][$name] : null; + if ($key === null) { + throw new PropelException("'$name' could not be found in the field names of type '$fromType'. These are: " . print_r(self::$fieldKeys[$fromType], true)); + } + return $toNames[$key]; + } + + /** + * Returns an array of field names. + * + * @param string $type The type of fieldnames to return: + * One of the class type constants BasePeer::TYPE_PHPNAME, BasePeer::TYPE_STUDLYPHPNAME + * BasePeer::TYPE_COLNAME, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_NUM + * @return array A list of field names + */ + + static public function getFieldNames($type = BasePeer::TYPE_PHPNAME) + { + if (!array_key_exists($type, self::$fieldNames)) { + throw new PropelException('Method getFieldNames() expects the parameter $type to be one of the class constants BasePeer::TYPE_PHPNAME, BasePeer::TYPE_STUDLYPHPNAME, BasePeer::TYPE_COLNAME, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_NUM. ' . $type . ' was given.'); + } + return self::$fieldNames[$type]; + } + + /** + * Convenience method which changes table.column to alias.column. + * + * Using this method you can maintain SQL abstraction while using column aliases. + * + * $c->addAlias("alias1", TablePeer::TABLE_NAME); + * $c->addJoin(TablePeer::alias("alias1", TablePeer::PRIMARY_KEY_COLUMN), TablePeer::PRIMARY_KEY_COLUMN); + * + * @param string $alias The alias for the current table. + * @param string $column The column name for current table. (i.e. CcShowPeer::COLUMN_NAME). + * @return string + */ + public static function alias($alias, $column) + { + return str_replace(CcShowPeer::TABLE_NAME.'.', $alias.'.', $column); + } + + /** + * Add all the columns needed to create a new object. + * + * Note: any columns that were marked with lazyLoad="true" in the + * XML schema will not be added to the select list and only loaded + * on demand. + * + * @param Criteria $criteria object containing the columns to add. + * @param string $alias optional table alias + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function addSelectColumns(Criteria $criteria, $alias = null) + { + if (null === $alias) { + $criteria->addSelectColumn(CcShowPeer::ID); + $criteria->addSelectColumn(CcShowPeer::NAME); + $criteria->addSelectColumn(CcShowPeer::FIRST_SHOW); + $criteria->addSelectColumn(CcShowPeer::LAST_SHOW); + $criteria->addSelectColumn(CcShowPeer::START_TIME); + $criteria->addSelectColumn(CcShowPeer::END_TIME); + $criteria->addSelectColumn(CcShowPeer::REPEATS); + $criteria->addSelectColumn(CcShowPeer::DAY); + $criteria->addSelectColumn(CcShowPeer::DESCRIPTION); + } else { + $criteria->addSelectColumn($alias . '.ID'); + $criteria->addSelectColumn($alias . '.NAME'); + $criteria->addSelectColumn($alias . '.FIRST_SHOW'); + $criteria->addSelectColumn($alias . '.LAST_SHOW'); + $criteria->addSelectColumn($alias . '.START_TIME'); + $criteria->addSelectColumn($alias . '.END_TIME'); + $criteria->addSelectColumn($alias . '.REPEATS'); + $criteria->addSelectColumn($alias . '.DAY'); + $criteria->addSelectColumn($alias . '.DESCRIPTION'); + } + } + + /** + * Returns the number of rows matching criteria. + * + * @param Criteria $criteria + * @param boolean $distinct Whether to select only distinct columns; deprecated: use Criteria->setDistinct() instead. + * @param PropelPDO $con + * @return int Number of matching rows. + */ + public static function doCount(Criteria $criteria, $distinct = false, PropelPDO $con = null) + { + // we may modify criteria, so copy it first + $criteria = clone $criteria; + + // We need to set the primary table name, since in the case that there are no WHERE columns + // it will be impossible for the BasePeer::createSelectSql() method to determine which + // tables go into the FROM clause. + $criteria->setPrimaryTableName(CcShowPeer::TABLE_NAME); + + if ($distinct && !in_array(Criteria::DISTINCT, $criteria->getSelectModifiers())) { + $criteria->setDistinct(); + } + + if (!$criteria->hasSelectClause()) { + CcShowPeer::addSelectColumns($criteria); + } + + $criteria->clearOrderByColumns(); // ORDER BY won't ever affect the count + $criteria->setDbName(self::DATABASE_NAME); // Set the correct dbName + + if ($con === null) { + $con = Propel::getConnection(CcShowPeer::DATABASE_NAME, Propel::CONNECTION_READ); + } + // BasePeer returns a PDOStatement + $stmt = BasePeer::doCount($criteria, $con); + + if ($row = $stmt->fetch(PDO::FETCH_NUM)) { + $count = (int) $row[0]; + } else { + $count = 0; // no rows returned; we infer that means 0 matches. + } + $stmt->closeCursor(); + return $count; + } + /** + * Method to select one object from the DB. + * + * @param Criteria $criteria object used to create the SELECT statement. + * @param PropelPDO $con + * @return CcShow + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function doSelectOne(Criteria $criteria, PropelPDO $con = null) + { + $critcopy = clone $criteria; + $critcopy->setLimit(1); + $objects = CcShowPeer::doSelect($critcopy, $con); + if ($objects) { + return $objects[0]; + } + return null; + } + /** + * Method to do selects. + * + * @param Criteria $criteria The Criteria object used to build the SELECT statement. + * @param PropelPDO $con + * @return array Array of selected Objects + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function doSelect(Criteria $criteria, PropelPDO $con = null) + { + return CcShowPeer::populateObjects(CcShowPeer::doSelectStmt($criteria, $con)); + } + /** + * Prepares the Criteria object and uses the parent doSelect() method to execute a PDOStatement. + * + * Use this method directly if you want to work with an executed statement durirectly (for example + * to perform your own object hydration). + * + * @param Criteria $criteria The Criteria object used to build the SELECT statement. + * @param PropelPDO $con The connection to use + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + * @return PDOStatement The executed PDOStatement object. + * @see BasePeer::doSelect() + */ + public static function doSelectStmt(Criteria $criteria, PropelPDO $con = null) + { + if ($con === null) { + $con = Propel::getConnection(CcShowPeer::DATABASE_NAME, Propel::CONNECTION_READ); + } + + if (!$criteria->hasSelectClause()) { + $criteria = clone $criteria; + CcShowPeer::addSelectColumns($criteria); + } + + // Set the correct dbName + $criteria->setDbName(self::DATABASE_NAME); + + // BasePeer returns a PDOStatement + return BasePeer::doSelect($criteria, $con); + } + /** + * Adds an object to the instance pool. + * + * Propel keeps cached copies of objects in an instance pool when they are retrieved + * from the database. In some cases -- especially when you override doSelect*() + * methods in your stub classes -- you may need to explicitly add objects + * to the cache in order to ensure that the same objects are always returned by doSelect*() + * and retrieveByPK*() calls. + * + * @param CcShow $value A CcShow object. + * @param string $key (optional) key to use for instance map (for performance boost if key was already calculated externally). + */ + public static function addInstanceToPool(CcShow $obj, $key = null) + { + if (Propel::isInstancePoolingEnabled()) { + if ($key === null) { + $key = (string) $obj->getDbId(); + } // if key === null + self::$instances[$key] = $obj; + } + } + + /** + * Removes an object from the instance pool. + * + * Propel keeps cached copies of objects in an instance pool when they are retrieved + * from the database. In some cases -- especially when you override doDelete + * methods in your stub classes -- you may need to explicitly remove objects + * from the cache in order to prevent returning objects that no longer exist. + * + * @param mixed $value A CcShow object or a primary key value. + */ + public static function removeInstanceFromPool($value) + { + if (Propel::isInstancePoolingEnabled() && $value !== null) { + if (is_object($value) && $value instanceof CcShow) { + $key = (string) $value->getDbId(); + } elseif (is_scalar($value)) { + // assume we've been passed a primary key + $key = (string) $value; + } else { + $e = new PropelException("Invalid value passed to removeInstanceFromPool(). Expected primary key or CcShow object; got " . (is_object($value) ? get_class($value) . ' object.' : var_export($value,true))); + throw $e; + } + + unset(self::$instances[$key]); + } + } // removeInstanceFromPool() + + /** + * Retrieves a string version of the primary key from the DB resultset row that can be used to uniquely identify a row in this table. + * + * For tables with a single-column primary key, that simple pkey value will be returned. For tables with + * a multi-column primary key, a serialize()d version of the primary key will be returned. + * + * @param string $key The key (@see getPrimaryKeyHash()) for this instance. + * @return CcShow Found object or NULL if 1) no instance exists for specified key or 2) instance pooling has been disabled. + * @see getPrimaryKeyHash() + */ + public static function getInstanceFromPool($key) + { + if (Propel::isInstancePoolingEnabled()) { + if (isset(self::$instances[$key])) { + return self::$instances[$key]; + } + } + return null; // just to be explicit + } + + /** + * Clear the instance pool. + * + * @return void + */ + public static function clearInstancePool() + { + self::$instances = array(); + } + + /** + * Method to invalidate the instance pool of all tables related to cc_show + * by a foreign key with ON DELETE CASCADE + */ + public static function clearRelatedInstancePool() + { + } + + /** + * Retrieves a string version of the primary key from the DB resultset row that can be used to uniquely identify a row in this table. + * + * For tables with a single-column primary key, that simple pkey value will be returned. For tables with + * a multi-column primary key, a serialize()d version of the primary key will be returned. + * + * @param array $row PropelPDO resultset row. + * @param int $startcol The 0-based offset for reading from the resultset row. + * @return string A string version of PK or NULL if the components of primary key in result array are all null. + */ + public static function getPrimaryKeyHashFromRow($row, $startcol = 0) + { + // If the PK cannot be derived from the row, return NULL. + if ($row[$startcol] === null) { + return null; + } + return (string) $row[$startcol]; + } + + /** + * Retrieves the primary key from the DB resultset row + * For tables with a single-column primary key, that simple pkey value will be returned. For tables with + * a multi-column primary key, an array of the primary key columns will be returned. + * + * @param array $row PropelPDO resultset row. + * @param int $startcol The 0-based offset for reading from the resultset row. + * @return mixed The primary key of the row + */ + public static function getPrimaryKeyFromRow($row, $startcol = 0) + { + return (int) $row[$startcol]; + } + + /** + * The returned array will contain objects of the default type or + * objects that inherit from the default. + * + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function populateObjects(PDOStatement $stmt) + { + $results = array(); + + // set the class once to avoid overhead in the loop + $cls = CcShowPeer::getOMClass(false); + // populate the object(s) + while ($row = $stmt->fetch(PDO::FETCH_NUM)) { + $key = CcShowPeer::getPrimaryKeyHashFromRow($row, 0); + if (null !== ($obj = CcShowPeer::getInstanceFromPool($key))) { + // We no longer rehydrate the object, since this can cause data loss. + // See http://www.propelorm.org/ticket/509 + // $obj->hydrate($row, 0, true); // rehydrate + $results[] = $obj; + } else { + $obj = new $cls(); + $obj->hydrate($row); + $results[] = $obj; + CcShowPeer::addInstanceToPool($obj, $key); + } // if key exists + } + $stmt->closeCursor(); + return $results; + } + /** + * Populates an object of the default type or an object that inherit from the default. + * + * @param array $row PropelPDO resultset row. + * @param int $startcol The 0-based offset for reading from the resultset row. + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + * @return array (CcShow object, last column rank) + */ + public static function populateObject($row, $startcol = 0) + { + $key = CcShowPeer::getPrimaryKeyHashFromRow($row, $startcol); + if (null !== ($obj = CcShowPeer::getInstanceFromPool($key))) { + // We no longer rehydrate the object, since this can cause data loss. + // See http://www.propelorm.org/ticket/509 + // $obj->hydrate($row, $startcol, true); // rehydrate + $col = $startcol + CcShowPeer::NUM_COLUMNS; + } else { + $cls = CcShowPeer::OM_CLASS; + $obj = new $cls(); + $col = $obj->hydrate($row, $startcol); + CcShowPeer::addInstanceToPool($obj, $key); + } + return array($obj, $col); + } + /** + * Returns the TableMap related to this peer. + * This method is not needed for general use but a specific application could have a need. + * @return TableMap + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function getTableMap() + { + return Propel::getDatabaseMap(self::DATABASE_NAME)->getTable(self::TABLE_NAME); + } + + /** + * Add a TableMap instance to the database for this peer class. + */ + public static function buildTableMap() + { + $dbMap = Propel::getDatabaseMap(BaseCcShowPeer::DATABASE_NAME); + if (!$dbMap->hasTable(BaseCcShowPeer::TABLE_NAME)) + { + $dbMap->addTableObject(new CcShowTableMap()); + } + } + + /** + * The class that the Peer will make instances of. + * + * If $withPrefix is true, the returned path + * uses a dot-path notation which is tranalted into a path + * relative to a location on the PHP include_path. + * (e.g. path.to.MyClass -> 'path/to/MyClass.php') + * + * @param boolean $withPrefix Whether or not to return the path with the class name + * @return string path.to.ClassName + */ + public static function getOMClass($withPrefix = true) + { + return $withPrefix ? CcShowPeer::CLASS_DEFAULT : CcShowPeer::OM_CLASS; + } + + /** + * Method perform an INSERT on the database, given a CcShow or Criteria object. + * + * @param mixed $values Criteria or CcShow object containing data that is used to create the INSERT statement. + * @param PropelPDO $con the PropelPDO connection to use + * @return mixed The new primary key. + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function doInsert($values, PropelPDO $con = null) + { + if ($con === null) { + $con = Propel::getConnection(CcShowPeer::DATABASE_NAME, Propel::CONNECTION_WRITE); + } + + if ($values instanceof Criteria) { + $criteria = clone $values; // rename for clarity + } else { + $criteria = $values->buildCriteria(); // build Criteria from CcShow object + } + + if ($criteria->containsKey(CcShowPeer::ID) && $criteria->keyContainsValue(CcShowPeer::ID) ) { + throw new PropelException('Cannot insert a value for auto-increment primary key ('.CcShowPeer::ID.')'); + } + + + // Set the correct dbName + $criteria->setDbName(self::DATABASE_NAME); + + try { + // use transaction because $criteria could contain info + // for more than one table (I guess, conceivably) + $con->beginTransaction(); + $pk = BasePeer::doInsert($criteria, $con); + $con->commit(); + } catch(PropelException $e) { + $con->rollBack(); + throw $e; + } + + return $pk; + } + + /** + * Method perform an UPDATE on the database, given a CcShow or Criteria object. + * + * @param mixed $values Criteria or CcShow object containing data that is used to create the UPDATE statement. + * @param PropelPDO $con The connection to use (specify PropelPDO connection object to exert more control over transactions). + * @return int The number of affected rows (if supported by underlying database driver). + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function doUpdate($values, PropelPDO $con = null) + { + if ($con === null) { + $con = Propel::getConnection(CcShowPeer::DATABASE_NAME, Propel::CONNECTION_WRITE); + } + + $selectCriteria = new Criteria(self::DATABASE_NAME); + + if ($values instanceof Criteria) { + $criteria = clone $values; // rename for clarity + + $comparison = $criteria->getComparison(CcShowPeer::ID); + $value = $criteria->remove(CcShowPeer::ID); + if ($value) { + $selectCriteria->add(CcShowPeer::ID, $value, $comparison); + } else { + $selectCriteria->setPrimaryTableName(CcShowPeer::TABLE_NAME); + } + + } else { // $values is CcShow object + $criteria = $values->buildCriteria(); // gets full criteria + $selectCriteria = $values->buildPkeyCriteria(); // gets criteria w/ primary key(s) + } + + // set the correct dbName + $criteria->setDbName(self::DATABASE_NAME); + + return BasePeer::doUpdate($selectCriteria, $criteria, $con); + } + + /** + * Method to DELETE all rows from the cc_show table. + * + * @return int The number of affected rows (if supported by underlying database driver). + */ + public static function doDeleteAll($con = null) + { + if ($con === null) { + $con = Propel::getConnection(CcShowPeer::DATABASE_NAME, Propel::CONNECTION_WRITE); + } + $affectedRows = 0; // initialize var to track total num of affected rows + try { + // use transaction because $criteria could contain info + // for more than one table or we could emulating ON DELETE CASCADE, etc. + $con->beginTransaction(); + $affectedRows += BasePeer::doDeleteAll(CcShowPeer::TABLE_NAME, $con, CcShowPeer::DATABASE_NAME); + // Because this db requires some delete cascade/set null emulation, we have to + // clear the cached instance *after* the emulation has happened (since + // instances get re-added by the select statement contained therein). + CcShowPeer::clearInstancePool(); + CcShowPeer::clearRelatedInstancePool(); + $con->commit(); + return $affectedRows; + } catch (PropelException $e) { + $con->rollBack(); + throw $e; + } + } + + /** + * Method perform a DELETE on the database, given a CcShow or Criteria object OR a primary key value. + * + * @param mixed $values Criteria or CcShow object or primary key or array of primary keys + * which is used to create the DELETE statement + * @param PropelPDO $con the connection to use + * @return int The number of affected rows (if supported by underlying database driver). This includes CASCADE-related rows + * if supported by native driver or if emulated using Propel. + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function doDelete($values, PropelPDO $con = null) + { + if ($con === null) { + $con = Propel::getConnection(CcShowPeer::DATABASE_NAME, Propel::CONNECTION_WRITE); + } + + if ($values instanceof Criteria) { + // invalidate the cache for all objects of this type, since we have no + // way of knowing (without running a query) what objects should be invalidated + // from the cache based on this Criteria. + CcShowPeer::clearInstancePool(); + // rename for clarity + $criteria = clone $values; + } elseif ($values instanceof CcShow) { // it's a model object + // invalidate the cache for this single object + CcShowPeer::removeInstanceFromPool($values); + // create criteria based on pk values + $criteria = $values->buildPkeyCriteria(); + } else { // it's a primary key, or an array of pks + $criteria = new Criteria(self::DATABASE_NAME); + $criteria->add(CcShowPeer::ID, (array) $values, Criteria::IN); + // invalidate the cache for this object(s) + foreach ((array) $values as $singleval) { + CcShowPeer::removeInstanceFromPool($singleval); + } + } + + // Set the correct dbName + $criteria->setDbName(self::DATABASE_NAME); + + $affectedRows = 0; // initialize var to track total num of affected rows + + try { + // use transaction because $criteria could contain info + // for more than one table or we could emulating ON DELETE CASCADE, etc. + $con->beginTransaction(); + + $affectedRows += BasePeer::doDelete($criteria, $con); + CcShowPeer::clearRelatedInstancePool(); + $con->commit(); + return $affectedRows; + } catch (PropelException $e) { + $con->rollBack(); + throw $e; + } + } + + /** + * Validates all modified columns of given CcShow object. + * If parameter $columns is either a single column name or an array of column names + * than only those columns are validated. + * + * NOTICE: This does not apply to primary or foreign keys for now. + * + * @param CcShow $obj The object to validate. + * @param mixed $cols Column name or array of column names. + * + * @return mixed TRUE if all columns are valid or the error message of the first invalid column. + */ + public static function doValidate(CcShow $obj, $cols = null) + { + $columns = array(); + + if ($cols) { + $dbMap = Propel::getDatabaseMap(CcShowPeer::DATABASE_NAME); + $tableMap = $dbMap->getTable(CcShowPeer::TABLE_NAME); + + if (! is_array($cols)) { + $cols = array($cols); + } + + foreach ($cols as $colName) { + if ($tableMap->containsColumn($colName)) { + $get = 'get' . $tableMap->getColumn($colName)->getPhpName(); + $columns[$colName] = $obj->$get(); + } + } + } else { + + } + + return BasePeer::doValidate(CcShowPeer::DATABASE_NAME, CcShowPeer::TABLE_NAME, $columns); + } + + /** + * Retrieve a single object by pkey. + * + * @param int $pk the primary key. + * @param PropelPDO $con the connection to use + * @return CcShow + */ + public static function retrieveByPK($pk, PropelPDO $con = null) + { + + if (null !== ($obj = CcShowPeer::getInstanceFromPool((string) $pk))) { + return $obj; + } + + if ($con === null) { + $con = Propel::getConnection(CcShowPeer::DATABASE_NAME, Propel::CONNECTION_READ); + } + + $criteria = new Criteria(CcShowPeer::DATABASE_NAME); + $criteria->add(CcShowPeer::ID, $pk); + + $v = CcShowPeer::doSelect($criteria, $con); + + return !empty($v) > 0 ? $v[0] : null; + } + + /** + * Retrieve multiple objects by pkey. + * + * @param array $pks List of primary keys + * @param PropelPDO $con the connection to use + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function retrieveByPKs($pks, PropelPDO $con = null) + { + if ($con === null) { + $con = Propel::getConnection(CcShowPeer::DATABASE_NAME, Propel::CONNECTION_READ); + } + + $objs = null; + if (empty($pks)) { + $objs = array(); + } else { + $criteria = new Criteria(CcShowPeer::DATABASE_NAME); + $criteria->add(CcShowPeer::ID, $pks, Criteria::IN); + $objs = CcShowPeer::doSelect($criteria, $con); + } + return $objs; + } + +} // BaseCcShowPeer + +// This is the static code needed to register the TableMap for this table with the main Propel class. +// +BaseCcShowPeer::buildTableMap(); + diff --git a/application/models/campcaster/om/BaseCcShowQuery.php b/application/models/campcaster/om/BaseCcShowQuery.php new file mode 100644 index 000000000..7dd2829e6 --- /dev/null +++ b/application/models/campcaster/om/BaseCcShowQuery.php @@ -0,0 +1,427 @@ +setModelAlias($modelAlias); + } + if ($criteria instanceof Criteria) { + $query->mergeWith($criteria); + } + return $query; + } + + /** + * Find object by primary key + * Use instance pooling to avoid a database query if the object exists + * + * $obj = $c->findPk(12, $con); + * + * @param mixed $key Primary key to use for the query + * @param PropelPDO $con an optional connection object + * + * @return CcShow|array|mixed the result, formatted by the current formatter + */ + public function findPk($key, $con = null) + { + if ((null !== ($obj = CcShowPeer::getInstanceFromPool((string) $key))) && $this->getFormatter()->isObjectFormatter()) { + // the object is alredy in the instance pool + return $obj; + } else { + // the object has not been requested yet, or the formatter is not an object formatter + $criteria = $this->isKeepQuery() ? clone $this : $this; + $stmt = $criteria + ->filterByPrimaryKey($key) + ->getSelectStatement($con); + return $criteria->getFormatter()->init($criteria)->formatOne($stmt); + } + } + + /** + * Find objects by primary key + * + * $objs = $c->findPks(array(12, 56, 832), $con); + * + * @param array $keys Primary keys to use for the query + * @param PropelPDO $con an optional connection object + * + * @return PropelObjectCollection|array|mixed the list of results, formatted by the current formatter + */ + public function findPks($keys, $con = null) + { + $criteria = $this->isKeepQuery() ? clone $this : $this; + return $this + ->filterByPrimaryKeys($keys) + ->find($con); + } + + /** + * Filter the query by primary key + * + * @param mixed $key Primary key to use for the query + * + * @return CcShowQuery The current query, for fluid interface + */ + public function filterByPrimaryKey($key) + { + return $this->addUsingAlias(CcShowPeer::ID, $key, Criteria::EQUAL); + } + + /** + * Filter the query by a list of primary keys + * + * @param array $keys The list of primary key to use for the query + * + * @return CcShowQuery The current query, for fluid interface + */ + public function filterByPrimaryKeys($keys) + { + return $this->addUsingAlias(CcShowPeer::ID, $keys, Criteria::IN); + } + + /** + * Filter the query on the id column + * + * @param int|array $dbId The value to use as filter. + * Accepts an associative array('min' => $minValue, 'max' => $maxValue) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcShowQuery The current query, for fluid interface + */ + public function filterByDbId($dbId = null, $comparison = null) + { + if (is_array($dbId) && null === $comparison) { + $comparison = Criteria::IN; + } + return $this->addUsingAlias(CcShowPeer::ID, $dbId, $comparison); + } + + /** + * Filter the query on the name column + * + * @param string $dbName The value to use as filter. + * Accepts wildcards (* and % trigger a LIKE) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcShowQuery The current query, for fluid interface + */ + public function filterByDbName($dbName = null, $comparison = null) + { + if (null === $comparison) { + if (is_array($dbName)) { + $comparison = Criteria::IN; + } elseif (preg_match('/[\%\*]/', $dbName)) { + $dbName = str_replace('*', '%', $dbName); + $comparison = Criteria::LIKE; + } + } + return $this->addUsingAlias(CcShowPeer::NAME, $dbName, $comparison); + } + + /** + * Filter the query on the first_show column + * + * @param string|array $dbFirstShow The value to use as filter. + * Accepts an associative array('min' => $minValue, 'max' => $maxValue) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcShowQuery The current query, for fluid interface + */ + public function filterByDbFirstShow($dbFirstShow = null, $comparison = null) + { + if (is_array($dbFirstShow)) { + $useMinMax = false; + if (isset($dbFirstShow['min'])) { + $this->addUsingAlias(CcShowPeer::FIRST_SHOW, $dbFirstShow['min'], Criteria::GREATER_EQUAL); + $useMinMax = true; + } + if (isset($dbFirstShow['max'])) { + $this->addUsingAlias(CcShowPeer::FIRST_SHOW, $dbFirstShow['max'], Criteria::LESS_EQUAL); + $useMinMax = true; + } + if ($useMinMax) { + return $this; + } + if (null === $comparison) { + $comparison = Criteria::IN; + } + } + return $this->addUsingAlias(CcShowPeer::FIRST_SHOW, $dbFirstShow, $comparison); + } + + /** + * Filter the query on the last_show column + * + * @param string|array $dbLastShow The value to use as filter. + * Accepts an associative array('min' => $minValue, 'max' => $maxValue) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcShowQuery The current query, for fluid interface + */ + public function filterByDbLastShow($dbLastShow = null, $comparison = null) + { + if (is_array($dbLastShow)) { + $useMinMax = false; + if (isset($dbLastShow['min'])) { + $this->addUsingAlias(CcShowPeer::LAST_SHOW, $dbLastShow['min'], Criteria::GREATER_EQUAL); + $useMinMax = true; + } + if (isset($dbLastShow['max'])) { + $this->addUsingAlias(CcShowPeer::LAST_SHOW, $dbLastShow['max'], Criteria::LESS_EQUAL); + $useMinMax = true; + } + if ($useMinMax) { + return $this; + } + if (null === $comparison) { + $comparison = Criteria::IN; + } + } + return $this->addUsingAlias(CcShowPeer::LAST_SHOW, $dbLastShow, $comparison); + } + + /** + * Filter the query on the start_time column + * + * @param string|array $dbStartTime The value to use as filter. + * Accepts an associative array('min' => $minValue, 'max' => $maxValue) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcShowQuery The current query, for fluid interface + */ + public function filterByDbStartTime($dbStartTime = null, $comparison = null) + { + if (is_array($dbStartTime)) { + $useMinMax = false; + if (isset($dbStartTime['min'])) { + $this->addUsingAlias(CcShowPeer::START_TIME, $dbStartTime['min'], Criteria::GREATER_EQUAL); + $useMinMax = true; + } + if (isset($dbStartTime['max'])) { + $this->addUsingAlias(CcShowPeer::START_TIME, $dbStartTime['max'], Criteria::LESS_EQUAL); + $useMinMax = true; + } + if ($useMinMax) { + return $this; + } + if (null === $comparison) { + $comparison = Criteria::IN; + } + } + return $this->addUsingAlias(CcShowPeer::START_TIME, $dbStartTime, $comparison); + } + + /** + * Filter the query on the end_time column + * + * @param string|array $dbEndTime The value to use as filter. + * Accepts an associative array('min' => $minValue, 'max' => $maxValue) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcShowQuery The current query, for fluid interface + */ + public function filterByDbEndTime($dbEndTime = null, $comparison = null) + { + if (is_array($dbEndTime)) { + $useMinMax = false; + if (isset($dbEndTime['min'])) { + $this->addUsingAlias(CcShowPeer::END_TIME, $dbEndTime['min'], Criteria::GREATER_EQUAL); + $useMinMax = true; + } + if (isset($dbEndTime['max'])) { + $this->addUsingAlias(CcShowPeer::END_TIME, $dbEndTime['max'], Criteria::LESS_EQUAL); + $useMinMax = true; + } + if ($useMinMax) { + return $this; + } + if (null === $comparison) { + $comparison = Criteria::IN; + } + } + return $this->addUsingAlias(CcShowPeer::END_TIME, $dbEndTime, $comparison); + } + + /** + * Filter the query on the repeats column + * + * @param int|array $dbRepeats The value to use as filter. + * Accepts an associative array('min' => $minValue, 'max' => $maxValue) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcShowQuery The current query, for fluid interface + */ + public function filterByDbRepeats($dbRepeats = null, $comparison = null) + { + if (is_array($dbRepeats)) { + $useMinMax = false; + if (isset($dbRepeats['min'])) { + $this->addUsingAlias(CcShowPeer::REPEATS, $dbRepeats['min'], Criteria::GREATER_EQUAL); + $useMinMax = true; + } + if (isset($dbRepeats['max'])) { + $this->addUsingAlias(CcShowPeer::REPEATS, $dbRepeats['max'], Criteria::LESS_EQUAL); + $useMinMax = true; + } + if ($useMinMax) { + return $this; + } + if (null === $comparison) { + $comparison = Criteria::IN; + } + } + return $this->addUsingAlias(CcShowPeer::REPEATS, $dbRepeats, $comparison); + } + + /** + * Filter the query on the day column + * + * @param int|array $dbDay The value to use as filter. + * Accepts an associative array('min' => $minValue, 'max' => $maxValue) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcShowQuery The current query, for fluid interface + */ + public function filterByDbDay($dbDay = null, $comparison = null) + { + if (is_array($dbDay)) { + $useMinMax = false; + if (isset($dbDay['min'])) { + $this->addUsingAlias(CcShowPeer::DAY, $dbDay['min'], Criteria::GREATER_EQUAL); + $useMinMax = true; + } + if (isset($dbDay['max'])) { + $this->addUsingAlias(CcShowPeer::DAY, $dbDay['max'], Criteria::LESS_EQUAL); + $useMinMax = true; + } + if ($useMinMax) { + return $this; + } + if (null === $comparison) { + $comparison = Criteria::IN; + } + } + return $this->addUsingAlias(CcShowPeer::DAY, $dbDay, $comparison); + } + + /** + * Filter the query on the description column + * + * @param string $dbDescription The value to use as filter. + * Accepts wildcards (* and % trigger a LIKE) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcShowQuery The current query, for fluid interface + */ + public function filterByDbDescription($dbDescription = null, $comparison = null) + { + if (null === $comparison) { + if (is_array($dbDescription)) { + $comparison = Criteria::IN; + } elseif (preg_match('/[\%\*]/', $dbDescription)) { + $dbDescription = str_replace('*', '%', $dbDescription); + $comparison = Criteria::LIKE; + } + } + return $this->addUsingAlias(CcShowPeer::DESCRIPTION, $dbDescription, $comparison); + } + + /** + * Exclude object from result + * + * @param CcShow $ccShow Object to remove from the list of results + * + * @return CcShowQuery The current query, for fluid interface + */ + public function prune($ccShow = null) + { + if ($ccShow) { + $this->addUsingAlias(CcShowPeer::ID, $ccShow->getDbId(), Criteria::NOT_EQUAL); + } + + return $this; + } + +} // BaseCcShowQuery diff --git a/application/models/campcaster/om/BaseCcSmemb.php b/application/models/campcaster/om/BaseCcSmemb.php new file mode 100644 index 000000000..bb98b5c1e --- /dev/null +++ b/application/models/campcaster/om/BaseCcSmemb.php @@ -0,0 +1,891 @@ +uid = 0; + $this->gid = 0; + $this->level = 0; + } + + /** + * Initializes internal state of BaseCcSmemb object. + * @see applyDefaults() + */ + public function __construct() + { + parent::__construct(); + $this->applyDefaultValues(); + } + + /** + * Get the [id] column value. + * + * @return int + */ + public function getId() + { + return $this->id; + } + + /** + * Get the [uid] column value. + * + * @return int + */ + public function getUid() + { + return $this->uid; + } + + /** + * Get the [gid] column value. + * + * @return int + */ + public function getGid() + { + return $this->gid; + } + + /** + * Get the [level] column value. + * + * @return int + */ + public function getLevel() + { + return $this->level; + } + + /** + * Get the [mid] column value. + * + * @return int + */ + public function getMid() + { + return $this->mid; + } + + /** + * Set the value of [id] column. + * + * @param int $v new value + * @return CcSmemb The current object (for fluent API support) + */ + public function setId($v) + { + if ($v !== null) { + $v = (int) $v; + } + + if ($this->id !== $v) { + $this->id = $v; + $this->modifiedColumns[] = CcSmembPeer::ID; + } + + return $this; + } // setId() + + /** + * Set the value of [uid] column. + * + * @param int $v new value + * @return CcSmemb The current object (for fluent API support) + */ + public function setUid($v) + { + if ($v !== null) { + $v = (int) $v; + } + + if ($this->uid !== $v || $this->isNew()) { + $this->uid = $v; + $this->modifiedColumns[] = CcSmembPeer::UID; + } + + return $this; + } // setUid() + + /** + * Set the value of [gid] column. + * + * @param int $v new value + * @return CcSmemb The current object (for fluent API support) + */ + public function setGid($v) + { + if ($v !== null) { + $v = (int) $v; + } + + if ($this->gid !== $v || $this->isNew()) { + $this->gid = $v; + $this->modifiedColumns[] = CcSmembPeer::GID; + } + + return $this; + } // setGid() + + /** + * Set the value of [level] column. + * + * @param int $v new value + * @return CcSmemb The current object (for fluent API support) + */ + public function setLevel($v) + { + if ($v !== null) { + $v = (int) $v; + } + + if ($this->level !== $v || $this->isNew()) { + $this->level = $v; + $this->modifiedColumns[] = CcSmembPeer::LEVEL; + } + + return $this; + } // setLevel() + + /** + * Set the value of [mid] column. + * + * @param int $v new value + * @return CcSmemb The current object (for fluent API support) + */ + public function setMid($v) + { + if ($v !== null) { + $v = (int) $v; + } + + if ($this->mid !== $v) { + $this->mid = $v; + $this->modifiedColumns[] = CcSmembPeer::MID; + } + + return $this; + } // setMid() + + /** + * Indicates whether the columns in this object are only set to default values. + * + * This method can be used in conjunction with isModified() to indicate whether an object is both + * modified _and_ has some values set which are non-default. + * + * @return boolean Whether the columns in this object are only been set with default values. + */ + public function hasOnlyDefaultValues() + { + if ($this->uid !== 0) { + return false; + } + + if ($this->gid !== 0) { + return false; + } + + if ($this->level !== 0) { + return false; + } + + // otherwise, everything was equal, so return TRUE + return true; + } // hasOnlyDefaultValues() + + /** + * Hydrates (populates) the object variables with values from the database resultset. + * + * An offset (0-based "start column") is specified so that objects can be hydrated + * with a subset of the columns in the resultset rows. This is needed, for example, + * for results of JOIN queries where the resultset row includes columns from two or + * more tables. + * + * @param array $row The row returned by PDOStatement->fetch(PDO::FETCH_NUM) + * @param int $startcol 0-based offset column which indicates which restultset column to start with. + * @param boolean $rehydrate Whether this object is being re-hydrated from the database. + * @return int next starting column + * @throws PropelException - Any caught Exception will be rewrapped as a PropelException. + */ + public function hydrate($row, $startcol = 0, $rehydrate = false) + { + try { + + $this->id = ($row[$startcol + 0] !== null) ? (int) $row[$startcol + 0] : null; + $this->uid = ($row[$startcol + 1] !== null) ? (int) $row[$startcol + 1] : null; + $this->gid = ($row[$startcol + 2] !== null) ? (int) $row[$startcol + 2] : null; + $this->level = ($row[$startcol + 3] !== null) ? (int) $row[$startcol + 3] : null; + $this->mid = ($row[$startcol + 4] !== null) ? (int) $row[$startcol + 4] : null; + $this->resetModified(); + + $this->setNew(false); + + if ($rehydrate) { + $this->ensureConsistency(); + } + + return $startcol + 5; // 5 = CcSmembPeer::NUM_COLUMNS - CcSmembPeer::NUM_LAZY_LOAD_COLUMNS). + + } catch (Exception $e) { + throw new PropelException("Error populating CcSmemb object", $e); + } + } + + /** + * Checks and repairs the internal consistency of the object. + * + * This method is executed after an already-instantiated object is re-hydrated + * from the database. It exists to check any foreign keys to make sure that + * the objects related to the current object are correct based on foreign key. + * + * You can override this method in the stub class, but you should always invoke + * the base method from the overridden method (i.e. parent::ensureConsistency()), + * in case your model changes. + * + * @throws PropelException + */ + public function ensureConsistency() + { + + } // ensureConsistency + + /** + * Reloads this object from datastore based on primary key and (optionally) resets all associated objects. + * + * This will only work if the object has been saved and has a valid primary key set. + * + * @param boolean $deep (optional) Whether to also de-associated any related objects. + * @param PropelPDO $con (optional) The PropelPDO connection to use. + * @return void + * @throws PropelException - if this object is deleted, unsaved or doesn't have pk match in db + */ + public function reload($deep = false, PropelPDO $con = null) + { + if ($this->isDeleted()) { + throw new PropelException("Cannot reload a deleted object."); + } + + if ($this->isNew()) { + throw new PropelException("Cannot reload an unsaved object."); + } + + if ($con === null) { + $con = Propel::getConnection(CcSmembPeer::DATABASE_NAME, Propel::CONNECTION_READ); + } + + // We don't need to alter the object instance pool; we're just modifying this instance + // already in the pool. + + $stmt = CcSmembPeer::doSelectStmt($this->buildPkeyCriteria(), $con); + $row = $stmt->fetch(PDO::FETCH_NUM); + $stmt->closeCursor(); + if (!$row) { + throw new PropelException('Cannot find matching row in the database to reload object values.'); + } + $this->hydrate($row, 0, true); // rehydrate + + if ($deep) { // also de-associate any related objects? + + } // if (deep) + } + + /** + * Removes this object from datastore and sets delete attribute. + * + * @param PropelPDO $con + * @return void + * @throws PropelException + * @see BaseObject::setDeleted() + * @see BaseObject::isDeleted() + */ + public function delete(PropelPDO $con = null) + { + if ($this->isDeleted()) { + throw new PropelException("This object has already been deleted."); + } + + if ($con === null) { + $con = Propel::getConnection(CcSmembPeer::DATABASE_NAME, Propel::CONNECTION_WRITE); + } + + $con->beginTransaction(); + try { + $ret = $this->preDelete($con); + if ($ret) { + CcSmembQuery::create() + ->filterByPrimaryKey($this->getPrimaryKey()) + ->delete($con); + $this->postDelete($con); + $con->commit(); + $this->setDeleted(true); + } else { + $con->commit(); + } + } catch (PropelException $e) { + $con->rollBack(); + throw $e; + } + } + + /** + * Persists this object to the database. + * + * If the object is new, it inserts it; otherwise an update is performed. + * All modified related objects will also be persisted in the doSave() + * method. This method wraps all precipitate database operations in a + * single transaction. + * + * @param PropelPDO $con + * @return int The number of rows affected by this insert/update and any referring fk objects' save() operations. + * @throws PropelException + * @see doSave() + */ + public function save(PropelPDO $con = null) + { + if ($this->isDeleted()) { + throw new PropelException("You cannot save an object that has been deleted."); + } + + if ($con === null) { + $con = Propel::getConnection(CcSmembPeer::DATABASE_NAME, Propel::CONNECTION_WRITE); + } + + $con->beginTransaction(); + $isInsert = $this->isNew(); + try { + $ret = $this->preSave($con); + if ($isInsert) { + $ret = $ret && $this->preInsert($con); + } else { + $ret = $ret && $this->preUpdate($con); + } + if ($ret) { + $affectedRows = $this->doSave($con); + if ($isInsert) { + $this->postInsert($con); + } else { + $this->postUpdate($con); + } + $this->postSave($con); + CcSmembPeer::addInstanceToPool($this); + } else { + $affectedRows = 0; + } + $con->commit(); + return $affectedRows; + } catch (PropelException $e) { + $con->rollBack(); + throw $e; + } + } + + /** + * Performs the work of inserting or updating the row in the database. + * + * If the object is new, it inserts it; otherwise an update is performed. + * All related objects are also updated in this method. + * + * @param PropelPDO $con + * @return int The number of rows affected by this insert/update and any referring fk objects' save() operations. + * @throws PropelException + * @see save() + */ + protected function doSave(PropelPDO $con) + { + $affectedRows = 0; // initialize var to track total num of affected rows + if (!$this->alreadyInSave) { + $this->alreadyInSave = true; + + + // If this object has been modified, then save it to the database. + if ($this->isModified()) { + if ($this->isNew()) { + $criteria = $this->buildCriteria(); + $pk = BasePeer::doInsert($criteria, $con); + $affectedRows = 1; + $this->setNew(false); + } else { + $affectedRows = CcSmembPeer::doUpdate($this, $con); + } + + $this->resetModified(); // [HL] After being saved an object is no longer 'modified' + } + + $this->alreadyInSave = false; + + } + return $affectedRows; + } // doSave() + + /** + * Array of ValidationFailed objects. + * @var array ValidationFailed[] + */ + protected $validationFailures = array(); + + /** + * Gets any ValidationFailed objects that resulted from last call to validate(). + * + * + * @return array ValidationFailed[] + * @see validate() + */ + public function getValidationFailures() + { + return $this->validationFailures; + } + + /** + * Validates the objects modified field values and all objects related to this table. + * + * If $columns is either a column name or an array of column names + * only those columns are validated. + * + * @param mixed $columns Column name or an array of column names. + * @return boolean Whether all columns pass validation. + * @see doValidate() + * @see getValidationFailures() + */ + public function validate($columns = null) + { + $res = $this->doValidate($columns); + if ($res === true) { + $this->validationFailures = array(); + return true; + } else { + $this->validationFailures = $res; + return false; + } + } + + /** + * This function performs the validation work for complex object models. + * + * In addition to checking the current object, all related objects will + * also be validated. If all pass then true is returned; otherwise + * an aggreagated array of ValidationFailed objects will be returned. + * + * @param array $columns Array of column names to validate. + * @return mixed true if all validations pass; array of ValidationFailed objets otherwise. + */ + protected function doValidate($columns = null) + { + if (!$this->alreadyInValidation) { + $this->alreadyInValidation = true; + $retval = null; + + $failureMap = array(); + + + if (($retval = CcSmembPeer::doValidate($this, $columns)) !== true) { + $failureMap = array_merge($failureMap, $retval); + } + + + + $this->alreadyInValidation = false; + } + + return (!empty($failureMap) ? $failureMap : true); + } + + /** + * Retrieves a field from the object by name passed in as a string. + * + * @param string $name name + * @param string $type The type of fieldname the $name is of: + * one of the class type constants BasePeer::TYPE_PHPNAME, BasePeer::TYPE_STUDLYPHPNAME + * BasePeer::TYPE_COLNAME, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_NUM + * @return mixed Value of field. + */ + public function getByName($name, $type = BasePeer::TYPE_PHPNAME) + { + $pos = CcSmembPeer::translateFieldName($name, $type, BasePeer::TYPE_NUM); + $field = $this->getByPosition($pos); + return $field; + } + + /** + * Retrieves a field from the object by Position as specified in the xml schema. + * Zero-based. + * + * @param int $pos position in xml schema + * @return mixed Value of field at $pos + */ + public function getByPosition($pos) + { + switch($pos) { + case 0: + return $this->getId(); + break; + case 1: + return $this->getUid(); + break; + case 2: + return $this->getGid(); + break; + case 3: + return $this->getLevel(); + break; + case 4: + return $this->getMid(); + break; + default: + return null; + break; + } // switch() + } + + /** + * Exports the object as an array. + * + * You can specify the key type of the array by passing one of the class + * type constants. + * + * @param string $keyType (optional) One of the class type constants BasePeer::TYPE_PHPNAME, BasePeer::TYPE_STUDLYPHPNAME, + * BasePeer::TYPE_COLNAME, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_NUM. + * Defaults to BasePeer::TYPE_PHPNAME. + * @param boolean $includeLazyLoadColumns (optional) Whether to include lazy loaded columns. Defaults to TRUE. + * + * @return array an associative array containing the field names (as keys) and field values + */ + public function toArray($keyType = BasePeer::TYPE_PHPNAME, $includeLazyLoadColumns = true) + { + $keys = CcSmembPeer::getFieldNames($keyType); + $result = array( + $keys[0] => $this->getId(), + $keys[1] => $this->getUid(), + $keys[2] => $this->getGid(), + $keys[3] => $this->getLevel(), + $keys[4] => $this->getMid(), + ); + return $result; + } + + /** + * Sets a field from the object by name passed in as a string. + * + * @param string $name peer name + * @param mixed $value field value + * @param string $type The type of fieldname the $name is of: + * one of the class type constants BasePeer::TYPE_PHPNAME, BasePeer::TYPE_STUDLYPHPNAME + * BasePeer::TYPE_COLNAME, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_NUM + * @return void + */ + public function setByName($name, $value, $type = BasePeer::TYPE_PHPNAME) + { + $pos = CcSmembPeer::translateFieldName($name, $type, BasePeer::TYPE_NUM); + return $this->setByPosition($pos, $value); + } + + /** + * Sets a field from the object by Position as specified in the xml schema. + * Zero-based. + * + * @param int $pos position in xml schema + * @param mixed $value field value + * @return void + */ + public function setByPosition($pos, $value) + { + switch($pos) { + case 0: + $this->setId($value); + break; + case 1: + $this->setUid($value); + break; + case 2: + $this->setGid($value); + break; + case 3: + $this->setLevel($value); + break; + case 4: + $this->setMid($value); + break; + } // switch() + } + + /** + * Populates the object using an array. + * + * This is particularly useful when populating an object from one of the + * request arrays (e.g. $_POST). This method goes through the column + * names, checking to see whether a matching key exists in populated + * array. If so the setByName() method is called for that column. + * + * You can specify the key type of the array by additionally passing one + * of the class type constants BasePeer::TYPE_PHPNAME, BasePeer::TYPE_STUDLYPHPNAME, + * BasePeer::TYPE_COLNAME, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_NUM. + * The default key type is the column's phpname (e.g. 'AuthorId') + * + * @param array $arr An array to populate the object from. + * @param string $keyType The type of keys the array uses. + * @return void + */ + public function fromArray($arr, $keyType = BasePeer::TYPE_PHPNAME) + { + $keys = CcSmembPeer::getFieldNames($keyType); + + if (array_key_exists($keys[0], $arr)) $this->setId($arr[$keys[0]]); + if (array_key_exists($keys[1], $arr)) $this->setUid($arr[$keys[1]]); + if (array_key_exists($keys[2], $arr)) $this->setGid($arr[$keys[2]]); + if (array_key_exists($keys[3], $arr)) $this->setLevel($arr[$keys[3]]); + if (array_key_exists($keys[4], $arr)) $this->setMid($arr[$keys[4]]); + } + + /** + * Build a Criteria object containing the values of all modified columns in this object. + * + * @return Criteria The Criteria object containing all modified values. + */ + public function buildCriteria() + { + $criteria = new Criteria(CcSmembPeer::DATABASE_NAME); + + if ($this->isColumnModified(CcSmembPeer::ID)) $criteria->add(CcSmembPeer::ID, $this->id); + if ($this->isColumnModified(CcSmembPeer::UID)) $criteria->add(CcSmembPeer::UID, $this->uid); + if ($this->isColumnModified(CcSmembPeer::GID)) $criteria->add(CcSmembPeer::GID, $this->gid); + if ($this->isColumnModified(CcSmembPeer::LEVEL)) $criteria->add(CcSmembPeer::LEVEL, $this->level); + if ($this->isColumnModified(CcSmembPeer::MID)) $criteria->add(CcSmembPeer::MID, $this->mid); + + return $criteria; + } + + /** + * Builds a Criteria object containing the primary key for this object. + * + * Unlike buildCriteria() this method includes the primary key values regardless + * of whether or not they have been modified. + * + * @return Criteria The Criteria object containing value(s) for primary key(s). + */ + public function buildPkeyCriteria() + { + $criteria = new Criteria(CcSmembPeer::DATABASE_NAME); + $criteria->add(CcSmembPeer::ID, $this->id); + + return $criteria; + } + + /** + * Returns the primary key for this object (row). + * @return int + */ + public function getPrimaryKey() + { + return $this->getId(); + } + + /** + * Generic method to set the primary key (id column). + * + * @param int $key Primary key. + * @return void + */ + public function setPrimaryKey($key) + { + $this->setId($key); + } + + /** + * Returns true if the primary key for this object is null. + * @return boolean + */ + public function isPrimaryKeyNull() + { + return null === $this->getId(); + } + + /** + * Sets contents of passed object to values from current object. + * + * If desired, this method can also make copies of all associated (fkey referrers) + * objects. + * + * @param object $copyObj An object of CcSmemb (or compatible) type. + * @param boolean $deepCopy Whether to also copy all rows that refer (by fkey) to the current row. + * @throws PropelException + */ + public function copyInto($copyObj, $deepCopy = false) + { + $copyObj->setId($this->id); + $copyObj->setUid($this->uid); + $copyObj->setGid($this->gid); + $copyObj->setLevel($this->level); + $copyObj->setMid($this->mid); + + $copyObj->setNew(true); + } + + /** + * Makes a copy of this object that will be inserted as a new row in table when saved. + * It creates a new object filling in the simple attributes, but skipping any primary + * keys that are defined for the table. + * + * If desired, this method can also make copies of all associated (fkey referrers) + * objects. + * + * @param boolean $deepCopy Whether to also copy all rows that refer (by fkey) to the current row. + * @return CcSmemb Clone of current object. + * @throws PropelException + */ + public function copy($deepCopy = false) + { + // we use get_class(), because this might be a subclass + $clazz = get_class($this); + $copyObj = new $clazz(); + $this->copyInto($copyObj, $deepCopy); + return $copyObj; + } + + /** + * Returns a peer instance associated with this om. + * + * Since Peer classes are not to have any instance attributes, this method returns the + * same instance for all member of this class. The method could therefore + * be static, but this would prevent one from overriding the behavior. + * + * @return CcSmembPeer + */ + public function getPeer() + { + if (self::$peer === null) { + self::$peer = new CcSmembPeer(); + } + return self::$peer; + } + + /** + * Clears the current object and sets all attributes to their default values + */ + public function clear() + { + $this->id = null; + $this->uid = null; + $this->gid = null; + $this->level = null; + $this->mid = null; + $this->alreadyInSave = false; + $this->alreadyInValidation = false; + $this->clearAllReferences(); + $this->applyDefaultValues(); + $this->resetModified(); + $this->setNew(true); + $this->setDeleted(false); + } + + /** + * Resets all collections of referencing foreign keys. + * + * This method is a user-space workaround for PHP's inability to garbage collect objects + * with circular references. This is currently necessary when using Propel in certain + * daemon or large-volumne/high-memory operations. + * + * @param boolean $deep Whether to also clear the references on all associated objects. + */ + public function clearAllReferences($deep = false) + { + if ($deep) { + } // if ($deep) + + } + + /** + * Catches calls to virtual methods + */ + public function __call($name, $params) + { + if (preg_match('/get(\w+)/', $name, $matches)) { + $virtualColumn = $matches[1]; + if ($this->hasVirtualColumn($virtualColumn)) { + return $this->getVirtualColumn($virtualColumn); + } + // no lcfirst in php<5.3... + $virtualColumn[0] = strtolower($virtualColumn[0]); + if ($this->hasVirtualColumn($virtualColumn)) { + return $this->getVirtualColumn($virtualColumn); + } + } + throw new PropelException('Call to undefined method: ' . $name); + } + +} // BaseCcSmemb diff --git a/application/models/campcaster/om/BaseCcSmembPeer.php b/application/models/campcaster/om/BaseCcSmembPeer.php new file mode 100644 index 000000000..152440c7e --- /dev/null +++ b/application/models/campcaster/om/BaseCcSmembPeer.php @@ -0,0 +1,750 @@ + array ('Id', 'Uid', 'Gid', 'Level', 'Mid', ), + BasePeer::TYPE_STUDLYPHPNAME => array ('id', 'uid', 'gid', 'level', 'mid', ), + BasePeer::TYPE_COLNAME => array (self::ID, self::UID, self::GID, self::LEVEL, self::MID, ), + BasePeer::TYPE_RAW_COLNAME => array ('ID', 'UID', 'GID', 'LEVEL', 'MID', ), + BasePeer::TYPE_FIELDNAME => array ('id', 'uid', 'gid', 'level', 'mid', ), + BasePeer::TYPE_NUM => array (0, 1, 2, 3, 4, ) + ); + + /** + * holds an array of keys for quick access to the fieldnames array + * + * first dimension keys are the type constants + * e.g. self::$fieldNames[BasePeer::TYPE_PHPNAME]['Id'] = 0 + */ + private static $fieldKeys = array ( + BasePeer::TYPE_PHPNAME => array ('Id' => 0, 'Uid' => 1, 'Gid' => 2, 'Level' => 3, 'Mid' => 4, ), + BasePeer::TYPE_STUDLYPHPNAME => array ('id' => 0, 'uid' => 1, 'gid' => 2, 'level' => 3, 'mid' => 4, ), + BasePeer::TYPE_COLNAME => array (self::ID => 0, self::UID => 1, self::GID => 2, self::LEVEL => 3, self::MID => 4, ), + BasePeer::TYPE_RAW_COLNAME => array ('ID' => 0, 'UID' => 1, 'GID' => 2, 'LEVEL' => 3, 'MID' => 4, ), + BasePeer::TYPE_FIELDNAME => array ('id' => 0, 'uid' => 1, 'gid' => 2, 'level' => 3, 'mid' => 4, ), + BasePeer::TYPE_NUM => array (0, 1, 2, 3, 4, ) + ); + + /** + * Translates a fieldname to another type + * + * @param string $name field name + * @param string $fromType One of the class type constants BasePeer::TYPE_PHPNAME, BasePeer::TYPE_STUDLYPHPNAME + * BasePeer::TYPE_COLNAME, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_NUM + * @param string $toType One of the class type constants + * @return string translated name of the field. + * @throws PropelException - if the specified name could not be found in the fieldname mappings. + */ + static public function translateFieldName($name, $fromType, $toType) + { + $toNames = self::getFieldNames($toType); + $key = isset(self::$fieldKeys[$fromType][$name]) ? self::$fieldKeys[$fromType][$name] : null; + if ($key === null) { + throw new PropelException("'$name' could not be found in the field names of type '$fromType'. These are: " . print_r(self::$fieldKeys[$fromType], true)); + } + return $toNames[$key]; + } + + /** + * Returns an array of field names. + * + * @param string $type The type of fieldnames to return: + * One of the class type constants BasePeer::TYPE_PHPNAME, BasePeer::TYPE_STUDLYPHPNAME + * BasePeer::TYPE_COLNAME, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_NUM + * @return array A list of field names + */ + + static public function getFieldNames($type = BasePeer::TYPE_PHPNAME) + { + if (!array_key_exists($type, self::$fieldNames)) { + throw new PropelException('Method getFieldNames() expects the parameter $type to be one of the class constants BasePeer::TYPE_PHPNAME, BasePeer::TYPE_STUDLYPHPNAME, BasePeer::TYPE_COLNAME, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_NUM. ' . $type . ' was given.'); + } + return self::$fieldNames[$type]; + } + + /** + * Convenience method which changes table.column to alias.column. + * + * Using this method you can maintain SQL abstraction while using column aliases. + * + * $c->addAlias("alias1", TablePeer::TABLE_NAME); + * $c->addJoin(TablePeer::alias("alias1", TablePeer::PRIMARY_KEY_COLUMN), TablePeer::PRIMARY_KEY_COLUMN); + * + * @param string $alias The alias for the current table. + * @param string $column The column name for current table. (i.e. CcSmembPeer::COLUMN_NAME). + * @return string + */ + public static function alias($alias, $column) + { + return str_replace(CcSmembPeer::TABLE_NAME.'.', $alias.'.', $column); + } + + /** + * Add all the columns needed to create a new object. + * + * Note: any columns that were marked with lazyLoad="true" in the + * XML schema will not be added to the select list and only loaded + * on demand. + * + * @param Criteria $criteria object containing the columns to add. + * @param string $alias optional table alias + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function addSelectColumns(Criteria $criteria, $alias = null) + { + if (null === $alias) { + $criteria->addSelectColumn(CcSmembPeer::ID); + $criteria->addSelectColumn(CcSmembPeer::UID); + $criteria->addSelectColumn(CcSmembPeer::GID); + $criteria->addSelectColumn(CcSmembPeer::LEVEL); + $criteria->addSelectColumn(CcSmembPeer::MID); + } else { + $criteria->addSelectColumn($alias . '.ID'); + $criteria->addSelectColumn($alias . '.UID'); + $criteria->addSelectColumn($alias . '.GID'); + $criteria->addSelectColumn($alias . '.LEVEL'); + $criteria->addSelectColumn($alias . '.MID'); + } + } + + /** + * Returns the number of rows matching criteria. + * + * @param Criteria $criteria + * @param boolean $distinct Whether to select only distinct columns; deprecated: use Criteria->setDistinct() instead. + * @param PropelPDO $con + * @return int Number of matching rows. + */ + public static function doCount(Criteria $criteria, $distinct = false, PropelPDO $con = null) + { + // we may modify criteria, so copy it first + $criteria = clone $criteria; + + // We need to set the primary table name, since in the case that there are no WHERE columns + // it will be impossible for the BasePeer::createSelectSql() method to determine which + // tables go into the FROM clause. + $criteria->setPrimaryTableName(CcSmembPeer::TABLE_NAME); + + if ($distinct && !in_array(Criteria::DISTINCT, $criteria->getSelectModifiers())) { + $criteria->setDistinct(); + } + + if (!$criteria->hasSelectClause()) { + CcSmembPeer::addSelectColumns($criteria); + } + + $criteria->clearOrderByColumns(); // ORDER BY won't ever affect the count + $criteria->setDbName(self::DATABASE_NAME); // Set the correct dbName + + if ($con === null) { + $con = Propel::getConnection(CcSmembPeer::DATABASE_NAME, Propel::CONNECTION_READ); + } + // BasePeer returns a PDOStatement + $stmt = BasePeer::doCount($criteria, $con); + + if ($row = $stmt->fetch(PDO::FETCH_NUM)) { + $count = (int) $row[0]; + } else { + $count = 0; // no rows returned; we infer that means 0 matches. + } + $stmt->closeCursor(); + return $count; + } + /** + * Method to select one object from the DB. + * + * @param Criteria $criteria object used to create the SELECT statement. + * @param PropelPDO $con + * @return CcSmemb + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function doSelectOne(Criteria $criteria, PropelPDO $con = null) + { + $critcopy = clone $criteria; + $critcopy->setLimit(1); + $objects = CcSmembPeer::doSelect($critcopy, $con); + if ($objects) { + return $objects[0]; + } + return null; + } + /** + * Method to do selects. + * + * @param Criteria $criteria The Criteria object used to build the SELECT statement. + * @param PropelPDO $con + * @return array Array of selected Objects + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function doSelect(Criteria $criteria, PropelPDO $con = null) + { + return CcSmembPeer::populateObjects(CcSmembPeer::doSelectStmt($criteria, $con)); + } + /** + * Prepares the Criteria object and uses the parent doSelect() method to execute a PDOStatement. + * + * Use this method directly if you want to work with an executed statement durirectly (for example + * to perform your own object hydration). + * + * @param Criteria $criteria The Criteria object used to build the SELECT statement. + * @param PropelPDO $con The connection to use + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + * @return PDOStatement The executed PDOStatement object. + * @see BasePeer::doSelect() + */ + public static function doSelectStmt(Criteria $criteria, PropelPDO $con = null) + { + if ($con === null) { + $con = Propel::getConnection(CcSmembPeer::DATABASE_NAME, Propel::CONNECTION_READ); + } + + if (!$criteria->hasSelectClause()) { + $criteria = clone $criteria; + CcSmembPeer::addSelectColumns($criteria); + } + + // Set the correct dbName + $criteria->setDbName(self::DATABASE_NAME); + + // BasePeer returns a PDOStatement + return BasePeer::doSelect($criteria, $con); + } + /** + * Adds an object to the instance pool. + * + * Propel keeps cached copies of objects in an instance pool when they are retrieved + * from the database. In some cases -- especially when you override doSelect*() + * methods in your stub classes -- you may need to explicitly add objects + * to the cache in order to ensure that the same objects are always returned by doSelect*() + * and retrieveByPK*() calls. + * + * @param CcSmemb $value A CcSmemb object. + * @param string $key (optional) key to use for instance map (for performance boost if key was already calculated externally). + */ + public static function addInstanceToPool(CcSmemb $obj, $key = null) + { + if (Propel::isInstancePoolingEnabled()) { + if ($key === null) { + $key = (string) $obj->getId(); + } // if key === null + self::$instances[$key] = $obj; + } + } + + /** + * Removes an object from the instance pool. + * + * Propel keeps cached copies of objects in an instance pool when they are retrieved + * from the database. In some cases -- especially when you override doDelete + * methods in your stub classes -- you may need to explicitly remove objects + * from the cache in order to prevent returning objects that no longer exist. + * + * @param mixed $value A CcSmemb object or a primary key value. + */ + public static function removeInstanceFromPool($value) + { + if (Propel::isInstancePoolingEnabled() && $value !== null) { + if (is_object($value) && $value instanceof CcSmemb) { + $key = (string) $value->getId(); + } elseif (is_scalar($value)) { + // assume we've been passed a primary key + $key = (string) $value; + } else { + $e = new PropelException("Invalid value passed to removeInstanceFromPool(). Expected primary key or CcSmemb object; got " . (is_object($value) ? get_class($value) . ' object.' : var_export($value,true))); + throw $e; + } + + unset(self::$instances[$key]); + } + } // removeInstanceFromPool() + + /** + * Retrieves a string version of the primary key from the DB resultset row that can be used to uniquely identify a row in this table. + * + * For tables with a single-column primary key, that simple pkey value will be returned. For tables with + * a multi-column primary key, a serialize()d version of the primary key will be returned. + * + * @param string $key The key (@see getPrimaryKeyHash()) for this instance. + * @return CcSmemb Found object or NULL if 1) no instance exists for specified key or 2) instance pooling has been disabled. + * @see getPrimaryKeyHash() + */ + public static function getInstanceFromPool($key) + { + if (Propel::isInstancePoolingEnabled()) { + if (isset(self::$instances[$key])) { + return self::$instances[$key]; + } + } + return null; // just to be explicit + } + + /** + * Clear the instance pool. + * + * @return void + */ + public static function clearInstancePool() + { + self::$instances = array(); + } + + /** + * Method to invalidate the instance pool of all tables related to cc_smemb + * by a foreign key with ON DELETE CASCADE + */ + public static function clearRelatedInstancePool() + { + } + + /** + * Retrieves a string version of the primary key from the DB resultset row that can be used to uniquely identify a row in this table. + * + * For tables with a single-column primary key, that simple pkey value will be returned. For tables with + * a multi-column primary key, a serialize()d version of the primary key will be returned. + * + * @param array $row PropelPDO resultset row. + * @param int $startcol The 0-based offset for reading from the resultset row. + * @return string A string version of PK or NULL if the components of primary key in result array are all null. + */ + public static function getPrimaryKeyHashFromRow($row, $startcol = 0) + { + // If the PK cannot be derived from the row, return NULL. + if ($row[$startcol] === null) { + return null; + } + return (string) $row[$startcol]; + } + + /** + * Retrieves the primary key from the DB resultset row + * For tables with a single-column primary key, that simple pkey value will be returned. For tables with + * a multi-column primary key, an array of the primary key columns will be returned. + * + * @param array $row PropelPDO resultset row. + * @param int $startcol The 0-based offset for reading from the resultset row. + * @return mixed The primary key of the row + */ + public static function getPrimaryKeyFromRow($row, $startcol = 0) + { + return (int) $row[$startcol]; + } + + /** + * The returned array will contain objects of the default type or + * objects that inherit from the default. + * + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function populateObjects(PDOStatement $stmt) + { + $results = array(); + + // set the class once to avoid overhead in the loop + $cls = CcSmembPeer::getOMClass(false); + // populate the object(s) + while ($row = $stmt->fetch(PDO::FETCH_NUM)) { + $key = CcSmembPeer::getPrimaryKeyHashFromRow($row, 0); + if (null !== ($obj = CcSmembPeer::getInstanceFromPool($key))) { + // We no longer rehydrate the object, since this can cause data loss. + // See http://www.propelorm.org/ticket/509 + // $obj->hydrate($row, 0, true); // rehydrate + $results[] = $obj; + } else { + $obj = new $cls(); + $obj->hydrate($row); + $results[] = $obj; + CcSmembPeer::addInstanceToPool($obj, $key); + } // if key exists + } + $stmt->closeCursor(); + return $results; + } + /** + * Populates an object of the default type or an object that inherit from the default. + * + * @param array $row PropelPDO resultset row. + * @param int $startcol The 0-based offset for reading from the resultset row. + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + * @return array (CcSmemb object, last column rank) + */ + public static function populateObject($row, $startcol = 0) + { + $key = CcSmembPeer::getPrimaryKeyHashFromRow($row, $startcol); + if (null !== ($obj = CcSmembPeer::getInstanceFromPool($key))) { + // We no longer rehydrate the object, since this can cause data loss. + // See http://www.propelorm.org/ticket/509 + // $obj->hydrate($row, $startcol, true); // rehydrate + $col = $startcol + CcSmembPeer::NUM_COLUMNS; + } else { + $cls = CcSmembPeer::OM_CLASS; + $obj = new $cls(); + $col = $obj->hydrate($row, $startcol); + CcSmembPeer::addInstanceToPool($obj, $key); + } + return array($obj, $col); + } + /** + * Returns the TableMap related to this peer. + * This method is not needed for general use but a specific application could have a need. + * @return TableMap + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function getTableMap() + { + return Propel::getDatabaseMap(self::DATABASE_NAME)->getTable(self::TABLE_NAME); + } + + /** + * Add a TableMap instance to the database for this peer class. + */ + public static function buildTableMap() + { + $dbMap = Propel::getDatabaseMap(BaseCcSmembPeer::DATABASE_NAME); + if (!$dbMap->hasTable(BaseCcSmembPeer::TABLE_NAME)) + { + $dbMap->addTableObject(new CcSmembTableMap()); + } + } + + /** + * The class that the Peer will make instances of. + * + * If $withPrefix is true, the returned path + * uses a dot-path notation which is tranalted into a path + * relative to a location on the PHP include_path. + * (e.g. path.to.MyClass -> 'path/to/MyClass.php') + * + * @param boolean $withPrefix Whether or not to return the path with the class name + * @return string path.to.ClassName + */ + public static function getOMClass($withPrefix = true) + { + return $withPrefix ? CcSmembPeer::CLASS_DEFAULT : CcSmembPeer::OM_CLASS; + } + + /** + * Method perform an INSERT on the database, given a CcSmemb or Criteria object. + * + * @param mixed $values Criteria or CcSmemb object containing data that is used to create the INSERT statement. + * @param PropelPDO $con the PropelPDO connection to use + * @return mixed The new primary key. + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function doInsert($values, PropelPDO $con = null) + { + if ($con === null) { + $con = Propel::getConnection(CcSmembPeer::DATABASE_NAME, Propel::CONNECTION_WRITE); + } + + if ($values instanceof Criteria) { + $criteria = clone $values; // rename for clarity + } else { + $criteria = $values->buildCriteria(); // build Criteria from CcSmemb object + } + + + // Set the correct dbName + $criteria->setDbName(self::DATABASE_NAME); + + try { + // use transaction because $criteria could contain info + // for more than one table (I guess, conceivably) + $con->beginTransaction(); + $pk = BasePeer::doInsert($criteria, $con); + $con->commit(); + } catch(PropelException $e) { + $con->rollBack(); + throw $e; + } + + return $pk; + } + + /** + * Method perform an UPDATE on the database, given a CcSmemb or Criteria object. + * + * @param mixed $values Criteria or CcSmemb object containing data that is used to create the UPDATE statement. + * @param PropelPDO $con The connection to use (specify PropelPDO connection object to exert more control over transactions). + * @return int The number of affected rows (if supported by underlying database driver). + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function doUpdate($values, PropelPDO $con = null) + { + if ($con === null) { + $con = Propel::getConnection(CcSmembPeer::DATABASE_NAME, Propel::CONNECTION_WRITE); + } + + $selectCriteria = new Criteria(self::DATABASE_NAME); + + if ($values instanceof Criteria) { + $criteria = clone $values; // rename for clarity + + $comparison = $criteria->getComparison(CcSmembPeer::ID); + $value = $criteria->remove(CcSmembPeer::ID); + if ($value) { + $selectCriteria->add(CcSmembPeer::ID, $value, $comparison); + } else { + $selectCriteria->setPrimaryTableName(CcSmembPeer::TABLE_NAME); + } + + } else { // $values is CcSmemb object + $criteria = $values->buildCriteria(); // gets full criteria + $selectCriteria = $values->buildPkeyCriteria(); // gets criteria w/ primary key(s) + } + + // set the correct dbName + $criteria->setDbName(self::DATABASE_NAME); + + return BasePeer::doUpdate($selectCriteria, $criteria, $con); + } + + /** + * Method to DELETE all rows from the cc_smemb table. + * + * @return int The number of affected rows (if supported by underlying database driver). + */ + public static function doDeleteAll($con = null) + { + if ($con === null) { + $con = Propel::getConnection(CcSmembPeer::DATABASE_NAME, Propel::CONNECTION_WRITE); + } + $affectedRows = 0; // initialize var to track total num of affected rows + try { + // use transaction because $criteria could contain info + // for more than one table or we could emulating ON DELETE CASCADE, etc. + $con->beginTransaction(); + $affectedRows += BasePeer::doDeleteAll(CcSmembPeer::TABLE_NAME, $con, CcSmembPeer::DATABASE_NAME); + // Because this db requires some delete cascade/set null emulation, we have to + // clear the cached instance *after* the emulation has happened (since + // instances get re-added by the select statement contained therein). + CcSmembPeer::clearInstancePool(); + CcSmembPeer::clearRelatedInstancePool(); + $con->commit(); + return $affectedRows; + } catch (PropelException $e) { + $con->rollBack(); + throw $e; + } + } + + /** + * Method perform a DELETE on the database, given a CcSmemb or Criteria object OR a primary key value. + * + * @param mixed $values Criteria or CcSmemb object or primary key or array of primary keys + * which is used to create the DELETE statement + * @param PropelPDO $con the connection to use + * @return int The number of affected rows (if supported by underlying database driver). This includes CASCADE-related rows + * if supported by native driver or if emulated using Propel. + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function doDelete($values, PropelPDO $con = null) + { + if ($con === null) { + $con = Propel::getConnection(CcSmembPeer::DATABASE_NAME, Propel::CONNECTION_WRITE); + } + + if ($values instanceof Criteria) { + // invalidate the cache for all objects of this type, since we have no + // way of knowing (without running a query) what objects should be invalidated + // from the cache based on this Criteria. + CcSmembPeer::clearInstancePool(); + // rename for clarity + $criteria = clone $values; + } elseif ($values instanceof CcSmemb) { // it's a model object + // invalidate the cache for this single object + CcSmembPeer::removeInstanceFromPool($values); + // create criteria based on pk values + $criteria = $values->buildPkeyCriteria(); + } else { // it's a primary key, or an array of pks + $criteria = new Criteria(self::DATABASE_NAME); + $criteria->add(CcSmembPeer::ID, (array) $values, Criteria::IN); + // invalidate the cache for this object(s) + foreach ((array) $values as $singleval) { + CcSmembPeer::removeInstanceFromPool($singleval); + } + } + + // Set the correct dbName + $criteria->setDbName(self::DATABASE_NAME); + + $affectedRows = 0; // initialize var to track total num of affected rows + + try { + // use transaction because $criteria could contain info + // for more than one table or we could emulating ON DELETE CASCADE, etc. + $con->beginTransaction(); + + $affectedRows += BasePeer::doDelete($criteria, $con); + CcSmembPeer::clearRelatedInstancePool(); + $con->commit(); + return $affectedRows; + } catch (PropelException $e) { + $con->rollBack(); + throw $e; + } + } + + /** + * Validates all modified columns of given CcSmemb object. + * If parameter $columns is either a single column name or an array of column names + * than only those columns are validated. + * + * NOTICE: This does not apply to primary or foreign keys for now. + * + * @param CcSmemb $obj The object to validate. + * @param mixed $cols Column name or array of column names. + * + * @return mixed TRUE if all columns are valid or the error message of the first invalid column. + */ + public static function doValidate(CcSmemb $obj, $cols = null) + { + $columns = array(); + + if ($cols) { + $dbMap = Propel::getDatabaseMap(CcSmembPeer::DATABASE_NAME); + $tableMap = $dbMap->getTable(CcSmembPeer::TABLE_NAME); + + if (! is_array($cols)) { + $cols = array($cols); + } + + foreach ($cols as $colName) { + if ($tableMap->containsColumn($colName)) { + $get = 'get' . $tableMap->getColumn($colName)->getPhpName(); + $columns[$colName] = $obj->$get(); + } + } + } else { + + } + + return BasePeer::doValidate(CcSmembPeer::DATABASE_NAME, CcSmembPeer::TABLE_NAME, $columns); + } + + /** + * Retrieve a single object by pkey. + * + * @param int $pk the primary key. + * @param PropelPDO $con the connection to use + * @return CcSmemb + */ + public static function retrieveByPK($pk, PropelPDO $con = null) + { + + if (null !== ($obj = CcSmembPeer::getInstanceFromPool((string) $pk))) { + return $obj; + } + + if ($con === null) { + $con = Propel::getConnection(CcSmembPeer::DATABASE_NAME, Propel::CONNECTION_READ); + } + + $criteria = new Criteria(CcSmembPeer::DATABASE_NAME); + $criteria->add(CcSmembPeer::ID, $pk); + + $v = CcSmembPeer::doSelect($criteria, $con); + + return !empty($v) > 0 ? $v[0] : null; + } + + /** + * Retrieve multiple objects by pkey. + * + * @param array $pks List of primary keys + * @param PropelPDO $con the connection to use + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function retrieveByPKs($pks, PropelPDO $con = null) + { + if ($con === null) { + $con = Propel::getConnection(CcSmembPeer::DATABASE_NAME, Propel::CONNECTION_READ); + } + + $objs = null; + if (empty($pks)) { + $objs = array(); + } else { + $criteria = new Criteria(CcSmembPeer::DATABASE_NAME); + $criteria->add(CcSmembPeer::ID, $pks, Criteria::IN); + $objs = CcSmembPeer::doSelect($criteria, $con); + } + return $objs; + } + +} // BaseCcSmembPeer + +// This is the static code needed to register the TableMap for this table with the main Propel class. +// +BaseCcSmembPeer::buildTableMap(); + diff --git a/application/models/campcaster/om/BaseCcSmembQuery.php b/application/models/campcaster/om/BaseCcSmembQuery.php new file mode 100644 index 000000000..c0ac62c18 --- /dev/null +++ b/application/models/campcaster/om/BaseCcSmembQuery.php @@ -0,0 +1,305 @@ +setModelAlias($modelAlias); + } + if ($criteria instanceof Criteria) { + $query->mergeWith($criteria); + } + return $query; + } + + /** + * Find object by primary key + * Use instance pooling to avoid a database query if the object exists + * + * $obj = $c->findPk(12, $con); + * + * @param mixed $key Primary key to use for the query + * @param PropelPDO $con an optional connection object + * + * @return CcSmemb|array|mixed the result, formatted by the current formatter + */ + public function findPk($key, $con = null) + { + if ((null !== ($obj = CcSmembPeer::getInstanceFromPool((string) $key))) && $this->getFormatter()->isObjectFormatter()) { + // the object is alredy in the instance pool + return $obj; + } else { + // the object has not been requested yet, or the formatter is not an object formatter + $criteria = $this->isKeepQuery() ? clone $this : $this; + $stmt = $criteria + ->filterByPrimaryKey($key) + ->getSelectStatement($con); + return $criteria->getFormatter()->init($criteria)->formatOne($stmt); + } + } + + /** + * Find objects by primary key + * + * $objs = $c->findPks(array(12, 56, 832), $con); + * + * @param array $keys Primary keys to use for the query + * @param PropelPDO $con an optional connection object + * + * @return PropelObjectCollection|array|mixed the list of results, formatted by the current formatter + */ + public function findPks($keys, $con = null) + { + $criteria = $this->isKeepQuery() ? clone $this : $this; + return $this + ->filterByPrimaryKeys($keys) + ->find($con); + } + + /** + * Filter the query by primary key + * + * @param mixed $key Primary key to use for the query + * + * @return CcSmembQuery The current query, for fluid interface + */ + public function filterByPrimaryKey($key) + { + return $this->addUsingAlias(CcSmembPeer::ID, $key, Criteria::EQUAL); + } + + /** + * Filter the query by a list of primary keys + * + * @param array $keys The list of primary key to use for the query + * + * @return CcSmembQuery The current query, for fluid interface + */ + public function filterByPrimaryKeys($keys) + { + return $this->addUsingAlias(CcSmembPeer::ID, $keys, Criteria::IN); + } + + /** + * Filter the query on the id column + * + * @param int|array $id The value to use as filter. + * Accepts an associative array('min' => $minValue, 'max' => $maxValue) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcSmembQuery The current query, for fluid interface + */ + public function filterById($id = null, $comparison = null) + { + if (is_array($id) && null === $comparison) { + $comparison = Criteria::IN; + } + return $this->addUsingAlias(CcSmembPeer::ID, $id, $comparison); + } + + /** + * Filter the query on the uid column + * + * @param int|array $uid The value to use as filter. + * Accepts an associative array('min' => $minValue, 'max' => $maxValue) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcSmembQuery The current query, for fluid interface + */ + public function filterByUid($uid = null, $comparison = null) + { + if (is_array($uid)) { + $useMinMax = false; + if (isset($uid['min'])) { + $this->addUsingAlias(CcSmembPeer::UID, $uid['min'], Criteria::GREATER_EQUAL); + $useMinMax = true; + } + if (isset($uid['max'])) { + $this->addUsingAlias(CcSmembPeer::UID, $uid['max'], Criteria::LESS_EQUAL); + $useMinMax = true; + } + if ($useMinMax) { + return $this; + } + if (null === $comparison) { + $comparison = Criteria::IN; + } + } + return $this->addUsingAlias(CcSmembPeer::UID, $uid, $comparison); + } + + /** + * Filter the query on the gid column + * + * @param int|array $gid The value to use as filter. + * Accepts an associative array('min' => $minValue, 'max' => $maxValue) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcSmembQuery The current query, for fluid interface + */ + public function filterByGid($gid = null, $comparison = null) + { + if (is_array($gid)) { + $useMinMax = false; + if (isset($gid['min'])) { + $this->addUsingAlias(CcSmembPeer::GID, $gid['min'], Criteria::GREATER_EQUAL); + $useMinMax = true; + } + if (isset($gid['max'])) { + $this->addUsingAlias(CcSmembPeer::GID, $gid['max'], Criteria::LESS_EQUAL); + $useMinMax = true; + } + if ($useMinMax) { + return $this; + } + if (null === $comparison) { + $comparison = Criteria::IN; + } + } + return $this->addUsingAlias(CcSmembPeer::GID, $gid, $comparison); + } + + /** + * Filter the query on the level column + * + * @param int|array $level The value to use as filter. + * Accepts an associative array('min' => $minValue, 'max' => $maxValue) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcSmembQuery The current query, for fluid interface + */ + public function filterByLevel($level = null, $comparison = null) + { + if (is_array($level)) { + $useMinMax = false; + if (isset($level['min'])) { + $this->addUsingAlias(CcSmembPeer::LEVEL, $level['min'], Criteria::GREATER_EQUAL); + $useMinMax = true; + } + if (isset($level['max'])) { + $this->addUsingAlias(CcSmembPeer::LEVEL, $level['max'], Criteria::LESS_EQUAL); + $useMinMax = true; + } + if ($useMinMax) { + return $this; + } + if (null === $comparison) { + $comparison = Criteria::IN; + } + } + return $this->addUsingAlias(CcSmembPeer::LEVEL, $level, $comparison); + } + + /** + * Filter the query on the mid column + * + * @param int|array $mid The value to use as filter. + * Accepts an associative array('min' => $minValue, 'max' => $maxValue) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcSmembQuery The current query, for fluid interface + */ + public function filterByMid($mid = null, $comparison = null) + { + if (is_array($mid)) { + $useMinMax = false; + if (isset($mid['min'])) { + $this->addUsingAlias(CcSmembPeer::MID, $mid['min'], Criteria::GREATER_EQUAL); + $useMinMax = true; + } + if (isset($mid['max'])) { + $this->addUsingAlias(CcSmembPeer::MID, $mid['max'], Criteria::LESS_EQUAL); + $useMinMax = true; + } + if ($useMinMax) { + return $this; + } + if (null === $comparison) { + $comparison = Criteria::IN; + } + } + return $this->addUsingAlias(CcSmembPeer::MID, $mid, $comparison); + } + + /** + * Exclude object from result + * + * @param CcSmemb $ccSmemb Object to remove from the list of results + * + * @return CcSmembQuery The current query, for fluid interface + */ + public function prune($ccSmemb = null) + { + if ($ccSmemb) { + $this->addUsingAlias(CcSmembPeer::ID, $ccSmemb->getId(), Criteria::NOT_EQUAL); + } + + return $this; + } + +} // BaseCcSmembQuery diff --git a/application/models/campcaster/om/BaseCcSubjs.php b/application/models/campcaster/om/BaseCcSubjs.php new file mode 100644 index 000000000..58796a099 --- /dev/null +++ b/application/models/campcaster/om/BaseCcSubjs.php @@ -0,0 +1,1969 @@ +login = ''; + $this->pass = ''; + $this->type = 'U'; + $this->realname = ''; + } + + /** + * Initializes internal state of BaseCcSubjs object. + * @see applyDefaults() + */ + public function __construct() + { + parent::__construct(); + $this->applyDefaultValues(); + } + + /** + * Get the [id] column value. + * + * @return int + */ + public function getId() + { + return $this->id; + } + + /** + * Get the [login] column value. + * + * @return string + */ + public function getLogin() + { + return $this->login; + } + + /** + * Get the [pass] column value. + * + * @return string + */ + public function getPass() + { + return $this->pass; + } + + /** + * Get the [type] column value. + * + * @return string + */ + public function getType() + { + return $this->type; + } + + /** + * Get the [realname] column value. + * + * @return string + */ + public function getRealname() + { + return $this->realname; + } + + /** + * Get the [optionally formatted] temporal [lastlogin] column value. + * + * + * @param string $format The date/time format string (either date()-style or strftime()-style). + * If format is NULL, then the raw DateTime object will be returned. + * @return mixed Formatted date/time value as string or DateTime object (if format is NULL), NULL if column is NULL + * @throws PropelException - if unable to parse/validate the date/time value. + */ + public function getLastlogin($format = 'Y-m-d H:i:s') + { + if ($this->lastlogin === null) { + return null; + } + + + + try { + $dt = new DateTime($this->lastlogin); + } catch (Exception $x) { + throw new PropelException("Internally stored date/time/timestamp value could not be converted to DateTime: " . var_export($this->lastlogin, true), $x); + } + + if ($format === null) { + // Because propel.useDateTimeClass is TRUE, we return a DateTime object. + return $dt; + } elseif (strpos($format, '%') !== false) { + return strftime($format, $dt->format('U')); + } else { + return $dt->format($format); + } + } + + /** + * Get the [optionally formatted] temporal [lastfail] column value. + * + * + * @param string $format The date/time format string (either date()-style or strftime()-style). + * If format is NULL, then the raw DateTime object will be returned. + * @return mixed Formatted date/time value as string or DateTime object (if format is NULL), NULL if column is NULL + * @throws PropelException - if unable to parse/validate the date/time value. + */ + public function getLastfail($format = 'Y-m-d H:i:s') + { + if ($this->lastfail === null) { + return null; + } + + + + try { + $dt = new DateTime($this->lastfail); + } catch (Exception $x) { + throw new PropelException("Internally stored date/time/timestamp value could not be converted to DateTime: " . var_export($this->lastfail, true), $x); + } + + if ($format === null) { + // Because propel.useDateTimeClass is TRUE, we return a DateTime object. + return $dt; + } elseif (strpos($format, '%') !== false) { + return strftime($format, $dt->format('U')); + } else { + return $dt->format($format); + } + } + + /** + * Set the value of [id] column. + * + * @param int $v new value + * @return CcSubjs The current object (for fluent API support) + */ + public function setId($v) + { + if ($v !== null) { + $v = (int) $v; + } + + if ($this->id !== $v) { + $this->id = $v; + $this->modifiedColumns[] = CcSubjsPeer::ID; + } + + return $this; + } // setId() + + /** + * Set the value of [login] column. + * + * @param string $v new value + * @return CcSubjs The current object (for fluent API support) + */ + public function setLogin($v) + { + if ($v !== null) { + $v = (string) $v; + } + + if ($this->login !== $v || $this->isNew()) { + $this->login = $v; + $this->modifiedColumns[] = CcSubjsPeer::LOGIN; + } + + return $this; + } // setLogin() + + /** + * Set the value of [pass] column. + * + * @param string $v new value + * @return CcSubjs The current object (for fluent API support) + */ + public function setPass($v) + { + if ($v !== null) { + $v = (string) $v; + } + + if ($this->pass !== $v || $this->isNew()) { + $this->pass = $v; + $this->modifiedColumns[] = CcSubjsPeer::PASS; + } + + return $this; + } // setPass() + + /** + * Set the value of [type] column. + * + * @param string $v new value + * @return CcSubjs The current object (for fluent API support) + */ + public function setType($v) + { + if ($v !== null) { + $v = (string) $v; + } + + if ($this->type !== $v || $this->isNew()) { + $this->type = $v; + $this->modifiedColumns[] = CcSubjsPeer::TYPE; + } + + return $this; + } // setType() + + /** + * Set the value of [realname] column. + * + * @param string $v new value + * @return CcSubjs The current object (for fluent API support) + */ + public function setRealname($v) + { + if ($v !== null) { + $v = (string) $v; + } + + if ($this->realname !== $v || $this->isNew()) { + $this->realname = $v; + $this->modifiedColumns[] = CcSubjsPeer::REALNAME; + } + + return $this; + } // setRealname() + + /** + * Sets the value of [lastlogin] column to a normalized version of the date/time value specified. + * + * @param mixed $v string, integer (timestamp), or DateTime value. Empty string will + * be treated as NULL for temporal objects. + * @return CcSubjs The current object (for fluent API support) + */ + public function setLastlogin($v) + { + // we treat '' as NULL for temporal objects because DateTime('') == DateTime('now') + // -- which is unexpected, to say the least. + if ($v === null || $v === '') { + $dt = null; + } elseif ($v instanceof DateTime) { + $dt = $v; + } else { + // some string/numeric value passed; we normalize that so that we can + // validate it. + try { + if (is_numeric($v)) { // if it's a unix timestamp + $dt = new DateTime('@'.$v, new DateTimeZone('UTC')); + // We have to explicitly specify and then change the time zone because of a + // DateTime bug: http://bugs.php.net/bug.php?id=43003 + $dt->setTimeZone(new DateTimeZone(date_default_timezone_get())); + } else { + $dt = new DateTime($v); + } + } catch (Exception $x) { + throw new PropelException('Error parsing date/time value: ' . var_export($v, true), $x); + } + } + + if ( $this->lastlogin !== null || $dt !== null ) { + // (nested ifs are a little easier to read in this case) + + $currNorm = ($this->lastlogin !== null && $tmpDt = new DateTime($this->lastlogin)) ? $tmpDt->format('Y-m-d\\TH:i:sO') : null; + $newNorm = ($dt !== null) ? $dt->format('Y-m-d\\TH:i:sO') : null; + + if ( ($currNorm !== $newNorm) // normalized values don't match + ) + { + $this->lastlogin = ($dt ? $dt->format('Y-m-d\\TH:i:sO') : null); + $this->modifiedColumns[] = CcSubjsPeer::LASTLOGIN; + } + } // if either are not null + + return $this; + } // setLastlogin() + + /** + * Sets the value of [lastfail] column to a normalized version of the date/time value specified. + * + * @param mixed $v string, integer (timestamp), or DateTime value. Empty string will + * be treated as NULL for temporal objects. + * @return CcSubjs The current object (for fluent API support) + */ + public function setLastfail($v) + { + // we treat '' as NULL for temporal objects because DateTime('') == DateTime('now') + // -- which is unexpected, to say the least. + if ($v === null || $v === '') { + $dt = null; + } elseif ($v instanceof DateTime) { + $dt = $v; + } else { + // some string/numeric value passed; we normalize that so that we can + // validate it. + try { + if (is_numeric($v)) { // if it's a unix timestamp + $dt = new DateTime('@'.$v, new DateTimeZone('UTC')); + // We have to explicitly specify and then change the time zone because of a + // DateTime bug: http://bugs.php.net/bug.php?id=43003 + $dt->setTimeZone(new DateTimeZone(date_default_timezone_get())); + } else { + $dt = new DateTime($v); + } + } catch (Exception $x) { + throw new PropelException('Error parsing date/time value: ' . var_export($v, true), $x); + } + } + + if ( $this->lastfail !== null || $dt !== null ) { + // (nested ifs are a little easier to read in this case) + + $currNorm = ($this->lastfail !== null && $tmpDt = new DateTime($this->lastfail)) ? $tmpDt->format('Y-m-d\\TH:i:sO') : null; + $newNorm = ($dt !== null) ? $dt->format('Y-m-d\\TH:i:sO') : null; + + if ( ($currNorm !== $newNorm) // normalized values don't match + ) + { + $this->lastfail = ($dt ? $dt->format('Y-m-d\\TH:i:sO') : null); + $this->modifiedColumns[] = CcSubjsPeer::LASTFAIL; + } + } // if either are not null + + return $this; + } // setLastfail() + + /** + * Indicates whether the columns in this object are only set to default values. + * + * This method can be used in conjunction with isModified() to indicate whether an object is both + * modified _and_ has some values set which are non-default. + * + * @return boolean Whether the columns in this object are only been set with default values. + */ + public function hasOnlyDefaultValues() + { + if ($this->login !== '') { + return false; + } + + if ($this->pass !== '') { + return false; + } + + if ($this->type !== 'U') { + return false; + } + + if ($this->realname !== '') { + return false; + } + + // otherwise, everything was equal, so return TRUE + return true; + } // hasOnlyDefaultValues() + + /** + * Hydrates (populates) the object variables with values from the database resultset. + * + * An offset (0-based "start column") is specified so that objects can be hydrated + * with a subset of the columns in the resultset rows. This is needed, for example, + * for results of JOIN queries where the resultset row includes columns from two or + * more tables. + * + * @param array $row The row returned by PDOStatement->fetch(PDO::FETCH_NUM) + * @param int $startcol 0-based offset column which indicates which restultset column to start with. + * @param boolean $rehydrate Whether this object is being re-hydrated from the database. + * @return int next starting column + * @throws PropelException - Any caught Exception will be rewrapped as a PropelException. + */ + public function hydrate($row, $startcol = 0, $rehydrate = false) + { + try { + + $this->id = ($row[$startcol + 0] !== null) ? (int) $row[$startcol + 0] : null; + $this->login = ($row[$startcol + 1] !== null) ? (string) $row[$startcol + 1] : null; + $this->pass = ($row[$startcol + 2] !== null) ? (string) $row[$startcol + 2] : null; + $this->type = ($row[$startcol + 3] !== null) ? (string) $row[$startcol + 3] : null; + $this->realname = ($row[$startcol + 4] !== null) ? (string) $row[$startcol + 4] : null; + $this->lastlogin = ($row[$startcol + 5] !== null) ? (string) $row[$startcol + 5] : null; + $this->lastfail = ($row[$startcol + 6] !== null) ? (string) $row[$startcol + 6] : null; + $this->resetModified(); + + $this->setNew(false); + + if ($rehydrate) { + $this->ensureConsistency(); + } + + return $startcol + 7; // 7 = CcSubjsPeer::NUM_COLUMNS - CcSubjsPeer::NUM_LAZY_LOAD_COLUMNS). + + } catch (Exception $e) { + throw new PropelException("Error populating CcSubjs object", $e); + } + } + + /** + * Checks and repairs the internal consistency of the object. + * + * This method is executed after an already-instantiated object is re-hydrated + * from the database. It exists to check any foreign keys to make sure that + * the objects related to the current object are correct based on foreign key. + * + * You can override this method in the stub class, but you should always invoke + * the base method from the overridden method (i.e. parent::ensureConsistency()), + * in case your model changes. + * + * @throws PropelException + */ + public function ensureConsistency() + { + + } // ensureConsistency + + /** + * Reloads this object from datastore based on primary key and (optionally) resets all associated objects. + * + * This will only work if the object has been saved and has a valid primary key set. + * + * @param boolean $deep (optional) Whether to also de-associated any related objects. + * @param PropelPDO $con (optional) The PropelPDO connection to use. + * @return void + * @throws PropelException - if this object is deleted, unsaved or doesn't have pk match in db + */ + public function reload($deep = false, PropelPDO $con = null) + { + if ($this->isDeleted()) { + throw new PropelException("Cannot reload a deleted object."); + } + + if ($this->isNew()) { + throw new PropelException("Cannot reload an unsaved object."); + } + + if ($con === null) { + $con = Propel::getConnection(CcSubjsPeer::DATABASE_NAME, Propel::CONNECTION_READ); + } + + // We don't need to alter the object instance pool; we're just modifying this instance + // already in the pool. + + $stmt = CcSubjsPeer::doSelectStmt($this->buildPkeyCriteria(), $con); + $row = $stmt->fetch(PDO::FETCH_NUM); + $stmt->closeCursor(); + if (!$row) { + throw new PropelException('Cannot find matching row in the database to reload object values.'); + } + $this->hydrate($row, 0, true); // rehydrate + + if ($deep) { // also de-associate any related objects? + + $this->collCcAccesss = null; + + $this->collCcFiless = null; + + $this->collCcPermss = null; + + $this->collCcPlaylists = null; + + $this->collCcPrefs = null; + + $this->collCcSesss = null; + + } // if (deep) + } + + /** + * Removes this object from datastore and sets delete attribute. + * + * @param PropelPDO $con + * @return void + * @throws PropelException + * @see BaseObject::setDeleted() + * @see BaseObject::isDeleted() + */ + public function delete(PropelPDO $con = null) + { + if ($this->isDeleted()) { + throw new PropelException("This object has already been deleted."); + } + + if ($con === null) { + $con = Propel::getConnection(CcSubjsPeer::DATABASE_NAME, Propel::CONNECTION_WRITE); + } + + $con->beginTransaction(); + try { + $ret = $this->preDelete($con); + if ($ret) { + CcSubjsQuery::create() + ->filterByPrimaryKey($this->getPrimaryKey()) + ->delete($con); + $this->postDelete($con); + $con->commit(); + $this->setDeleted(true); + } else { + $con->commit(); + } + } catch (PropelException $e) { + $con->rollBack(); + throw $e; + } + } + + /** + * Persists this object to the database. + * + * If the object is new, it inserts it; otherwise an update is performed. + * All modified related objects will also be persisted in the doSave() + * method. This method wraps all precipitate database operations in a + * single transaction. + * + * @param PropelPDO $con + * @return int The number of rows affected by this insert/update and any referring fk objects' save() operations. + * @throws PropelException + * @see doSave() + */ + public function save(PropelPDO $con = null) + { + if ($this->isDeleted()) { + throw new PropelException("You cannot save an object that has been deleted."); + } + + if ($con === null) { + $con = Propel::getConnection(CcSubjsPeer::DATABASE_NAME, Propel::CONNECTION_WRITE); + } + + $con->beginTransaction(); + $isInsert = $this->isNew(); + try { + $ret = $this->preSave($con); + if ($isInsert) { + $ret = $ret && $this->preInsert($con); + } else { + $ret = $ret && $this->preUpdate($con); + } + if ($ret) { + $affectedRows = $this->doSave($con); + if ($isInsert) { + $this->postInsert($con); + } else { + $this->postUpdate($con); + } + $this->postSave($con); + CcSubjsPeer::addInstanceToPool($this); + } else { + $affectedRows = 0; + } + $con->commit(); + return $affectedRows; + } catch (PropelException $e) { + $con->rollBack(); + throw $e; + } + } + + /** + * Performs the work of inserting or updating the row in the database. + * + * If the object is new, it inserts it; otherwise an update is performed. + * All related objects are also updated in this method. + * + * @param PropelPDO $con + * @return int The number of rows affected by this insert/update and any referring fk objects' save() operations. + * @throws PropelException + * @see save() + */ + protected function doSave(PropelPDO $con) + { + $affectedRows = 0; // initialize var to track total num of affected rows + if (!$this->alreadyInSave) { + $this->alreadyInSave = true; + + + // If this object has been modified, then save it to the database. + if ($this->isModified()) { + if ($this->isNew()) { + $criteria = $this->buildCriteria(); + $pk = BasePeer::doInsert($criteria, $con); + $affectedRows = 1; + $this->setNew(false); + } else { + $affectedRows = CcSubjsPeer::doUpdate($this, $con); + } + + $this->resetModified(); // [HL] After being saved an object is no longer 'modified' + } + + if ($this->collCcAccesss !== null) { + foreach ($this->collCcAccesss as $referrerFK) { + if (!$referrerFK->isDeleted()) { + $affectedRows += $referrerFK->save($con); + } + } + } + + if ($this->collCcFiless !== null) { + foreach ($this->collCcFiless as $referrerFK) { + if (!$referrerFK->isDeleted()) { + $affectedRows += $referrerFK->save($con); + } + } + } + + if ($this->collCcPermss !== null) { + foreach ($this->collCcPermss as $referrerFK) { + if (!$referrerFK->isDeleted()) { + $affectedRows += $referrerFK->save($con); + } + } + } + + if ($this->collCcPlaylists !== null) { + foreach ($this->collCcPlaylists as $referrerFK) { + if (!$referrerFK->isDeleted()) { + $affectedRows += $referrerFK->save($con); + } + } + } + + if ($this->collCcPrefs !== null) { + foreach ($this->collCcPrefs as $referrerFK) { + if (!$referrerFK->isDeleted()) { + $affectedRows += $referrerFK->save($con); + } + } + } + + if ($this->collCcSesss !== null) { + foreach ($this->collCcSesss as $referrerFK) { + if (!$referrerFK->isDeleted()) { + $affectedRows += $referrerFK->save($con); + } + } + } + + $this->alreadyInSave = false; + + } + return $affectedRows; + } // doSave() + + /** + * Array of ValidationFailed objects. + * @var array ValidationFailed[] + */ + protected $validationFailures = array(); + + /** + * Gets any ValidationFailed objects that resulted from last call to validate(). + * + * + * @return array ValidationFailed[] + * @see validate() + */ + public function getValidationFailures() + { + return $this->validationFailures; + } + + /** + * Validates the objects modified field values and all objects related to this table. + * + * If $columns is either a column name or an array of column names + * only those columns are validated. + * + * @param mixed $columns Column name or an array of column names. + * @return boolean Whether all columns pass validation. + * @see doValidate() + * @see getValidationFailures() + */ + public function validate($columns = null) + { + $res = $this->doValidate($columns); + if ($res === true) { + $this->validationFailures = array(); + return true; + } else { + $this->validationFailures = $res; + return false; + } + } + + /** + * This function performs the validation work for complex object models. + * + * In addition to checking the current object, all related objects will + * also be validated. If all pass then true is returned; otherwise + * an aggreagated array of ValidationFailed objects will be returned. + * + * @param array $columns Array of column names to validate. + * @return mixed true if all validations pass; array of ValidationFailed objets otherwise. + */ + protected function doValidate($columns = null) + { + if (!$this->alreadyInValidation) { + $this->alreadyInValidation = true; + $retval = null; + + $failureMap = array(); + + + if (($retval = CcSubjsPeer::doValidate($this, $columns)) !== true) { + $failureMap = array_merge($failureMap, $retval); + } + + + if ($this->collCcAccesss !== null) { + foreach ($this->collCcAccesss as $referrerFK) { + if (!$referrerFK->validate($columns)) { + $failureMap = array_merge($failureMap, $referrerFK->getValidationFailures()); + } + } + } + + if ($this->collCcFiless !== null) { + foreach ($this->collCcFiless as $referrerFK) { + if (!$referrerFK->validate($columns)) { + $failureMap = array_merge($failureMap, $referrerFK->getValidationFailures()); + } + } + } + + if ($this->collCcPermss !== null) { + foreach ($this->collCcPermss as $referrerFK) { + if (!$referrerFK->validate($columns)) { + $failureMap = array_merge($failureMap, $referrerFK->getValidationFailures()); + } + } + } + + if ($this->collCcPlaylists !== null) { + foreach ($this->collCcPlaylists as $referrerFK) { + if (!$referrerFK->validate($columns)) { + $failureMap = array_merge($failureMap, $referrerFK->getValidationFailures()); + } + } + } + + if ($this->collCcPrefs !== null) { + foreach ($this->collCcPrefs as $referrerFK) { + if (!$referrerFK->validate($columns)) { + $failureMap = array_merge($failureMap, $referrerFK->getValidationFailures()); + } + } + } + + if ($this->collCcSesss !== null) { + foreach ($this->collCcSesss as $referrerFK) { + if (!$referrerFK->validate($columns)) { + $failureMap = array_merge($failureMap, $referrerFK->getValidationFailures()); + } + } + } + + + $this->alreadyInValidation = false; + } + + return (!empty($failureMap) ? $failureMap : true); + } + + /** + * Retrieves a field from the object by name passed in as a string. + * + * @param string $name name + * @param string $type The type of fieldname the $name is of: + * one of the class type constants BasePeer::TYPE_PHPNAME, BasePeer::TYPE_STUDLYPHPNAME + * BasePeer::TYPE_COLNAME, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_NUM + * @return mixed Value of field. + */ + public function getByName($name, $type = BasePeer::TYPE_PHPNAME) + { + $pos = CcSubjsPeer::translateFieldName($name, $type, BasePeer::TYPE_NUM); + $field = $this->getByPosition($pos); + return $field; + } + + /** + * Retrieves a field from the object by Position as specified in the xml schema. + * Zero-based. + * + * @param int $pos position in xml schema + * @return mixed Value of field at $pos + */ + public function getByPosition($pos) + { + switch($pos) { + case 0: + return $this->getId(); + break; + case 1: + return $this->getLogin(); + break; + case 2: + return $this->getPass(); + break; + case 3: + return $this->getType(); + break; + case 4: + return $this->getRealname(); + break; + case 5: + return $this->getLastlogin(); + break; + case 6: + return $this->getLastfail(); + break; + default: + return null; + break; + } // switch() + } + + /** + * Exports the object as an array. + * + * You can specify the key type of the array by passing one of the class + * type constants. + * + * @param string $keyType (optional) One of the class type constants BasePeer::TYPE_PHPNAME, BasePeer::TYPE_STUDLYPHPNAME, + * BasePeer::TYPE_COLNAME, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_NUM. + * Defaults to BasePeer::TYPE_PHPNAME. + * @param boolean $includeLazyLoadColumns (optional) Whether to include lazy loaded columns. Defaults to TRUE. + * + * @return array an associative array containing the field names (as keys) and field values + */ + public function toArray($keyType = BasePeer::TYPE_PHPNAME, $includeLazyLoadColumns = true) + { + $keys = CcSubjsPeer::getFieldNames($keyType); + $result = array( + $keys[0] => $this->getId(), + $keys[1] => $this->getLogin(), + $keys[2] => $this->getPass(), + $keys[3] => $this->getType(), + $keys[4] => $this->getRealname(), + $keys[5] => $this->getLastlogin(), + $keys[6] => $this->getLastfail(), + ); + return $result; + } + + /** + * Sets a field from the object by name passed in as a string. + * + * @param string $name peer name + * @param mixed $value field value + * @param string $type The type of fieldname the $name is of: + * one of the class type constants BasePeer::TYPE_PHPNAME, BasePeer::TYPE_STUDLYPHPNAME + * BasePeer::TYPE_COLNAME, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_NUM + * @return void + */ + public function setByName($name, $value, $type = BasePeer::TYPE_PHPNAME) + { + $pos = CcSubjsPeer::translateFieldName($name, $type, BasePeer::TYPE_NUM); + return $this->setByPosition($pos, $value); + } + + /** + * Sets a field from the object by Position as specified in the xml schema. + * Zero-based. + * + * @param int $pos position in xml schema + * @param mixed $value field value + * @return void + */ + public function setByPosition($pos, $value) + { + switch($pos) { + case 0: + $this->setId($value); + break; + case 1: + $this->setLogin($value); + break; + case 2: + $this->setPass($value); + break; + case 3: + $this->setType($value); + break; + case 4: + $this->setRealname($value); + break; + case 5: + $this->setLastlogin($value); + break; + case 6: + $this->setLastfail($value); + break; + } // switch() + } + + /** + * Populates the object using an array. + * + * This is particularly useful when populating an object from one of the + * request arrays (e.g. $_POST). This method goes through the column + * names, checking to see whether a matching key exists in populated + * array. If so the setByName() method is called for that column. + * + * You can specify the key type of the array by additionally passing one + * of the class type constants BasePeer::TYPE_PHPNAME, BasePeer::TYPE_STUDLYPHPNAME, + * BasePeer::TYPE_COLNAME, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_NUM. + * The default key type is the column's phpname (e.g. 'AuthorId') + * + * @param array $arr An array to populate the object from. + * @param string $keyType The type of keys the array uses. + * @return void + */ + public function fromArray($arr, $keyType = BasePeer::TYPE_PHPNAME) + { + $keys = CcSubjsPeer::getFieldNames($keyType); + + if (array_key_exists($keys[0], $arr)) $this->setId($arr[$keys[0]]); + if (array_key_exists($keys[1], $arr)) $this->setLogin($arr[$keys[1]]); + if (array_key_exists($keys[2], $arr)) $this->setPass($arr[$keys[2]]); + if (array_key_exists($keys[3], $arr)) $this->setType($arr[$keys[3]]); + if (array_key_exists($keys[4], $arr)) $this->setRealname($arr[$keys[4]]); + if (array_key_exists($keys[5], $arr)) $this->setLastlogin($arr[$keys[5]]); + if (array_key_exists($keys[6], $arr)) $this->setLastfail($arr[$keys[6]]); + } + + /** + * Build a Criteria object containing the values of all modified columns in this object. + * + * @return Criteria The Criteria object containing all modified values. + */ + public function buildCriteria() + { + $criteria = new Criteria(CcSubjsPeer::DATABASE_NAME); + + if ($this->isColumnModified(CcSubjsPeer::ID)) $criteria->add(CcSubjsPeer::ID, $this->id); + if ($this->isColumnModified(CcSubjsPeer::LOGIN)) $criteria->add(CcSubjsPeer::LOGIN, $this->login); + if ($this->isColumnModified(CcSubjsPeer::PASS)) $criteria->add(CcSubjsPeer::PASS, $this->pass); + if ($this->isColumnModified(CcSubjsPeer::TYPE)) $criteria->add(CcSubjsPeer::TYPE, $this->type); + if ($this->isColumnModified(CcSubjsPeer::REALNAME)) $criteria->add(CcSubjsPeer::REALNAME, $this->realname); + if ($this->isColumnModified(CcSubjsPeer::LASTLOGIN)) $criteria->add(CcSubjsPeer::LASTLOGIN, $this->lastlogin); + if ($this->isColumnModified(CcSubjsPeer::LASTFAIL)) $criteria->add(CcSubjsPeer::LASTFAIL, $this->lastfail); + + return $criteria; + } + + /** + * Builds a Criteria object containing the primary key for this object. + * + * Unlike buildCriteria() this method includes the primary key values regardless + * of whether or not they have been modified. + * + * @return Criteria The Criteria object containing value(s) for primary key(s). + */ + public function buildPkeyCriteria() + { + $criteria = new Criteria(CcSubjsPeer::DATABASE_NAME); + $criteria->add(CcSubjsPeer::ID, $this->id); + + return $criteria; + } + + /** + * Returns the primary key for this object (row). + * @return int + */ + public function getPrimaryKey() + { + return $this->getId(); + } + + /** + * Generic method to set the primary key (id column). + * + * @param int $key Primary key. + * @return void + */ + public function setPrimaryKey($key) + { + $this->setId($key); + } + + /** + * Returns true if the primary key for this object is null. + * @return boolean + */ + public function isPrimaryKeyNull() + { + return null === $this->getId(); + } + + /** + * Sets contents of passed object to values from current object. + * + * If desired, this method can also make copies of all associated (fkey referrers) + * objects. + * + * @param object $copyObj An object of CcSubjs (or compatible) type. + * @param boolean $deepCopy Whether to also copy all rows that refer (by fkey) to the current row. + * @throws PropelException + */ + public function copyInto($copyObj, $deepCopy = false) + { + $copyObj->setId($this->id); + $copyObj->setLogin($this->login); + $copyObj->setPass($this->pass); + $copyObj->setType($this->type); + $copyObj->setRealname($this->realname); + $copyObj->setLastlogin($this->lastlogin); + $copyObj->setLastfail($this->lastfail); + + if ($deepCopy) { + // important: temporarily setNew(false) because this affects the behavior of + // the getter/setter methods for fkey referrer objects. + $copyObj->setNew(false); + + foreach ($this->getCcAccesss() as $relObj) { + if ($relObj !== $this) { // ensure that we don't try to copy a reference to ourselves + $copyObj->addCcAccess($relObj->copy($deepCopy)); + } + } + + foreach ($this->getCcFiless() as $relObj) { + if ($relObj !== $this) { // ensure that we don't try to copy a reference to ourselves + $copyObj->addCcFiles($relObj->copy($deepCopy)); + } + } + + foreach ($this->getCcPermss() as $relObj) { + if ($relObj !== $this) { // ensure that we don't try to copy a reference to ourselves + $copyObj->addCcPerms($relObj->copy($deepCopy)); + } + } + + foreach ($this->getCcPlaylists() as $relObj) { + if ($relObj !== $this) { // ensure that we don't try to copy a reference to ourselves + $copyObj->addCcPlaylist($relObj->copy($deepCopy)); + } + } + + foreach ($this->getCcPrefs() as $relObj) { + if ($relObj !== $this) { // ensure that we don't try to copy a reference to ourselves + $copyObj->addCcPref($relObj->copy($deepCopy)); + } + } + + foreach ($this->getCcSesss() as $relObj) { + if ($relObj !== $this) { // ensure that we don't try to copy a reference to ourselves + $copyObj->addCcSess($relObj->copy($deepCopy)); + } + } + + } // if ($deepCopy) + + + $copyObj->setNew(true); + } + + /** + * Makes a copy of this object that will be inserted as a new row in table when saved. + * It creates a new object filling in the simple attributes, but skipping any primary + * keys that are defined for the table. + * + * If desired, this method can also make copies of all associated (fkey referrers) + * objects. + * + * @param boolean $deepCopy Whether to also copy all rows that refer (by fkey) to the current row. + * @return CcSubjs Clone of current object. + * @throws PropelException + */ + public function copy($deepCopy = false) + { + // we use get_class(), because this might be a subclass + $clazz = get_class($this); + $copyObj = new $clazz(); + $this->copyInto($copyObj, $deepCopy); + return $copyObj; + } + + /** + * Returns a peer instance associated with this om. + * + * Since Peer classes are not to have any instance attributes, this method returns the + * same instance for all member of this class. The method could therefore + * be static, but this would prevent one from overriding the behavior. + * + * @return CcSubjsPeer + */ + public function getPeer() + { + if (self::$peer === null) { + self::$peer = new CcSubjsPeer(); + } + return self::$peer; + } + + /** + * Clears out the collCcAccesss collection + * + * This does not modify the database; however, it will remove any associated objects, causing + * them to be refetched by subsequent calls to accessor method. + * + * @return void + * @see addCcAccesss() + */ + public function clearCcAccesss() + { + $this->collCcAccesss = null; // important to set this to NULL since that means it is uninitialized + } + + /** + * Initializes the collCcAccesss collection. + * + * By default this just sets the collCcAccesss collection to an empty array (like clearcollCcAccesss()); + * however, you may wish to override this method in your stub class to provide setting appropriate + * to your application -- for example, setting the initial array to the values stored in database. + * + * @return void + */ + public function initCcAccesss() + { + $this->collCcAccesss = new PropelObjectCollection(); + $this->collCcAccesss->setModel('CcAccess'); + } + + /** + * Gets an array of CcAccess objects which contain a foreign key that references this object. + * + * If the $criteria is not null, it is used to always fetch the results from the database. + * Otherwise the results are fetched from the database the first time, then cached. + * Next time the same method is called without $criteria, the cached collection is returned. + * If this CcSubjs is new, it will return + * an empty collection or the current collection; the criteria is ignored on a new object. + * + * @param Criteria $criteria optional Criteria object to narrow the query + * @param PropelPDO $con optional connection object + * @return PropelCollection|array CcAccess[] List of CcAccess objects + * @throws PropelException + */ + public function getCcAccesss($criteria = null, PropelPDO $con = null) + { + if(null === $this->collCcAccesss || null !== $criteria) { + if ($this->isNew() && null === $this->collCcAccesss) { + // return empty collection + $this->initCcAccesss(); + } else { + $collCcAccesss = CcAccessQuery::create(null, $criteria) + ->filterByCcSubjs($this) + ->find($con); + if (null !== $criteria) { + return $collCcAccesss; + } + $this->collCcAccesss = $collCcAccesss; + } + } + return $this->collCcAccesss; + } + + /** + * Returns the number of related CcAccess objects. + * + * @param Criteria $criteria + * @param boolean $distinct + * @param PropelPDO $con + * @return int Count of related CcAccess objects. + * @throws PropelException + */ + public function countCcAccesss(Criteria $criteria = null, $distinct = false, PropelPDO $con = null) + { + if(null === $this->collCcAccesss || null !== $criteria) { + if ($this->isNew() && null === $this->collCcAccesss) { + return 0; + } else { + $query = CcAccessQuery::create(null, $criteria); + if($distinct) { + $query->distinct(); + } + return $query + ->filterByCcSubjs($this) + ->count($con); + } + } else { + return count($this->collCcAccesss); + } + } + + /** + * Method called to associate a CcAccess object to this object + * through the CcAccess foreign key attribute. + * + * @param CcAccess $l CcAccess + * @return void + * @throws PropelException + */ + public function addCcAccess(CcAccess $l) + { + if ($this->collCcAccesss === null) { + $this->initCcAccesss(); + } + if (!$this->collCcAccesss->contains($l)) { // only add it if the **same** object is not already associated + $this->collCcAccesss[]= $l; + $l->setCcSubjs($this); + } + } + + /** + * Clears out the collCcFiless collection + * + * This does not modify the database; however, it will remove any associated objects, causing + * them to be refetched by subsequent calls to accessor method. + * + * @return void + * @see addCcFiless() + */ + public function clearCcFiless() + { + $this->collCcFiless = null; // important to set this to NULL since that means it is uninitialized + } + + /** + * Initializes the collCcFiless collection. + * + * By default this just sets the collCcFiless collection to an empty array (like clearcollCcFiless()); + * however, you may wish to override this method in your stub class to provide setting appropriate + * to your application -- for example, setting the initial array to the values stored in database. + * + * @return void + */ + public function initCcFiless() + { + $this->collCcFiless = new PropelObjectCollection(); + $this->collCcFiless->setModel('CcFiles'); + } + + /** + * Gets an array of CcFiles objects which contain a foreign key that references this object. + * + * If the $criteria is not null, it is used to always fetch the results from the database. + * Otherwise the results are fetched from the database the first time, then cached. + * Next time the same method is called without $criteria, the cached collection is returned. + * If this CcSubjs is new, it will return + * an empty collection or the current collection; the criteria is ignored on a new object. + * + * @param Criteria $criteria optional Criteria object to narrow the query + * @param PropelPDO $con optional connection object + * @return PropelCollection|array CcFiles[] List of CcFiles objects + * @throws PropelException + */ + public function getCcFiless($criteria = null, PropelPDO $con = null) + { + if(null === $this->collCcFiless || null !== $criteria) { + if ($this->isNew() && null === $this->collCcFiless) { + // return empty collection + $this->initCcFiless(); + } else { + $collCcFiless = CcFilesQuery::create(null, $criteria) + ->filterByCcSubjs($this) + ->find($con); + if (null !== $criteria) { + return $collCcFiless; + } + $this->collCcFiless = $collCcFiless; + } + } + return $this->collCcFiless; + } + + /** + * Returns the number of related CcFiles objects. + * + * @param Criteria $criteria + * @param boolean $distinct + * @param PropelPDO $con + * @return int Count of related CcFiles objects. + * @throws PropelException + */ + public function countCcFiless(Criteria $criteria = null, $distinct = false, PropelPDO $con = null) + { + if(null === $this->collCcFiless || null !== $criteria) { + if ($this->isNew() && null === $this->collCcFiless) { + return 0; + } else { + $query = CcFilesQuery::create(null, $criteria); + if($distinct) { + $query->distinct(); + } + return $query + ->filterByCcSubjs($this) + ->count($con); + } + } else { + return count($this->collCcFiless); + } + } + + /** + * Method called to associate a CcFiles object to this object + * through the CcFiles foreign key attribute. + * + * @param CcFiles $l CcFiles + * @return void + * @throws PropelException + */ + public function addCcFiles(CcFiles $l) + { + if ($this->collCcFiless === null) { + $this->initCcFiless(); + } + if (!$this->collCcFiless->contains($l)) { // only add it if the **same** object is not already associated + $this->collCcFiless[]= $l; + $l->setCcSubjs($this); + } + } + + /** + * Clears out the collCcPermss collection + * + * This does not modify the database; however, it will remove any associated objects, causing + * them to be refetched by subsequent calls to accessor method. + * + * @return void + * @see addCcPermss() + */ + public function clearCcPermss() + { + $this->collCcPermss = null; // important to set this to NULL since that means it is uninitialized + } + + /** + * Initializes the collCcPermss collection. + * + * By default this just sets the collCcPermss collection to an empty array (like clearcollCcPermss()); + * however, you may wish to override this method in your stub class to provide setting appropriate + * to your application -- for example, setting the initial array to the values stored in database. + * + * @return void + */ + public function initCcPermss() + { + $this->collCcPermss = new PropelObjectCollection(); + $this->collCcPermss->setModel('CcPerms'); + } + + /** + * Gets an array of CcPerms objects which contain a foreign key that references this object. + * + * If the $criteria is not null, it is used to always fetch the results from the database. + * Otherwise the results are fetched from the database the first time, then cached. + * Next time the same method is called without $criteria, the cached collection is returned. + * If this CcSubjs is new, it will return + * an empty collection or the current collection; the criteria is ignored on a new object. + * + * @param Criteria $criteria optional Criteria object to narrow the query + * @param PropelPDO $con optional connection object + * @return PropelCollection|array CcPerms[] List of CcPerms objects + * @throws PropelException + */ + public function getCcPermss($criteria = null, PropelPDO $con = null) + { + if(null === $this->collCcPermss || null !== $criteria) { + if ($this->isNew() && null === $this->collCcPermss) { + // return empty collection + $this->initCcPermss(); + } else { + $collCcPermss = CcPermsQuery::create(null, $criteria) + ->filterByCcSubjs($this) + ->find($con); + if (null !== $criteria) { + return $collCcPermss; + } + $this->collCcPermss = $collCcPermss; + } + } + return $this->collCcPermss; + } + + /** + * Returns the number of related CcPerms objects. + * + * @param Criteria $criteria + * @param boolean $distinct + * @param PropelPDO $con + * @return int Count of related CcPerms objects. + * @throws PropelException + */ + public function countCcPermss(Criteria $criteria = null, $distinct = false, PropelPDO $con = null) + { + if(null === $this->collCcPermss || null !== $criteria) { + if ($this->isNew() && null === $this->collCcPermss) { + return 0; + } else { + $query = CcPermsQuery::create(null, $criteria); + if($distinct) { + $query->distinct(); + } + return $query + ->filterByCcSubjs($this) + ->count($con); + } + } else { + return count($this->collCcPermss); + } + } + + /** + * Method called to associate a CcPerms object to this object + * through the CcPerms foreign key attribute. + * + * @param CcPerms $l CcPerms + * @return void + * @throws PropelException + */ + public function addCcPerms(CcPerms $l) + { + if ($this->collCcPermss === null) { + $this->initCcPermss(); + } + if (!$this->collCcPermss->contains($l)) { // only add it if the **same** object is not already associated + $this->collCcPermss[]= $l; + $l->setCcSubjs($this); + } + } + + /** + * Clears out the collCcPlaylists collection + * + * This does not modify the database; however, it will remove any associated objects, causing + * them to be refetched by subsequent calls to accessor method. + * + * @return void + * @see addCcPlaylists() + */ + public function clearCcPlaylists() + { + $this->collCcPlaylists = null; // important to set this to NULL since that means it is uninitialized + } + + /** + * Initializes the collCcPlaylists collection. + * + * By default this just sets the collCcPlaylists collection to an empty array (like clearcollCcPlaylists()); + * however, you may wish to override this method in your stub class to provide setting appropriate + * to your application -- for example, setting the initial array to the values stored in database. + * + * @return void + */ + public function initCcPlaylists() + { + $this->collCcPlaylists = new PropelObjectCollection(); + $this->collCcPlaylists->setModel('CcPlaylist'); + } + + /** + * Gets an array of CcPlaylist objects which contain a foreign key that references this object. + * + * If the $criteria is not null, it is used to always fetch the results from the database. + * Otherwise the results are fetched from the database the first time, then cached. + * Next time the same method is called without $criteria, the cached collection is returned. + * If this CcSubjs is new, it will return + * an empty collection or the current collection; the criteria is ignored on a new object. + * + * @param Criteria $criteria optional Criteria object to narrow the query + * @param PropelPDO $con optional connection object + * @return PropelCollection|array CcPlaylist[] List of CcPlaylist objects + * @throws PropelException + */ + public function getCcPlaylists($criteria = null, PropelPDO $con = null) + { + if(null === $this->collCcPlaylists || null !== $criteria) { + if ($this->isNew() && null === $this->collCcPlaylists) { + // return empty collection + $this->initCcPlaylists(); + } else { + $collCcPlaylists = CcPlaylistQuery::create(null, $criteria) + ->filterByCcSubjs($this) + ->find($con); + if (null !== $criteria) { + return $collCcPlaylists; + } + $this->collCcPlaylists = $collCcPlaylists; + } + } + return $this->collCcPlaylists; + } + + /** + * Returns the number of related CcPlaylist objects. + * + * @param Criteria $criteria + * @param boolean $distinct + * @param PropelPDO $con + * @return int Count of related CcPlaylist objects. + * @throws PropelException + */ + public function countCcPlaylists(Criteria $criteria = null, $distinct = false, PropelPDO $con = null) + { + if(null === $this->collCcPlaylists || null !== $criteria) { + if ($this->isNew() && null === $this->collCcPlaylists) { + return 0; + } else { + $query = CcPlaylistQuery::create(null, $criteria); + if($distinct) { + $query->distinct(); + } + return $query + ->filterByCcSubjs($this) + ->count($con); + } + } else { + return count($this->collCcPlaylists); + } + } + + /** + * Method called to associate a CcPlaylist object to this object + * through the CcPlaylist foreign key attribute. + * + * @param CcPlaylist $l CcPlaylist + * @return void + * @throws PropelException + */ + public function addCcPlaylist(CcPlaylist $l) + { + if ($this->collCcPlaylists === null) { + $this->initCcPlaylists(); + } + if (!$this->collCcPlaylists->contains($l)) { // only add it if the **same** object is not already associated + $this->collCcPlaylists[]= $l; + $l->setCcSubjs($this); + } + } + + /** + * Clears out the collCcPrefs collection + * + * This does not modify the database; however, it will remove any associated objects, causing + * them to be refetched by subsequent calls to accessor method. + * + * @return void + * @see addCcPrefs() + */ + public function clearCcPrefs() + { + $this->collCcPrefs = null; // important to set this to NULL since that means it is uninitialized + } + + /** + * Initializes the collCcPrefs collection. + * + * By default this just sets the collCcPrefs collection to an empty array (like clearcollCcPrefs()); + * however, you may wish to override this method in your stub class to provide setting appropriate + * to your application -- for example, setting the initial array to the values stored in database. + * + * @return void + */ + public function initCcPrefs() + { + $this->collCcPrefs = new PropelObjectCollection(); + $this->collCcPrefs->setModel('CcPref'); + } + + /** + * Gets an array of CcPref objects which contain a foreign key that references this object. + * + * If the $criteria is not null, it is used to always fetch the results from the database. + * Otherwise the results are fetched from the database the first time, then cached. + * Next time the same method is called without $criteria, the cached collection is returned. + * If this CcSubjs is new, it will return + * an empty collection or the current collection; the criteria is ignored on a new object. + * + * @param Criteria $criteria optional Criteria object to narrow the query + * @param PropelPDO $con optional connection object + * @return PropelCollection|array CcPref[] List of CcPref objects + * @throws PropelException + */ + public function getCcPrefs($criteria = null, PropelPDO $con = null) + { + if(null === $this->collCcPrefs || null !== $criteria) { + if ($this->isNew() && null === $this->collCcPrefs) { + // return empty collection + $this->initCcPrefs(); + } else { + $collCcPrefs = CcPrefQuery::create(null, $criteria) + ->filterByCcSubjs($this) + ->find($con); + if (null !== $criteria) { + return $collCcPrefs; + } + $this->collCcPrefs = $collCcPrefs; + } + } + return $this->collCcPrefs; + } + + /** + * Returns the number of related CcPref objects. + * + * @param Criteria $criteria + * @param boolean $distinct + * @param PropelPDO $con + * @return int Count of related CcPref objects. + * @throws PropelException + */ + public function countCcPrefs(Criteria $criteria = null, $distinct = false, PropelPDO $con = null) + { + if(null === $this->collCcPrefs || null !== $criteria) { + if ($this->isNew() && null === $this->collCcPrefs) { + return 0; + } else { + $query = CcPrefQuery::create(null, $criteria); + if($distinct) { + $query->distinct(); + } + return $query + ->filterByCcSubjs($this) + ->count($con); + } + } else { + return count($this->collCcPrefs); + } + } + + /** + * Method called to associate a CcPref object to this object + * through the CcPref foreign key attribute. + * + * @param CcPref $l CcPref + * @return void + * @throws PropelException + */ + public function addCcPref(CcPref $l) + { + if ($this->collCcPrefs === null) { + $this->initCcPrefs(); + } + if (!$this->collCcPrefs->contains($l)) { // only add it if the **same** object is not already associated + $this->collCcPrefs[]= $l; + $l->setCcSubjs($this); + } + } + + /** + * Clears out the collCcSesss collection + * + * This does not modify the database; however, it will remove any associated objects, causing + * them to be refetched by subsequent calls to accessor method. + * + * @return void + * @see addCcSesss() + */ + public function clearCcSesss() + { + $this->collCcSesss = null; // important to set this to NULL since that means it is uninitialized + } + + /** + * Initializes the collCcSesss collection. + * + * By default this just sets the collCcSesss collection to an empty array (like clearcollCcSesss()); + * however, you may wish to override this method in your stub class to provide setting appropriate + * to your application -- for example, setting the initial array to the values stored in database. + * + * @return void + */ + public function initCcSesss() + { + $this->collCcSesss = new PropelObjectCollection(); + $this->collCcSesss->setModel('CcSess'); + } + + /** + * Gets an array of CcSess objects which contain a foreign key that references this object. + * + * If the $criteria is not null, it is used to always fetch the results from the database. + * Otherwise the results are fetched from the database the first time, then cached. + * Next time the same method is called without $criteria, the cached collection is returned. + * If this CcSubjs is new, it will return + * an empty collection or the current collection; the criteria is ignored on a new object. + * + * @param Criteria $criteria optional Criteria object to narrow the query + * @param PropelPDO $con optional connection object + * @return PropelCollection|array CcSess[] List of CcSess objects + * @throws PropelException + */ + public function getCcSesss($criteria = null, PropelPDO $con = null) + { + if(null === $this->collCcSesss || null !== $criteria) { + if ($this->isNew() && null === $this->collCcSesss) { + // return empty collection + $this->initCcSesss(); + } else { + $collCcSesss = CcSessQuery::create(null, $criteria) + ->filterByCcSubjs($this) + ->find($con); + if (null !== $criteria) { + return $collCcSesss; + } + $this->collCcSesss = $collCcSesss; + } + } + return $this->collCcSesss; + } + + /** + * Returns the number of related CcSess objects. + * + * @param Criteria $criteria + * @param boolean $distinct + * @param PropelPDO $con + * @return int Count of related CcSess objects. + * @throws PropelException + */ + public function countCcSesss(Criteria $criteria = null, $distinct = false, PropelPDO $con = null) + { + if(null === $this->collCcSesss || null !== $criteria) { + if ($this->isNew() && null === $this->collCcSesss) { + return 0; + } else { + $query = CcSessQuery::create(null, $criteria); + if($distinct) { + $query->distinct(); + } + return $query + ->filterByCcSubjs($this) + ->count($con); + } + } else { + return count($this->collCcSesss); + } + } + + /** + * Method called to associate a CcSess object to this object + * through the CcSess foreign key attribute. + * + * @param CcSess $l CcSess + * @return void + * @throws PropelException + */ + public function addCcSess(CcSess $l) + { + if ($this->collCcSesss === null) { + $this->initCcSesss(); + } + if (!$this->collCcSesss->contains($l)) { // only add it if the **same** object is not already associated + $this->collCcSesss[]= $l; + $l->setCcSubjs($this); + } + } + + /** + * Clears the current object and sets all attributes to their default values + */ + public function clear() + { + $this->id = null; + $this->login = null; + $this->pass = null; + $this->type = null; + $this->realname = null; + $this->lastlogin = null; + $this->lastfail = null; + $this->alreadyInSave = false; + $this->alreadyInValidation = false; + $this->clearAllReferences(); + $this->applyDefaultValues(); + $this->resetModified(); + $this->setNew(true); + $this->setDeleted(false); + } + + /** + * Resets all collections of referencing foreign keys. + * + * This method is a user-space workaround for PHP's inability to garbage collect objects + * with circular references. This is currently necessary when using Propel in certain + * daemon or large-volumne/high-memory operations. + * + * @param boolean $deep Whether to also clear the references on all associated objects. + */ + public function clearAllReferences($deep = false) + { + if ($deep) { + if ($this->collCcAccesss) { + foreach ((array) $this->collCcAccesss as $o) { + $o->clearAllReferences($deep); + } + } + if ($this->collCcFiless) { + foreach ((array) $this->collCcFiless as $o) { + $o->clearAllReferences($deep); + } + } + if ($this->collCcPermss) { + foreach ((array) $this->collCcPermss as $o) { + $o->clearAllReferences($deep); + } + } + if ($this->collCcPlaylists) { + foreach ((array) $this->collCcPlaylists as $o) { + $o->clearAllReferences($deep); + } + } + if ($this->collCcPrefs) { + foreach ((array) $this->collCcPrefs as $o) { + $o->clearAllReferences($deep); + } + } + if ($this->collCcSesss) { + foreach ((array) $this->collCcSesss as $o) { + $o->clearAllReferences($deep); + } + } + } // if ($deep) + + $this->collCcAccesss = null; + $this->collCcFiless = null; + $this->collCcPermss = null; + $this->collCcPlaylists = null; + $this->collCcPrefs = null; + $this->collCcSesss = null; + } + + /** + * Catches calls to virtual methods + */ + public function __call($name, $params) + { + if (preg_match('/get(\w+)/', $name, $matches)) { + $virtualColumn = $matches[1]; + if ($this->hasVirtualColumn($virtualColumn)) { + return $this->getVirtualColumn($virtualColumn); + } + // no lcfirst in php<5.3... + $virtualColumn[0] = strtolower($virtualColumn[0]); + if ($this->hasVirtualColumn($virtualColumn)) { + return $this->getVirtualColumn($virtualColumn); + } + } + throw new PropelException('Call to undefined method: ' . $name); + } + +} // BaseCcSubjs diff --git a/application/models/campcaster/om/BaseCcSubjsPeer.php b/application/models/campcaster/om/BaseCcSubjsPeer.php new file mode 100644 index 000000000..f4bc75f9f --- /dev/null +++ b/application/models/campcaster/om/BaseCcSubjsPeer.php @@ -0,0 +1,769 @@ + array ('Id', 'Login', 'Pass', 'Type', 'Realname', 'Lastlogin', 'Lastfail', ), + BasePeer::TYPE_STUDLYPHPNAME => array ('id', 'login', 'pass', 'type', 'realname', 'lastlogin', 'lastfail', ), + BasePeer::TYPE_COLNAME => array (self::ID, self::LOGIN, self::PASS, self::TYPE, self::REALNAME, self::LASTLOGIN, self::LASTFAIL, ), + BasePeer::TYPE_RAW_COLNAME => array ('ID', 'LOGIN', 'PASS', 'TYPE', 'REALNAME', 'LASTLOGIN', 'LASTFAIL', ), + BasePeer::TYPE_FIELDNAME => array ('id', 'login', 'pass', 'type', 'realname', 'lastlogin', 'lastfail', ), + BasePeer::TYPE_NUM => array (0, 1, 2, 3, 4, 5, 6, ) + ); + + /** + * holds an array of keys for quick access to the fieldnames array + * + * first dimension keys are the type constants + * e.g. self::$fieldNames[BasePeer::TYPE_PHPNAME]['Id'] = 0 + */ + private static $fieldKeys = array ( + BasePeer::TYPE_PHPNAME => array ('Id' => 0, 'Login' => 1, 'Pass' => 2, 'Type' => 3, 'Realname' => 4, 'Lastlogin' => 5, 'Lastfail' => 6, ), + BasePeer::TYPE_STUDLYPHPNAME => array ('id' => 0, 'login' => 1, 'pass' => 2, 'type' => 3, 'realname' => 4, 'lastlogin' => 5, 'lastfail' => 6, ), + BasePeer::TYPE_COLNAME => array (self::ID => 0, self::LOGIN => 1, self::PASS => 2, self::TYPE => 3, self::REALNAME => 4, self::LASTLOGIN => 5, self::LASTFAIL => 6, ), + BasePeer::TYPE_RAW_COLNAME => array ('ID' => 0, 'LOGIN' => 1, 'PASS' => 2, 'TYPE' => 3, 'REALNAME' => 4, 'LASTLOGIN' => 5, 'LASTFAIL' => 6, ), + BasePeer::TYPE_FIELDNAME => array ('id' => 0, 'login' => 1, 'pass' => 2, 'type' => 3, 'realname' => 4, 'lastlogin' => 5, 'lastfail' => 6, ), + BasePeer::TYPE_NUM => array (0, 1, 2, 3, 4, 5, 6, ) + ); + + /** + * Translates a fieldname to another type + * + * @param string $name field name + * @param string $fromType One of the class type constants BasePeer::TYPE_PHPNAME, BasePeer::TYPE_STUDLYPHPNAME + * BasePeer::TYPE_COLNAME, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_NUM + * @param string $toType One of the class type constants + * @return string translated name of the field. + * @throws PropelException - if the specified name could not be found in the fieldname mappings. + */ + static public function translateFieldName($name, $fromType, $toType) + { + $toNames = self::getFieldNames($toType); + $key = isset(self::$fieldKeys[$fromType][$name]) ? self::$fieldKeys[$fromType][$name] : null; + if ($key === null) { + throw new PropelException("'$name' could not be found in the field names of type '$fromType'. These are: " . print_r(self::$fieldKeys[$fromType], true)); + } + return $toNames[$key]; + } + + /** + * Returns an array of field names. + * + * @param string $type The type of fieldnames to return: + * One of the class type constants BasePeer::TYPE_PHPNAME, BasePeer::TYPE_STUDLYPHPNAME + * BasePeer::TYPE_COLNAME, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_NUM + * @return array A list of field names + */ + + static public function getFieldNames($type = BasePeer::TYPE_PHPNAME) + { + if (!array_key_exists($type, self::$fieldNames)) { + throw new PropelException('Method getFieldNames() expects the parameter $type to be one of the class constants BasePeer::TYPE_PHPNAME, BasePeer::TYPE_STUDLYPHPNAME, BasePeer::TYPE_COLNAME, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_NUM. ' . $type . ' was given.'); + } + return self::$fieldNames[$type]; + } + + /** + * Convenience method which changes table.column to alias.column. + * + * Using this method you can maintain SQL abstraction while using column aliases. + * + * $c->addAlias("alias1", TablePeer::TABLE_NAME); + * $c->addJoin(TablePeer::alias("alias1", TablePeer::PRIMARY_KEY_COLUMN), TablePeer::PRIMARY_KEY_COLUMN); + * + * @param string $alias The alias for the current table. + * @param string $column The column name for current table. (i.e. CcSubjsPeer::COLUMN_NAME). + * @return string + */ + public static function alias($alias, $column) + { + return str_replace(CcSubjsPeer::TABLE_NAME.'.', $alias.'.', $column); + } + + /** + * Add all the columns needed to create a new object. + * + * Note: any columns that were marked with lazyLoad="true" in the + * XML schema will not be added to the select list and only loaded + * on demand. + * + * @param Criteria $criteria object containing the columns to add. + * @param string $alias optional table alias + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function addSelectColumns(Criteria $criteria, $alias = null) + { + if (null === $alias) { + $criteria->addSelectColumn(CcSubjsPeer::ID); + $criteria->addSelectColumn(CcSubjsPeer::LOGIN); + $criteria->addSelectColumn(CcSubjsPeer::PASS); + $criteria->addSelectColumn(CcSubjsPeer::TYPE); + $criteria->addSelectColumn(CcSubjsPeer::REALNAME); + $criteria->addSelectColumn(CcSubjsPeer::LASTLOGIN); + $criteria->addSelectColumn(CcSubjsPeer::LASTFAIL); + } else { + $criteria->addSelectColumn($alias . '.ID'); + $criteria->addSelectColumn($alias . '.LOGIN'); + $criteria->addSelectColumn($alias . '.PASS'); + $criteria->addSelectColumn($alias . '.TYPE'); + $criteria->addSelectColumn($alias . '.REALNAME'); + $criteria->addSelectColumn($alias . '.LASTLOGIN'); + $criteria->addSelectColumn($alias . '.LASTFAIL'); + } + } + + /** + * Returns the number of rows matching criteria. + * + * @param Criteria $criteria + * @param boolean $distinct Whether to select only distinct columns; deprecated: use Criteria->setDistinct() instead. + * @param PropelPDO $con + * @return int Number of matching rows. + */ + public static function doCount(Criteria $criteria, $distinct = false, PropelPDO $con = null) + { + // we may modify criteria, so copy it first + $criteria = clone $criteria; + + // We need to set the primary table name, since in the case that there are no WHERE columns + // it will be impossible for the BasePeer::createSelectSql() method to determine which + // tables go into the FROM clause. + $criteria->setPrimaryTableName(CcSubjsPeer::TABLE_NAME); + + if ($distinct && !in_array(Criteria::DISTINCT, $criteria->getSelectModifiers())) { + $criteria->setDistinct(); + } + + if (!$criteria->hasSelectClause()) { + CcSubjsPeer::addSelectColumns($criteria); + } + + $criteria->clearOrderByColumns(); // ORDER BY won't ever affect the count + $criteria->setDbName(self::DATABASE_NAME); // Set the correct dbName + + if ($con === null) { + $con = Propel::getConnection(CcSubjsPeer::DATABASE_NAME, Propel::CONNECTION_READ); + } + // BasePeer returns a PDOStatement + $stmt = BasePeer::doCount($criteria, $con); + + if ($row = $stmt->fetch(PDO::FETCH_NUM)) { + $count = (int) $row[0]; + } else { + $count = 0; // no rows returned; we infer that means 0 matches. + } + $stmt->closeCursor(); + return $count; + } + /** + * Method to select one object from the DB. + * + * @param Criteria $criteria object used to create the SELECT statement. + * @param PropelPDO $con + * @return CcSubjs + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function doSelectOne(Criteria $criteria, PropelPDO $con = null) + { + $critcopy = clone $criteria; + $critcopy->setLimit(1); + $objects = CcSubjsPeer::doSelect($critcopy, $con); + if ($objects) { + return $objects[0]; + } + return null; + } + /** + * Method to do selects. + * + * @param Criteria $criteria The Criteria object used to build the SELECT statement. + * @param PropelPDO $con + * @return array Array of selected Objects + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function doSelect(Criteria $criteria, PropelPDO $con = null) + { + return CcSubjsPeer::populateObjects(CcSubjsPeer::doSelectStmt($criteria, $con)); + } + /** + * Prepares the Criteria object and uses the parent doSelect() method to execute a PDOStatement. + * + * Use this method directly if you want to work with an executed statement durirectly (for example + * to perform your own object hydration). + * + * @param Criteria $criteria The Criteria object used to build the SELECT statement. + * @param PropelPDO $con The connection to use + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + * @return PDOStatement The executed PDOStatement object. + * @see BasePeer::doSelect() + */ + public static function doSelectStmt(Criteria $criteria, PropelPDO $con = null) + { + if ($con === null) { + $con = Propel::getConnection(CcSubjsPeer::DATABASE_NAME, Propel::CONNECTION_READ); + } + + if (!$criteria->hasSelectClause()) { + $criteria = clone $criteria; + CcSubjsPeer::addSelectColumns($criteria); + } + + // Set the correct dbName + $criteria->setDbName(self::DATABASE_NAME); + + // BasePeer returns a PDOStatement + return BasePeer::doSelect($criteria, $con); + } + /** + * Adds an object to the instance pool. + * + * Propel keeps cached copies of objects in an instance pool when they are retrieved + * from the database. In some cases -- especially when you override doSelect*() + * methods in your stub classes -- you may need to explicitly add objects + * to the cache in order to ensure that the same objects are always returned by doSelect*() + * and retrieveByPK*() calls. + * + * @param CcSubjs $value A CcSubjs object. + * @param string $key (optional) key to use for instance map (for performance boost if key was already calculated externally). + */ + public static function addInstanceToPool(CcSubjs $obj, $key = null) + { + if (Propel::isInstancePoolingEnabled()) { + if ($key === null) { + $key = (string) $obj->getId(); + } // if key === null + self::$instances[$key] = $obj; + } + } + + /** + * Removes an object from the instance pool. + * + * Propel keeps cached copies of objects in an instance pool when they are retrieved + * from the database. In some cases -- especially when you override doDelete + * methods in your stub classes -- you may need to explicitly remove objects + * from the cache in order to prevent returning objects that no longer exist. + * + * @param mixed $value A CcSubjs object or a primary key value. + */ + public static function removeInstanceFromPool($value) + { + if (Propel::isInstancePoolingEnabled() && $value !== null) { + if (is_object($value) && $value instanceof CcSubjs) { + $key = (string) $value->getId(); + } elseif (is_scalar($value)) { + // assume we've been passed a primary key + $key = (string) $value; + } else { + $e = new PropelException("Invalid value passed to removeInstanceFromPool(). Expected primary key or CcSubjs object; got " . (is_object($value) ? get_class($value) . ' object.' : var_export($value,true))); + throw $e; + } + + unset(self::$instances[$key]); + } + } // removeInstanceFromPool() + + /** + * Retrieves a string version of the primary key from the DB resultset row that can be used to uniquely identify a row in this table. + * + * For tables with a single-column primary key, that simple pkey value will be returned. For tables with + * a multi-column primary key, a serialize()d version of the primary key will be returned. + * + * @param string $key The key (@see getPrimaryKeyHash()) for this instance. + * @return CcSubjs Found object or NULL if 1) no instance exists for specified key or 2) instance pooling has been disabled. + * @see getPrimaryKeyHash() + */ + public static function getInstanceFromPool($key) + { + if (Propel::isInstancePoolingEnabled()) { + if (isset(self::$instances[$key])) { + return self::$instances[$key]; + } + } + return null; // just to be explicit + } + + /** + * Clear the instance pool. + * + * @return void + */ + public static function clearInstancePool() + { + self::$instances = array(); + } + + /** + * Method to invalidate the instance pool of all tables related to cc_subjs + * by a foreign key with ON DELETE CASCADE + */ + public static function clearRelatedInstancePool() + { + // Invalidate objects in CcPermsPeer instance pool, + // since one or more of them may be deleted by ON DELETE CASCADE/SETNULL rule. + CcPermsPeer::clearInstancePool(); + // Invalidate objects in CcPrefPeer instance pool, + // since one or more of them may be deleted by ON DELETE CASCADE/SETNULL rule. + CcPrefPeer::clearInstancePool(); + // Invalidate objects in CcSessPeer instance pool, + // since one or more of them may be deleted by ON DELETE CASCADE/SETNULL rule. + CcSessPeer::clearInstancePool(); + } + + /** + * Retrieves a string version of the primary key from the DB resultset row that can be used to uniquely identify a row in this table. + * + * For tables with a single-column primary key, that simple pkey value will be returned. For tables with + * a multi-column primary key, a serialize()d version of the primary key will be returned. + * + * @param array $row PropelPDO resultset row. + * @param int $startcol The 0-based offset for reading from the resultset row. + * @return string A string version of PK or NULL if the components of primary key in result array are all null. + */ + public static function getPrimaryKeyHashFromRow($row, $startcol = 0) + { + // If the PK cannot be derived from the row, return NULL. + if ($row[$startcol] === null) { + return null; + } + return (string) $row[$startcol]; + } + + /** + * Retrieves the primary key from the DB resultset row + * For tables with a single-column primary key, that simple pkey value will be returned. For tables with + * a multi-column primary key, an array of the primary key columns will be returned. + * + * @param array $row PropelPDO resultset row. + * @param int $startcol The 0-based offset for reading from the resultset row. + * @return mixed The primary key of the row + */ + public static function getPrimaryKeyFromRow($row, $startcol = 0) + { + return (int) $row[$startcol]; + } + + /** + * The returned array will contain objects of the default type or + * objects that inherit from the default. + * + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function populateObjects(PDOStatement $stmt) + { + $results = array(); + + // set the class once to avoid overhead in the loop + $cls = CcSubjsPeer::getOMClass(false); + // populate the object(s) + while ($row = $stmt->fetch(PDO::FETCH_NUM)) { + $key = CcSubjsPeer::getPrimaryKeyHashFromRow($row, 0); + if (null !== ($obj = CcSubjsPeer::getInstanceFromPool($key))) { + // We no longer rehydrate the object, since this can cause data loss. + // See http://www.propelorm.org/ticket/509 + // $obj->hydrate($row, 0, true); // rehydrate + $results[] = $obj; + } else { + $obj = new $cls(); + $obj->hydrate($row); + $results[] = $obj; + CcSubjsPeer::addInstanceToPool($obj, $key); + } // if key exists + } + $stmt->closeCursor(); + return $results; + } + /** + * Populates an object of the default type or an object that inherit from the default. + * + * @param array $row PropelPDO resultset row. + * @param int $startcol The 0-based offset for reading from the resultset row. + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + * @return array (CcSubjs object, last column rank) + */ + public static function populateObject($row, $startcol = 0) + { + $key = CcSubjsPeer::getPrimaryKeyHashFromRow($row, $startcol); + if (null !== ($obj = CcSubjsPeer::getInstanceFromPool($key))) { + // We no longer rehydrate the object, since this can cause data loss. + // See http://www.propelorm.org/ticket/509 + // $obj->hydrate($row, $startcol, true); // rehydrate + $col = $startcol + CcSubjsPeer::NUM_COLUMNS; + } else { + $cls = CcSubjsPeer::OM_CLASS; + $obj = new $cls(); + $col = $obj->hydrate($row, $startcol); + CcSubjsPeer::addInstanceToPool($obj, $key); + } + return array($obj, $col); + } + /** + * Returns the TableMap related to this peer. + * This method is not needed for general use but a specific application could have a need. + * @return TableMap + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function getTableMap() + { + return Propel::getDatabaseMap(self::DATABASE_NAME)->getTable(self::TABLE_NAME); + } + + /** + * Add a TableMap instance to the database for this peer class. + */ + public static function buildTableMap() + { + $dbMap = Propel::getDatabaseMap(BaseCcSubjsPeer::DATABASE_NAME); + if (!$dbMap->hasTable(BaseCcSubjsPeer::TABLE_NAME)) + { + $dbMap->addTableObject(new CcSubjsTableMap()); + } + } + + /** + * The class that the Peer will make instances of. + * + * If $withPrefix is true, the returned path + * uses a dot-path notation which is tranalted into a path + * relative to a location on the PHP include_path. + * (e.g. path.to.MyClass -> 'path/to/MyClass.php') + * + * @param boolean $withPrefix Whether or not to return the path with the class name + * @return string path.to.ClassName + */ + public static function getOMClass($withPrefix = true) + { + return $withPrefix ? CcSubjsPeer::CLASS_DEFAULT : CcSubjsPeer::OM_CLASS; + } + + /** + * Method perform an INSERT on the database, given a CcSubjs or Criteria object. + * + * @param mixed $values Criteria or CcSubjs object containing data that is used to create the INSERT statement. + * @param PropelPDO $con the PropelPDO connection to use + * @return mixed The new primary key. + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function doInsert($values, PropelPDO $con = null) + { + if ($con === null) { + $con = Propel::getConnection(CcSubjsPeer::DATABASE_NAME, Propel::CONNECTION_WRITE); + } + + if ($values instanceof Criteria) { + $criteria = clone $values; // rename for clarity + } else { + $criteria = $values->buildCriteria(); // build Criteria from CcSubjs object + } + + + // Set the correct dbName + $criteria->setDbName(self::DATABASE_NAME); + + try { + // use transaction because $criteria could contain info + // for more than one table (I guess, conceivably) + $con->beginTransaction(); + $pk = BasePeer::doInsert($criteria, $con); + $con->commit(); + } catch(PropelException $e) { + $con->rollBack(); + throw $e; + } + + return $pk; + } + + /** + * Method perform an UPDATE on the database, given a CcSubjs or Criteria object. + * + * @param mixed $values Criteria or CcSubjs object containing data that is used to create the UPDATE statement. + * @param PropelPDO $con The connection to use (specify PropelPDO connection object to exert more control over transactions). + * @return int The number of affected rows (if supported by underlying database driver). + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function doUpdate($values, PropelPDO $con = null) + { + if ($con === null) { + $con = Propel::getConnection(CcSubjsPeer::DATABASE_NAME, Propel::CONNECTION_WRITE); + } + + $selectCriteria = new Criteria(self::DATABASE_NAME); + + if ($values instanceof Criteria) { + $criteria = clone $values; // rename for clarity + + $comparison = $criteria->getComparison(CcSubjsPeer::ID); + $value = $criteria->remove(CcSubjsPeer::ID); + if ($value) { + $selectCriteria->add(CcSubjsPeer::ID, $value, $comparison); + } else { + $selectCriteria->setPrimaryTableName(CcSubjsPeer::TABLE_NAME); + } + + } else { // $values is CcSubjs object + $criteria = $values->buildCriteria(); // gets full criteria + $selectCriteria = $values->buildPkeyCriteria(); // gets criteria w/ primary key(s) + } + + // set the correct dbName + $criteria->setDbName(self::DATABASE_NAME); + + return BasePeer::doUpdate($selectCriteria, $criteria, $con); + } + + /** + * Method to DELETE all rows from the cc_subjs table. + * + * @return int The number of affected rows (if supported by underlying database driver). + */ + public static function doDeleteAll($con = null) + { + if ($con === null) { + $con = Propel::getConnection(CcSubjsPeer::DATABASE_NAME, Propel::CONNECTION_WRITE); + } + $affectedRows = 0; // initialize var to track total num of affected rows + try { + // use transaction because $criteria could contain info + // for more than one table or we could emulating ON DELETE CASCADE, etc. + $con->beginTransaction(); + $affectedRows += BasePeer::doDeleteAll(CcSubjsPeer::TABLE_NAME, $con, CcSubjsPeer::DATABASE_NAME); + // Because this db requires some delete cascade/set null emulation, we have to + // clear the cached instance *after* the emulation has happened (since + // instances get re-added by the select statement contained therein). + CcSubjsPeer::clearInstancePool(); + CcSubjsPeer::clearRelatedInstancePool(); + $con->commit(); + return $affectedRows; + } catch (PropelException $e) { + $con->rollBack(); + throw $e; + } + } + + /** + * Method perform a DELETE on the database, given a CcSubjs or Criteria object OR a primary key value. + * + * @param mixed $values Criteria or CcSubjs object or primary key or array of primary keys + * which is used to create the DELETE statement + * @param PropelPDO $con the connection to use + * @return int The number of affected rows (if supported by underlying database driver). This includes CASCADE-related rows + * if supported by native driver or if emulated using Propel. + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function doDelete($values, PropelPDO $con = null) + { + if ($con === null) { + $con = Propel::getConnection(CcSubjsPeer::DATABASE_NAME, Propel::CONNECTION_WRITE); + } + + if ($values instanceof Criteria) { + // invalidate the cache for all objects of this type, since we have no + // way of knowing (without running a query) what objects should be invalidated + // from the cache based on this Criteria. + CcSubjsPeer::clearInstancePool(); + // rename for clarity + $criteria = clone $values; + } elseif ($values instanceof CcSubjs) { // it's a model object + // invalidate the cache for this single object + CcSubjsPeer::removeInstanceFromPool($values); + // create criteria based on pk values + $criteria = $values->buildPkeyCriteria(); + } else { // it's a primary key, or an array of pks + $criteria = new Criteria(self::DATABASE_NAME); + $criteria->add(CcSubjsPeer::ID, (array) $values, Criteria::IN); + // invalidate the cache for this object(s) + foreach ((array) $values as $singleval) { + CcSubjsPeer::removeInstanceFromPool($singleval); + } + } + + // Set the correct dbName + $criteria->setDbName(self::DATABASE_NAME); + + $affectedRows = 0; // initialize var to track total num of affected rows + + try { + // use transaction because $criteria could contain info + // for more than one table or we could emulating ON DELETE CASCADE, etc. + $con->beginTransaction(); + + $affectedRows += BasePeer::doDelete($criteria, $con); + CcSubjsPeer::clearRelatedInstancePool(); + $con->commit(); + return $affectedRows; + } catch (PropelException $e) { + $con->rollBack(); + throw $e; + } + } + + /** + * Validates all modified columns of given CcSubjs object. + * If parameter $columns is either a single column name or an array of column names + * than only those columns are validated. + * + * NOTICE: This does not apply to primary or foreign keys for now. + * + * @param CcSubjs $obj The object to validate. + * @param mixed $cols Column name or array of column names. + * + * @return mixed TRUE if all columns are valid or the error message of the first invalid column. + */ + public static function doValidate(CcSubjs $obj, $cols = null) + { + $columns = array(); + + if ($cols) { + $dbMap = Propel::getDatabaseMap(CcSubjsPeer::DATABASE_NAME); + $tableMap = $dbMap->getTable(CcSubjsPeer::TABLE_NAME); + + if (! is_array($cols)) { + $cols = array($cols); + } + + foreach ($cols as $colName) { + if ($tableMap->containsColumn($colName)) { + $get = 'get' . $tableMap->getColumn($colName)->getPhpName(); + $columns[$colName] = $obj->$get(); + } + } + } else { + + } + + return BasePeer::doValidate(CcSubjsPeer::DATABASE_NAME, CcSubjsPeer::TABLE_NAME, $columns); + } + + /** + * Retrieve a single object by pkey. + * + * @param int $pk the primary key. + * @param PropelPDO $con the connection to use + * @return CcSubjs + */ + public static function retrieveByPK($pk, PropelPDO $con = null) + { + + if (null !== ($obj = CcSubjsPeer::getInstanceFromPool((string) $pk))) { + return $obj; + } + + if ($con === null) { + $con = Propel::getConnection(CcSubjsPeer::DATABASE_NAME, Propel::CONNECTION_READ); + } + + $criteria = new Criteria(CcSubjsPeer::DATABASE_NAME); + $criteria->add(CcSubjsPeer::ID, $pk); + + $v = CcSubjsPeer::doSelect($criteria, $con); + + return !empty($v) > 0 ? $v[0] : null; + } + + /** + * Retrieve multiple objects by pkey. + * + * @param array $pks List of primary keys + * @param PropelPDO $con the connection to use + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function retrieveByPKs($pks, PropelPDO $con = null) + { + if ($con === null) { + $con = Propel::getConnection(CcSubjsPeer::DATABASE_NAME, Propel::CONNECTION_READ); + } + + $objs = null; + if (empty($pks)) { + $objs = array(); + } else { + $criteria = new Criteria(CcSubjsPeer::DATABASE_NAME); + $criteria->add(CcSubjsPeer::ID, $pks, Criteria::IN); + $objs = CcSubjsPeer::doSelect($criteria, $con); + } + return $objs; + } + +} // BaseCcSubjsPeer + +// This is the static code needed to register the TableMap for this table with the main Propel class. +// +BaseCcSubjsPeer::buildTableMap(); + diff --git a/application/models/campcaster/om/BaseCcSubjsQuery.php b/application/models/campcaster/om/BaseCcSubjsQuery.php new file mode 100644 index 000000000..af3b6fe5e --- /dev/null +++ b/application/models/campcaster/om/BaseCcSubjsQuery.php @@ -0,0 +1,747 @@ +setModelAlias($modelAlias); + } + if ($criteria instanceof Criteria) { + $query->mergeWith($criteria); + } + return $query; + } + + /** + * Find object by primary key + * Use instance pooling to avoid a database query if the object exists + * + * $obj = $c->findPk(12, $con); + * + * @param mixed $key Primary key to use for the query + * @param PropelPDO $con an optional connection object + * + * @return CcSubjs|array|mixed the result, formatted by the current formatter + */ + public function findPk($key, $con = null) + { + if ((null !== ($obj = CcSubjsPeer::getInstanceFromPool((string) $key))) && $this->getFormatter()->isObjectFormatter()) { + // the object is alredy in the instance pool + return $obj; + } else { + // the object has not been requested yet, or the formatter is not an object formatter + $criteria = $this->isKeepQuery() ? clone $this : $this; + $stmt = $criteria + ->filterByPrimaryKey($key) + ->getSelectStatement($con); + return $criteria->getFormatter()->init($criteria)->formatOne($stmt); + } + } + + /** + * Find objects by primary key + * + * $objs = $c->findPks(array(12, 56, 832), $con); + * + * @param array $keys Primary keys to use for the query + * @param PropelPDO $con an optional connection object + * + * @return PropelObjectCollection|array|mixed the list of results, formatted by the current formatter + */ + public function findPks($keys, $con = null) + { + $criteria = $this->isKeepQuery() ? clone $this : $this; + return $this + ->filterByPrimaryKeys($keys) + ->find($con); + } + + /** + * Filter the query by primary key + * + * @param mixed $key Primary key to use for the query + * + * @return CcSubjsQuery The current query, for fluid interface + */ + public function filterByPrimaryKey($key) + { + return $this->addUsingAlias(CcSubjsPeer::ID, $key, Criteria::EQUAL); + } + + /** + * Filter the query by a list of primary keys + * + * @param array $keys The list of primary key to use for the query + * + * @return CcSubjsQuery The current query, for fluid interface + */ + public function filterByPrimaryKeys($keys) + { + return $this->addUsingAlias(CcSubjsPeer::ID, $keys, Criteria::IN); + } + + /** + * Filter the query on the id column + * + * @param int|array $id The value to use as filter. + * Accepts an associative array('min' => $minValue, 'max' => $maxValue) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcSubjsQuery The current query, for fluid interface + */ + public function filterById($id = null, $comparison = null) + { + if (is_array($id) && null === $comparison) { + $comparison = Criteria::IN; + } + return $this->addUsingAlias(CcSubjsPeer::ID, $id, $comparison); + } + + /** + * Filter the query on the login column + * + * @param string $login The value to use as filter. + * Accepts wildcards (* and % trigger a LIKE) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcSubjsQuery The current query, for fluid interface + */ + public function filterByLogin($login = null, $comparison = null) + { + if (null === $comparison) { + if (is_array($login)) { + $comparison = Criteria::IN; + } elseif (preg_match('/[\%\*]/', $login)) { + $login = str_replace('*', '%', $login); + $comparison = Criteria::LIKE; + } + } + return $this->addUsingAlias(CcSubjsPeer::LOGIN, $login, $comparison); + } + + /** + * Filter the query on the pass column + * + * @param string $pass The value to use as filter. + * Accepts wildcards (* and % trigger a LIKE) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcSubjsQuery The current query, for fluid interface + */ + public function filterByPass($pass = null, $comparison = null) + { + if (null === $comparison) { + if (is_array($pass)) { + $comparison = Criteria::IN; + } elseif (preg_match('/[\%\*]/', $pass)) { + $pass = str_replace('*', '%', $pass); + $comparison = Criteria::LIKE; + } + } + return $this->addUsingAlias(CcSubjsPeer::PASS, $pass, $comparison); + } + + /** + * Filter the query on the type column + * + * @param string $type The value to use as filter. + * Accepts wildcards (* and % trigger a LIKE) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcSubjsQuery The current query, for fluid interface + */ + public function filterByType($type = null, $comparison = null) + { + if (null === $comparison) { + if (is_array($type)) { + $comparison = Criteria::IN; + } elseif (preg_match('/[\%\*]/', $type)) { + $type = str_replace('*', '%', $type); + $comparison = Criteria::LIKE; + } + } + return $this->addUsingAlias(CcSubjsPeer::TYPE, $type, $comparison); + } + + /** + * Filter the query on the realname column + * + * @param string $realname The value to use as filter. + * Accepts wildcards (* and % trigger a LIKE) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcSubjsQuery The current query, for fluid interface + */ + public function filterByRealname($realname = null, $comparison = null) + { + if (null === $comparison) { + if (is_array($realname)) { + $comparison = Criteria::IN; + } elseif (preg_match('/[\%\*]/', $realname)) { + $realname = str_replace('*', '%', $realname); + $comparison = Criteria::LIKE; + } + } + return $this->addUsingAlias(CcSubjsPeer::REALNAME, $realname, $comparison); + } + + /** + * Filter the query on the lastlogin column + * + * @param string|array $lastlogin The value to use as filter. + * Accepts an associative array('min' => $minValue, 'max' => $maxValue) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcSubjsQuery The current query, for fluid interface + */ + public function filterByLastlogin($lastlogin = null, $comparison = null) + { + if (is_array($lastlogin)) { + $useMinMax = false; + if (isset($lastlogin['min'])) { + $this->addUsingAlias(CcSubjsPeer::LASTLOGIN, $lastlogin['min'], Criteria::GREATER_EQUAL); + $useMinMax = true; + } + if (isset($lastlogin['max'])) { + $this->addUsingAlias(CcSubjsPeer::LASTLOGIN, $lastlogin['max'], Criteria::LESS_EQUAL); + $useMinMax = true; + } + if ($useMinMax) { + return $this; + } + if (null === $comparison) { + $comparison = Criteria::IN; + } + } + return $this->addUsingAlias(CcSubjsPeer::LASTLOGIN, $lastlogin, $comparison); + } + + /** + * Filter the query on the lastfail column + * + * @param string|array $lastfail The value to use as filter. + * Accepts an associative array('min' => $minValue, 'max' => $maxValue) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcSubjsQuery The current query, for fluid interface + */ + public function filterByLastfail($lastfail = null, $comparison = null) + { + if (is_array($lastfail)) { + $useMinMax = false; + if (isset($lastfail['min'])) { + $this->addUsingAlias(CcSubjsPeer::LASTFAIL, $lastfail['min'], Criteria::GREATER_EQUAL); + $useMinMax = true; + } + if (isset($lastfail['max'])) { + $this->addUsingAlias(CcSubjsPeer::LASTFAIL, $lastfail['max'], Criteria::LESS_EQUAL); + $useMinMax = true; + } + if ($useMinMax) { + return $this; + } + if (null === $comparison) { + $comparison = Criteria::IN; + } + } + return $this->addUsingAlias(CcSubjsPeer::LASTFAIL, $lastfail, $comparison); + } + + /** + * Filter the query by a related CcAccess object + * + * @param CcAccess $ccAccess the related object to use as filter + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcSubjsQuery The current query, for fluid interface + */ + public function filterByCcAccess($ccAccess, $comparison = null) + { + return $this + ->addUsingAlias(CcSubjsPeer::ID, $ccAccess->getOwner(), $comparison); + } + + /** + * Adds a JOIN clause to the query using the CcAccess relation + * + * @param string $relationAlias optional alias for the relation + * @param string $joinType Accepted values are null, 'left join', 'right join', 'inner join' + * + * @return CcSubjsQuery The current query, for fluid interface + */ + public function joinCcAccess($relationAlias = '', $joinType = Criteria::LEFT_JOIN) + { + $tableMap = $this->getTableMap(); + $relationMap = $tableMap->getRelation('CcAccess'); + + // create a ModelJoin object for this join + $join = new ModelJoin(); + $join->setJoinType($joinType); + $join->setRelationMap($relationMap, $this->useAliasInSQL ? $this->getModelAlias() : null, $relationAlias); + if ($previousJoin = $this->getPreviousJoin()) { + $join->setPreviousJoin($previousJoin); + } + + // add the ModelJoin to the current object + if($relationAlias) { + $this->addAlias($relationAlias, $relationMap->getRightTable()->getName()); + $this->addJoinObject($join, $relationAlias); + } else { + $this->addJoinObject($join, 'CcAccess'); + } + + return $this; + } + + /** + * Use the CcAccess relation CcAccess object + * + * @see useQuery() + * + * @param string $relationAlias optional alias for the relation, + * to be used as main alias in the secondary query + * @param string $joinType Accepted values are null, 'left join', 'right join', 'inner join' + * + * @return CcAccessQuery A secondary query class using the current class as primary query + */ + public function useCcAccessQuery($relationAlias = '', $joinType = Criteria::LEFT_JOIN) + { + return $this + ->joinCcAccess($relationAlias, $joinType) + ->useQuery($relationAlias ? $relationAlias : 'CcAccess', 'CcAccessQuery'); + } + + /** + * Filter the query by a related CcFiles object + * + * @param CcFiles $ccFiles the related object to use as filter + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcSubjsQuery The current query, for fluid interface + */ + public function filterByCcFiles($ccFiles, $comparison = null) + { + return $this + ->addUsingAlias(CcSubjsPeer::ID, $ccFiles->getEditedby(), $comparison); + } + + /** + * Adds a JOIN clause to the query using the CcFiles relation + * + * @param string $relationAlias optional alias for the relation + * @param string $joinType Accepted values are null, 'left join', 'right join', 'inner join' + * + * @return CcSubjsQuery The current query, for fluid interface + */ + public function joinCcFiles($relationAlias = '', $joinType = Criteria::LEFT_JOIN) + { + $tableMap = $this->getTableMap(); + $relationMap = $tableMap->getRelation('CcFiles'); + + // create a ModelJoin object for this join + $join = new ModelJoin(); + $join->setJoinType($joinType); + $join->setRelationMap($relationMap, $this->useAliasInSQL ? $this->getModelAlias() : null, $relationAlias); + if ($previousJoin = $this->getPreviousJoin()) { + $join->setPreviousJoin($previousJoin); + } + + // add the ModelJoin to the current object + if($relationAlias) { + $this->addAlias($relationAlias, $relationMap->getRightTable()->getName()); + $this->addJoinObject($join, $relationAlias); + } else { + $this->addJoinObject($join, 'CcFiles'); + } + + return $this; + } + + /** + * Use the CcFiles relation CcFiles object + * + * @see useQuery() + * + * @param string $relationAlias optional alias for the relation, + * to be used as main alias in the secondary query + * @param string $joinType Accepted values are null, 'left join', 'right join', 'inner join' + * + * @return CcFilesQuery A secondary query class using the current class as primary query + */ + public function useCcFilesQuery($relationAlias = '', $joinType = Criteria::LEFT_JOIN) + { + return $this + ->joinCcFiles($relationAlias, $joinType) + ->useQuery($relationAlias ? $relationAlias : 'CcFiles', 'CcFilesQuery'); + } + + /** + * Filter the query by a related CcPerms object + * + * @param CcPerms $ccPerms the related object to use as filter + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcSubjsQuery The current query, for fluid interface + */ + public function filterByCcPerms($ccPerms, $comparison = null) + { + return $this + ->addUsingAlias(CcSubjsPeer::ID, $ccPerms->getSubj(), $comparison); + } + + /** + * Adds a JOIN clause to the query using the CcPerms relation + * + * @param string $relationAlias optional alias for the relation + * @param string $joinType Accepted values are null, 'left join', 'right join', 'inner join' + * + * @return CcSubjsQuery The current query, for fluid interface + */ + public function joinCcPerms($relationAlias = '', $joinType = Criteria::LEFT_JOIN) + { + $tableMap = $this->getTableMap(); + $relationMap = $tableMap->getRelation('CcPerms'); + + // create a ModelJoin object for this join + $join = new ModelJoin(); + $join->setJoinType($joinType); + $join->setRelationMap($relationMap, $this->useAliasInSQL ? $this->getModelAlias() : null, $relationAlias); + if ($previousJoin = $this->getPreviousJoin()) { + $join->setPreviousJoin($previousJoin); + } + + // add the ModelJoin to the current object + if($relationAlias) { + $this->addAlias($relationAlias, $relationMap->getRightTable()->getName()); + $this->addJoinObject($join, $relationAlias); + } else { + $this->addJoinObject($join, 'CcPerms'); + } + + return $this; + } + + /** + * Use the CcPerms relation CcPerms object + * + * @see useQuery() + * + * @param string $relationAlias optional alias for the relation, + * to be used as main alias in the secondary query + * @param string $joinType Accepted values are null, 'left join', 'right join', 'inner join' + * + * @return CcPermsQuery A secondary query class using the current class as primary query + */ + public function useCcPermsQuery($relationAlias = '', $joinType = Criteria::LEFT_JOIN) + { + return $this + ->joinCcPerms($relationAlias, $joinType) + ->useQuery($relationAlias ? $relationAlias : 'CcPerms', 'CcPermsQuery'); + } + + /** + * Filter the query by a related CcPlaylist object + * + * @param CcPlaylist $ccPlaylist the related object to use as filter + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcSubjsQuery The current query, for fluid interface + */ + public function filterByCcPlaylist($ccPlaylist, $comparison = null) + { + return $this + ->addUsingAlias(CcSubjsPeer::ID, $ccPlaylist->getDbEditedby(), $comparison); + } + + /** + * Adds a JOIN clause to the query using the CcPlaylist relation + * + * @param string $relationAlias optional alias for the relation + * @param string $joinType Accepted values are null, 'left join', 'right join', 'inner join' + * + * @return CcSubjsQuery The current query, for fluid interface + */ + public function joinCcPlaylist($relationAlias = '', $joinType = Criteria::LEFT_JOIN) + { + $tableMap = $this->getTableMap(); + $relationMap = $tableMap->getRelation('CcPlaylist'); + + // create a ModelJoin object for this join + $join = new ModelJoin(); + $join->setJoinType($joinType); + $join->setRelationMap($relationMap, $this->useAliasInSQL ? $this->getModelAlias() : null, $relationAlias); + if ($previousJoin = $this->getPreviousJoin()) { + $join->setPreviousJoin($previousJoin); + } + + // add the ModelJoin to the current object + if($relationAlias) { + $this->addAlias($relationAlias, $relationMap->getRightTable()->getName()); + $this->addJoinObject($join, $relationAlias); + } else { + $this->addJoinObject($join, 'CcPlaylist'); + } + + return $this; + } + + /** + * Use the CcPlaylist relation CcPlaylist object + * + * @see useQuery() + * + * @param string $relationAlias optional alias for the relation, + * to be used as main alias in the secondary query + * @param string $joinType Accepted values are null, 'left join', 'right join', 'inner join' + * + * @return CcPlaylistQuery A secondary query class using the current class as primary query + */ + public function useCcPlaylistQuery($relationAlias = '', $joinType = Criteria::LEFT_JOIN) + { + return $this + ->joinCcPlaylist($relationAlias, $joinType) + ->useQuery($relationAlias ? $relationAlias : 'CcPlaylist', 'CcPlaylistQuery'); + } + + /** + * Filter the query by a related CcPref object + * + * @param CcPref $ccPref the related object to use as filter + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcSubjsQuery The current query, for fluid interface + */ + public function filterByCcPref($ccPref, $comparison = null) + { + return $this + ->addUsingAlias(CcSubjsPeer::ID, $ccPref->getSubjid(), $comparison); + } + + /** + * Adds a JOIN clause to the query using the CcPref relation + * + * @param string $relationAlias optional alias for the relation + * @param string $joinType Accepted values are null, 'left join', 'right join', 'inner join' + * + * @return CcSubjsQuery The current query, for fluid interface + */ + public function joinCcPref($relationAlias = '', $joinType = Criteria::LEFT_JOIN) + { + $tableMap = $this->getTableMap(); + $relationMap = $tableMap->getRelation('CcPref'); + + // create a ModelJoin object for this join + $join = new ModelJoin(); + $join->setJoinType($joinType); + $join->setRelationMap($relationMap, $this->useAliasInSQL ? $this->getModelAlias() : null, $relationAlias); + if ($previousJoin = $this->getPreviousJoin()) { + $join->setPreviousJoin($previousJoin); + } + + // add the ModelJoin to the current object + if($relationAlias) { + $this->addAlias($relationAlias, $relationMap->getRightTable()->getName()); + $this->addJoinObject($join, $relationAlias); + } else { + $this->addJoinObject($join, 'CcPref'); + } + + return $this; + } + + /** + * Use the CcPref relation CcPref object + * + * @see useQuery() + * + * @param string $relationAlias optional alias for the relation, + * to be used as main alias in the secondary query + * @param string $joinType Accepted values are null, 'left join', 'right join', 'inner join' + * + * @return CcPrefQuery A secondary query class using the current class as primary query + */ + public function useCcPrefQuery($relationAlias = '', $joinType = Criteria::LEFT_JOIN) + { + return $this + ->joinCcPref($relationAlias, $joinType) + ->useQuery($relationAlias ? $relationAlias : 'CcPref', 'CcPrefQuery'); + } + + /** + * Filter the query by a related CcSess object + * + * @param CcSess $ccSess the related object to use as filter + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcSubjsQuery The current query, for fluid interface + */ + public function filterByCcSess($ccSess, $comparison = null) + { + return $this + ->addUsingAlias(CcSubjsPeer::ID, $ccSess->getUserid(), $comparison); + } + + /** + * Adds a JOIN clause to the query using the CcSess relation + * + * @param string $relationAlias optional alias for the relation + * @param string $joinType Accepted values are null, 'left join', 'right join', 'inner join' + * + * @return CcSubjsQuery The current query, for fluid interface + */ + public function joinCcSess($relationAlias = '', $joinType = Criteria::LEFT_JOIN) + { + $tableMap = $this->getTableMap(); + $relationMap = $tableMap->getRelation('CcSess'); + + // create a ModelJoin object for this join + $join = new ModelJoin(); + $join->setJoinType($joinType); + $join->setRelationMap($relationMap, $this->useAliasInSQL ? $this->getModelAlias() : null, $relationAlias); + if ($previousJoin = $this->getPreviousJoin()) { + $join->setPreviousJoin($previousJoin); + } + + // add the ModelJoin to the current object + if($relationAlias) { + $this->addAlias($relationAlias, $relationMap->getRightTable()->getName()); + $this->addJoinObject($join, $relationAlias); + } else { + $this->addJoinObject($join, 'CcSess'); + } + + return $this; + } + + /** + * Use the CcSess relation CcSess object + * + * @see useQuery() + * + * @param string $relationAlias optional alias for the relation, + * to be used as main alias in the secondary query + * @param string $joinType Accepted values are null, 'left join', 'right join', 'inner join' + * + * @return CcSessQuery A secondary query class using the current class as primary query + */ + public function useCcSessQuery($relationAlias = '', $joinType = Criteria::LEFT_JOIN) + { + return $this + ->joinCcSess($relationAlias, $joinType) + ->useQuery($relationAlias ? $relationAlias : 'CcSess', 'CcSessQuery'); + } + + /** + * Exclude object from result + * + * @param CcSubjs $ccSubjs Object to remove from the list of results + * + * @return CcSubjsQuery The current query, for fluid interface + */ + public function prune($ccSubjs = null) + { + if ($ccSubjs) { + $this->addUsingAlias(CcSubjsPeer::ID, $ccSubjs->getId(), Criteria::NOT_EQUAL); + } + + return $this; + } + +} // BaseCcSubjsQuery diff --git a/application/models/campcaster/om/BaseCcTrans.php b/application/models/campcaster/om/BaseCcTrans.php new file mode 100644 index 000000000..e7ce7ef9f --- /dev/null +++ b/application/models/campcaster/om/BaseCcTrans.php @@ -0,0 +1,1903 @@ +lock = 'N'; + } + + /** + * Initializes internal state of BaseCcTrans object. + * @see applyDefaults() + */ + public function __construct() + { + parent::__construct(); + $this->applyDefaultValues(); + } + + /** + * Get the [id] column value. + * + * @return int + */ + public function getId() + { + return $this->id; + } + + /** + * Get the [trtok] column value. + * + * @return string + */ + public function getTrtok() + { + return $this->trtok; + } + + /** + * Get the [direction] column value. + * + * @return string + */ + public function getDirection() + { + return $this->direction; + } + + /** + * Get the [state] column value. + * + * @return string + */ + public function getState() + { + return $this->state; + } + + /** + * Get the [trtype] column value. + * + * @return string + */ + public function getTrtype() + { + return $this->trtype; + } + + /** + * Get the [lock] column value. + * + * @return string + */ + public function getLock() + { + return $this->lock; + } + + /** + * Get the [target] column value. + * + * @return string + */ + public function getTarget() + { + return $this->target; + } + + /** + * Get the [rtrtok] column value. + * + * @return string + */ + public function getRtrtok() + { + return $this->rtrtok; + } + + /** + * Get the [mdtrtok] column value. + * + * @return string + */ + public function getMdtrtok() + { + return $this->mdtrtok; + } + + /** + * Get the [gunid] column value. + * + * @return string + */ + public function getGunid() + { + return $this->gunid; + } + + /** + * Get the [pdtoken] column value. + * + * @return string + */ + public function getPdtoken() + { + return $this->pdtoken; + } + + /** + * Get the [url] column value. + * + * @return string + */ + public function getUrl() + { + return $this->url; + } + + /** + * Get the [localfile] column value. + * + * @return string + */ + public function getLocalfile() + { + return $this->localfile; + } + + /** + * Get the [fname] column value. + * + * @return string + */ + public function getFname() + { + return $this->fname; + } + + /** + * Get the [title] column value. + * + * @return string + */ + public function getTitle() + { + return $this->title; + } + + /** + * Get the [expectedsum] column value. + * + * @return string + */ + public function getExpectedsum() + { + return $this->expectedsum; + } + + /** + * Get the [realsum] column value. + * + * @return string + */ + public function getRealsum() + { + return $this->realsum; + } + + /** + * Get the [expectedsize] column value. + * + * @return int + */ + public function getExpectedsize() + { + return $this->expectedsize; + } + + /** + * Get the [realsize] column value. + * + * @return int + */ + public function getRealsize() + { + return $this->realsize; + } + + /** + * Get the [uid] column value. + * + * @return int + */ + public function getUid() + { + return $this->uid; + } + + /** + * Get the [errmsg] column value. + * + * @return string + */ + public function getErrmsg() + { + return $this->errmsg; + } + + /** + * Get the [jobpid] column value. + * + * @return int + */ + public function getJobpid() + { + return $this->jobpid; + } + + /** + * Get the [optionally formatted] temporal [start] column value. + * + * + * @param string $format The date/time format string (either date()-style or strftime()-style). + * If format is NULL, then the raw DateTime object will be returned. + * @return mixed Formatted date/time value as string or DateTime object (if format is NULL), NULL if column is NULL + * @throws PropelException - if unable to parse/validate the date/time value. + */ + public function getStart($format = 'Y-m-d H:i:s') + { + if ($this->start === null) { + return null; + } + + + + try { + $dt = new DateTime($this->start); + } catch (Exception $x) { + throw new PropelException("Internally stored date/time/timestamp value could not be converted to DateTime: " . var_export($this->start, true), $x); + } + + if ($format === null) { + // Because propel.useDateTimeClass is TRUE, we return a DateTime object. + return $dt; + } elseif (strpos($format, '%') !== false) { + return strftime($format, $dt->format('U')); + } else { + return $dt->format($format); + } + } + + /** + * Get the [optionally formatted] temporal [ts] column value. + * + * + * @param string $format The date/time format string (either date()-style or strftime()-style). + * If format is NULL, then the raw DateTime object will be returned. + * @return mixed Formatted date/time value as string or DateTime object (if format is NULL), NULL if column is NULL + * @throws PropelException - if unable to parse/validate the date/time value. + */ + public function getTs($format = 'Y-m-d H:i:s') + { + if ($this->ts === null) { + return null; + } + + + + try { + $dt = new DateTime($this->ts); + } catch (Exception $x) { + throw new PropelException("Internally stored date/time/timestamp value could not be converted to DateTime: " . var_export($this->ts, true), $x); + } + + if ($format === null) { + // Because propel.useDateTimeClass is TRUE, we return a DateTime object. + return $dt; + } elseif (strpos($format, '%') !== false) { + return strftime($format, $dt->format('U')); + } else { + return $dt->format($format); + } + } + + /** + * Set the value of [id] column. + * + * @param int $v new value + * @return CcTrans The current object (for fluent API support) + */ + public function setId($v) + { + if ($v !== null) { + $v = (int) $v; + } + + if ($this->id !== $v) { + $this->id = $v; + $this->modifiedColumns[] = CcTransPeer::ID; + } + + return $this; + } // setId() + + /** + * Set the value of [trtok] column. + * + * @param string $v new value + * @return CcTrans The current object (for fluent API support) + */ + public function setTrtok($v) + { + if ($v !== null) { + $v = (string) $v; + } + + if ($this->trtok !== $v) { + $this->trtok = $v; + $this->modifiedColumns[] = CcTransPeer::TRTOK; + } + + return $this; + } // setTrtok() + + /** + * Set the value of [direction] column. + * + * @param string $v new value + * @return CcTrans The current object (for fluent API support) + */ + public function setDirection($v) + { + if ($v !== null) { + $v = (string) $v; + } + + if ($this->direction !== $v) { + $this->direction = $v; + $this->modifiedColumns[] = CcTransPeer::DIRECTION; + } + + return $this; + } // setDirection() + + /** + * Set the value of [state] column. + * + * @param string $v new value + * @return CcTrans The current object (for fluent API support) + */ + public function setState($v) + { + if ($v !== null) { + $v = (string) $v; + } + + if ($this->state !== $v) { + $this->state = $v; + $this->modifiedColumns[] = CcTransPeer::STATE; + } + + return $this; + } // setState() + + /** + * Set the value of [trtype] column. + * + * @param string $v new value + * @return CcTrans The current object (for fluent API support) + */ + public function setTrtype($v) + { + if ($v !== null) { + $v = (string) $v; + } + + if ($this->trtype !== $v) { + $this->trtype = $v; + $this->modifiedColumns[] = CcTransPeer::TRTYPE; + } + + return $this; + } // setTrtype() + + /** + * Set the value of [lock] column. + * + * @param string $v new value + * @return CcTrans The current object (for fluent API support) + */ + public function setLock($v) + { + if ($v !== null) { + $v = (string) $v; + } + + if ($this->lock !== $v || $this->isNew()) { + $this->lock = $v; + $this->modifiedColumns[] = CcTransPeer::LOCK; + } + + return $this; + } // setLock() + + /** + * Set the value of [target] column. + * + * @param string $v new value + * @return CcTrans The current object (for fluent API support) + */ + public function setTarget($v) + { + if ($v !== null) { + $v = (string) $v; + } + + if ($this->target !== $v) { + $this->target = $v; + $this->modifiedColumns[] = CcTransPeer::TARGET; + } + + return $this; + } // setTarget() + + /** + * Set the value of [rtrtok] column. + * + * @param string $v new value + * @return CcTrans The current object (for fluent API support) + */ + public function setRtrtok($v) + { + if ($v !== null) { + $v = (string) $v; + } + + if ($this->rtrtok !== $v) { + $this->rtrtok = $v; + $this->modifiedColumns[] = CcTransPeer::RTRTOK; + } + + return $this; + } // setRtrtok() + + /** + * Set the value of [mdtrtok] column. + * + * @param string $v new value + * @return CcTrans The current object (for fluent API support) + */ + public function setMdtrtok($v) + { + if ($v !== null) { + $v = (string) $v; + } + + if ($this->mdtrtok !== $v) { + $this->mdtrtok = $v; + $this->modifiedColumns[] = CcTransPeer::MDTRTOK; + } + + return $this; + } // setMdtrtok() + + /** + * Set the value of [gunid] column. + * + * @param string $v new value + * @return CcTrans The current object (for fluent API support) + */ + public function setGunid($v) + { + if ($v !== null) { + $v = (string) $v; + } + + if ($this->gunid !== $v) { + $this->gunid = $v; + $this->modifiedColumns[] = CcTransPeer::GUNID; + } + + return $this; + } // setGunid() + + /** + * Set the value of [pdtoken] column. + * + * @param string $v new value + * @return CcTrans The current object (for fluent API support) + */ + public function setPdtoken($v) + { + if ($v !== null) { + $v = (string) $v; + } + + if ($this->pdtoken !== $v) { + $this->pdtoken = $v; + $this->modifiedColumns[] = CcTransPeer::PDTOKEN; + } + + return $this; + } // setPdtoken() + + /** + * Set the value of [url] column. + * + * @param string $v new value + * @return CcTrans The current object (for fluent API support) + */ + public function setUrl($v) + { + if ($v !== null) { + $v = (string) $v; + } + + if ($this->url !== $v) { + $this->url = $v; + $this->modifiedColumns[] = CcTransPeer::URL; + } + + return $this; + } // setUrl() + + /** + * Set the value of [localfile] column. + * + * @param string $v new value + * @return CcTrans The current object (for fluent API support) + */ + public function setLocalfile($v) + { + if ($v !== null) { + $v = (string) $v; + } + + if ($this->localfile !== $v) { + $this->localfile = $v; + $this->modifiedColumns[] = CcTransPeer::LOCALFILE; + } + + return $this; + } // setLocalfile() + + /** + * Set the value of [fname] column. + * + * @param string $v new value + * @return CcTrans The current object (for fluent API support) + */ + public function setFname($v) + { + if ($v !== null) { + $v = (string) $v; + } + + if ($this->fname !== $v) { + $this->fname = $v; + $this->modifiedColumns[] = CcTransPeer::FNAME; + } + + return $this; + } // setFname() + + /** + * Set the value of [title] column. + * + * @param string $v new value + * @return CcTrans The current object (for fluent API support) + */ + public function setTitle($v) + { + if ($v !== null) { + $v = (string) $v; + } + + if ($this->title !== $v) { + $this->title = $v; + $this->modifiedColumns[] = CcTransPeer::TITLE; + } + + return $this; + } // setTitle() + + /** + * Set the value of [expectedsum] column. + * + * @param string $v new value + * @return CcTrans The current object (for fluent API support) + */ + public function setExpectedsum($v) + { + if ($v !== null) { + $v = (string) $v; + } + + if ($this->expectedsum !== $v) { + $this->expectedsum = $v; + $this->modifiedColumns[] = CcTransPeer::EXPECTEDSUM; + } + + return $this; + } // setExpectedsum() + + /** + * Set the value of [realsum] column. + * + * @param string $v new value + * @return CcTrans The current object (for fluent API support) + */ + public function setRealsum($v) + { + if ($v !== null) { + $v = (string) $v; + } + + if ($this->realsum !== $v) { + $this->realsum = $v; + $this->modifiedColumns[] = CcTransPeer::REALSUM; + } + + return $this; + } // setRealsum() + + /** + * Set the value of [expectedsize] column. + * + * @param int $v new value + * @return CcTrans The current object (for fluent API support) + */ + public function setExpectedsize($v) + { + if ($v !== null) { + $v = (int) $v; + } + + if ($this->expectedsize !== $v) { + $this->expectedsize = $v; + $this->modifiedColumns[] = CcTransPeer::EXPECTEDSIZE; + } + + return $this; + } // setExpectedsize() + + /** + * Set the value of [realsize] column. + * + * @param int $v new value + * @return CcTrans The current object (for fluent API support) + */ + public function setRealsize($v) + { + if ($v !== null) { + $v = (int) $v; + } + + if ($this->realsize !== $v) { + $this->realsize = $v; + $this->modifiedColumns[] = CcTransPeer::REALSIZE; + } + + return $this; + } // setRealsize() + + /** + * Set the value of [uid] column. + * + * @param int $v new value + * @return CcTrans The current object (for fluent API support) + */ + public function setUid($v) + { + if ($v !== null) { + $v = (int) $v; + } + + if ($this->uid !== $v) { + $this->uid = $v; + $this->modifiedColumns[] = CcTransPeer::UID; + } + + return $this; + } // setUid() + + /** + * Set the value of [errmsg] column. + * + * @param string $v new value + * @return CcTrans The current object (for fluent API support) + */ + public function setErrmsg($v) + { + if ($v !== null) { + $v = (string) $v; + } + + if ($this->errmsg !== $v) { + $this->errmsg = $v; + $this->modifiedColumns[] = CcTransPeer::ERRMSG; + } + + return $this; + } // setErrmsg() + + /** + * Set the value of [jobpid] column. + * + * @param int $v new value + * @return CcTrans The current object (for fluent API support) + */ + public function setJobpid($v) + { + if ($v !== null) { + $v = (int) $v; + } + + if ($this->jobpid !== $v) { + $this->jobpid = $v; + $this->modifiedColumns[] = CcTransPeer::JOBPID; + } + + return $this; + } // setJobpid() + + /** + * Sets the value of [start] column to a normalized version of the date/time value specified. + * + * @param mixed $v string, integer (timestamp), or DateTime value. Empty string will + * be treated as NULL for temporal objects. + * @return CcTrans The current object (for fluent API support) + */ + public function setStart($v) + { + // we treat '' as NULL for temporal objects because DateTime('') == DateTime('now') + // -- which is unexpected, to say the least. + if ($v === null || $v === '') { + $dt = null; + } elseif ($v instanceof DateTime) { + $dt = $v; + } else { + // some string/numeric value passed; we normalize that so that we can + // validate it. + try { + if (is_numeric($v)) { // if it's a unix timestamp + $dt = new DateTime('@'.$v, new DateTimeZone('UTC')); + // We have to explicitly specify and then change the time zone because of a + // DateTime bug: http://bugs.php.net/bug.php?id=43003 + $dt->setTimeZone(new DateTimeZone(date_default_timezone_get())); + } else { + $dt = new DateTime($v); + } + } catch (Exception $x) { + throw new PropelException('Error parsing date/time value: ' . var_export($v, true), $x); + } + } + + if ( $this->start !== null || $dt !== null ) { + // (nested ifs are a little easier to read in this case) + + $currNorm = ($this->start !== null && $tmpDt = new DateTime($this->start)) ? $tmpDt->format('Y-m-d\\TH:i:sO') : null; + $newNorm = ($dt !== null) ? $dt->format('Y-m-d\\TH:i:sO') : null; + + if ( ($currNorm !== $newNorm) // normalized values don't match + ) + { + $this->start = ($dt ? $dt->format('Y-m-d\\TH:i:sO') : null); + $this->modifiedColumns[] = CcTransPeer::START; + } + } // if either are not null + + return $this; + } // setStart() + + /** + * Sets the value of [ts] column to a normalized version of the date/time value specified. + * + * @param mixed $v string, integer (timestamp), or DateTime value. Empty string will + * be treated as NULL for temporal objects. + * @return CcTrans The current object (for fluent API support) + */ + public function setTs($v) + { + // we treat '' as NULL for temporal objects because DateTime('') == DateTime('now') + // -- which is unexpected, to say the least. + if ($v === null || $v === '') { + $dt = null; + } elseif ($v instanceof DateTime) { + $dt = $v; + } else { + // some string/numeric value passed; we normalize that so that we can + // validate it. + try { + if (is_numeric($v)) { // if it's a unix timestamp + $dt = new DateTime('@'.$v, new DateTimeZone('UTC')); + // We have to explicitly specify and then change the time zone because of a + // DateTime bug: http://bugs.php.net/bug.php?id=43003 + $dt->setTimeZone(new DateTimeZone(date_default_timezone_get())); + } else { + $dt = new DateTime($v); + } + } catch (Exception $x) { + throw new PropelException('Error parsing date/time value: ' . var_export($v, true), $x); + } + } + + if ( $this->ts !== null || $dt !== null ) { + // (nested ifs are a little easier to read in this case) + + $currNorm = ($this->ts !== null && $tmpDt = new DateTime($this->ts)) ? $tmpDt->format('Y-m-d\\TH:i:sO') : null; + $newNorm = ($dt !== null) ? $dt->format('Y-m-d\\TH:i:sO') : null; + + if ( ($currNorm !== $newNorm) // normalized values don't match + ) + { + $this->ts = ($dt ? $dt->format('Y-m-d\\TH:i:sO') : null); + $this->modifiedColumns[] = CcTransPeer::TS; + } + } // if either are not null + + return $this; + } // setTs() + + /** + * Indicates whether the columns in this object are only set to default values. + * + * This method can be used in conjunction with isModified() to indicate whether an object is both + * modified _and_ has some values set which are non-default. + * + * @return boolean Whether the columns in this object are only been set with default values. + */ + public function hasOnlyDefaultValues() + { + if ($this->lock !== 'N') { + return false; + } + + // otherwise, everything was equal, so return TRUE + return true; + } // hasOnlyDefaultValues() + + /** + * Hydrates (populates) the object variables with values from the database resultset. + * + * An offset (0-based "start column") is specified so that objects can be hydrated + * with a subset of the columns in the resultset rows. This is needed, for example, + * for results of JOIN queries where the resultset row includes columns from two or + * more tables. + * + * @param array $row The row returned by PDOStatement->fetch(PDO::FETCH_NUM) + * @param int $startcol 0-based offset column which indicates which restultset column to start with. + * @param boolean $rehydrate Whether this object is being re-hydrated from the database. + * @return int next starting column + * @throws PropelException - Any caught Exception will be rewrapped as a PropelException. + */ + public function hydrate($row, $startcol = 0, $rehydrate = false) + { + try { + + $this->id = ($row[$startcol + 0] !== null) ? (int) $row[$startcol + 0] : null; + $this->trtok = ($row[$startcol + 1] !== null) ? (string) $row[$startcol + 1] : null; + $this->direction = ($row[$startcol + 2] !== null) ? (string) $row[$startcol + 2] : null; + $this->state = ($row[$startcol + 3] !== null) ? (string) $row[$startcol + 3] : null; + $this->trtype = ($row[$startcol + 4] !== null) ? (string) $row[$startcol + 4] : null; + $this->lock = ($row[$startcol + 5] !== null) ? (string) $row[$startcol + 5] : null; + $this->target = ($row[$startcol + 6] !== null) ? (string) $row[$startcol + 6] : null; + $this->rtrtok = ($row[$startcol + 7] !== null) ? (string) $row[$startcol + 7] : null; + $this->mdtrtok = ($row[$startcol + 8] !== null) ? (string) $row[$startcol + 8] : null; + $this->gunid = ($row[$startcol + 9] !== null) ? (string) $row[$startcol + 9] : null; + $this->pdtoken = ($row[$startcol + 10] !== null) ? (string) $row[$startcol + 10] : null; + $this->url = ($row[$startcol + 11] !== null) ? (string) $row[$startcol + 11] : null; + $this->localfile = ($row[$startcol + 12] !== null) ? (string) $row[$startcol + 12] : null; + $this->fname = ($row[$startcol + 13] !== null) ? (string) $row[$startcol + 13] : null; + $this->title = ($row[$startcol + 14] !== null) ? (string) $row[$startcol + 14] : null; + $this->expectedsum = ($row[$startcol + 15] !== null) ? (string) $row[$startcol + 15] : null; + $this->realsum = ($row[$startcol + 16] !== null) ? (string) $row[$startcol + 16] : null; + $this->expectedsize = ($row[$startcol + 17] !== null) ? (int) $row[$startcol + 17] : null; + $this->realsize = ($row[$startcol + 18] !== null) ? (int) $row[$startcol + 18] : null; + $this->uid = ($row[$startcol + 19] !== null) ? (int) $row[$startcol + 19] : null; + $this->errmsg = ($row[$startcol + 20] !== null) ? (string) $row[$startcol + 20] : null; + $this->jobpid = ($row[$startcol + 21] !== null) ? (int) $row[$startcol + 21] : null; + $this->start = ($row[$startcol + 22] !== null) ? (string) $row[$startcol + 22] : null; + $this->ts = ($row[$startcol + 23] !== null) ? (string) $row[$startcol + 23] : null; + $this->resetModified(); + + $this->setNew(false); + + if ($rehydrate) { + $this->ensureConsistency(); + } + + return $startcol + 24; // 24 = CcTransPeer::NUM_COLUMNS - CcTransPeer::NUM_LAZY_LOAD_COLUMNS). + + } catch (Exception $e) { + throw new PropelException("Error populating CcTrans object", $e); + } + } + + /** + * Checks and repairs the internal consistency of the object. + * + * This method is executed after an already-instantiated object is re-hydrated + * from the database. It exists to check any foreign keys to make sure that + * the objects related to the current object are correct based on foreign key. + * + * You can override this method in the stub class, but you should always invoke + * the base method from the overridden method (i.e. parent::ensureConsistency()), + * in case your model changes. + * + * @throws PropelException + */ + public function ensureConsistency() + { + + } // ensureConsistency + + /** + * Reloads this object from datastore based on primary key and (optionally) resets all associated objects. + * + * This will only work if the object has been saved and has a valid primary key set. + * + * @param boolean $deep (optional) Whether to also de-associated any related objects. + * @param PropelPDO $con (optional) The PropelPDO connection to use. + * @return void + * @throws PropelException - if this object is deleted, unsaved or doesn't have pk match in db + */ + public function reload($deep = false, PropelPDO $con = null) + { + if ($this->isDeleted()) { + throw new PropelException("Cannot reload a deleted object."); + } + + if ($this->isNew()) { + throw new PropelException("Cannot reload an unsaved object."); + } + + if ($con === null) { + $con = Propel::getConnection(CcTransPeer::DATABASE_NAME, Propel::CONNECTION_READ); + } + + // We don't need to alter the object instance pool; we're just modifying this instance + // already in the pool. + + $stmt = CcTransPeer::doSelectStmt($this->buildPkeyCriteria(), $con); + $row = $stmt->fetch(PDO::FETCH_NUM); + $stmt->closeCursor(); + if (!$row) { + throw new PropelException('Cannot find matching row in the database to reload object values.'); + } + $this->hydrate($row, 0, true); // rehydrate + + if ($deep) { // also de-associate any related objects? + + } // if (deep) + } + + /** + * Removes this object from datastore and sets delete attribute. + * + * @param PropelPDO $con + * @return void + * @throws PropelException + * @see BaseObject::setDeleted() + * @see BaseObject::isDeleted() + */ + public function delete(PropelPDO $con = null) + { + if ($this->isDeleted()) { + throw new PropelException("This object has already been deleted."); + } + + if ($con === null) { + $con = Propel::getConnection(CcTransPeer::DATABASE_NAME, Propel::CONNECTION_WRITE); + } + + $con->beginTransaction(); + try { + $ret = $this->preDelete($con); + if ($ret) { + CcTransQuery::create() + ->filterByPrimaryKey($this->getPrimaryKey()) + ->delete($con); + $this->postDelete($con); + $con->commit(); + $this->setDeleted(true); + } else { + $con->commit(); + } + } catch (PropelException $e) { + $con->rollBack(); + throw $e; + } + } + + /** + * Persists this object to the database. + * + * If the object is new, it inserts it; otherwise an update is performed. + * All modified related objects will also be persisted in the doSave() + * method. This method wraps all precipitate database operations in a + * single transaction. + * + * @param PropelPDO $con + * @return int The number of rows affected by this insert/update and any referring fk objects' save() operations. + * @throws PropelException + * @see doSave() + */ + public function save(PropelPDO $con = null) + { + if ($this->isDeleted()) { + throw new PropelException("You cannot save an object that has been deleted."); + } + + if ($con === null) { + $con = Propel::getConnection(CcTransPeer::DATABASE_NAME, Propel::CONNECTION_WRITE); + } + + $con->beginTransaction(); + $isInsert = $this->isNew(); + try { + $ret = $this->preSave($con); + if ($isInsert) { + $ret = $ret && $this->preInsert($con); + } else { + $ret = $ret && $this->preUpdate($con); + } + if ($ret) { + $affectedRows = $this->doSave($con); + if ($isInsert) { + $this->postInsert($con); + } else { + $this->postUpdate($con); + } + $this->postSave($con); + CcTransPeer::addInstanceToPool($this); + } else { + $affectedRows = 0; + } + $con->commit(); + return $affectedRows; + } catch (PropelException $e) { + $con->rollBack(); + throw $e; + } + } + + /** + * Performs the work of inserting or updating the row in the database. + * + * If the object is new, it inserts it; otherwise an update is performed. + * All related objects are also updated in this method. + * + * @param PropelPDO $con + * @return int The number of rows affected by this insert/update and any referring fk objects' save() operations. + * @throws PropelException + * @see save() + */ + protected function doSave(PropelPDO $con) + { + $affectedRows = 0; // initialize var to track total num of affected rows + if (!$this->alreadyInSave) { + $this->alreadyInSave = true; + + if ($this->isNew() ) { + $this->modifiedColumns[] = CcTransPeer::ID; + } + + // If this object has been modified, then save it to the database. + if ($this->isModified()) { + if ($this->isNew()) { + $criteria = $this->buildCriteria(); + if ($criteria->keyContainsValue(CcTransPeer::ID) ) { + throw new PropelException('Cannot insert a value for auto-increment primary key ('.CcTransPeer::ID.')'); + } + + $pk = BasePeer::doInsert($criteria, $con); + $affectedRows = 1; + $this->setId($pk); //[IMV] update autoincrement primary key + $this->setNew(false); + } else { + $affectedRows = CcTransPeer::doUpdate($this, $con); + } + + $this->resetModified(); // [HL] After being saved an object is no longer 'modified' + } + + $this->alreadyInSave = false; + + } + return $affectedRows; + } // doSave() + + /** + * Array of ValidationFailed objects. + * @var array ValidationFailed[] + */ + protected $validationFailures = array(); + + /** + * Gets any ValidationFailed objects that resulted from last call to validate(). + * + * + * @return array ValidationFailed[] + * @see validate() + */ + public function getValidationFailures() + { + return $this->validationFailures; + } + + /** + * Validates the objects modified field values and all objects related to this table. + * + * If $columns is either a column name or an array of column names + * only those columns are validated. + * + * @param mixed $columns Column name or an array of column names. + * @return boolean Whether all columns pass validation. + * @see doValidate() + * @see getValidationFailures() + */ + public function validate($columns = null) + { + $res = $this->doValidate($columns); + if ($res === true) { + $this->validationFailures = array(); + return true; + } else { + $this->validationFailures = $res; + return false; + } + } + + /** + * This function performs the validation work for complex object models. + * + * In addition to checking the current object, all related objects will + * also be validated. If all pass then true is returned; otherwise + * an aggreagated array of ValidationFailed objects will be returned. + * + * @param array $columns Array of column names to validate. + * @return mixed true if all validations pass; array of ValidationFailed objets otherwise. + */ + protected function doValidate($columns = null) + { + if (!$this->alreadyInValidation) { + $this->alreadyInValidation = true; + $retval = null; + + $failureMap = array(); + + + if (($retval = CcTransPeer::doValidate($this, $columns)) !== true) { + $failureMap = array_merge($failureMap, $retval); + } + + + + $this->alreadyInValidation = false; + } + + return (!empty($failureMap) ? $failureMap : true); + } + + /** + * Retrieves a field from the object by name passed in as a string. + * + * @param string $name name + * @param string $type The type of fieldname the $name is of: + * one of the class type constants BasePeer::TYPE_PHPNAME, BasePeer::TYPE_STUDLYPHPNAME + * BasePeer::TYPE_COLNAME, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_NUM + * @return mixed Value of field. + */ + public function getByName($name, $type = BasePeer::TYPE_PHPNAME) + { + $pos = CcTransPeer::translateFieldName($name, $type, BasePeer::TYPE_NUM); + $field = $this->getByPosition($pos); + return $field; + } + + /** + * Retrieves a field from the object by Position as specified in the xml schema. + * Zero-based. + * + * @param int $pos position in xml schema + * @return mixed Value of field at $pos + */ + public function getByPosition($pos) + { + switch($pos) { + case 0: + return $this->getId(); + break; + case 1: + return $this->getTrtok(); + break; + case 2: + return $this->getDirection(); + break; + case 3: + return $this->getState(); + break; + case 4: + return $this->getTrtype(); + break; + case 5: + return $this->getLock(); + break; + case 6: + return $this->getTarget(); + break; + case 7: + return $this->getRtrtok(); + break; + case 8: + return $this->getMdtrtok(); + break; + case 9: + return $this->getGunid(); + break; + case 10: + return $this->getPdtoken(); + break; + case 11: + return $this->getUrl(); + break; + case 12: + return $this->getLocalfile(); + break; + case 13: + return $this->getFname(); + break; + case 14: + return $this->getTitle(); + break; + case 15: + return $this->getExpectedsum(); + break; + case 16: + return $this->getRealsum(); + break; + case 17: + return $this->getExpectedsize(); + break; + case 18: + return $this->getRealsize(); + break; + case 19: + return $this->getUid(); + break; + case 20: + return $this->getErrmsg(); + break; + case 21: + return $this->getJobpid(); + break; + case 22: + return $this->getStart(); + break; + case 23: + return $this->getTs(); + break; + default: + return null; + break; + } // switch() + } + + /** + * Exports the object as an array. + * + * You can specify the key type of the array by passing one of the class + * type constants. + * + * @param string $keyType (optional) One of the class type constants BasePeer::TYPE_PHPNAME, BasePeer::TYPE_STUDLYPHPNAME, + * BasePeer::TYPE_COLNAME, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_NUM. + * Defaults to BasePeer::TYPE_PHPNAME. + * @param boolean $includeLazyLoadColumns (optional) Whether to include lazy loaded columns. Defaults to TRUE. + * + * @return array an associative array containing the field names (as keys) and field values + */ + public function toArray($keyType = BasePeer::TYPE_PHPNAME, $includeLazyLoadColumns = true) + { + $keys = CcTransPeer::getFieldNames($keyType); + $result = array( + $keys[0] => $this->getId(), + $keys[1] => $this->getTrtok(), + $keys[2] => $this->getDirection(), + $keys[3] => $this->getState(), + $keys[4] => $this->getTrtype(), + $keys[5] => $this->getLock(), + $keys[6] => $this->getTarget(), + $keys[7] => $this->getRtrtok(), + $keys[8] => $this->getMdtrtok(), + $keys[9] => $this->getGunid(), + $keys[10] => $this->getPdtoken(), + $keys[11] => $this->getUrl(), + $keys[12] => $this->getLocalfile(), + $keys[13] => $this->getFname(), + $keys[14] => $this->getTitle(), + $keys[15] => $this->getExpectedsum(), + $keys[16] => $this->getRealsum(), + $keys[17] => $this->getExpectedsize(), + $keys[18] => $this->getRealsize(), + $keys[19] => $this->getUid(), + $keys[20] => $this->getErrmsg(), + $keys[21] => $this->getJobpid(), + $keys[22] => $this->getStart(), + $keys[23] => $this->getTs(), + ); + return $result; + } + + /** + * Sets a field from the object by name passed in as a string. + * + * @param string $name peer name + * @param mixed $value field value + * @param string $type The type of fieldname the $name is of: + * one of the class type constants BasePeer::TYPE_PHPNAME, BasePeer::TYPE_STUDLYPHPNAME + * BasePeer::TYPE_COLNAME, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_NUM + * @return void + */ + public function setByName($name, $value, $type = BasePeer::TYPE_PHPNAME) + { + $pos = CcTransPeer::translateFieldName($name, $type, BasePeer::TYPE_NUM); + return $this->setByPosition($pos, $value); + } + + /** + * Sets a field from the object by Position as specified in the xml schema. + * Zero-based. + * + * @param int $pos position in xml schema + * @param mixed $value field value + * @return void + */ + public function setByPosition($pos, $value) + { + switch($pos) { + case 0: + $this->setId($value); + break; + case 1: + $this->setTrtok($value); + break; + case 2: + $this->setDirection($value); + break; + case 3: + $this->setState($value); + break; + case 4: + $this->setTrtype($value); + break; + case 5: + $this->setLock($value); + break; + case 6: + $this->setTarget($value); + break; + case 7: + $this->setRtrtok($value); + break; + case 8: + $this->setMdtrtok($value); + break; + case 9: + $this->setGunid($value); + break; + case 10: + $this->setPdtoken($value); + break; + case 11: + $this->setUrl($value); + break; + case 12: + $this->setLocalfile($value); + break; + case 13: + $this->setFname($value); + break; + case 14: + $this->setTitle($value); + break; + case 15: + $this->setExpectedsum($value); + break; + case 16: + $this->setRealsum($value); + break; + case 17: + $this->setExpectedsize($value); + break; + case 18: + $this->setRealsize($value); + break; + case 19: + $this->setUid($value); + break; + case 20: + $this->setErrmsg($value); + break; + case 21: + $this->setJobpid($value); + break; + case 22: + $this->setStart($value); + break; + case 23: + $this->setTs($value); + break; + } // switch() + } + + /** + * Populates the object using an array. + * + * This is particularly useful when populating an object from one of the + * request arrays (e.g. $_POST). This method goes through the column + * names, checking to see whether a matching key exists in populated + * array. If so the setByName() method is called for that column. + * + * You can specify the key type of the array by additionally passing one + * of the class type constants BasePeer::TYPE_PHPNAME, BasePeer::TYPE_STUDLYPHPNAME, + * BasePeer::TYPE_COLNAME, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_NUM. + * The default key type is the column's phpname (e.g. 'AuthorId') + * + * @param array $arr An array to populate the object from. + * @param string $keyType The type of keys the array uses. + * @return void + */ + public function fromArray($arr, $keyType = BasePeer::TYPE_PHPNAME) + { + $keys = CcTransPeer::getFieldNames($keyType); + + if (array_key_exists($keys[0], $arr)) $this->setId($arr[$keys[0]]); + if (array_key_exists($keys[1], $arr)) $this->setTrtok($arr[$keys[1]]); + if (array_key_exists($keys[2], $arr)) $this->setDirection($arr[$keys[2]]); + if (array_key_exists($keys[3], $arr)) $this->setState($arr[$keys[3]]); + if (array_key_exists($keys[4], $arr)) $this->setTrtype($arr[$keys[4]]); + if (array_key_exists($keys[5], $arr)) $this->setLock($arr[$keys[5]]); + if (array_key_exists($keys[6], $arr)) $this->setTarget($arr[$keys[6]]); + if (array_key_exists($keys[7], $arr)) $this->setRtrtok($arr[$keys[7]]); + if (array_key_exists($keys[8], $arr)) $this->setMdtrtok($arr[$keys[8]]); + if (array_key_exists($keys[9], $arr)) $this->setGunid($arr[$keys[9]]); + if (array_key_exists($keys[10], $arr)) $this->setPdtoken($arr[$keys[10]]); + if (array_key_exists($keys[11], $arr)) $this->setUrl($arr[$keys[11]]); + if (array_key_exists($keys[12], $arr)) $this->setLocalfile($arr[$keys[12]]); + if (array_key_exists($keys[13], $arr)) $this->setFname($arr[$keys[13]]); + if (array_key_exists($keys[14], $arr)) $this->setTitle($arr[$keys[14]]); + if (array_key_exists($keys[15], $arr)) $this->setExpectedsum($arr[$keys[15]]); + if (array_key_exists($keys[16], $arr)) $this->setRealsum($arr[$keys[16]]); + if (array_key_exists($keys[17], $arr)) $this->setExpectedsize($arr[$keys[17]]); + if (array_key_exists($keys[18], $arr)) $this->setRealsize($arr[$keys[18]]); + if (array_key_exists($keys[19], $arr)) $this->setUid($arr[$keys[19]]); + if (array_key_exists($keys[20], $arr)) $this->setErrmsg($arr[$keys[20]]); + if (array_key_exists($keys[21], $arr)) $this->setJobpid($arr[$keys[21]]); + if (array_key_exists($keys[22], $arr)) $this->setStart($arr[$keys[22]]); + if (array_key_exists($keys[23], $arr)) $this->setTs($arr[$keys[23]]); + } + + /** + * Build a Criteria object containing the values of all modified columns in this object. + * + * @return Criteria The Criteria object containing all modified values. + */ + public function buildCriteria() + { + $criteria = new Criteria(CcTransPeer::DATABASE_NAME); + + if ($this->isColumnModified(CcTransPeer::ID)) $criteria->add(CcTransPeer::ID, $this->id); + if ($this->isColumnModified(CcTransPeer::TRTOK)) $criteria->add(CcTransPeer::TRTOK, $this->trtok); + if ($this->isColumnModified(CcTransPeer::DIRECTION)) $criteria->add(CcTransPeer::DIRECTION, $this->direction); + if ($this->isColumnModified(CcTransPeer::STATE)) $criteria->add(CcTransPeer::STATE, $this->state); + if ($this->isColumnModified(CcTransPeer::TRTYPE)) $criteria->add(CcTransPeer::TRTYPE, $this->trtype); + if ($this->isColumnModified(CcTransPeer::LOCK)) $criteria->add(CcTransPeer::LOCK, $this->lock); + if ($this->isColumnModified(CcTransPeer::TARGET)) $criteria->add(CcTransPeer::TARGET, $this->target); + if ($this->isColumnModified(CcTransPeer::RTRTOK)) $criteria->add(CcTransPeer::RTRTOK, $this->rtrtok); + if ($this->isColumnModified(CcTransPeer::MDTRTOK)) $criteria->add(CcTransPeer::MDTRTOK, $this->mdtrtok); + if ($this->isColumnModified(CcTransPeer::GUNID)) $criteria->add(CcTransPeer::GUNID, $this->gunid); + if ($this->isColumnModified(CcTransPeer::PDTOKEN)) $criteria->add(CcTransPeer::PDTOKEN, $this->pdtoken); + if ($this->isColumnModified(CcTransPeer::URL)) $criteria->add(CcTransPeer::URL, $this->url); + if ($this->isColumnModified(CcTransPeer::LOCALFILE)) $criteria->add(CcTransPeer::LOCALFILE, $this->localfile); + if ($this->isColumnModified(CcTransPeer::FNAME)) $criteria->add(CcTransPeer::FNAME, $this->fname); + if ($this->isColumnModified(CcTransPeer::TITLE)) $criteria->add(CcTransPeer::TITLE, $this->title); + if ($this->isColumnModified(CcTransPeer::EXPECTEDSUM)) $criteria->add(CcTransPeer::EXPECTEDSUM, $this->expectedsum); + if ($this->isColumnModified(CcTransPeer::REALSUM)) $criteria->add(CcTransPeer::REALSUM, $this->realsum); + if ($this->isColumnModified(CcTransPeer::EXPECTEDSIZE)) $criteria->add(CcTransPeer::EXPECTEDSIZE, $this->expectedsize); + if ($this->isColumnModified(CcTransPeer::REALSIZE)) $criteria->add(CcTransPeer::REALSIZE, $this->realsize); + if ($this->isColumnModified(CcTransPeer::UID)) $criteria->add(CcTransPeer::UID, $this->uid); + if ($this->isColumnModified(CcTransPeer::ERRMSG)) $criteria->add(CcTransPeer::ERRMSG, $this->errmsg); + if ($this->isColumnModified(CcTransPeer::JOBPID)) $criteria->add(CcTransPeer::JOBPID, $this->jobpid); + if ($this->isColumnModified(CcTransPeer::START)) $criteria->add(CcTransPeer::START, $this->start); + if ($this->isColumnModified(CcTransPeer::TS)) $criteria->add(CcTransPeer::TS, $this->ts); + + return $criteria; + } + + /** + * Builds a Criteria object containing the primary key for this object. + * + * Unlike buildCriteria() this method includes the primary key values regardless + * of whether or not they have been modified. + * + * @return Criteria The Criteria object containing value(s) for primary key(s). + */ + public function buildPkeyCriteria() + { + $criteria = new Criteria(CcTransPeer::DATABASE_NAME); + $criteria->add(CcTransPeer::ID, $this->id); + + return $criteria; + } + + /** + * Returns the primary key for this object (row). + * @return int + */ + public function getPrimaryKey() + { + return $this->getId(); + } + + /** + * Generic method to set the primary key (id column). + * + * @param int $key Primary key. + * @return void + */ + public function setPrimaryKey($key) + { + $this->setId($key); + } + + /** + * Returns true if the primary key for this object is null. + * @return boolean + */ + public function isPrimaryKeyNull() + { + return null === $this->getId(); + } + + /** + * Sets contents of passed object to values from current object. + * + * If desired, this method can also make copies of all associated (fkey referrers) + * objects. + * + * @param object $copyObj An object of CcTrans (or compatible) type. + * @param boolean $deepCopy Whether to also copy all rows that refer (by fkey) to the current row. + * @throws PropelException + */ + public function copyInto($copyObj, $deepCopy = false) + { + $copyObj->setTrtok($this->trtok); + $copyObj->setDirection($this->direction); + $copyObj->setState($this->state); + $copyObj->setTrtype($this->trtype); + $copyObj->setLock($this->lock); + $copyObj->setTarget($this->target); + $copyObj->setRtrtok($this->rtrtok); + $copyObj->setMdtrtok($this->mdtrtok); + $copyObj->setGunid($this->gunid); + $copyObj->setPdtoken($this->pdtoken); + $copyObj->setUrl($this->url); + $copyObj->setLocalfile($this->localfile); + $copyObj->setFname($this->fname); + $copyObj->setTitle($this->title); + $copyObj->setExpectedsum($this->expectedsum); + $copyObj->setRealsum($this->realsum); + $copyObj->setExpectedsize($this->expectedsize); + $copyObj->setRealsize($this->realsize); + $copyObj->setUid($this->uid); + $copyObj->setErrmsg($this->errmsg); + $copyObj->setJobpid($this->jobpid); + $copyObj->setStart($this->start); + $copyObj->setTs($this->ts); + + $copyObj->setNew(true); + $copyObj->setId(NULL); // this is a auto-increment column, so set to default value + } + + /** + * Makes a copy of this object that will be inserted as a new row in table when saved. + * It creates a new object filling in the simple attributes, but skipping any primary + * keys that are defined for the table. + * + * If desired, this method can also make copies of all associated (fkey referrers) + * objects. + * + * @param boolean $deepCopy Whether to also copy all rows that refer (by fkey) to the current row. + * @return CcTrans Clone of current object. + * @throws PropelException + */ + public function copy($deepCopy = false) + { + // we use get_class(), because this might be a subclass + $clazz = get_class($this); + $copyObj = new $clazz(); + $this->copyInto($copyObj, $deepCopy); + return $copyObj; + } + + /** + * Returns a peer instance associated with this om. + * + * Since Peer classes are not to have any instance attributes, this method returns the + * same instance for all member of this class. The method could therefore + * be static, but this would prevent one from overriding the behavior. + * + * @return CcTransPeer + */ + public function getPeer() + { + if (self::$peer === null) { + self::$peer = new CcTransPeer(); + } + return self::$peer; + } + + /** + * Clears the current object and sets all attributes to their default values + */ + public function clear() + { + $this->id = null; + $this->trtok = null; + $this->direction = null; + $this->state = null; + $this->trtype = null; + $this->lock = null; + $this->target = null; + $this->rtrtok = null; + $this->mdtrtok = null; + $this->gunid = null; + $this->pdtoken = null; + $this->url = null; + $this->localfile = null; + $this->fname = null; + $this->title = null; + $this->expectedsum = null; + $this->realsum = null; + $this->expectedsize = null; + $this->realsize = null; + $this->uid = null; + $this->errmsg = null; + $this->jobpid = null; + $this->start = null; + $this->ts = null; + $this->alreadyInSave = false; + $this->alreadyInValidation = false; + $this->clearAllReferences(); + $this->applyDefaultValues(); + $this->resetModified(); + $this->setNew(true); + $this->setDeleted(false); + } + + /** + * Resets all collections of referencing foreign keys. + * + * This method is a user-space workaround for PHP's inability to garbage collect objects + * with circular references. This is currently necessary when using Propel in certain + * daemon or large-volumne/high-memory operations. + * + * @param boolean $deep Whether to also clear the references on all associated objects. + */ + public function clearAllReferences($deep = false) + { + if ($deep) { + } // if ($deep) + + } + + /** + * Catches calls to virtual methods + */ + public function __call($name, $params) + { + if (preg_match('/get(\w+)/', $name, $matches)) { + $virtualColumn = $matches[1]; + if ($this->hasVirtualColumn($virtualColumn)) { + return $this->getVirtualColumn($virtualColumn); + } + // no lcfirst in php<5.3... + $virtualColumn[0] = strtolower($virtualColumn[0]); + if ($this->hasVirtualColumn($virtualColumn)) { + return $this->getVirtualColumn($virtualColumn); + } + } + throw new PropelException('Call to undefined method: ' . $name); + } + +} // BaseCcTrans diff --git a/application/models/campcaster/om/BaseCcTransPeer.php b/application/models/campcaster/om/BaseCcTransPeer.php new file mode 100644 index 000000000..c8dd333a9 --- /dev/null +++ b/application/models/campcaster/om/BaseCcTransPeer.php @@ -0,0 +1,849 @@ + array ('Id', 'Trtok', 'Direction', 'State', 'Trtype', 'Lock', 'Target', 'Rtrtok', 'Mdtrtok', 'Gunid', 'Pdtoken', 'Url', 'Localfile', 'Fname', 'Title', 'Expectedsum', 'Realsum', 'Expectedsize', 'Realsize', 'Uid', 'Errmsg', 'Jobpid', 'Start', 'Ts', ), + BasePeer::TYPE_STUDLYPHPNAME => array ('id', 'trtok', 'direction', 'state', 'trtype', 'lock', 'target', 'rtrtok', 'mdtrtok', 'gunid', 'pdtoken', 'url', 'localfile', 'fname', 'title', 'expectedsum', 'realsum', 'expectedsize', 'realsize', 'uid', 'errmsg', 'jobpid', 'start', 'ts', ), + BasePeer::TYPE_COLNAME => array (self::ID, self::TRTOK, self::DIRECTION, self::STATE, self::TRTYPE, self::LOCK, self::TARGET, self::RTRTOK, self::MDTRTOK, self::GUNID, self::PDTOKEN, self::URL, self::LOCALFILE, self::FNAME, self::TITLE, self::EXPECTEDSUM, self::REALSUM, self::EXPECTEDSIZE, self::REALSIZE, self::UID, self::ERRMSG, self::JOBPID, self::START, self::TS, ), + BasePeer::TYPE_RAW_COLNAME => array ('ID', 'TRTOK', 'DIRECTION', 'STATE', 'TRTYPE', 'LOCK', 'TARGET', 'RTRTOK', 'MDTRTOK', 'GUNID', 'PDTOKEN', 'URL', 'LOCALFILE', 'FNAME', 'TITLE', 'EXPECTEDSUM', 'REALSUM', 'EXPECTEDSIZE', 'REALSIZE', 'UID', 'ERRMSG', 'JOBPID', 'START', 'TS', ), + BasePeer::TYPE_FIELDNAME => array ('id', 'trtok', 'direction', 'state', 'trtype', 'lock', 'target', 'rtrtok', 'mdtrtok', 'gunid', 'pdtoken', 'url', 'localfile', 'fname', 'title', 'expectedsum', 'realsum', 'expectedsize', 'realsize', 'uid', 'errmsg', 'jobpid', 'start', 'ts', ), + BasePeer::TYPE_NUM => array (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, ) + ); + + /** + * holds an array of keys for quick access to the fieldnames array + * + * first dimension keys are the type constants + * e.g. self::$fieldNames[BasePeer::TYPE_PHPNAME]['Id'] = 0 + */ + private static $fieldKeys = array ( + BasePeer::TYPE_PHPNAME => array ('Id' => 0, 'Trtok' => 1, 'Direction' => 2, 'State' => 3, 'Trtype' => 4, 'Lock' => 5, 'Target' => 6, 'Rtrtok' => 7, 'Mdtrtok' => 8, 'Gunid' => 9, 'Pdtoken' => 10, 'Url' => 11, 'Localfile' => 12, 'Fname' => 13, 'Title' => 14, 'Expectedsum' => 15, 'Realsum' => 16, 'Expectedsize' => 17, 'Realsize' => 18, 'Uid' => 19, 'Errmsg' => 20, 'Jobpid' => 21, 'Start' => 22, 'Ts' => 23, ), + BasePeer::TYPE_STUDLYPHPNAME => array ('id' => 0, 'trtok' => 1, 'direction' => 2, 'state' => 3, 'trtype' => 4, 'lock' => 5, 'target' => 6, 'rtrtok' => 7, 'mdtrtok' => 8, 'gunid' => 9, 'pdtoken' => 10, 'url' => 11, 'localfile' => 12, 'fname' => 13, 'title' => 14, 'expectedsum' => 15, 'realsum' => 16, 'expectedsize' => 17, 'realsize' => 18, 'uid' => 19, 'errmsg' => 20, 'jobpid' => 21, 'start' => 22, 'ts' => 23, ), + BasePeer::TYPE_COLNAME => array (self::ID => 0, self::TRTOK => 1, self::DIRECTION => 2, self::STATE => 3, self::TRTYPE => 4, self::LOCK => 5, self::TARGET => 6, self::RTRTOK => 7, self::MDTRTOK => 8, self::GUNID => 9, self::PDTOKEN => 10, self::URL => 11, self::LOCALFILE => 12, self::FNAME => 13, self::TITLE => 14, self::EXPECTEDSUM => 15, self::REALSUM => 16, self::EXPECTEDSIZE => 17, self::REALSIZE => 18, self::UID => 19, self::ERRMSG => 20, self::JOBPID => 21, self::START => 22, self::TS => 23, ), + BasePeer::TYPE_RAW_COLNAME => array ('ID' => 0, 'TRTOK' => 1, 'DIRECTION' => 2, 'STATE' => 3, 'TRTYPE' => 4, 'LOCK' => 5, 'TARGET' => 6, 'RTRTOK' => 7, 'MDTRTOK' => 8, 'GUNID' => 9, 'PDTOKEN' => 10, 'URL' => 11, 'LOCALFILE' => 12, 'FNAME' => 13, 'TITLE' => 14, 'EXPECTEDSUM' => 15, 'REALSUM' => 16, 'EXPECTEDSIZE' => 17, 'REALSIZE' => 18, 'UID' => 19, 'ERRMSG' => 20, 'JOBPID' => 21, 'START' => 22, 'TS' => 23, ), + BasePeer::TYPE_FIELDNAME => array ('id' => 0, 'trtok' => 1, 'direction' => 2, 'state' => 3, 'trtype' => 4, 'lock' => 5, 'target' => 6, 'rtrtok' => 7, 'mdtrtok' => 8, 'gunid' => 9, 'pdtoken' => 10, 'url' => 11, 'localfile' => 12, 'fname' => 13, 'title' => 14, 'expectedsum' => 15, 'realsum' => 16, 'expectedsize' => 17, 'realsize' => 18, 'uid' => 19, 'errmsg' => 20, 'jobpid' => 21, 'start' => 22, 'ts' => 23, ), + BasePeer::TYPE_NUM => array (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, ) + ); + + /** + * Translates a fieldname to another type + * + * @param string $name field name + * @param string $fromType One of the class type constants BasePeer::TYPE_PHPNAME, BasePeer::TYPE_STUDLYPHPNAME + * BasePeer::TYPE_COLNAME, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_NUM + * @param string $toType One of the class type constants + * @return string translated name of the field. + * @throws PropelException - if the specified name could not be found in the fieldname mappings. + */ + static public function translateFieldName($name, $fromType, $toType) + { + $toNames = self::getFieldNames($toType); + $key = isset(self::$fieldKeys[$fromType][$name]) ? self::$fieldKeys[$fromType][$name] : null; + if ($key === null) { + throw new PropelException("'$name' could not be found in the field names of type '$fromType'. These are: " . print_r(self::$fieldKeys[$fromType], true)); + } + return $toNames[$key]; + } + + /** + * Returns an array of field names. + * + * @param string $type The type of fieldnames to return: + * One of the class type constants BasePeer::TYPE_PHPNAME, BasePeer::TYPE_STUDLYPHPNAME + * BasePeer::TYPE_COLNAME, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_NUM + * @return array A list of field names + */ + + static public function getFieldNames($type = BasePeer::TYPE_PHPNAME) + { + if (!array_key_exists($type, self::$fieldNames)) { + throw new PropelException('Method getFieldNames() expects the parameter $type to be one of the class constants BasePeer::TYPE_PHPNAME, BasePeer::TYPE_STUDLYPHPNAME, BasePeer::TYPE_COLNAME, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_NUM. ' . $type . ' was given.'); + } + return self::$fieldNames[$type]; + } + + /** + * Convenience method which changes table.column to alias.column. + * + * Using this method you can maintain SQL abstraction while using column aliases. + * + * $c->addAlias("alias1", TablePeer::TABLE_NAME); + * $c->addJoin(TablePeer::alias("alias1", TablePeer::PRIMARY_KEY_COLUMN), TablePeer::PRIMARY_KEY_COLUMN); + * + * @param string $alias The alias for the current table. + * @param string $column The column name for current table. (i.e. CcTransPeer::COLUMN_NAME). + * @return string + */ + public static function alias($alias, $column) + { + return str_replace(CcTransPeer::TABLE_NAME.'.', $alias.'.', $column); + } + + /** + * Add all the columns needed to create a new object. + * + * Note: any columns that were marked with lazyLoad="true" in the + * XML schema will not be added to the select list and only loaded + * on demand. + * + * @param Criteria $criteria object containing the columns to add. + * @param string $alias optional table alias + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function addSelectColumns(Criteria $criteria, $alias = null) + { + if (null === $alias) { + $criteria->addSelectColumn(CcTransPeer::ID); + $criteria->addSelectColumn(CcTransPeer::TRTOK); + $criteria->addSelectColumn(CcTransPeer::DIRECTION); + $criteria->addSelectColumn(CcTransPeer::STATE); + $criteria->addSelectColumn(CcTransPeer::TRTYPE); + $criteria->addSelectColumn(CcTransPeer::LOCK); + $criteria->addSelectColumn(CcTransPeer::TARGET); + $criteria->addSelectColumn(CcTransPeer::RTRTOK); + $criteria->addSelectColumn(CcTransPeer::MDTRTOK); + $criteria->addSelectColumn(CcTransPeer::GUNID); + $criteria->addSelectColumn(CcTransPeer::PDTOKEN); + $criteria->addSelectColumn(CcTransPeer::URL); + $criteria->addSelectColumn(CcTransPeer::LOCALFILE); + $criteria->addSelectColumn(CcTransPeer::FNAME); + $criteria->addSelectColumn(CcTransPeer::TITLE); + $criteria->addSelectColumn(CcTransPeer::EXPECTEDSUM); + $criteria->addSelectColumn(CcTransPeer::REALSUM); + $criteria->addSelectColumn(CcTransPeer::EXPECTEDSIZE); + $criteria->addSelectColumn(CcTransPeer::REALSIZE); + $criteria->addSelectColumn(CcTransPeer::UID); + $criteria->addSelectColumn(CcTransPeer::ERRMSG); + $criteria->addSelectColumn(CcTransPeer::JOBPID); + $criteria->addSelectColumn(CcTransPeer::START); + $criteria->addSelectColumn(CcTransPeer::TS); + } else { + $criteria->addSelectColumn($alias . '.ID'); + $criteria->addSelectColumn($alias . '.TRTOK'); + $criteria->addSelectColumn($alias . '.DIRECTION'); + $criteria->addSelectColumn($alias . '.STATE'); + $criteria->addSelectColumn($alias . '.TRTYPE'); + $criteria->addSelectColumn($alias . '.LOCK'); + $criteria->addSelectColumn($alias . '.TARGET'); + $criteria->addSelectColumn($alias . '.RTRTOK'); + $criteria->addSelectColumn($alias . '.MDTRTOK'); + $criteria->addSelectColumn($alias . '.GUNID'); + $criteria->addSelectColumn($alias . '.PDTOKEN'); + $criteria->addSelectColumn($alias . '.URL'); + $criteria->addSelectColumn($alias . '.LOCALFILE'); + $criteria->addSelectColumn($alias . '.FNAME'); + $criteria->addSelectColumn($alias . '.TITLE'); + $criteria->addSelectColumn($alias . '.EXPECTEDSUM'); + $criteria->addSelectColumn($alias . '.REALSUM'); + $criteria->addSelectColumn($alias . '.EXPECTEDSIZE'); + $criteria->addSelectColumn($alias . '.REALSIZE'); + $criteria->addSelectColumn($alias . '.UID'); + $criteria->addSelectColumn($alias . '.ERRMSG'); + $criteria->addSelectColumn($alias . '.JOBPID'); + $criteria->addSelectColumn($alias . '.START'); + $criteria->addSelectColumn($alias . '.TS'); + } + } + + /** + * Returns the number of rows matching criteria. + * + * @param Criteria $criteria + * @param boolean $distinct Whether to select only distinct columns; deprecated: use Criteria->setDistinct() instead. + * @param PropelPDO $con + * @return int Number of matching rows. + */ + public static function doCount(Criteria $criteria, $distinct = false, PropelPDO $con = null) + { + // we may modify criteria, so copy it first + $criteria = clone $criteria; + + // We need to set the primary table name, since in the case that there are no WHERE columns + // it will be impossible for the BasePeer::createSelectSql() method to determine which + // tables go into the FROM clause. + $criteria->setPrimaryTableName(CcTransPeer::TABLE_NAME); + + if ($distinct && !in_array(Criteria::DISTINCT, $criteria->getSelectModifiers())) { + $criteria->setDistinct(); + } + + if (!$criteria->hasSelectClause()) { + CcTransPeer::addSelectColumns($criteria); + } + + $criteria->clearOrderByColumns(); // ORDER BY won't ever affect the count + $criteria->setDbName(self::DATABASE_NAME); // Set the correct dbName + + if ($con === null) { + $con = Propel::getConnection(CcTransPeer::DATABASE_NAME, Propel::CONNECTION_READ); + } + // BasePeer returns a PDOStatement + $stmt = BasePeer::doCount($criteria, $con); + + if ($row = $stmt->fetch(PDO::FETCH_NUM)) { + $count = (int) $row[0]; + } else { + $count = 0; // no rows returned; we infer that means 0 matches. + } + $stmt->closeCursor(); + return $count; + } + /** + * Method to select one object from the DB. + * + * @param Criteria $criteria object used to create the SELECT statement. + * @param PropelPDO $con + * @return CcTrans + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function doSelectOne(Criteria $criteria, PropelPDO $con = null) + { + $critcopy = clone $criteria; + $critcopy->setLimit(1); + $objects = CcTransPeer::doSelect($critcopy, $con); + if ($objects) { + return $objects[0]; + } + return null; + } + /** + * Method to do selects. + * + * @param Criteria $criteria The Criteria object used to build the SELECT statement. + * @param PropelPDO $con + * @return array Array of selected Objects + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function doSelect(Criteria $criteria, PropelPDO $con = null) + { + return CcTransPeer::populateObjects(CcTransPeer::doSelectStmt($criteria, $con)); + } + /** + * Prepares the Criteria object and uses the parent doSelect() method to execute a PDOStatement. + * + * Use this method directly if you want to work with an executed statement durirectly (for example + * to perform your own object hydration). + * + * @param Criteria $criteria The Criteria object used to build the SELECT statement. + * @param PropelPDO $con The connection to use + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + * @return PDOStatement The executed PDOStatement object. + * @see BasePeer::doSelect() + */ + public static function doSelectStmt(Criteria $criteria, PropelPDO $con = null) + { + if ($con === null) { + $con = Propel::getConnection(CcTransPeer::DATABASE_NAME, Propel::CONNECTION_READ); + } + + if (!$criteria->hasSelectClause()) { + $criteria = clone $criteria; + CcTransPeer::addSelectColumns($criteria); + } + + // Set the correct dbName + $criteria->setDbName(self::DATABASE_NAME); + + // BasePeer returns a PDOStatement + return BasePeer::doSelect($criteria, $con); + } + /** + * Adds an object to the instance pool. + * + * Propel keeps cached copies of objects in an instance pool when they are retrieved + * from the database. In some cases -- especially when you override doSelect*() + * methods in your stub classes -- you may need to explicitly add objects + * to the cache in order to ensure that the same objects are always returned by doSelect*() + * and retrieveByPK*() calls. + * + * @param CcTrans $value A CcTrans object. + * @param string $key (optional) key to use for instance map (for performance boost if key was already calculated externally). + */ + public static function addInstanceToPool(CcTrans $obj, $key = null) + { + if (Propel::isInstancePoolingEnabled()) { + if ($key === null) { + $key = (string) $obj->getId(); + } // if key === null + self::$instances[$key] = $obj; + } + } + + /** + * Removes an object from the instance pool. + * + * Propel keeps cached copies of objects in an instance pool when they are retrieved + * from the database. In some cases -- especially when you override doDelete + * methods in your stub classes -- you may need to explicitly remove objects + * from the cache in order to prevent returning objects that no longer exist. + * + * @param mixed $value A CcTrans object or a primary key value. + */ + public static function removeInstanceFromPool($value) + { + if (Propel::isInstancePoolingEnabled() && $value !== null) { + if (is_object($value) && $value instanceof CcTrans) { + $key = (string) $value->getId(); + } elseif (is_scalar($value)) { + // assume we've been passed a primary key + $key = (string) $value; + } else { + $e = new PropelException("Invalid value passed to removeInstanceFromPool(). Expected primary key or CcTrans object; got " . (is_object($value) ? get_class($value) . ' object.' : var_export($value,true))); + throw $e; + } + + unset(self::$instances[$key]); + } + } // removeInstanceFromPool() + + /** + * Retrieves a string version of the primary key from the DB resultset row that can be used to uniquely identify a row in this table. + * + * For tables with a single-column primary key, that simple pkey value will be returned. For tables with + * a multi-column primary key, a serialize()d version of the primary key will be returned. + * + * @param string $key The key (@see getPrimaryKeyHash()) for this instance. + * @return CcTrans Found object or NULL if 1) no instance exists for specified key or 2) instance pooling has been disabled. + * @see getPrimaryKeyHash() + */ + public static function getInstanceFromPool($key) + { + if (Propel::isInstancePoolingEnabled()) { + if (isset(self::$instances[$key])) { + return self::$instances[$key]; + } + } + return null; // just to be explicit + } + + /** + * Clear the instance pool. + * + * @return void + */ + public static function clearInstancePool() + { + self::$instances = array(); + } + + /** + * Method to invalidate the instance pool of all tables related to cc_trans + * by a foreign key with ON DELETE CASCADE + */ + public static function clearRelatedInstancePool() + { + } + + /** + * Retrieves a string version of the primary key from the DB resultset row that can be used to uniquely identify a row in this table. + * + * For tables with a single-column primary key, that simple pkey value will be returned. For tables with + * a multi-column primary key, a serialize()d version of the primary key will be returned. + * + * @param array $row PropelPDO resultset row. + * @param int $startcol The 0-based offset for reading from the resultset row. + * @return string A string version of PK or NULL if the components of primary key in result array are all null. + */ + public static function getPrimaryKeyHashFromRow($row, $startcol = 0) + { + // If the PK cannot be derived from the row, return NULL. + if ($row[$startcol] === null) { + return null; + } + return (string) $row[$startcol]; + } + + /** + * Retrieves the primary key from the DB resultset row + * For tables with a single-column primary key, that simple pkey value will be returned. For tables with + * a multi-column primary key, an array of the primary key columns will be returned. + * + * @param array $row PropelPDO resultset row. + * @param int $startcol The 0-based offset for reading from the resultset row. + * @return mixed The primary key of the row + */ + public static function getPrimaryKeyFromRow($row, $startcol = 0) + { + return (int) $row[$startcol]; + } + + /** + * The returned array will contain objects of the default type or + * objects that inherit from the default. + * + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function populateObjects(PDOStatement $stmt) + { + $results = array(); + + // set the class once to avoid overhead in the loop + $cls = CcTransPeer::getOMClass(false); + // populate the object(s) + while ($row = $stmt->fetch(PDO::FETCH_NUM)) { + $key = CcTransPeer::getPrimaryKeyHashFromRow($row, 0); + if (null !== ($obj = CcTransPeer::getInstanceFromPool($key))) { + // We no longer rehydrate the object, since this can cause data loss. + // See http://www.propelorm.org/ticket/509 + // $obj->hydrate($row, 0, true); // rehydrate + $results[] = $obj; + } else { + $obj = new $cls(); + $obj->hydrate($row); + $results[] = $obj; + CcTransPeer::addInstanceToPool($obj, $key); + } // if key exists + } + $stmt->closeCursor(); + return $results; + } + /** + * Populates an object of the default type or an object that inherit from the default. + * + * @param array $row PropelPDO resultset row. + * @param int $startcol The 0-based offset for reading from the resultset row. + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + * @return array (CcTrans object, last column rank) + */ + public static function populateObject($row, $startcol = 0) + { + $key = CcTransPeer::getPrimaryKeyHashFromRow($row, $startcol); + if (null !== ($obj = CcTransPeer::getInstanceFromPool($key))) { + // We no longer rehydrate the object, since this can cause data loss. + // See http://www.propelorm.org/ticket/509 + // $obj->hydrate($row, $startcol, true); // rehydrate + $col = $startcol + CcTransPeer::NUM_COLUMNS; + } else { + $cls = CcTransPeer::OM_CLASS; + $obj = new $cls(); + $col = $obj->hydrate($row, $startcol); + CcTransPeer::addInstanceToPool($obj, $key); + } + return array($obj, $col); + } + /** + * Returns the TableMap related to this peer. + * This method is not needed for general use but a specific application could have a need. + * @return TableMap + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function getTableMap() + { + return Propel::getDatabaseMap(self::DATABASE_NAME)->getTable(self::TABLE_NAME); + } + + /** + * Add a TableMap instance to the database for this peer class. + */ + public static function buildTableMap() + { + $dbMap = Propel::getDatabaseMap(BaseCcTransPeer::DATABASE_NAME); + if (!$dbMap->hasTable(BaseCcTransPeer::TABLE_NAME)) + { + $dbMap->addTableObject(new CcTransTableMap()); + } + } + + /** + * The class that the Peer will make instances of. + * + * If $withPrefix is true, the returned path + * uses a dot-path notation which is tranalted into a path + * relative to a location on the PHP include_path. + * (e.g. path.to.MyClass -> 'path/to/MyClass.php') + * + * @param boolean $withPrefix Whether or not to return the path with the class name + * @return string path.to.ClassName + */ + public static function getOMClass($withPrefix = true) + { + return $withPrefix ? CcTransPeer::CLASS_DEFAULT : CcTransPeer::OM_CLASS; + } + + /** + * Method perform an INSERT on the database, given a CcTrans or Criteria object. + * + * @param mixed $values Criteria or CcTrans object containing data that is used to create the INSERT statement. + * @param PropelPDO $con the PropelPDO connection to use + * @return mixed The new primary key. + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function doInsert($values, PropelPDO $con = null) + { + if ($con === null) { + $con = Propel::getConnection(CcTransPeer::DATABASE_NAME, Propel::CONNECTION_WRITE); + } + + if ($values instanceof Criteria) { + $criteria = clone $values; // rename for clarity + } else { + $criteria = $values->buildCriteria(); // build Criteria from CcTrans object + } + + if ($criteria->containsKey(CcTransPeer::ID) && $criteria->keyContainsValue(CcTransPeer::ID) ) { + throw new PropelException('Cannot insert a value for auto-increment primary key ('.CcTransPeer::ID.')'); + } + + + // Set the correct dbName + $criteria->setDbName(self::DATABASE_NAME); + + try { + // use transaction because $criteria could contain info + // for more than one table (I guess, conceivably) + $con->beginTransaction(); + $pk = BasePeer::doInsert($criteria, $con); + $con->commit(); + } catch(PropelException $e) { + $con->rollBack(); + throw $e; + } + + return $pk; + } + + /** + * Method perform an UPDATE on the database, given a CcTrans or Criteria object. + * + * @param mixed $values Criteria or CcTrans object containing data that is used to create the UPDATE statement. + * @param PropelPDO $con The connection to use (specify PropelPDO connection object to exert more control over transactions). + * @return int The number of affected rows (if supported by underlying database driver). + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function doUpdate($values, PropelPDO $con = null) + { + if ($con === null) { + $con = Propel::getConnection(CcTransPeer::DATABASE_NAME, Propel::CONNECTION_WRITE); + } + + $selectCriteria = new Criteria(self::DATABASE_NAME); + + if ($values instanceof Criteria) { + $criteria = clone $values; // rename for clarity + + $comparison = $criteria->getComparison(CcTransPeer::ID); + $value = $criteria->remove(CcTransPeer::ID); + if ($value) { + $selectCriteria->add(CcTransPeer::ID, $value, $comparison); + } else { + $selectCriteria->setPrimaryTableName(CcTransPeer::TABLE_NAME); + } + + } else { // $values is CcTrans object + $criteria = $values->buildCriteria(); // gets full criteria + $selectCriteria = $values->buildPkeyCriteria(); // gets criteria w/ primary key(s) + } + + // set the correct dbName + $criteria->setDbName(self::DATABASE_NAME); + + return BasePeer::doUpdate($selectCriteria, $criteria, $con); + } + + /** + * Method to DELETE all rows from the cc_trans table. + * + * @return int The number of affected rows (if supported by underlying database driver). + */ + public static function doDeleteAll($con = null) + { + if ($con === null) { + $con = Propel::getConnection(CcTransPeer::DATABASE_NAME, Propel::CONNECTION_WRITE); + } + $affectedRows = 0; // initialize var to track total num of affected rows + try { + // use transaction because $criteria could contain info + // for more than one table or we could emulating ON DELETE CASCADE, etc. + $con->beginTransaction(); + $affectedRows += BasePeer::doDeleteAll(CcTransPeer::TABLE_NAME, $con, CcTransPeer::DATABASE_NAME); + // Because this db requires some delete cascade/set null emulation, we have to + // clear the cached instance *after* the emulation has happened (since + // instances get re-added by the select statement contained therein). + CcTransPeer::clearInstancePool(); + CcTransPeer::clearRelatedInstancePool(); + $con->commit(); + return $affectedRows; + } catch (PropelException $e) { + $con->rollBack(); + throw $e; + } + } + + /** + * Method perform a DELETE on the database, given a CcTrans or Criteria object OR a primary key value. + * + * @param mixed $values Criteria or CcTrans object or primary key or array of primary keys + * which is used to create the DELETE statement + * @param PropelPDO $con the connection to use + * @return int The number of affected rows (if supported by underlying database driver). This includes CASCADE-related rows + * if supported by native driver or if emulated using Propel. + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function doDelete($values, PropelPDO $con = null) + { + if ($con === null) { + $con = Propel::getConnection(CcTransPeer::DATABASE_NAME, Propel::CONNECTION_WRITE); + } + + if ($values instanceof Criteria) { + // invalidate the cache for all objects of this type, since we have no + // way of knowing (without running a query) what objects should be invalidated + // from the cache based on this Criteria. + CcTransPeer::clearInstancePool(); + // rename for clarity + $criteria = clone $values; + } elseif ($values instanceof CcTrans) { // it's a model object + // invalidate the cache for this single object + CcTransPeer::removeInstanceFromPool($values); + // create criteria based on pk values + $criteria = $values->buildPkeyCriteria(); + } else { // it's a primary key, or an array of pks + $criteria = new Criteria(self::DATABASE_NAME); + $criteria->add(CcTransPeer::ID, (array) $values, Criteria::IN); + // invalidate the cache for this object(s) + foreach ((array) $values as $singleval) { + CcTransPeer::removeInstanceFromPool($singleval); + } + } + + // Set the correct dbName + $criteria->setDbName(self::DATABASE_NAME); + + $affectedRows = 0; // initialize var to track total num of affected rows + + try { + // use transaction because $criteria could contain info + // for more than one table or we could emulating ON DELETE CASCADE, etc. + $con->beginTransaction(); + + $affectedRows += BasePeer::doDelete($criteria, $con); + CcTransPeer::clearRelatedInstancePool(); + $con->commit(); + return $affectedRows; + } catch (PropelException $e) { + $con->rollBack(); + throw $e; + } + } + + /** + * Validates all modified columns of given CcTrans object. + * If parameter $columns is either a single column name or an array of column names + * than only those columns are validated. + * + * NOTICE: This does not apply to primary or foreign keys for now. + * + * @param CcTrans $obj The object to validate. + * @param mixed $cols Column name or array of column names. + * + * @return mixed TRUE if all columns are valid or the error message of the first invalid column. + */ + public static function doValidate(CcTrans $obj, $cols = null) + { + $columns = array(); + + if ($cols) { + $dbMap = Propel::getDatabaseMap(CcTransPeer::DATABASE_NAME); + $tableMap = $dbMap->getTable(CcTransPeer::TABLE_NAME); + + if (! is_array($cols)) { + $cols = array($cols); + } + + foreach ($cols as $colName) { + if ($tableMap->containsColumn($colName)) { + $get = 'get' . $tableMap->getColumn($colName)->getPhpName(); + $columns[$colName] = $obj->$get(); + } + } + } else { + + } + + return BasePeer::doValidate(CcTransPeer::DATABASE_NAME, CcTransPeer::TABLE_NAME, $columns); + } + + /** + * Retrieve a single object by pkey. + * + * @param int $pk the primary key. + * @param PropelPDO $con the connection to use + * @return CcTrans + */ + public static function retrieveByPK($pk, PropelPDO $con = null) + { + + if (null !== ($obj = CcTransPeer::getInstanceFromPool((string) $pk))) { + return $obj; + } + + if ($con === null) { + $con = Propel::getConnection(CcTransPeer::DATABASE_NAME, Propel::CONNECTION_READ); + } + + $criteria = new Criteria(CcTransPeer::DATABASE_NAME); + $criteria->add(CcTransPeer::ID, $pk); + + $v = CcTransPeer::doSelect($criteria, $con); + + return !empty($v) > 0 ? $v[0] : null; + } + + /** + * Retrieve multiple objects by pkey. + * + * @param array $pks List of primary keys + * @param PropelPDO $con the connection to use + * @throws PropelException Any exceptions caught during processing will be + * rethrown wrapped into a PropelException. + */ + public static function retrieveByPKs($pks, PropelPDO $con = null) + { + if ($con === null) { + $con = Propel::getConnection(CcTransPeer::DATABASE_NAME, Propel::CONNECTION_READ); + } + + $objs = null; + if (empty($pks)) { + $objs = array(); + } else { + $criteria = new Criteria(CcTransPeer::DATABASE_NAME); + $criteria->add(CcTransPeer::ID, $pks, Criteria::IN); + $objs = CcTransPeer::doSelect($criteria, $con); + } + return $objs; + } + +} // BaseCcTransPeer + +// This is the static code needed to register the TableMap for this table with the main Propel class. +// +BaseCcTransPeer::buildTableMap(); + diff --git a/application/models/campcaster/om/BaseCcTransQuery.php b/application/models/campcaster/om/BaseCcTransQuery.php new file mode 100644 index 000000000..e00396ef6 --- /dev/null +++ b/application/models/campcaster/om/BaseCcTransQuery.php @@ -0,0 +1,826 @@ +setModelAlias($modelAlias); + } + if ($criteria instanceof Criteria) { + $query->mergeWith($criteria); + } + return $query; + } + + /** + * Find object by primary key + * Use instance pooling to avoid a database query if the object exists + * + * $obj = $c->findPk(12, $con); + * + * @param mixed $key Primary key to use for the query + * @param PropelPDO $con an optional connection object + * + * @return CcTrans|array|mixed the result, formatted by the current formatter + */ + public function findPk($key, $con = null) + { + if ((null !== ($obj = CcTransPeer::getInstanceFromPool((string) $key))) && $this->getFormatter()->isObjectFormatter()) { + // the object is alredy in the instance pool + return $obj; + } else { + // the object has not been requested yet, or the formatter is not an object formatter + $criteria = $this->isKeepQuery() ? clone $this : $this; + $stmt = $criteria + ->filterByPrimaryKey($key) + ->getSelectStatement($con); + return $criteria->getFormatter()->init($criteria)->formatOne($stmt); + } + } + + /** + * Find objects by primary key + * + * $objs = $c->findPks(array(12, 56, 832), $con); + * + * @param array $keys Primary keys to use for the query + * @param PropelPDO $con an optional connection object + * + * @return PropelObjectCollection|array|mixed the list of results, formatted by the current formatter + */ + public function findPks($keys, $con = null) + { + $criteria = $this->isKeepQuery() ? clone $this : $this; + return $this + ->filterByPrimaryKeys($keys) + ->find($con); + } + + /** + * Filter the query by primary key + * + * @param mixed $key Primary key to use for the query + * + * @return CcTransQuery The current query, for fluid interface + */ + public function filterByPrimaryKey($key) + { + return $this->addUsingAlias(CcTransPeer::ID, $key, Criteria::EQUAL); + } + + /** + * Filter the query by a list of primary keys + * + * @param array $keys The list of primary key to use for the query + * + * @return CcTransQuery The current query, for fluid interface + */ + public function filterByPrimaryKeys($keys) + { + return $this->addUsingAlias(CcTransPeer::ID, $keys, Criteria::IN); + } + + /** + * Filter the query on the id column + * + * @param int|array $id The value to use as filter. + * Accepts an associative array('min' => $minValue, 'max' => $maxValue) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcTransQuery The current query, for fluid interface + */ + public function filterById($id = null, $comparison = null) + { + if (is_array($id) && null === $comparison) { + $comparison = Criteria::IN; + } + return $this->addUsingAlias(CcTransPeer::ID, $id, $comparison); + } + + /** + * Filter the query on the trtok column + * + * @param string $trtok The value to use as filter. + * Accepts wildcards (* and % trigger a LIKE) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcTransQuery The current query, for fluid interface + */ + public function filterByTrtok($trtok = null, $comparison = null) + { + if (null === $comparison) { + if (is_array($trtok)) { + $comparison = Criteria::IN; + } elseif (preg_match('/[\%\*]/', $trtok)) { + $trtok = str_replace('*', '%', $trtok); + $comparison = Criteria::LIKE; + } + } + return $this->addUsingAlias(CcTransPeer::TRTOK, $trtok, $comparison); + } + + /** + * Filter the query on the direction column + * + * @param string $direction The value to use as filter. + * Accepts wildcards (* and % trigger a LIKE) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcTransQuery The current query, for fluid interface + */ + public function filterByDirection($direction = null, $comparison = null) + { + if (null === $comparison) { + if (is_array($direction)) { + $comparison = Criteria::IN; + } elseif (preg_match('/[\%\*]/', $direction)) { + $direction = str_replace('*', '%', $direction); + $comparison = Criteria::LIKE; + } + } + return $this->addUsingAlias(CcTransPeer::DIRECTION, $direction, $comparison); + } + + /** + * Filter the query on the state column + * + * @param string $state The value to use as filter. + * Accepts wildcards (* and % trigger a LIKE) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcTransQuery The current query, for fluid interface + */ + public function filterByState($state = null, $comparison = null) + { + if (null === $comparison) { + if (is_array($state)) { + $comparison = Criteria::IN; + } elseif (preg_match('/[\%\*]/', $state)) { + $state = str_replace('*', '%', $state); + $comparison = Criteria::LIKE; + } + } + return $this->addUsingAlias(CcTransPeer::STATE, $state, $comparison); + } + + /** + * Filter the query on the trtype column + * + * @param string $trtype The value to use as filter. + * Accepts wildcards (* and % trigger a LIKE) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcTransQuery The current query, for fluid interface + */ + public function filterByTrtype($trtype = null, $comparison = null) + { + if (null === $comparison) { + if (is_array($trtype)) { + $comparison = Criteria::IN; + } elseif (preg_match('/[\%\*]/', $trtype)) { + $trtype = str_replace('*', '%', $trtype); + $comparison = Criteria::LIKE; + } + } + return $this->addUsingAlias(CcTransPeer::TRTYPE, $trtype, $comparison); + } + + /** + * Filter the query on the lock column + * + * @param string $lock The value to use as filter. + * Accepts wildcards (* and % trigger a LIKE) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcTransQuery The current query, for fluid interface + */ + public function filterByLock($lock = null, $comparison = null) + { + if (null === $comparison) { + if (is_array($lock)) { + $comparison = Criteria::IN; + } elseif (preg_match('/[\%\*]/', $lock)) { + $lock = str_replace('*', '%', $lock); + $comparison = Criteria::LIKE; + } + } + return $this->addUsingAlias(CcTransPeer::LOCK, $lock, $comparison); + } + + /** + * Filter the query on the target column + * + * @param string $target The value to use as filter. + * Accepts wildcards (* and % trigger a LIKE) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcTransQuery The current query, for fluid interface + */ + public function filterByTarget($target = null, $comparison = null) + { + if (null === $comparison) { + if (is_array($target)) { + $comparison = Criteria::IN; + } elseif (preg_match('/[\%\*]/', $target)) { + $target = str_replace('*', '%', $target); + $comparison = Criteria::LIKE; + } + } + return $this->addUsingAlias(CcTransPeer::TARGET, $target, $comparison); + } + + /** + * Filter the query on the rtrtok column + * + * @param string $rtrtok The value to use as filter. + * Accepts wildcards (* and % trigger a LIKE) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcTransQuery The current query, for fluid interface + */ + public function filterByRtrtok($rtrtok = null, $comparison = null) + { + if (null === $comparison) { + if (is_array($rtrtok)) { + $comparison = Criteria::IN; + } elseif (preg_match('/[\%\*]/', $rtrtok)) { + $rtrtok = str_replace('*', '%', $rtrtok); + $comparison = Criteria::LIKE; + } + } + return $this->addUsingAlias(CcTransPeer::RTRTOK, $rtrtok, $comparison); + } + + /** + * Filter the query on the mdtrtok column + * + * @param string $mdtrtok The value to use as filter. + * Accepts wildcards (* and % trigger a LIKE) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcTransQuery The current query, for fluid interface + */ + public function filterByMdtrtok($mdtrtok = null, $comparison = null) + { + if (null === $comparison) { + if (is_array($mdtrtok)) { + $comparison = Criteria::IN; + } elseif (preg_match('/[\%\*]/', $mdtrtok)) { + $mdtrtok = str_replace('*', '%', $mdtrtok); + $comparison = Criteria::LIKE; + } + } + return $this->addUsingAlias(CcTransPeer::MDTRTOK, $mdtrtok, $comparison); + } + + /** + * Filter the query on the gunid column + * + * @param string $gunid The value to use as filter. + * Accepts wildcards (* and % trigger a LIKE) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcTransQuery The current query, for fluid interface + */ + public function filterByGunid($gunid = null, $comparison = null) + { + if (null === $comparison) { + if (is_array($gunid)) { + $comparison = Criteria::IN; + } elseif (preg_match('/[\%\*]/', $gunid)) { + $gunid = str_replace('*', '%', $gunid); + $comparison = Criteria::LIKE; + } + } + return $this->addUsingAlias(CcTransPeer::GUNID, $gunid, $comparison); + } + + /** + * Filter the query on the pdtoken column + * + * @param string|array $pdtoken The value to use as filter. + * Accepts an associative array('min' => $minValue, 'max' => $maxValue) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcTransQuery The current query, for fluid interface + */ + public function filterByPdtoken($pdtoken = null, $comparison = null) + { + if (is_array($pdtoken)) { + $useMinMax = false; + if (isset($pdtoken['min'])) { + $this->addUsingAlias(CcTransPeer::PDTOKEN, $pdtoken['min'], Criteria::GREATER_EQUAL); + $useMinMax = true; + } + if (isset($pdtoken['max'])) { + $this->addUsingAlias(CcTransPeer::PDTOKEN, $pdtoken['max'], Criteria::LESS_EQUAL); + $useMinMax = true; + } + if ($useMinMax) { + return $this; + } + if (null === $comparison) { + $comparison = Criteria::IN; + } + } + return $this->addUsingAlias(CcTransPeer::PDTOKEN, $pdtoken, $comparison); + } + + /** + * Filter the query on the url column + * + * @param string $url The value to use as filter. + * Accepts wildcards (* and % trigger a LIKE) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcTransQuery The current query, for fluid interface + */ + public function filterByUrl($url = null, $comparison = null) + { + if (null === $comparison) { + if (is_array($url)) { + $comparison = Criteria::IN; + } elseif (preg_match('/[\%\*]/', $url)) { + $url = str_replace('*', '%', $url); + $comparison = Criteria::LIKE; + } + } + return $this->addUsingAlias(CcTransPeer::URL, $url, $comparison); + } + + /** + * Filter the query on the localfile column + * + * @param string $localfile The value to use as filter. + * Accepts wildcards (* and % trigger a LIKE) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcTransQuery The current query, for fluid interface + */ + public function filterByLocalfile($localfile = null, $comparison = null) + { + if (null === $comparison) { + if (is_array($localfile)) { + $comparison = Criteria::IN; + } elseif (preg_match('/[\%\*]/', $localfile)) { + $localfile = str_replace('*', '%', $localfile); + $comparison = Criteria::LIKE; + } + } + return $this->addUsingAlias(CcTransPeer::LOCALFILE, $localfile, $comparison); + } + + /** + * Filter the query on the fname column + * + * @param string $fname The value to use as filter. + * Accepts wildcards (* and % trigger a LIKE) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcTransQuery The current query, for fluid interface + */ + public function filterByFname($fname = null, $comparison = null) + { + if (null === $comparison) { + if (is_array($fname)) { + $comparison = Criteria::IN; + } elseif (preg_match('/[\%\*]/', $fname)) { + $fname = str_replace('*', '%', $fname); + $comparison = Criteria::LIKE; + } + } + return $this->addUsingAlias(CcTransPeer::FNAME, $fname, $comparison); + } + + /** + * Filter the query on the title column + * + * @param string $title The value to use as filter. + * Accepts wildcards (* and % trigger a LIKE) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcTransQuery The current query, for fluid interface + */ + public function filterByTitle($title = null, $comparison = null) + { + if (null === $comparison) { + if (is_array($title)) { + $comparison = Criteria::IN; + } elseif (preg_match('/[\%\*]/', $title)) { + $title = str_replace('*', '%', $title); + $comparison = Criteria::LIKE; + } + } + return $this->addUsingAlias(CcTransPeer::TITLE, $title, $comparison); + } + + /** + * Filter the query on the expectedsum column + * + * @param string $expectedsum The value to use as filter. + * Accepts wildcards (* and % trigger a LIKE) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcTransQuery The current query, for fluid interface + */ + public function filterByExpectedsum($expectedsum = null, $comparison = null) + { + if (null === $comparison) { + if (is_array($expectedsum)) { + $comparison = Criteria::IN; + } elseif (preg_match('/[\%\*]/', $expectedsum)) { + $expectedsum = str_replace('*', '%', $expectedsum); + $comparison = Criteria::LIKE; + } + } + return $this->addUsingAlias(CcTransPeer::EXPECTEDSUM, $expectedsum, $comparison); + } + + /** + * Filter the query on the realsum column + * + * @param string $realsum The value to use as filter. + * Accepts wildcards (* and % trigger a LIKE) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcTransQuery The current query, for fluid interface + */ + public function filterByRealsum($realsum = null, $comparison = null) + { + if (null === $comparison) { + if (is_array($realsum)) { + $comparison = Criteria::IN; + } elseif (preg_match('/[\%\*]/', $realsum)) { + $realsum = str_replace('*', '%', $realsum); + $comparison = Criteria::LIKE; + } + } + return $this->addUsingAlias(CcTransPeer::REALSUM, $realsum, $comparison); + } + + /** + * Filter the query on the expectedsize column + * + * @param int|array $expectedsize The value to use as filter. + * Accepts an associative array('min' => $minValue, 'max' => $maxValue) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcTransQuery The current query, for fluid interface + */ + public function filterByExpectedsize($expectedsize = null, $comparison = null) + { + if (is_array($expectedsize)) { + $useMinMax = false; + if (isset($expectedsize['min'])) { + $this->addUsingAlias(CcTransPeer::EXPECTEDSIZE, $expectedsize['min'], Criteria::GREATER_EQUAL); + $useMinMax = true; + } + if (isset($expectedsize['max'])) { + $this->addUsingAlias(CcTransPeer::EXPECTEDSIZE, $expectedsize['max'], Criteria::LESS_EQUAL); + $useMinMax = true; + } + if ($useMinMax) { + return $this; + } + if (null === $comparison) { + $comparison = Criteria::IN; + } + } + return $this->addUsingAlias(CcTransPeer::EXPECTEDSIZE, $expectedsize, $comparison); + } + + /** + * Filter the query on the realsize column + * + * @param int|array $realsize The value to use as filter. + * Accepts an associative array('min' => $minValue, 'max' => $maxValue) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcTransQuery The current query, for fluid interface + */ + public function filterByRealsize($realsize = null, $comparison = null) + { + if (is_array($realsize)) { + $useMinMax = false; + if (isset($realsize['min'])) { + $this->addUsingAlias(CcTransPeer::REALSIZE, $realsize['min'], Criteria::GREATER_EQUAL); + $useMinMax = true; + } + if (isset($realsize['max'])) { + $this->addUsingAlias(CcTransPeer::REALSIZE, $realsize['max'], Criteria::LESS_EQUAL); + $useMinMax = true; + } + if ($useMinMax) { + return $this; + } + if (null === $comparison) { + $comparison = Criteria::IN; + } + } + return $this->addUsingAlias(CcTransPeer::REALSIZE, $realsize, $comparison); + } + + /** + * Filter the query on the uid column + * + * @param int|array $uid The value to use as filter. + * Accepts an associative array('min' => $minValue, 'max' => $maxValue) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcTransQuery The current query, for fluid interface + */ + public function filterByUid($uid = null, $comparison = null) + { + if (is_array($uid)) { + $useMinMax = false; + if (isset($uid['min'])) { + $this->addUsingAlias(CcTransPeer::UID, $uid['min'], Criteria::GREATER_EQUAL); + $useMinMax = true; + } + if (isset($uid['max'])) { + $this->addUsingAlias(CcTransPeer::UID, $uid['max'], Criteria::LESS_EQUAL); + $useMinMax = true; + } + if ($useMinMax) { + return $this; + } + if (null === $comparison) { + $comparison = Criteria::IN; + } + } + return $this->addUsingAlias(CcTransPeer::UID, $uid, $comparison); + } + + /** + * Filter the query on the errmsg column + * + * @param string $errmsg The value to use as filter. + * Accepts wildcards (* and % trigger a LIKE) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcTransQuery The current query, for fluid interface + */ + public function filterByErrmsg($errmsg = null, $comparison = null) + { + if (null === $comparison) { + if (is_array($errmsg)) { + $comparison = Criteria::IN; + } elseif (preg_match('/[\%\*]/', $errmsg)) { + $errmsg = str_replace('*', '%', $errmsg); + $comparison = Criteria::LIKE; + } + } + return $this->addUsingAlias(CcTransPeer::ERRMSG, $errmsg, $comparison); + } + + /** + * Filter the query on the jobpid column + * + * @param int|array $jobpid The value to use as filter. + * Accepts an associative array('min' => $minValue, 'max' => $maxValue) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcTransQuery The current query, for fluid interface + */ + public function filterByJobpid($jobpid = null, $comparison = null) + { + if (is_array($jobpid)) { + $useMinMax = false; + if (isset($jobpid['min'])) { + $this->addUsingAlias(CcTransPeer::JOBPID, $jobpid['min'], Criteria::GREATER_EQUAL); + $useMinMax = true; + } + if (isset($jobpid['max'])) { + $this->addUsingAlias(CcTransPeer::JOBPID, $jobpid['max'], Criteria::LESS_EQUAL); + $useMinMax = true; + } + if ($useMinMax) { + return $this; + } + if (null === $comparison) { + $comparison = Criteria::IN; + } + } + return $this->addUsingAlias(CcTransPeer::JOBPID, $jobpid, $comparison); + } + + /** + * Filter the query on the start column + * + * @param string|array $start The value to use as filter. + * Accepts an associative array('min' => $minValue, 'max' => $maxValue) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcTransQuery The current query, for fluid interface + */ + public function filterByStart($start = null, $comparison = null) + { + if (is_array($start)) { + $useMinMax = false; + if (isset($start['min'])) { + $this->addUsingAlias(CcTransPeer::START, $start['min'], Criteria::GREATER_EQUAL); + $useMinMax = true; + } + if (isset($start['max'])) { + $this->addUsingAlias(CcTransPeer::START, $start['max'], Criteria::LESS_EQUAL); + $useMinMax = true; + } + if ($useMinMax) { + return $this; + } + if (null === $comparison) { + $comparison = Criteria::IN; + } + } + return $this->addUsingAlias(CcTransPeer::START, $start, $comparison); + } + + /** + * Filter the query on the ts column + * + * @param string|array $ts The value to use as filter. + * Accepts an associative array('min' => $minValue, 'max' => $maxValue) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcTransQuery The current query, for fluid interface + */ + public function filterByTs($ts = null, $comparison = null) + { + if (is_array($ts)) { + $useMinMax = false; + if (isset($ts['min'])) { + $this->addUsingAlias(CcTransPeer::TS, $ts['min'], Criteria::GREATER_EQUAL); + $useMinMax = true; + } + if (isset($ts['max'])) { + $this->addUsingAlias(CcTransPeer::TS, $ts['max'], Criteria::LESS_EQUAL); + $useMinMax = true; + } + if ($useMinMax) { + return $this; + } + if (null === $comparison) { + $comparison = Criteria::IN; + } + } + return $this->addUsingAlias(CcTransPeer::TS, $ts, $comparison); + } + + /** + * Exclude object from result + * + * @param CcTrans $ccTrans Object to remove from the list of results + * + * @return CcTransQuery The current query, for fluid interface + */ + public function prune($ccTrans = null) + { + if ($ccTrans) { + $this->addUsingAlias(CcTransPeer::ID, $ccTrans->getId(), Criteria::NOT_EQUAL); + } + + return $this; + } + +} // BaseCcTransQuery diff --git a/application/models/configure b/application/models/configure new file mode 100755 index 000000000..26ccda218 --- /dev/null +++ b/application/models/configure @@ -0,0 +1,47 @@ +#!/bin/sh +#------------------------------------------------------------------------------- +# Copyright (c) 2010 Sourcefabric O.P.S. +# +# This file is part of the Campcaster project. +# http://campcaster.campware.org/ +# +# Campcaster is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# Campcaster is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Campcaster; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +#------------------------------------------------------------------------------- + +#------------------------------------------------------------------------------- +# Run this script to configure the environment. +# +# This script in effect calls the real automake / autoconf configure script +#------------------------------------------------------------------------------- + +# assume we're in $basedir +reldir=`dirname $0` +basedir=`cd $reldir; pwd;` +test -z "$basedir" && basedir=. + +bindir=$basedir/bin +tmpdir=$basedir/tmp + + +autogen=$bindir/autogen.sh +configure=$tmpdir/configure + +if [ ! -x $configure ]; then + (cd $basedir && $autogen $*) +fi + +(cd $tmpdir && $configure $*) + diff --git a/application/models/cron/Cron.php b/application/models/cron/Cron.php new file mode 100755 index 000000000..fba702b62 --- /dev/null +++ b/application/models/cron/Cron.php @@ -0,0 +1,214 @@ + + * $cron = new Cron(); + * $access = $cron->openCrontab('write'); + * if ($access != 'write') { + * do { + * $access = $cron->forceWriteable(); + * } while ($access != 'write'); + * } + * $cron->addCronJob('*','*','*','*','*', + * 'ClassName', + * array('first','secound','third') + * ); + * $cron->closeCrontab(); + *
    + * @package Campcaster + * @subpackage StorageServer.Cron + */ +class Cron { + /** + * @var Crontab + */ + public $ct; + + /** + * @var array This array created with getCommand() function + */ + private $params; + + /** + * @var string available values: read | write + */ + private $ctAccess = 'read'; + + private $lockfile; + private $cronfile; + private $paramdir; + private $cronUserName; + + /** + * Constructor + */ + function Cron() { + global $CC_CONFIG; + $this->lockfile = $CC_CONFIG['lockfile']; + $this->cronfile = $CC_CONFIG['cronfile']; + $this->paramdir = $CC_CONFIG['paramdir']; + $this->cronUserName = $CC_CONFIG['cronUserName']; + } + + + /* ==================================================== Cronjob functions */ + /** + * Add a cronjob to the crontab + * + * @access public + * @param string $m minute + * @param string $h hour + * @param string $dom day of month + * @param string $mo month + * @param string $dow day of week + * @param string $className name of class, which's execute() is called by croncall.php + * @param string $params the parameter(s) + * @return bool true if success else PEAR error. + */ + function addCronJob($m, $h, $dom, $mo, $dow, $className, $params) + { + if ($this->ctAccess == 'write') { + $this->ct->addCron($m, $h, $dom, $mo, $dow, + $this->getCommand($className, $params)); + return true; + } else { + return PEAR::raiseError('CronJob::addCronJob : '. + 'The crontab is not writable'); + } + } + + /** + * This function return with the active cronjobs + * + * @access public + * @return array array of cronjob struct + */ + function listCronJob() + { + return $this->ct->getByType(CRON_CMD); + } + + /** + * Remove a cronjob. + * + * @access public + * @param int $index index of the cronjobs' array. + * @return bool true if success else PEAR error. + */ + function removeCronJob($index) + { + if ($this->ctAccess == 'write') { + $this->crontab->delEntry($index); + return true; + } else { + return PEAR::raiseError('CronJob::removeCronJob : '. + 'The crontab is not writable'); + } + } + + /* ==================================================== Crontab functions */ + /** + * Open the crontab + * + * @access public + * @param string $access only for listing 'read', for add and delete 'write' + * @return string sucessed access - available values read | write + */ + function openCrontab($access = 'read') + { + $access = strtolower($access); + $this->ct = new Crontab($this->cronUserName); + if ($access == 'write' && + $this->isCrontabWritable() && + $this->lockCrontab()) { + $this->ctAccess = $access; + } else { + $this->ctAccess = 'read'; + } + return $this->ctAccess; + } + + /** + * Close the crontab + * + * @access public + * @return bool true if everything is ok, false is the lock file can't delete + */ + function closeCrontab() + { + if ($this->ctAccess == 'write') { + $this->ct->writeCrontab(); + } + return $this->ctAccess == 'write' ? $this->unlockCrontab() : true; + } + + /** + * Check the crontab is writable + * + * @access private + * @return bool + */ + function isCrontabWritable() + { + return !is_file($this->lockfile); + } + + /** + * Try to lock the crontab + * + * @access private + * @return bool true if the locking is success + */ + function lockCrontab() + { + return @touch($this->lockfile); + } + + /** + * Try to unlock the crontab + * + * @access private + * @return bool true if the unlocking is success + */ + function unlockCrontab() + { + return unlink($this->lockfile); + } + + /** + * If the crontab opened with read access. This function force set + * the access to write. + * + * @access public + * @return bool true if the setting is success + */ + function forceWriteable() + { + if ($this->isCrontabWritable() && $this->lockCrontab()) { + $this->ctAccess = 'write'; + return true; + } + return false; + } + + /* ======================================================= Misc functions */ + /** + * Get the shell command for the cronjob + * + * @param string $className name of the class what is called by croncall.php + * @param mixed $params with this parameter could be called the execute() of class + * @return string shell command + */ + function getCommand($className, $params) + { + $this->params = array ( + 'class' => $className, + 'params' => $params + ); + return $this->cronfile.' "'.str_replace('"','\"',serialize($this->params)).'"'; + } +} +?> \ No newline at end of file diff --git a/application/models/cron/CronJob.php b/application/models/cron/CronJob.php new file mode 100755 index 000000000..bcd1c82d6 --- /dev/null +++ b/application/models/cron/CronJob.php @@ -0,0 +1,17 @@ + \ No newline at end of file diff --git a/application/models/cron/Crontab.php b/application/models/cron/Crontab.php new file mode 100755 index 000000000..92b40ac38 --- /dev/null +++ b/application/models/cron/Crontab.php @@ -0,0 +1,284 @@ + "value" + * or a line can be a comment (string beginning with #) + * or it can be a special command (beginning with an @) + * @var array + */ + private $crontabs; + + /** + * The user for whom the crontab will be manipulated + * @var string + */ + private $user; + + /** + * Lists the type of line of each line in $crontabs. + * can be: any of the CRON_* constants. + * so $linetype[5] is the type of $crontabs[5]. + * @var string + */ + private $linetypes; + + // }}} + + /** + * Constructor + * + * Initialises $this->crontabs + * + * @param string $user the user for whom the crontab will be manipulated + */ + function Crontab($user) + { + $this->user = $user; + $this->readCrontab(); + } + + /** + * This reads the crontab of $this->user and parses it in $this->crontabs + * + */ + function readCrontab() + { + // return code is 0 or 1 if crontab was empty, elsewhere stop here + $cmd = "crontab -u {$this->user} -l"; + @exec("crontab -u {$this->user} -l", $crons, $return); + if ($return > 1) { + return PEAR::raiseError("*** Can't read crontab ***\n". + " Set crontab manually!\n"); + } + + foreach ($crons as $line) + { + $line = trim($line); // discarding all prepending spaces and tabs + + // empty lines.. + if (!$line) { + $this->crontabs[] = "empty line"; + $this->linetypes[] = CRON_EMPTY; + continue; + } + + // checking if this is a comment + if ($line[0] == "#") { + $this->crontabs[] = trim($line); + $this->linetypes[] = CRON_COMMENT; + continue; + } + + // Checking if this is an assignment + if (ereg("(.*)=(.*)", $line, $assign)) { + $this->crontabs[] = array ("name" => $assign[1], "value" => $assign[2]); + $this->linetypes[] = CRON_ASSIGN; + continue; + } + + // Checking if this is a special @-entry. check man 5 crontab for more info + if ($line[0] == '@') { + $this->crontabs[] = split("[ \t]", $line, 2); + $this->linetypes[] = CRON_SPECIAL; + continue; + } + + // It's a regular crontab-entry + $ct = split("[ \t]", $line, 6); + $this->addCron($ct[0], $ct[1], $ct[2], $ct[3], $ct[4], $ct[5]); + } + } + + /** + * Writes the current crontab + */ + function writeCrontab() + { + global $DEBUG, $PATH; + + if (empty($this->linetypes)) { + return; + } + $filename = ($DEBUG ? tempnam("$PATH/crons", "cron") : tempnam("/tmp", "cron")); + $file = fopen($filename, "w"); + + foreach($this->linetypes as $i => $line) { + switch ($this->linetypes[$i]) { + case CRON_COMMENT: + $line = $this->crontabs[$i]; + break; + case CRON_ASSIGN: + $line = $this->crontabs[$i][name]." = ".$this->crontabs[$i][value]; + break; + case CRON_CMD: + $line = implode(" ", $this->crontabs[$i]); + break; + case CRON_SPECIAL: + $line = implode(" ", $this->crontabs[$i]); + break; + case CRON_EMPTY: + $line = "\n"; // an empty line in the crontab-file + break; + default: + unset($line); + echo "Something very weird is going on. This line ($i) has an unknown type.\n"; + break; + } + + // echo "line $i : $line\n"; + + if ($line) { + $r = @fwrite($file, $line."\n"); + if($r === FALSE) { + return PEAR::raiseError("*** Can't write crontab ***\n". + " Set crontab manually!\n"); + } + } + } + fclose($file); + + if ($DEBUG) { + echo "DEBUGMODE: not updating crontab. writing to $filename instead.\n"; + } else { + exec("crontab -u {$this->user} $filename", $returnar, $return); + if ($return != 0) { + echo "Error running crontab ($return). $filename not deleted\n"; + } else { + unlink($filename); + } + } + } + + + /** + * Add a item of type CRON_CMD to the end of $this->crontabs + * + * @param string $m + * minute + * @param string $h + * hour + * @param string $dom + * day of month + * @param string $mo + * month + * @param string $dow + * day of week + * @param string $cmd + * command + * + */ + function addCron($m, $h, $dom, $mo, $dow, $cmd) + { + $this->crontabs[] = array ("minute" => $m, "hour" => $h, "dayofmonth" => $dom, "month" => $mo, "dayofweek" => $dow, "command" => $cmd); + $this->linetypes[] = CRON_CMD; + } + + + /** + * Add a comment to the cron to the end of $this->crontabs + * + * @param string $comment + */ + function addComment($comment) + { + $this->crontabs[] = "# $comment\n"; + $this->linetypes[] = CRON_COMMENT; + } + + + /** + * Add a special command (check man 5 crontab for more information) + * + * @param string $sdate special date + * string meaning + * ------ ------- + * @reboot Run once, at startup. + * @yearly Run once a year, "0 0 1 1 *". + * @annually (same as @yearly) + * @monthly Run once a month, "0 0 1 * *". + * @weekly Run once a week, "0 0 * * 0". + * @daily Run once a day, "0 0 * * *". + * @midnight (same as @daily) + * @hourly Run once an hour, "0 * * * *". + * @param string $cmd command + */ + function addSpecial($sdate, $cmd) + { + $this->crontabs[] = array ("special" => $sdate, "command" => $cmd); + $this->linetypes[] = CRON_SPECIAL; + } + + + /** + * Add an assignment (name = value) + * + * @param string $name + * @param string $value + */ + function addAssign($name, $value) + { + $this->crontabs[] = array ("name" => $name, "value" => $value); + $this->linetypes[] = CRON_ASSIGN; + } + + + /** + * Delete a line from the arrays. + * + * @param int $index the index in $this->crontabs + */ + function delEntry($index) + { + unset ($this->crontabs[$index]); + unset ($this->linetypes[$index]); + } + + + /** + * Get all the lines of a certain type in an array + * + * @param string $type + */ + function getByType($type) + { + if ($type < CRON_COMMENT || $type > CRON_EMPTY) + { + trigger_error("Wrong type: $type", E_USER_WARNING); + return 0; + } + + $returnar = array (); + for ($i = 0; $i < count($this->linetypes); $i ++) + if ($this->linetypes[$i] == $type) + $returnar[] = $this->crontabs[$i]; + + return $returnar; + } +} +?> \ No newline at end of file diff --git a/application/models/cron/croncall.php b/application/models/cron/croncall.php new file mode 100755 index 000000000..bae49815b --- /dev/null +++ b/application/models/cron/croncall.php @@ -0,0 +1,9 @@ +#!/usr/bin/php +execute($p['params']); +exit(0); +?> \ No newline at end of file diff --git a/application/models/cron/transportCron.php b/application/models/cron/transportCron.php new file mode 100755 index 000000000..63845ee9d --- /dev/null +++ b/application/models/cron/transportCron.php @@ -0,0 +1,21 @@ +#!/usr/bin/php +setErrorHandling(PEAR_ERROR_RETURN); +$CC_DBC->setFetchMode(DB_FETCHMODE_ASSOC); + +$gb = new LocStor(); +$tr = new Transport($gb); + +$r = $tr->cronMain(); +if (!$r) { + exit(1); +} + +exit(0); +?> \ No newline at end of file diff --git a/application/models/cron/transportCronJob.php b/application/models/cron/transportCronJob.php new file mode 100755 index 000000000..795dd659a --- /dev/null +++ b/application/models/cron/transportCronJob.php @@ -0,0 +1,42 @@ +#!/usr/bin/php +setErrorHandling(PEAR_ERROR_RETURN); +$CC_DBC->setFetchMode(DB_FETCHMODE_ASSOC); + +$gb = new LocStor(); +$tr = new Transport($gb); + +$pid = getmypid(); +list(, $trtok) = $_SERVER['argv']; +if (TR_LOG_LEVEL > 1) { + $tr->trLog("transportCronJob($pid) start ($trtok)"); +} + +// 4-pass on job: +$cnt = 4; +for ($i = 0; $i < $cnt; $i++, sleep(1)) { + // run the action: + $r = $tr->cronCallMethod($trtok); + if (PEAR::isError($r)) { + $tr->trLogPear("transportCronJob($pid): ($trtok): ", $r); + } else { +# $tr->trLog("X transportCronJob: ".var_export($r, TRUE)); + if ($r !== TRUE) { + $tr->trLog("transportCronJob($pid): ($trtok): nonTRUE returned"); + } + } + #if(!$r) exit(1); + #sleep(2); +} + +if (TR_LOG_LEVEL>1) { + $tr->trLog("transportCronJob($pid) end ($trtok)"); +} +exit(0); +?> \ No newline at end of file diff --git a/application/models/genres.xml b/application/models/genres.xml new file mode 100644 index 000000000..9afb92275 --- /dev/null +++ b/application/models/genres.xml @@ -0,0 +1,136 @@ + + + + + + +]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/application/models/index.php b/application/models/index.php new file mode 100644 index 000000000..cc0d4bdf3 --- /dev/null +++ b/application/models/index.php @@ -0,0 +1,17 @@ + + +StorageServer module + +

    StorageServer module

    +
    +HTML client
    +XmlRpc test
    +Test
    + + \ No newline at end of file diff --git a/application/models/playlistFormat.php b/application/models/playlistFormat.php new file mode 100644 index 000000000..457feb938 --- /dev/null +++ b/application/models/playlistFormat.php @@ -0,0 +1,117 @@ +'playlist', + 'playlist'=>array( + 'childs'=>array( + // 'repeatable'=>array('playlistElement'), + 'optional'=>array('metadata', 'playlistElement'), + ), + 'attrs'=>array( + 'required'=>array('id'), + 'implied'=>array('title', 'playlength'), + ), + ), + 'playlistElement'=>array( + 'childs'=>array( + 'oneof'=>array('audioClip', 'playlist'), + 'optional'=>array('fadeInfo'), + ), + 'attrs'=>array( + 'required'=>array('id', 'relativeOffset', 'clipStart', 'clipEnd', 'clipLength'), + ), + ), + 'audioClip'=>array( + 'childs'=>array( + 'optional'=>array('metadata'), + ), + 'attrs'=>array( + 'implied'=>array('id', 'title', 'playlength', 'uri'), + ), + ), + 'fadeInfo'=>array( + 'attrs'=>array( + 'required'=>array('id', 'fadeIn', 'fadeOut'), + ), + ), + 'metadata'=>array( + 'childs'=>array( + 'optional'=>array( + 'dc:title', 'dcterms:extent', 'dc:creator', 'dc:description', + 'dcterms:alternative', 'ls:filename', 'ls:mtime', + ), + ), + 'namespaces'=>array( + 'dc'=>"http://purl.org/dc/elements/1.1/", + 'dcterms'=>"http://purl.org/dc/terms/", + 'xbmf'=>"http://www.streamonthefly.org/xbmf", + 'xsi'=>"http://www.w3.org/2001/XMLSchema-instance", + 'xml'=>"http://www.w3.org/XML/1998/namespace", + ), + ), + 'dc:title'=>array( + 'type'=>'Text', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'dcterms:alternative'=>array( + 'type'=>'Text', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'dcterms:extent'=>array( + 'type'=>'Time', + 'regexp'=>'^\d{2}:\d{2}:\d{2}.\d{6}$', + ), + 'dc:creator'=>array( + 'type'=>'Text', + 'area'=>'Music', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'dc:description'=>array( + 'type'=>'Longtext', + 'area'=>'Music', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'playlength'=>array( + 'type'=>'Time', + 'regexp'=>'^((\d{2}:)?\d{2}:)?\d{1,2}(.\d{6})?$', + ), + 'id'=>array( + 'type'=>'Attribute', + 'regexp'=>'^[0-9a-f]{16}$', + ), + 'fadeIn'=>array( + 'type'=>'Attribute', + 'regexp'=>'^((\d{2}:)?\d{2}:)?\d{1,2}(.\d{6})?$', + ), + 'fadeOut'=>array( + 'type'=>'Attribute', + 'regexp'=>'^((\d{2}:)?\d{2}:)?\d{1,2}(.\d{6})?$', + ), + 'ls:filename'=>array( + 'type'=>'Text', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'ls:mtime'=>array( + 'type'=>'Int', +// 'regexp'=>'^\d{4}(-\d{2}(-\d{2}(T\d{2}:\d{2}(:\d{2}\.\d+)?(Z)|([\+\-]?\d{2}:\d{2}))?)?)?$', + ), +/* + ''=>array( + 'childs'=>array(''), + 'attrs'=>array('implied'=>array()), + ), +*/ +); + +/* +? +ls:filename Text auto +*/ +?> \ No newline at end of file diff --git a/application/models/webstreamFormat.php b/application/models/webstreamFormat.php new file mode 100644 index 000000000..219230779 --- /dev/null +++ b/application/models/webstreamFormat.php @@ -0,0 +1,345 @@ +'audioClip', + 'audioClip'=>array( + 'childs'=>array( + 'required'=>array('metadata'), + ), + ), + 'metadata'=>array( + 'childs'=>array( + 'required'=>array( + 'dc:title', 'dcterms:extent', 'ls:url' + ), + 'optional'=>array( + 'dc:identifier', + 'dc:creator', 'dc:source', 'ls:genre', + 'ls:year', 'dc:type', 'dc:description', 'dc:format', + 'ls:bpm', 'ls:rating', 'ls:encoded_by', 'ls:track_num', + 'ls:disc_num', 'ls:disc_num', 'dc:publisher', 'ls:composer', + 'ls:bitrate', 'ls:channels', 'ls:samplerate', 'ls:encoder', + 'ls:crc', 'ls:lyrics', 'ls:orchestra', 'ls:conductor', + 'ls:lyricist', 'ls:originallyricist', 'ls:radiostationname', + 'ls:audiofileinfourl', 'ls:artisturl', 'ls:audiosourceurl', + 'ls:radiostationurl', 'ls:buycdurl', 'ls:isrcnumber', + 'ls:catalognumber', 'ls:originalartist', 'dc:rights', + 'ls:license', 'dc:title', 'dcterms:temporal', + 'dcterms:spatial', 'dcterms:entity', 'dc:description', + 'dc:creator', 'dc:subject', 'dc:type', 'dc:format', + 'dc:contributor', 'dc:language', 'dc:rights', + 'dcterms:isPartOf', 'dc:date', + 'dc:publisher', + // extra + 'dcterms:alternative', 'ls:filename', 'ls:mtime', + // added lately by sebastian + 'ls:mood', + ), + ), + 'namespaces'=>array( + 'dc'=>"http://purl.org/dc/elements/1.1/", + 'dcterms'=>"http://purl.org/dc/terms/", + 'xbmf'=>"http://www.streamonthefly.org/xbmf", + 'xsi'=>"http://www.w3.org/2001/XMLSchema-instance", + 'xml'=>"http://www.w3.org/XML/1998/namespace", + ), + ), + 'ls:url'=>array( + 'type'=>'URL', + ), + 'dc:identifier'=>array( + 'type'=>'Text', + 'auto'=>TRUE, + ), + 'dc:title'=>array( + 'type'=>'Text', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'dcterms:alternative'=>array( + 'type'=>'Text', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'dcterms:extent'=>array( + 'type'=>'Time', + 'regexp'=>'^\d{2}:\d{2}:\d{2}.\d{6}$', + ), + 'dc:creator'=>array( + 'type'=>'Text', + 'area'=>'Music', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'dc:source'=>array( + 'type'=>'Text', + 'area'=>'Music', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'ls:genre'=>array( + 'type'=>'Menu', + 'area'=>'Music', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'ls:year'=>array( + 'type'=>'Menu', + 'area'=>'Music', + ), + 'dc:type'=>array( + 'type'=>'Menu', + 'area'=>'Music', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'dc:description'=>array( + 'type'=>'Longtext', + 'area'=>'Music', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'dc:format'=>array( + 'type'=>'Menu', + 'area'=>'Music', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'ls:bpm'=>array( + 'type'=>'Number', + 'area'=>'Music', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'ls:rating'=>array( + 'type'=>'Number', + 'area'=>'Music', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'ls:encoded_by'=>array( + 'type'=>'Text', + 'area'=>'Music', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'ls:track_num'=>array( + 'type'=>'Menu', + 'area'=>'Music', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'ls:disc_num'=>array( + 'type'=>'Menu', + 'area'=>'Music', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'ls:disc_num'=>array( + 'type'=>'Menu', + 'area'=>'Music', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'dc:publisher'=>array( + 'type'=>'Text', + 'area'=>'Music', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'ls:composer'=>array( + 'type'=>'Text', + 'area'=>'Music', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'ls:bitrate'=>array( + 'type'=>'Number', + 'area'=>'Music', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'ls:channels'=>array( + 'type'=>'Menu', + 'area'=>'Music', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'ls:samplerate'=>array( + 'type'=>'Menu', + 'area'=>'Music', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'ls:encoder'=>array( + 'type'=>'Text', + 'area'=>'Music', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'ls:crc'=>array( + 'type'=>'Number', + 'area'=>'Music', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'ls:lyrics'=>array( + 'type'=>'Longtext', + 'area'=>'Music', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'ls:orchestra'=>array( + 'type'=>'Text', + 'area'=>'Music', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'ls:conductor'=>array( + 'type'=>'Text', + 'area'=>'Music', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'ls:lyricist'=>array( + 'type'=>'Text', + 'area'=>'Music', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'ls:originallyricist'=>array( + 'type'=>'Text', + 'area'=>'Music', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'ls:radiostationname'=>array( + 'type'=>'Text', + 'area'=>'Music', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'ls:audiofileinfourl'=>array( + 'type'=>'URL', + 'area'=>'Music', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'ls:artisturl'=>array( + 'type'=>'URL', + 'area'=>'Music', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'ls:audiosourceurl'=>array( + 'type'=>'URL', + 'area'=>'Music', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'ls:radiostationurl'=>array( + 'type'=>'URL', + 'area'=>'Music', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'ls:buycdurl'=>array( + 'type'=>'URL', + 'area'=>'Music', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'ls:isrcnumber'=>array( + 'type'=>'Number', + 'area'=>'Music', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'ls:catalognumber'=>array( + 'type'=>'Number', + 'area'=>'Music', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'ls:originalartist'=>array( + 'type'=>'Text', + 'area'=>'Music', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'dc:rights'=>array( + 'type'=>'Text', + 'area'=>'Music', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'ls:license'=>array( + 'type'=>'Text', + 'area'=>'Music', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'dc:title'=>array( + 'type'=>'Text', + 'area'=>'Talk', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'dcterms:temporal'=>array( + 'type'=>'Time/Date', + 'area'=>'Talk', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'dcterms:spatial'=>array( + 'type'=>'Menu', + 'area'=>'Talk', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'dcterms:entity'=>array( + 'type'=>'Text', + 'area'=>'Talk', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'dc:description'=>array( + 'type'=>'Longtext', + 'area'=>'Talk', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'dc:creator'=>array( + 'type'=>'Menu', + 'area'=>'Talk', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'dc:subject'=>array( + 'type'=>'Text', + 'area'=>'Talk', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'dc:type'=>array( + 'type'=>'Menu', + 'area'=>'Talk', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'dc:format'=>array( + 'type'=>'Menu', + 'area'=>'Talk', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'dc:contributor'=>array( + 'type'=>'Text', + 'area'=>'Talk', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'dc:language'=>array( + 'type'=>'Menu', + 'area'=>'Talk', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'dc:rights'=>array( + 'type'=>'Menu', + 'area'=>'Talk', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'dcterms:isPartOf'=>array( + 'type'=>'Text', + 'area'=>'Talk', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'dc:date'=>array( + 'type'=>'Date', + 'area'=>'Talk', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'dc:publisher'=>array( + 'type'=>'Text', + 'area'=>'Talk', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'ls:filename'=>array( + 'type'=>'Text', + 'attrs'=>array('implied'=>array('xml:lang')), + ), + 'ls:mtime'=>array( + 'type'=>'Int', +// 'regexp'=>'^\d{4}(-\d{2}(-\d{2}(T\d{2}:\d{2}(:\d{2}\.\d+)?(Z)|([\+\-]?\d{2}:\d{2}))?)?)?$', + ), +/* + ''=>array( + 'type'=>'', + 'area'=>'', + 'attrs'=>array(), + ), +*/ +); + +/* +? +ls:filename Text auto +*/ +?> \ No newline at end of file diff --git a/application/models/xmlrpc/XR_LocStor.php b/application/models/xmlrpc/XR_LocStor.php new file mode 100644 index 000000000..f8df60978 --- /dev/null +++ b/application/models/xmlrpc/XR_LocStor.php @@ -0,0 +1,3833 @@ + + *
  • version : string
  • + * + * + * On errors, returns an XML-RPC error response. + * The possible error codes and error message are: + *
      + *
    • 3 - Incorrect parameters passed to method: + * Wanted ... , got ... at param
    • + *
    • 801 - wrong 1st parameter, struct expected.
    • + *
    • 805 - xr_getVersion: + * <message from lower layer>
    • + *
    + * + * @param XML_RPC_Message $input + * @return XML_RPC_Response + * @see Subjects::getVersion + */ +// public function xr_getVersion($input) +// { +// list($ok, $r) = XR_LocStor::xr_getParams($input); +// if (!$ok) { +// return $r; +// } +// $res = $this->getVersion(); +// if (PEAR::isError($res)) { +// return new XML_RPC_Response(0, 805, +// "xr_getVersion: ".$res->getMessage(). +// " ".$res->getUserInfo() +// ); +// } +// return new XML_RPC_Response( +// XML_RPC_encode(array('version'=>$res)) +// ); +// } + public function xr_getVersion() + { +// list($ok, $r) = XR_LocStor::xr_getParams($input); +// if (!$ok) { +// return $r; +// } + $res = $this->getVersion(); +// if (PEAR::isError($res)) { +// return new XML_RPC_Response(0, 805, +// "xr_getVersion: ".$res->getMessage(). +// " ".$res->getUserInfo() +// ); +// } + return new XML_RPC_Response( + XML_RPC_encode(array('version'=>$res)) + ); + } + + + /* ------------------------------------------------------- authentication */ + /** + * Checks the login name and password of the user and return + * true if login data are correct, othervise return false. + * + * The XML-RPC name of this method is "locstor.authenticate". + * + * Input parameters: XML-RPC struct with the following fields: + *
      + *
    • login : string - login name
    • + *
    • pass : string - password
    • + *
    + * On success, returns a XML-RPC struct with single field: + *
      + *
    • authenticate : boolean
    • + *
    + * + * On errors, returns an XML-RPC error response. + * The possible error codes and error message are: + *
      + *
    • 3 - Incorrect parameters passed to method: + * Wanted ... , got ... at param
    • + *
    • 801 - wrong 1st parameter, struct expected.
    • + *
    • 804 - xr_authenticate: database error
    • + *
    + * + * @param XML_RPC_Message $input + * @return XML_RPC_Response + * @see Subjects::authenticate + */ + public function xr_authenticate($input) + { + list($ok, $r) = XR_LocStor::xr_getParams($input); + if (!$ok) { + return $r; + } + $res = $this->authenticate($r['login'], $r['pass']); + if (PEAR::isError($res)) { + return new XML_RPC_Response(0, 804, + "xr_authenticate: ".$res->getMessage(). + " ".$res->getUserInfo() + ); + } + $retval = ($res !== FALSE); + return new XML_RPC_Response( + XML_RPC_encode(array('authenticate'=>$retval)) + ); + } + + + /** + * Checks the login name and password of the user. If the login is + * correct, a new session ID string is generated, to be used in all + * subsequent XML-RPC calls as the "sessid" field of the + * parameters. + * + * The XML-RPC name of this method is "locstor.login". + * + * The input parameters are an XML-RPC struct with the following + * fields: + *
      + *
    • login : string - login name
    • + *
    • pass : string - password
    • + *
    + * + * On success, returns a XML-RPC struct with single field: + *
      + *
    • sessid : string - the newly generated session ID
    • + *
    + * + * On errors, returns an XML-RPC error response. + * The possible error codes and error message are: + *
      + *
    • 3 - Incorrect parameters passed to method: + * Wanted ... , got ... at param
    • + *
    • 801 - wrong 1st parameter, struct expected.
    • + *
    • 802 - xr_login: login failed - + * incorrect username or password.
    • + *
    • 804 - xr_login:: database error
    • + *
    + * + * @param XML_RPC_Message $input + * @return XML_RPC_Response + * @see Alib::login + */ + public function xr_login($input) + { + list($ok, $r) = XR_LocStor::xr_getParams($input); + if (!$ok) { + return $r; + } + $res = Alib::Login($r['login'], $r['pass']); + if (PEAR::isError($res)) { + return new XML_RPC_Response(0, 804, + "xr_login: ".$res->getMessage(). + " ".$res->getUserInfo() + ); + } + if ($res === FALSE) { + return new XML_RPC_Response(0, 802, + "xr_login: login failed - incorrect username or password." + ); + } else { + return new XML_RPC_Response(XML_RPC_encode(array('sessid'=>$res))); + } + } + + /** + * Logout, destroy session and return status. + * If session is not valid error message is returned. + * + * The XML-RPC name of this method is "locstor.logout". + * + * The input parameters are an XML-RPC struct with the following + * fields: + *
      + *
    • sessid : string - session id
    • + *
    + * + * On success, returns a XML-RPC struct with single field: + *
      + *
    • status : boolean - TRUE
    • + *
    + * + * On errors, returns an XML-RPC error response. + * The possible error codes and error message are: + *
      + *
    • 3 - Incorrect parameters passed to method: + * Wanted ... , got ... at param
    • + *
    • 801 - wrong 1st parameter, struct expected.
    • + *
    • 803 - xr_logout: logout failed - not logged.
    • + *
    + * + * @param XML_RPC_Message $input + * @return XML_RPC_Response + */ + public function xr_logout($input) + { + list($ok, $r) = XR_LocStor::xr_getParams($input); + if (!$ok) { + return $r; + } + $res = Alib::Logout($r['sessid']); + if (PEAR::isError($res)) { + return new XML_RPC_Response(0, 803, + "xr_logout: ".$res->getMessage(). + " ".$res->getUserInfo() + ); + } + return new XML_RPC_Response(XML_RPC_encode(array('status'=>$res))); + } + + /* ---------------------------------------------------------------- store */ + /** + * Open writable URL for store new AudioClip or replace existing one. + * Writing to returned URL is possible using HTTP PUT method + * (as e.g. curl -T <filename> command does) + * + * The XML-RPC name of this method is "locstor.storeAudioClipOpen". + * + * The input parameters are an XML-RPC struct with the following + * fields: + *
      + *
    • sessid : string - session id
    • + *
    • gunid : string - global unique id of AudioCLip, + * if gunid is empty string, new one is generated + * (returned by subsequent storeAudioClipClose call) + *
    • + *
    • metadata : string - metadata XML string + * (as defined in Campcaster::Core::AudioClip Class Reference, + * examples are in storageServer/var/tests/*.xml) + *
    • + *
    • fname : string - human readable mnemonic file name + * with extension corresponding to filetype
    • + *
    • chsum : string - md5 checksum of media file
    • + *
    + * + * On success, returns a XML-RPC struct: + *
      + *
    • url : string - writable URL for HTTP PUT
    • + *
    • token : string - access token
    • + *
    + * + * On errors, returns an XML-RPC error response. + * The possible error codes and error message are: + *
      + *
    • 3 - Incorrect parameters passed to method: + * Wanted ... , got ... at param
    • + *
    • 801 - wrong 1st parameter, struct expected.
    • + *
    • 805 - xr_storeAudioClipOpen: + * <message from lower layer>
    • + *
    • 888 - If the file being uploaded is a duplicate of + * a file already in the system.
    • + *
    + * + * @param XML_RPC_Message $input + * @return XML_RPC_Response + * @see LocStor::storeAudioClipOpen + */ + public function xr_storeAudioClipOpen($input) + { + list($ok, $r) = XR_LocStor::xr_getParams($input); + if (!$ok) { + return $r; + } + $res = $this->storeAudioClipOpen($r['sessid'], $r['gunid'], + $r['metadata'], $r['fname'], $r['chsum']); + if (PEAR::isError($res)) { + $code = 805; + if ($res->getCode() == 888) { + $code = 888; + } + return new XML_RPC_Response(0, $code, + "xr_storeAudioClipOpen: ".$res->getMessage(). + " ".$res->getUserInfo() + ); + } + return new XML_RPC_Response(XML_RPC_encode($res)); + } + + /** + * Close writable URL for store new AudioClip or replace existing one. + * + * The XML-RPC name of this method is "locstor.storeAudioClipClose". + * + * The input parameters are an XML-RPC struct with the following + * fields: + *
      + *
    • sessid : string - session id
    • + *
    • token : string - access token
    • + *
    + * + * On success, returns a XML-RPC struct with single field: + *
      + *
    • gunid : string - gunid of stored file
    • + *
    + * + * On errors, returns an XML-RPC error response. + * The possible error codes and error message are: + *
      + *
    • 3 - Incorrect parameters passed to method: + * Wanted ... , got ... at param
    • + *
    • 801 - wrong 1st parameter, struct expected.
    • + *
    • 805 - xr_storeAudioClipClose: + * <message from lower layer>
    • + *
    • 850 - wrong 1st parameter, struct expected.
    • + *
    + * + * @param XML_RPC_Message $input + * @return XML_RPC_Response + * @see LocStor::storeAudioClipClose + */ + public function xr_storeAudioClipClose($input) + { + list($ok, $r) = XR_LocStor::xr_getParams($input); + if (!$ok) { + return $r; + } + $res = $this->storeAudioClipClose($r['sessid'], $r['token']); + if (PEAR::isError($res)) { + $ec0 = intval($res->getCode()); + $ec = ($ec0 == GBERR_TOKEN ? 800+$ec0 : 805 ); + return new XML_RPC_Response(0, $ec, + "xr_storeAudioClipClose: ".$res->getMessage(). + " ".$res->getUserInfo() + ); + } + return new XML_RPC_Response(XML_RPC_encode(array('gunid'=>$res))); + } + + /** + * Store audio stream identified by URL - no raw audio data + * + * The XML-RPC name of this method is "locstor.storeWebstream". + * + * The input parameters are an XML-RPC struct with the following + * fields: + *
      + *
    • sessid : string - session id
    • + *
    • gunid : string - global unique id of AudioCLip
    • + *
    • metadata : string - metadata XML string
    • + *
    • fname : string - human readable mnemonic file name + * with extension corresponding to filetype
    • + *
    • url : string - URL of the webstrea,
    • + *
    + * + * On success, returns a XML-RPC struct with single field: + *
      + *
    • gunid : string - gunid of stored file
    • + *
    + * + * On errors, returns an XML-RPC error response. + * The possible error codes and error message are: + *
      + *
    • 3 - Incorrect parameters passed to method: + * Wanted ... , got ... at param
    • + *
    • 801 - wrong 1st parameter, struct expected.
    • + *
    • 805 - xr_storeWebstream: + * <message from lower layer>
    • + *
    + * + * @param XML_RPC_Message $input + * @return XML_RPC_Response + * @see LocStor::storeWebstream + */ + public function xr_storeWebstream($input) + { + list($ok, $r) = XR_LocStor::xr_getParams($input); + if (!$ok) { + return $r; + } + $res = $this->storeWebstream( + $r['sessid'], $r['gunid'], $r['metadata'], $r['fname'], $r['url'] + ); + if (PEAR::isError($res)) { + return new XML_RPC_Response(0, 805, + "xr_storeWebstream: ".$res->getMessage(). + " ".$res->getUserInfo() + ); + } + return new XML_RPC_Response(XML_RPC_encode(array('gunid'=>$res))); + } + + /* ------------------------------------------------ access raw audio data */ + /** + * Make access to audio clip. + * + * The XML-RPC name of this method is "locstor.accessRawAudioData". + * + * The input parameters are an XML-RPC struct with the following + * fields: + *
      + *
    • sessid : string - session id
    • + *
    • gunid : string - global unique id of AudioClip
    • + *
    + * + * On success, returns a XML-RPC struct: + *
      + *
    • url : string - local access url
    • + *
    • token : string - access token
    • + *
    + * + * On errors, returns an XML-RPC error response. + * The possible error codes and error message are: + *
      + *
    • 3 - Incorrect parameters passed to method: + * Wanted ... , got ... at param
    • + *
    • 801 - wrong 1st parameter, struct expected.
    • + *
    • 805 - xr_accessRawAudioData: + * <message from lower layer>
    • + *
    + * + * @param XML_RPC_Message $input + * @return XML_RPC_Response + * @see LocStor::accessRawAudioData + */ + public function xr_accessRawAudioData($input) + { + list($ok, $r) = XR_LocStor::xr_getParams($input); + if (!$ok) { + return $r; + } + $res = $this->accessRawAudioData($r['sessid'], $r['gunid']); + if (PEAR::isError($res)) { + return new XML_RPC_Response(0, 805, + "xr_accessRawAudioData: ".$res->getMessage(). + " ".$res->getUserInfo() + ); + } + return new XML_RPC_Response(XML_RPC_encode($res)); + } + + /** + * Release access to audio clip + * + * The XML-RPC name of this method is "locstor.releaseRawAudioData". + * + * The input parameters are an XML-RPC struct with the following + * fields: + *
      + *
    • token : string - access token + * returned by locstor.accessRawAudioData
    • + *
    + * + * On success, returns a XML-RPC struct with single field: + *
      + *
    • status : boolean
    • + *
    + * + * On errors, returns an XML-RPC error response. + * The possible error codes and error message are: + *
      + *
    • 3 - Incorrect parameters passed to method: + * Wanted ... , got ... at param
    • + *
    • 801 - wrong 1st parameter, struct expected.
    • + *
    • 805 - xr_releaseRawAudioData: + * <message from lower layer>
    • + *
    + * + * @param XML_RPC_Message $input + * @return XML_RPC_Response + * @see LocStor::releaseRawAudioData + */ + public function xr_releaseRawAudioData($input) + { + list($ok, $r) = XR_LocStor::xr_getParams($input); + if (!$ok) { + return $r; + } + $res = $this->releaseRawAudioData(NULL, $r['token']); + if (PEAR::isError($res)) { + return new XML_RPC_Response(0, 805, + "xr_releaseRawAudioData: ".$res->getMessage(). + " ".$res->getUserInfo() + ); + } + return new XML_RPC_Response(XML_RPC_encode(array('status'=>$res))); + } + + /* ---------------------------------------------- download raw audio data */ + /** + * Create downlodable URL for stored file + * + * The XML-RPC name of this method is "locstor.downloadRawAudioDataOpen". + * + * The input parameters are an XML-RPC struct with the following + * fields: + *
      + *
    • sessid : string - session id
    • + *
    • gunid : string - global unique id of AudioClip
    • + *
    + * + * On success, returns a XML-RPC struct: + *
      + *
    • url : string - downloadable url
    • + *
    • token : string - download token
    • + *
    • chsum : string - md5 checksum
    • + *
    • size : int - file size
    • + *
    • filename : string - human readable mnemonic file name
    • + *
    + * + * On errors, returns an XML-RPC error response. + * The possible error codes and error message are: + *
      + *
    • 3 - Incorrect parameters passed to method: + * Wanted ... , got ... at param
    • + *
    • 801 - wrong 1st parameter, struct expected.
    • + *
    • 805 - xr_accessRawAudioDataOpen: + * <message from lower layer>
    • + *
    • 847 - invalid gunid.
    • + *
    + * + * @param XML_RPC_Message $input + * @return XML_RPC_Response + * @see LocStor::downloadRawAudioDataOpen + */ + public function xr_downloadRawAudioDataOpen($input) + { + list($ok, $r) = XR_LocStor::xr_getParams($input); + if (!$ok) { + return $r; + } + $res = $this->downloadRawAudioDataOpen($r['sessid'], $r['gunid']); + if (PEAR::isError($res)) { + $ec0 = intval($res->getCode()); + $ec = ($ec0 == GBERR_NOTF ? 800+$ec0 : 805 ); + return new XML_RPC_Response(0, $ec, + "xr_downloadRawAudioDataOpen: ".$res->getMessage(). + " ".$res->getUserInfo() + ); + } + return new XML_RPC_Response(XML_RPC_encode($res)); + } + + /** + * Delete downlodable URL with media file. + * + * The XML-RPC name of this method is "locstor.downloadRawAudioDataClose". + * + * The input parameters are an XML-RPC struct with the following + * fields: + *
      + *
    • sessid : string - session id
    • + *
    • token : string - download token + * returned by locstor.downloadRawAudioDataOpen
    • + *
    + * + * On success, returns a XML-RPC struct with single field: + *
      + *
    • gunid : string - global unique ID
    • + *
    + * + * On errors, returns an XML-RPC error response. + * The possible error codes and error message are: + *
      + *
    • 3 - Incorrect parameters passed to method: + * Wanted ... , got ... at param
    • + *
    • 801 - wrong 1st parameter, struct expected.
    • + *
    • 805 - xr_releaseRawAudioDataClose: + * <message from lower layer>
    • + *
    + * + * @param XML_RPC_Message $input + * @return XML_RPC_Response + * @see LocStor::downloadRawAudioDataClose + */ + public function xr_downloadRawAudioDataClose($input) + { + list($ok, $r) = XR_LocStor::xr_getParams($input); + if (!$ok) { + return $r; + } + $res = $this->downloadRawAudioDataClose($r['token']); + if (PEAR::isError($res)) { + return new XML_RPC_Response(0, 805, + "xr_downloadRawAudioDataClose: ".$res->getMessage(). + " ".$res->getUserInfo() + ); + } + return new XML_RPC_Response(XML_RPC_encode(array('gunid'=>$res))); + } + + /* ---------------------------------------------------- download metadata */ + /** + * Create downlodable URL for metadata part of stored file + * + * The XML-RPC name of this method is "locstor.downloadMetadataOpen". + * + * The input parameters are an XML-RPC struct with the following + * fields: + *
      + *
    • sessid : string - session id
    • + *
    • gunid : string - global unique id of AudioClip
    • + *
    + * + * On success, returns a XML-RPC struct: + *
      + *
    • url : string - downloadable url
    • + *
    • token : string - download token
    • + *
    • chsum : string - md5 checksum
    • + *
    • filename : string - mnemonic filename
    • + *
    + * + * On errors, returns an XML-RPC error response. + * The possible error codes and error message are: + *
      + *
    • 3 - Incorrect parameters passed to method: + * Wanted ... , got ... at param
    • + *
    • 801 - wrong 1st parameter, struct expected.
    • + *
    • 805 - xr_downloadMetadataOpen: + * <message from lower layer>
    • + *
    + * + * @param XML_RPC_Message $input + * @return XML_RPC_Response + * @see LocStor::downloadRawAudioDataOpen + */ + public function xr_downloadMetadataOpen($input) + { + list($ok, $r) = XR_LocStor::xr_getParams($input); + if (!$ok) { + return $r; + } + #$this->debugLog("{$r['sessid']}, {$r['gunid']}"); + $res = $this->downloadMetadataOpen($r['sessid'], $r['gunid']); + if (PEAR::isError($res)) { + return new XML_RPC_Response(0, 805, + "xr_downloadMetadataOpen: ".$res->getMessage(). + " ".$res->getUserInfo() + ); + } + return new XML_RPC_Response(XML_RPC_encode($res)); + } + + /** + * Delete downlodable URL with metadata. + * + * The XML-RPC name of this method is "locstor.downloadMetadataClose". + * + * The input parameters are an XML-RPC struct with the following + * fields: + *
      + *
    • sessid : string - session id
    • + *
    • token : string - download token + * returned by locstor.downloadRawAudioDataOpen
    • + *
    + * + * On success, returns a XML-RPC struct with single field: + *
      + *
    • gunid : string - global unique ID
    • + *
    + * + * On errors, returns an XML-RPC error response. + * The possible error codes and error message are: + *
      + *
    • 3 - Incorrect parameters passed to method: + * Wanted ... , got ... at param
    • + *
    • 801 - wrong 1st parameter, struct expected.
    • + *
    • 805 - xr_downloadMetadataClose: + * <message from lower layer>
    • + *
    + * + * @param XML_RPC_Message $input + * @return XML_RPC_Response + * @see LocStor::downloadRawAudioDataClose + */ + public function xr_downloadMetadataClose($input) + { + list($ok, $r) = XR_LocStor::xr_getParams($input); + if (!$ok) { + return $r; + } + $res = $this->downloadMetadataClose($r['token']); + if (PEAR::isError($res)) { + return new XML_RPC_Response(0, 805, + "xr_downloadMetadataClose: ".$res->getMessage(). + " ".$res->getUserInfo() + ); + } + return new XML_RPC_Response(XML_RPC_encode(array('gunid'=>$res))); + } + + /* --------------------------------------------------------------- delete */ + /** + * Delete existing audio clip - DISABLED now! + * + * The XML-RPC name of this method is "locstor.deleteAudioClip". + * + * The input parameters are an XML-RPC struct with the following + * fields: + *
      + *
    • sessid : string - session id
    • + *
    • gunid : string - global unique id of AudioCLip
    • + *
    + * + * On success, returns a XML-RPC struct with single field: + *
      + *
    • status : boolean - TRUE
    • + *
    + * + * On errors, returns an XML-RPC error response. + * The possible error codes and error message are: + *
      + *
    • 3 - Incorrect parameters passed to method: + * Wanted ... , got ... at param
    • + *
    • 801 - wrong 1st parameter, struct expected.
    • + *
    • 805 - xr_deleteAudioClip: + * <message from lower layer>
    • + *
    + * + * @param XML_RPC_Message $input + * @return XML_RPC_Response + * @see LocStor::deleteAudioClip + */ + public function xr_deleteAudioClip($input) + { + list($ok, $r) = XR_LocStor::xr_getParams($input); + if (!$ok) { + return $r; + } + if (!isset($r['forced'])) { + $r['forced']=FALSE; + } + $res = $this->deleteAudioClip($r['sessid'], $r['gunid'], $r['forced']); + if (!$r['forced']) { + return new XML_RPC_Response(0, 805, "xr_deleteAudioClip: method disabled"); + } + if (PEAR::isError($res)) { + return new XML_RPC_Response(0, 805, + "xr_deleteAudioClip: ".$res->getMessage(). + " ".$res->getUserInfo() + ); + } + return new XML_RPC_Response(XML_RPC_encode(array('status'=>$res))); + } + + /*====================================================== playlist methods */ + /** + * Create a new Playlist metafile. + * + * The XML-RPC name of this method is "locstor.createPlaylist". + * + * The input parameters are an XML-RPC struct with the following + * fields: + *
      + *
    • sessid : string - session id
    • + *
    • plid : string - global unique id of Playlist
    • + *
    • fname : string - human readable menmonic file name
    • + *
    + * + * On success, returns a XML-RPC struct with single field: + *
      + *
    • plid : string
    • + *
    + * + * On errors, returns an XML-RPC error response. + * The possible error codes and error message are: + *
      + *
    • 3 - Incorrect parameters passed to method: + * Wanted ... , got ... at param
    • + *
    • 801 - wrong 1st parameter, struct expected.
    • + *
    • 805 - xr_createPlaylist: + * <message from lower layer>
    • + *
    + * + * @param XML_RPC_Message $input + * @return XML_RPC_Response + * @see LocStor::createPlaylist + */ + public function xr_createPlaylist($input) + { + list($ok, $r) = XR_LocStor::xr_getParams($input); + if (!$ok) { + return $r; + } + $res = $this->createPlaylist($r['sessid'], $r['plid'], $r['fname']); + if (PEAR::isError($res)) { + return new XML_RPC_Response(0, 805, + "xr_createPlaylist: ".$res->getMessage(). + " ".$res->getUserInfo() + ); + } + return new XML_RPC_Response(XML_RPC_encode(array('plid'=>$res))); + } + + /** + * Open a Playlist metafile for editing. + * Open readable URL and mark file as beeing edited. + * + * The XML-RPC name of this method is "locstor.editPlaylist". + * + * The input parameters are an XML-RPC struct with the following + * fields: + *
      + *
    • sessid : string - session id
    • + *
    • plid : string - global unique id of Playlist
    • + *
    + * + * On success, returns a XML-RPC struct with single field: + *
      + *
    • url : string - readable url
    • + *
    • token : string - playlist token
    • + *
    • chsum : string - md5 checksum
    • + *
    + * + * On errors, returns an XML-RPC error response. + * The possible error codes and error message are: + *
      + *
    • 3 - Incorrect parameters passed to method: + * Wanted ... , got ... at param
    • + *
    • 801 - wrong 1st parameter, struct expected.
    • + *
    • 805 - xr_editPlaylist: + * <message from lower layer>
    • + *
    + * + * @param XML_RPC_Message $input + * @return XML_RPC_Response + * @see LocStor::editPlaylist + */ + public function xr_editPlaylist($input) + { + list($ok, $r) = XR_LocStor::xr_getParams($input); + if (!$ok) { + return $r; + } + $res = $this->editPlaylist($r['sessid'], $r['plid']); + if (PEAR::isError($res)) { + return new XML_RPC_Response(0, 805, + "xr_editPlaylist: ".$res->getMessage(). + " ".$res->getUserInfo() + ); + } + return new XML_RPC_Response(XML_RPC_encode($res)); + } + + /** + * Store a new Playlist metafile in place of the old one. + * + * The XML-RPC name of this method is "locstor.savePlaylist". + * + * The input parameters are an XML-RPC struct with the following + * fields: + *
      + *
    • sessid : string - session id
    • + *
    • token : string - playlist token + * returned by locstor.editPlaylist
    • + *
    • newPlaylist : string - new Playlist in XML string
    • + *
    + * + * On success, returns a XML-RPC struct with single field: + *
      + *
    • plid : string - playlistId
    • + *
    + * + * On errors, returns an XML-RPC error response. + * The possible error codes and error message are: + *
      + *
    • 3 - Incorrect parameters passed to method: + * Wanted ... , got ... at param
    • + *
    • 801 - wrong 1st parameter, struct expected.
    • + *
    • 805 - xr_savePlaylist: + * <message from lower layer>
    • + *
    + * + * @param XML_RPC_Message $input + * @return XML_RPC_Response + * @see LocStor::savePlaylist + */ + public function xr_savePlaylist($input) + { + list($ok, $r) = XR_LocStor::xr_getParams($input); + if (!$ok) { + return $r; + } + $res = $this->savePlaylist($r['sessid'], $r['token'], $r['newPlaylist']); + if (PEAR::isError($res)) { + return new XML_RPC_Response(0, 805, + "xr_savePlaylist: ".$res->getMessage(). + " ".$res->getUserInfo() + ); + } + return new XML_RPC_Response(XML_RPC_encode(array('plid'=>$res))); + } + + /** + * RollBack playlist changes to the locked state + * + * The XML-RPC name of this method is "locstor.revertEditedPlaylist". + * + * The input parameters are an XML-RPC struct with the following + * fields: + *
      + *
    • sessid : string - session id
    • + *
    • token : string - playlist token + * returned by locstor.editPlaylist
    • + *
    + * + * On success, returns a XML-RPC struct with single field: + *
      + *
    • plid : string - playlistId
    • + *
    + * + * On errors, returns an XML-RPC error response. + * The possible error codes and error message are: + *
      + *
    • 3 - Incorrect parameters passed to method: + * Wanted ... , got ... at param
    • + *
    • 801 - wrong 1st parameter, struct expected.
    • + *
    • 805 - xr_revertEditedPlaylist: + * <message from lower layer>
    • + *
    + * + * @param XML_RPC_Message $input + * @return XML_RPC_Response + * @see LocStor::revertEditedPlaylist + */ + public function xr_revertEditedPlaylist($input) + { + list($ok, $r) = XR_LocStor::xr_getParams($input); + if (!$ok) { + return $r; + } + $res = $this->revertEditedPlaylist($r['token'], $r['sessid']); + if (PEAR::isError($res)) { + return new XML_RPC_Response(0, 805, + "xr_revertEditedPlaylist: ".$res->getMessage(). + " ".$res->getUserInfo() + ); + } + return new XML_RPC_Response(XML_RPC_encode(array('plid'=>$res))); + } + + /* ------------------------------------------------------- delete playlist*/ + /** + * Delete a Playlist metafile - DISABLED now! + * + * The XML-RPC name of this method is "locstor.deletePlaylist". + * + * The input parameters are an XML-RPC struct with the following + * fields: + *
      + *
    • sessid : string - session id
    • + *
    • plid : string - global unique id of Playlist
    • + *
    + * + * On success, returns a XML-RPC struct with single field: + *
      + *
    • status : boolean - TRUE
    • + *
    + * + * On errors, returns an XML-RPC error response. + * The possible error codes and error message are: + *
      + *
    • 3 - Incorrect parameters passed to method: + * Wanted ... , got ... at param
    • + *
    • 801 - wrong 1st parameter, struct expected.
    • + *
    • 805 - xr_deletePlaylist: + * <message from lower layer>
    • + *
    + * + * @param XML_RPC_Message $input + * @return XML_RPC_Response + * @see LocStor::deletePlaylist + */ + public function xr_deletePlaylist($input) + { + list($ok, $r) = XR_LocStor::xr_getParams($input); + if (!$ok) { + return $r; + } + if (!isset($r['forced'])) { + $r['forced']=FALSE; + } + $res = $this->deletePlaylist($r['sessid'], $r['plid'], $r['forced']); + if (! $r['forced']) { + return new XML_RPC_Response(0, 805,"xr_deletePlaylist: method disabled"); + } + if (PEAR::isError($res)) { + $ec0 = intval($res->getCode()); + $ec = ($ec0 == GBERR_FILENEX ? 800+$ec0 : 805 ); + return new XML_RPC_Response(0, $ec, + "xr_deletePlaylist: ".$res->getMessage(). + " ".$res->getUserInfo() + ); + } + return new XML_RPC_Response(XML_RPC_encode(array('status'=>$res))); + } + + /* ------------------------------------------------------- access playlist*/ + /** + * Access (read) a Playlist metafile. + * + * The XML-RPC name of this method is "locstor.accessPlaylist". + * + * The input parameters are an XML-RPC struct with the following + * fields: + *
      + *
    • sessid : string - session id
    • + *
    • plid : string - global unique id of Playlist
    • + *
    • recursive : boolean - flag for recursive access content + * inside playlist (default: false)
    • + *
    + * + * On success, returns an XML-RPC struct with the following fields: + *
      + *
    • url : string - readable url of accessed playlist in + * XML format
    • + *
    • token : string - playlist token
    • + *
    • chsum : string - md5 checksum
    • + *
    • content: array of structs - recursive access (optional)
    • + *
    + * + * The content field contains a struct for each playlist + * element contained in the playlist. For audio clips, this struct is + * of type {url, token}; for sub-playlists, it is of type + * {url, token, chsum, content}. + * + * On errors, returns an XML-RPC error response. + * The possible error codes and error message are: + *
      + *
    • 3 - Incorrect parameters passed to method: + * Wanted ... , got ... at param
    • + *
    • 801 - wrong 1st parameter, struct expected.
    • + *
    • 805 - xr_accessPlaylist: + * <message from lower layer>
    • + *
    • 847 - invalid plid.
    • + *
    + * + * @param XML_RPC_Message $input + * @return XML_RPC_Response + * @see LocStor::accessPlaylist + */ + public function xr_accessPlaylist($input) + { + list($ok, $r) = XR_LocStor::xr_getParams($input); + if (!$ok) { + return $r; + } + if (!isset($r['recursive']) || is_null($r['recursive'])) { + $r['recursive']=FALSE; + } + $res = $this->accessPlaylist($r['sessid'], $r['plid'], (boolean)$r['recursive']); + if (PEAR::isError($res)) { + $ec0 = intval($res->getCode()); + $ec = ($ec0 == GBERR_NOTF ? 800+$ec0 : 805 ); + return new XML_RPC_Response(0, $ec, + "xr_accessPlaylist: ".$res->getMessage(). + " ".$res->getUserInfo() + ); + } + return new XML_RPC_Response(XML_RPC_encode($res)); + } + + /** + * Release the resources obtained earlier by accessPlaylist(). + * + * The XML-RPC name of this method is "locstor.releasePlaylist". + * + * The input parameters are an XML-RPC struct with the following + * fields: + *
      + *
    • token : string - playlist token + * returned by locstor.accessPlaylist
    • + *
    • recursive : boolean - flag for recursive release content + * accessed by recursive accessPlaylist + * (ignored now - true forced)
    • + *
    + * + * On success, returns a XML-RPC struct with single field: + *
      + *
    • plid : string - playlist ID
    • + *
    + * + * On errors, returns an XML-RPC error response. + * The possible error codes and error message are: + *
      + *
    • 3 - Incorrect parameters passed to method: + * Wanted ... , got ... at param
    • + *
    • 801 - wrong 1st parameter, struct expected.
    • + *
    • 805 - xr_releasePlaylist: + * <message from lower layer>
    • + *
    + * + * @param XML_RPC_Message $input + * @return XML_RPC_Response + * @see LocStor::releasePlaylist + */ + public function xr_releasePlaylist($input) + { + list($ok, $r) = XR_LocStor::xr_getParams($input); + if (!$ok) { + return $r; + } + //if (!isset($r['recursive']) || is_null($r['recursive'])) $r['recursive']=FALSE; + $res = $this->releasePlaylist(NULL, $r['token'], TRUE); + if (PEAR::isError($res)) { + return new XML_RPC_Response(0, 805, + "xr_releasePlaylist: ".$res->getMessage(). + " ".$res->getUserInfo() + ); + } + return new XML_RPC_Response(XML_RPC_encode(array('plid'=>$res))); + } + + /* -------------------------------------------------------- playlist info */ + /** + * Check whether a Playlist metafile with the given playlist ID exists. + * + * The XML-RPC name of this method is "locstor.existsPlaylist". + * + * The input parameters are an XML-RPC struct with the following + * fields: + *
      + *
    • sessid : string - session id
    • + *
    • plid : string - global unique id of Playlist
    • + *
    + * + * On success, returns a XML-RPC struct with single field: + *
      + *
    • exists : boolean
    • + *
    + * + * On errors, returns an XML-RPC error response. + * The possible error codes and error message are: + *
      + *
    • 3 - Incorrect parameters passed to method: + * Wanted ... , got ... at param
    • + *
    • 801 - wrong 1st parameter, struct expected.
    • + *
    • 805 - xr_existsPlaylist: + * <message from lower layer>
    • + *
    + * + * @param XML_RPC_Message $input + * @return XML_RPC_Response + * @see LocStor::existsPlaylist + */ + public function xr_existsPlaylist($input) + { + list($ok, $r) = XR_LocStor::xr_getParams($input); + if (!$ok) { + return $r; + } + $res = $this->existsPlaylist($r['sessid'], $r['plid']); + if (PEAR::isError($res)) { + return new XML_RPC_Response(0, 805, + "xr_existsPlaylist: ".$res->getMessage(). + " ".$res->getUserInfo() + ); + } + return new XML_RPC_Response(XML_RPC_encode(array('exists'=>$res))); + } + + /** + * Check whether a Playlist metafile with the given playlist ID + * is available for editing, i.e., exists and is not marked as + * beeing edited. + * + * The XML-RPC name of this method is "locstor.playlistIsAvailable". + * + * The input parameters are an XML-RPC struct with the following + * fields: + *
      + *
    • sessid : string - session id
    • + *
    • plid : string - global unique id of Playlist
    • + *
    + * + * On success, returns a XML-RPC struct with single field: + *
      + *
    • available : boolean
    • + *
    • ownerid : int - local user id
    • + *
    • ownerlogin : string - local username
    • + *
    + * + * On errors, returns an XML-RPC error response. + * The possible error codes and error message are: + *
      + *
    • 3 - Incorrect parameters passed to method: + * Wanted ... , got ... at param
    • + *
    • 801 - wrong 1st parameter, struct expected.
    • + *
    • 805 - xr_playlistIsAvailable: + * <message from lower layer>
    • + *
    + * + * @param XML_RPC_Message $input + * @return XML_RPC_Response + * @see LocStor::playlistIsAvailable + */ + public function xr_playlistIsAvailable($input) + { + list($ok, $r) = XR_LocStor::xr_getParams($input); + if (!$ok) { + return $r; + } + $res = $this->playlistIsAvailable($r['sessid'], $r['plid'], TRUE); + $ownerId = ($res === TRUE ? NULL : $res); + $ownerLogin = (is_null($ownerId) ? NULL : Subjects::GetSubjName($ownerId)); + if (PEAR::isError($res)) { + return new XML_RPC_Response(0, 805, + "xr_playlistIsAvailable: ".$res->getMessage(). + " ".$res->getUserInfo() + ); + } + return new XML_RPC_Response(XML_RPC_encode(array( + 'available' => ($res === TRUE), + 'ownerid' => $ownerId, + 'ownerlogin' => $ownerLogin, + ))); + } + + /* ------------------------------------------------------ export playlist */ + /** + * Create a tarfile with playlist export - playlist and all matching + * sub-playlists and media files (if desired) + * + * The XML-RPC name of this method is "locstor.exportPlaylistOpen". + * + * The input parameters are an XML-RPC struct with the following + * fields: + *
      + *
    • sessid : string - session id
    • + *
    • plids : array of strings - global unique IDs of Playlists
    • + *
    • type : string - playlist format, values: lspl | smil
    • + *
    • standalone : boolean - if only playlist should be exported or + * with all related files
    • + *
    + * + * On success, returns a XML-RPC struct with following fields: + *
      + *
    • url : string - readable url
    • + *
    • token : string - access token
    • + *
    + * + * On errors, returns an XML-RPC error response. + * The possible error codes and error message are: + *
      + *
    • 3 - Incorrect parameters passed to method: + * Wanted ... , got ... at param
    • + *
    • 801 - wrong 1st parameter, struct expected.
    • + *
    • 805 - xr_exportPlaylistOpen: + * <message from lower layer>
    • + *
    + * + * @param XML_RPC_Message $input + * @return XML_RPC_Response + * @see LocStor::exportPlaylistOpen + */ + public function xr_exportPlaylistOpen($input) + { + list($ok, $r) = XR_LocStor::xr_getParams($input); + if (!$ok) { + return $r; + } + if (!isset($r['standalone']) || empty($r['standalone'])) { + $r['standalone']=FALSE; + } + $res = $this->exportPlaylistOpen( + $r['sessid'], $r['plids'], $r['type'], $r['standalone'] + ); + if (PEAR::isError($res)) { + return new XML_RPC_Response(0, 805, + "xr_exportPlaylistOpen: ".$res->getMessage(). + " ".$res->getUserInfo() + ); + } + return new XML_RPC_Response(XML_RPC_encode(array( + 'url' => $res['url'], + 'token' => $res['token'], + ))); + } + + /** + * Close playlist export previously opened by the exportPlaylistOpen method + * + * The XML-RPC name of this method is "locstor.exportPlaylistClose". + * + * The input parameters are an XML-RPC struct with the following + * fields: + *
      + *
    • token : string - access token
    • + *
    + * + * On success, returns a XML-RPC struct with single field: + *
      + *
    • status : boolean - status/li> + *
    + * + * On errors, returns an XML-RPC error response. + * The possible error codes and error message are: + *
      + *
    • 3 - Incorrect parameters passed to method: + * Wanted ... , got ... at param
    • + *
    • 801 - wrong 1st parameter, struct expected.
    • + *
    • 805 - xr_exportPlaylistClose: + * <message from lower layer>
    • + *
    + * + * @param XML_RPC_Message $input + * @return XML_RPC_Response + * @see LocStor::exportPlaylistClose + */ + public function xr_exportPlaylistClose($input) + { + list($ok, $r) = XR_LocStor::xr_getParams($input); + if (!$ok) { + return $r; + } + $res = $this->exportPlaylistClose($r['token']); + if (PEAR::isError($res)) { + return new XML_RPC_Response(0, 805, + "xr_exportPlaylistClose: ".$res->getMessage(). + " ".$res->getUserInfo() + ); + } + return new XML_RPC_Response(XML_RPC_encode(array('status'=>TRUE))); + } + + /* ------------------------------------------------------ import playlist */ + /** + * Open writable URL for import playlist in LS Archive format + * + * The XML-RPC name of this method is "locstor.importPlaylistOpen". + * + * The input parameters are an XML-RPC struct with the following + * fields: + *
      + *
    • sessid : string - session id
    • + *
    • chsum : string - md5 checksum of imported file
    • + *
    + * + * On success, returns a XML-RPC struct with following fields: + *
      + *
    • url : string - writable url
    • + *
    • token : string - PUT token
    • + *
    + * + * On errors, returns an XML-RPC error response. + * The possible error codes and error message are: + *
      + *
    • 3 - Incorrect parameters passed to method: + * Wanted ... , got ... at param
    • + *
    • 801 - wrong 1st parameter, struct expected.
    • + *
    • 805 - xr_importPlaylistOpen: + * <message from lower layer>
    • + *
    + * + * @param XML_RPC_Message $input + * @return XML_RPC_Response + * @see LocStor::importPlaylistOpen + */ + public function xr_importPlaylistOpen($input) + { + list($ok, $r) = XR_LocStor::xr_getParams($input); + if (!$ok) { + return $r; + } + $res = $this->importPlaylistOpen($r['sessid'], $r['chsum']); + if (PEAR::isError($res)) { + return new XML_RPC_Response(0, 805, + "xr_importPlaylistOpen: ".$res->getMessage(). + " ".$res->getUserInfo() + ); + } + return new XML_RPC_Response(XML_RPC_encode(array( + 'url'=>$res['url'], + 'token'=>$res['token'], + ))); + } + + /** + * Open writable URL for import playlist in LS Archive format + * + * The XML-RPC name of this method is "locstor.importPlaylistClose". + * + * The input parameters are an XML-RPC struct with the following + * fields: + *
      + *
    • token : string - access token
    • + *
    + * + * On success, returns a XML-RPC struct with following fields: + *
      + *
    • gunid : string - global id
    • + *
    + * + * On errors, returns an XML-RPC error response. + * The possible error codes and error message are: + *
      + *
    • 3 - Incorrect parameters passed to method: + * Wanted ... , got ... at param
    • + *
    • 801 - wrong 1st parameter, struct expected.
    • + *
    • 805 - xr_importPlaylistClose: + * <message from lower layer>
    • + *
    + * + * @param XML_RPC_Message $input + * @return XML_RPC_Response + * @see LocStor::importPlaylistClose + */ + public function xr_importPlaylistClose($input) + { + list($ok, $r) = XR_LocStor::xr_getParams($input); + if (!$ok) { + return $r; + } + $res = $this->importPlaylistClose($r['token']); + if (PEAR::isError($res)) { + return new XML_RPC_Response(0, 805, + "xr_importPlaylistClose: ".$res->getMessage(). + " ".$res->getUserInfo() + ); + } + return new XML_RPC_Response(XML_RPC_encode(array( + 'gunid'=>$res, + ))); + } + + /* ---------------------------------------------- render playlist to file */ + /** + * Render playlist to ogg file (open handle) + * + * The XML-RPC name of this method is "locstor.renderPlaylistToFileOpen". + * + * The input parameters are an XML-RPC struct with the following + * fields: + *
      + *
    • sessid : string - session id
    • + *
    • plid : string - playlist gunid
    • + *
    + * + * On success, returns a XML-RPC struct with following fields: + *
      + *
    • token : string - render token
    • + *
    + * + * On errors, returns an XML-RPC error response. + * The possible error codes and error message are: + *
      + *
    • 3 - Incorrect parameters passed to method: + * Wanted ... , got ... at param
    • + *
    • 801 - wrong 1st parameter, struct expected.
    • + *
    • 805 - xr_renderPlaylistToFileOpen: + * <message from lower layer>
    • + *
    + * + * @param XML_RPC_Message $input + * @return XML_RPC_Response + * @see LocStor::renderPlaylistToFileOpen + */ + public function xr_renderPlaylistToFileOpen($input) + { + list($ok, $r) = XR_LocStor::xr_getParams($input); + if (!$ok) { + return $r; + } + $res = $this->renderPlaylistToFileOpen($r['sessid'], $r['plid']); + if (PEAR::isError($res)) { + return new XML_RPC_Response(0, 805, + "xr_renderPlaylistToFileOpen: ".$res->getMessage(). + " ".$res->getUserInfo() + ); + } + return new XML_RPC_Response(XML_RPC_encode(array( + 'token'=>$res['token'], + ))); + } + + /** + * Render playlist to ogg file (check results) + * + * The XML-RPC name of this method is "locstor.renderPlaylistToFileCheck". + * + * The input parameters are an XML-RPC struct with the following + * fields: + *
      + *
    • token : string - render token
    • + *
    + * + * On success, returns a XML-RPC struct with following fields: + *
      + *
    • status : string - success | working | fault
    • + *
    • url : string - readable url
    • + *
    + * + * On errors, returns an XML-RPC error response. + * The possible error codes and error message are: + *
      + *
    • 3 - Incorrect parameters passed to method: + * Wanted ... , got ... at param
    • + *
    • 801 - wrong 1st parameter, struct expected.
    • + *
    • 805 - xr_renderPlaylistToFileCheck: + * <message from lower layer>
    • + *
    + * + * @param XML_RPC_Message $input + * @return XML_RPC_Response + * @see LocStor::renderPlaylistToFileCheck + */ + public function xr_renderPlaylistToFileCheck($input) + { + list($ok, $r) = XR_LocStor::xr_getParams($input); + if (!$ok) { + return $r; + } + $res = $this->renderPlaylistToFileCheck($r['token']); + if (PEAR::isError($res)) { + return new XML_RPC_Response(0, 805, + "xr_renderPlaylistToFileCheck: ".$res->getMessage(). + " ".$res->getUserInfo() + ); + } + return new XML_RPC_Response(XML_RPC_encode(array( + 'url'=>$res['url'], + 'status'=>$res['status'], + ))); + } + + /** + * Render playlist to ogg file (close handle) + * + * The XML-RPC name of this method is "locstor.renderPlaylistToFileClose". + * + * The input parameters are an XML-RPC struct with the following + * fields: + *
      + *
    • token : string - render token
    • + *
    + * + * On success, returns a XML-RPC struct with following fields: + *
      + *
    • status : boolean
    • + *
    + * + * On errors, returns an XML-RPC error response. + * The possible error codes and error message are: + *
      + *
    • 3 - Incorrect parameters passed to method: + * Wanted ... , got ... at param
    • + *
    • 801 - wrong 1st parameter, struct expected.
    • + *
    • 805 - xr_renderPlaylistToFileClose: + * <message from lower layer>
    • + *
    + * + * @param XML_RPC_Message $input + * @return XML_RPC_Response + * @see LocStor::renderPlaylistToFileClose + */ + public function xr_renderPlaylistToFileClose($input) + { + list($ok, $r) = XR_LocStor::xr_getParams($input); + if (!$ok) { + return $r; + } + $res = $this->renderPlaylistToFileClose($r['token']); + if (PEAR::isError($res)) { + return new XML_RPC_Response(0, 805, + "xr_renderPlaylistToFileClose: ".$res->getMessage(). + " ".$res->getUserInfo() + ); + } + return new XML_RPC_Response(XML_RPC_encode(array( + 'status'=>$res['status'], + ))); + } + + /* ------------------------------------------- render playlist to storage */ + /** + * Render playlist to storage media clip (open handle) + * + * The XML-RPC name of this method is "locstor.renderPlaylistToStorageOpen". + * + * The input parameters are an XML-RPC struct with the following + * fields: + *
      + *
    • sessid : string - session id
    • + *
    • plid : string - playlist gunid
    • + *
    + * + * On success, returns a XML-RPC struct with following fields: + *
      + *
    • token : string - render token
    • + *
    + * + * On errors, returns an XML-RPC error response. + * The possible error codes and error message are: + *
      + *
    • 3 - Incorrect parameters passed to method: + * Wanted ... , got ... at param
    • + *
    • 801 - wrong 1st parameter, struct expected.
    • + *
    • 805 - xr_renderPlaylistToStorageOpen: + * <message from lower layer>
    • + *
    + * + * @param XML_RPC_Message $input + * @return XML_RPC_Response + * @see LocStor::renderPlaylistToStorageOpen + */ + public function xr_renderPlaylistToStorageOpen($input) + { + list($ok, $r) = XR_LocStor::xr_getParams($input); + if (!$ok) { + return $r; + } + $res = $this->renderPlaylistToStorageOpen($r['sessid'], $r['plid']); + if (PEAR::isError($res)) { + return new XML_RPC_Response(0, 805, + "xr_renderPlaylistToStorageOpen: ".$res->getMessage(). + " ".$res->getUserInfo() + ); + } + return new XML_RPC_Response(XML_RPC_encode(array( + 'token'=>$res['token'], + ))); + } + + /** + * Render playlist to storage media clip (check results) + * + * The XML-RPC name of this method is "locstor.renderPlaylistToStorageCheck". + * + * The input parameters are an XML-RPC struct with the following + * fields: + *
      + *
    • token : string - render token
    • + *
    + * + * On success, returns a XML-RPC struct with following fields: + *
      + *
    • status : string - success | working | fault
    • + *
    • gunid : string - gunid of result file
    • + *
    + * + * On errors, returns an XML-RPC error response. + * The possible error codes and error message are: + *
      + *
    • 3 - Incorrect parameters passed to method: + * Wanted ... , got ... at param
    • + *
    • 801 - wrong 1st parameter, struct expected.
    • + *
    • 805 - xr_renderPlaylistToStorageCheck: + * <message from lower layer>
    • + *
    + * + * @param XML_RPC_Message $input + * @return XML_RPC_Response + * @see LocStor::renderPlaylistToStorageCheck + */ + public function xr_renderPlaylistToStorageCheck($input) + { + list($ok, $r) = XR_LocStor::xr_getParams($input); + if (!$ok) { + return $r; + } + $res = $this->renderPlaylistToStorageCheck($r['token']); + if (PEAR::isError($res)) { + return new XML_RPC_Response(0, 805, + "xr_renderPlaylistToStorageCheck: ".$res->getMessage(). + " ".$res->getUserInfo() + ); + } + return new XML_RPC_Response(XML_RPC_encode(array( + 'status'=>$res['status'], + 'gunid'=>$res['gunid'], + ))); + } + + /* ----------------------------------------------- render playlist to RSS */ + /** + * Render playlist to RSS file (open handle) + * + * The XML-RPC name of this method is "locstor.renderPlaylistToRSSOpen". + * + * The input parameters are an XML-RPC struct with the following + * fields: + *
      + *
    • sessid : string - session id
    • + *
    • plid : string - playlist gunid
    • + *
    + * + * On success, returns a XML-RPC struct with following fields: + *
      + *
    • token : string - render token
    • + *
    + * + * On errors, returns an XML-RPC error response. + * The possible error codes and error message are: + *
      + *
    • 3 - Incorrect parameters passed to method: + * Wanted ... , got ... at param
    • + *
    • 801 - wrong 1st parameter, struct expected.
    • + *
    • 805 - xr_renderPlaylistToRSSOpen: + * <message from lower layer>
    • + *
    + * + * @param XML_RPC_Message $input + * @return XML_RPC_Response + * @see LocStor::renderPlaylistToRSSOpen + */ + public function xr_renderPlaylistToRSSOpen($input) + { + list($ok, $r) = XR_LocStor::xr_getParams($input); + if (!$ok) { + return $r; + } + $res = $this->renderPlaylistToRSSOpen($r['sessid'], $r['plid']); + if (PEAR::isError($res)) { + return new XML_RPC_Response(0, 805, + "xr_renderPlaylistToRSSOpen: ".$res->getMessage(). + " ".$res->getUserInfo() + ); + } + return new XML_RPC_Response(XML_RPC_encode(array( + 'token'=>$res['token'], + ))); + } + + /** + * Render playlist to RSS file (check results) + * + * The XML-RPC name of this method is "locstor.renderPlaylistToRSSCheck". + * + * The input parameters are an XML-RPC struct with the following + * fields: + *
      + *
    • token : string - render token
    • + *
    + * + * On success, returns a XML-RPC struct with following fields: + *
      + *
    • status : string - success | working | fault
    • + *
    • url : string - readable url
    • + *
    + * + * On errors, returns an XML-RPC error response. + * The possible error codes and error message are: + *
      + *
    • 3 - Incorrect parameters passed to method: + * Wanted ... , got ... at param
    • + *
    • 801 - wrong 1st parameter, struct expected.
    • + *
    • 805 - xr_renderPlaylistToRSSCheck: + * <message from lower layer>
    • + *
    + * + * @param XML_RPC_Message $input + * @return XML_RPC_Response + * @see LocStor::renderPlaylistToRSSCheck + */ + public function xr_renderPlaylistToRSSCheck($input) + { + list($ok, $r) = XR_LocStor::xr_getParams($input); + if (!$ok) { + return $r; + } + $res = $this->renderPlaylistToRSSCheck($r['token']); + if (PEAR::isError($res)) { + return new XML_RPC_Response(0, 805, + "xr_renderPlaylistToRSSCheck: ".$res->getMessage(). + " ".$res->getUserInfo() + ); + } + return new XML_RPC_Response(XML_RPC_encode(array( + 'url'=>$res['url'], + 'status'=>$res['status'], + ))); + } + + /** + * Render playlist to RSS file (close handle) + * + * The XML-RPC name of this method is "locstor.renderPlaylistToRSSClose". + * + * The input parameters are an XML-RPC struct with the following + * fields: + *
      + *
    • token : string - render token
    • + *
    + * + * On success, returns a XML-RPC struct with following fields: + *
      + *
    • status : boolean
    • + *
    + * + * On errors, returns an XML-RPC error response. + * The possible error codes and error message are: + *
      + *
    • 3 - Incorrect parameters passed to method: + * Wanted ... , got ... at param
    • + *
    • 801 - wrong 1st parameter, struct expected.
    • + *
    • 805 - xr_renderPlaylistToRSSClose: + * <message from lower layer>
    • + *
    + * + * @param XML_RPC_Message $input + * @return XML_RPC_Response + * @see LocStor::renderPlaylistToRSSClose + */ + public function xr_renderPlaylistToRSSClose($input) + { + list($ok, $r) = XR_LocStor::xr_getParams($input); + if (!$ok) { + return $r; + } + $res = $this->renderPlaylistToRSSClose($r['token']); + if (PEAR::isError($res)) { + return new XML_RPC_Response(0, 805, + "xr_renderPlaylistToRSSClose: ".$res->getMessage(). + " ".$res->getUserInfo() + ); + } + return new XML_RPC_Response(XML_RPC_encode(array( + 'status'=>$res['status'], + ))); + } + + /*==================================================storage admin methods */ + /* ------------------------------------------------------- backup methods */ + /** + * Create backup of storage (open handle) + * + * The XML-RPC name of this method is "locstor.createBackupOpen". + * + * The input parameters are an XML-RPC struct with the following + * fields: + *
      + *
    • sessid : string - session id
    • + *
    • criteria : struct - see search criteria
    • + *
    + * + * On success, returns a XML-RPC struct with following fields: + *
      + *
    • token : string - backup token
    • + *
    + * + * On errors, returns an XML-RPC error response. + * The possible error codes and error message are: + *
      + *
    • 3 - Incorrect parameters passed to method: + * Wanted ... , got ... at param
    • + *
    • 801 - wrong 1st parameter, struct expected.
    • + *
    • 805 - xr_createBackupOpen: + * <message from lower layer>
    • + *
    + * + * @param XML_RPC_Message $input + * @return XML_RPC_Response + * @see LocStor::createBackupOpen + */ + public function xr_createBackupOpen($input) + { + list($ok, $r) = XR_LocStor::xr_getParams($input); + if (!$ok) { + return $r; + } + +# return new XML_RPC_Response(XML_RPC_encode(var_export($this, TRUE))); + + $res = $this->createBackupOpen($r['sessid'], $r['criteria']); + if (PEAR::isError($res)) { + return new XML_RPC_Response(0, 805, + "xr_createBackupOpen: ".$res->getMessage(). + " ".$res->getUserInfo() + ); + } + return new XML_RPC_Response(XML_RPC_encode(array( + 'token'=>$res['token'], + ))); + } + + /** + * Create backup of storage (check results) + * + * The XML-RPC name of this method is "locstor.createBackupCheck". + * + * The input parameters are an XML-RPC struct with the following + * fields: + *
      + *
    • token : string - backup token
    • + *
    + * + * On success, returns a XML-RPC struct with following fields: + *
      + *
    • status : string - success | working | fault
    • + *
    • url : string - readable url
    • + *
    • metafile : string - archive metafile in XML format
    • + *
    • faultString : string - error message + * (use only if status==fault)
    • + *
    + * + * On errors, returns an XML-RPC error response. + * The possible error codes and error message are: + *
      + *
    • 3 - Incorrect parameters passed to method: + * Wanted ... , got ... at param
    • + *
    • 801 - wrong 1st parameter, struct expected.
    • + *
    • 805 - xr_createBackupCheck: + * <message from lower layer>
    • + *
    + * + * @param XML_RPC_Message $input + * @return XML_RPC_Response + * @see LocStor::createBackupCheck + */ + //
  • 854 - backup process fault
  • + public function xr_createBackupCheck($input) + { + list($ok, $r) = XR_LocStor::xr_getParams($input); + if (!$ok) { + return $r; + } + $res = $this->createBackupCheck($r['token']); + + if (PEAR::isError($res)) { + $ec0 = intval($res->getCode()); + $ec = ($ec0 == GBERR_BGERR ? 800+$ec0 : 805 ); + return new XML_RPC_Response(0, $ec, + "xr_createBackupCheck: ".$res->getMessage(). + " ".$res->getUserInfo() + ); + } + return new XML_RPC_Response(XML_RPC_encode($res)); + } + + /** + * Create backup of storage (list results) + * + * The XML-RPC name of this method is "locstor.createBackupList". + * + * The input parameters are an XML-RPC struct with the following + * fields: + *
      + *
    • stat : string - backup status
    • + *
    + * + * On success, returns a XML-RPC array of struct with following fields: + *
      + *
    • status : string - success | working | fault
    • + *
    • url : string - readable url
    • + *
    • metafile : string - archive metafile in XML format
    • + *
    • faultString : string - error message + * (use only if status==fault)
    • + *
    + * + * On errors, returns an XML-RPC error response. + * The possible error codes and error message are: + *
      + *
    • 3 - Incorrect parameters passed to method: + * Wanted ... , got ... at param
    • + *
    • 801 - wrong 1st parameter, struct expected.
    • + *
    • 805 - xr_createBackupCheck: + * <message from lower layer>
    • + *
    + * + * @param XML_RPC_Message $input + * @return XML_RPC_Response + * @see LocStor::createBackupCheck + */ + //
  • 854 - backup process fault
  • + public function xr_createBackupList($input) + { + list($ok, $r) = XR_LocStor::xr_getParams($input); + if (!$ok) { + return $r; + } + if (!isset($r['stat']) || is_null($r['stat'])) { + $r['stat']=''; + } + $res = $this->createBackupList($r['stat']); + if (PEAR::isError($res)) { + $ec0 = intval($res->getCode()); + $ec = ($ec0 == GBERR_BGERR ? 800+$ec0 : 805 ); + return new XML_RPC_Response(0, $ec, + "xr_createBackupCheck: ".$res->getMessage(). + " ".$res->getUserInfo() + ); + } + return new XML_RPC_Response(XML_RPC_encode($res)); + } + + /** + * Create backup of storage (close handle) + * + * The XML-RPC name of this method is "locstor.createBackupClose". + * + * The input parameters are an XML-RPC struct with the following + * fields: + *
      + *
    • token : string - backup token
    • + *
    + * + * On success, returns a XML-RPC struct with following fields: + *
      + *
    • status : boolean
    • + *
    + * + * On errors, returns an XML-RPC error response. + * The possible error codes and error message are: + *
      + *
    • 3 - Incorrect parameters passed to method: + * Wanted ... , got ... at param
    • + *
    • 801 - wrong 1st parameter, struct expected.
    • + *
    • 805 - xr_createBackupClose: + * <message from lower layer>
    • + *
    + * + * @param XML_RPC_Message $input + * @return XML_RPC_Response + * @see LocStor::createBackupClose + */ + public function xr_createBackupClose($input) + { + list($ok, $r) = XR_LocStor::xr_getParams($input); + if (!$ok) { + return $r; + } + $res = $this->createBackupClose($r['token']); + if (PEAR::isError($res)) { + return new XML_RPC_Response(0, 805, + "xr_createBackupClose: ".$res->getMessage(). + " ".$res->getUserInfo() + ); + } + return new XML_RPC_Response(XML_RPC_encode(array( + 'status'=>$res['status'], + ))); + } + /* ------------------------------------------------------ restore methods */ + /** + * Open restore a backup file + * + * The XML-RPC name of this method is "locstor.restoreBackupOpen". + * + * The input parameters are an XML-RPC struct with the following + * fields: + *
      + *
    • sessid : string - session id
    • + *
    • chsum : string - md5 checksum of restore file
    • + *
    + * + * On success, returns a XML-RPC struct with following fields: + *
      + *
    • url : string - writable URL for HTTP PUT
    • + *
    • token : string - PUT token
    • + *
    + * + * On errors, returns an XML-RPC error response. + * The possible error codes and error message are: + *
      + *
    • 3 - Incorrect parameters passed to method: + * Wanted ... , got ... at param
    • + *
    • 801 - wrong 1st parameter, struct expected.
    • + *
    • 805 - xr_restoreBackupOpen: + * <message from lower layer>
    • + *
    + * + * @param XML_RPC_Message $input + * @return XML_RPC_Response + * @see LocStor::restoreBackupOpen + */ + public function xr_restoreBackupOpen($input) + { + list($ok, $r) = XR_LocStor::xr_getParams($input); + if (!$ok) { + return $r; + } + $res = $this->restoreBackupOpen($r['sessid'], $r['chsum']); + if (PEAR::isError($res)) { + return new XML_RPC_Response(0, 805, + "xr_restoreBackupOpen: ".$res->getMessage(). + " ".$res->getUserInfo() + ); + } + unset($res['fname']); + return new XML_RPC_Response(XML_RPC_encode($res)); + } + + /** + * Close writable URL for restore a backup file and start the restore + * process + * + * The XML-RPC name of this method is "locstor.restoreBackupClosePut". + * + * The input parameters are an XML-RPC struct with the following + * fields: + *
      + *
    • sessid : string - session id
    • + *
    • token : string - PUT token
    • + *
    + * + * On success, returns a XML-RPC struct with following fields: + *
      + *
    • token : string - restore token
    • + *
    + * + * On errors, returns an XML-RPC error response. + * The possible error codes and error message are: + *
      + *
    • 3 - Incorrect parameters passed to method: + * Wanted ... , got ... at param
    • + *
    • 801 - wrong 1st parameter, struct expected.
    • + *
    • 805 - xr_restoreBackupClosePut: + * <message from lower layer>
    • + *
    + * + * @param XML_RPC_Message $input + * @return XML_RPC_Response + * @see LocStor::restoreBackupClosePut + */ + public function xr_restoreBackupClosePut($input) + { + list($ok, $r) = XR_LocStor::xr_getParams($input); + if (!$ok) { + return $r; + } + $res = $this->restoreBackupClosePut($r['sessid'], $r['token']); + if (PEAR::isError($res)) { + return new XML_RPC_Response(0, 805, + "xr_restoreBackupClosePut: ".$res->getMessage(). + " ".$res->getUserInfo() + ); + } + return new XML_RPC_Response(XML_RPC_encode($res)); + } + + /** + * Check the state of restore procedure + * + * The XML-RPC name of this method is "locstor.restoreBackupCheck". + * + * The input parameters are an XML-RPC struct with the following + * fields: + *
      + *
    • token : string - restore token
    • + *
    + * + * On success, returns a XML-RPC struct with following fields: + *
      + *
    • status : string - success | working | fault
    • + *
    • faultString: string - description of fault
    • + *
    + * + * On errors, returns an XML-RPC error response. + * The possible error codes and error message are: + *
      + *
    • 3 - Incorrect parameters passed to method: + * Wanted ... , got ... at param
    • + *
    • 801 - wrong 1st parameter, struct expected.
    • + *
    • 805 - xr_restoreBackupCheck: + * <message from lower layer>
    • + *
    + * + * @param XML_RPC_Message $input + * @return XML_RPC_Response + * @see LocStor::restoreBackupCheck + */ + public function xr_restoreBackupCheck($input) + { + list($ok, $r) = XR_LocStor::xr_getParams($input); + if (!$ok) { + return $r; + } + $res = $this->restoreBackupCheck($r['token']); + if (PEAR::isError($res)) { + return new XML_RPC_Response(0, 805, + "xr_restoreBackupCheck: ".$res->getMessage(). + " ".$res->getUserInfo() + ); + } +# return new XML_RPC_Response(XML_RPC_encode(array( +# 'status'=>$res, +# ))); + return new XML_RPC_Response(XML_RPC_encode($res)); + } + + /** + * Close the restore process + * + * The XML-RPC name of this method is "locstor.restoreBackupClose". + * + * The input parameters are an XML-RPC struct with the following + * fields: + *
      + *
    • token : string - restore token
    • + *
    + * + * On success, returns a XML-RPC struct with following fields: + *
      + *
    • status : string - status
    • + *
    + * + * On errors, returns an XML-RPC error response. + * The possible error codes and error message are: + *
      + *
    • 3 - Incorrect parameters passed to method: + * Wanted ... , got ... at param
    • + *
    • 801 - wrong 1st parameter, struct expected.
    • + *
    • 805 - xr_restoreBackupClose: + * <message from lower layer>
    • + *
    + * + * @param XML_RPC_Message $input + * @return XML_RPC_Response + * @see LocStor::restoreBackupClose + */ + public function xr_restoreBackupClose($input) + { + list($ok, $r) = XR_LocStor::xr_getParams($input); + if (!$ok) { + return $r; + } + $res = $this->restoreBackupClose($r['token']); + if (PEAR::isError($res)) { + return new XML_RPC_Response(0, 805, + "xr_restoreBackupClose: ".$res->getMessage(). + " ".$res->getUserInfo() + ); + } +# return new XML_RPC_Response(XML_RPC_encode(array( +# 'gunid'=>$res, +# ))); + return new XML_RPC_Response(XML_RPC_encode($res)); + } + + /*========================================================== info methods */ + /** + * Check if audio clip exists and return TRUE/FALSE + * + * The XML-RPC name of this method is "locstor.existsAudioClip". + * + * The input parameters are an XML-RPC struct with the following + * fields: + *
      + *
    • sessid : string - session id
    • + *
    • gunid : string - global unique id of AudioCLip
    • + *
    + * + * On success, returns a XML-RPC struct with single field: + *
      + *
    • exists : boolean
    • + *
    + * + * On errors, returns an XML-RPC error response. + * The possible error codes and error message are: + *
      + *
    • 3 - Incorrect parameters passed to method: + * Wanted ... , got ... at param
    • + *
    • 801 - wrong 1st parameter, struct expected.
    • + *
    • 805 - xr_existsAudioClip: + * <message from lower layer>
    • + *
    + * + * @param XML_RPC_Message $input + * @return XML_RPC_Response + * @see LocStor::existsAudioClip + */ + public function xr_existsAudioClip($input) + { + list($ok, $r) = XR_LocStor::xr_getParams($input); + if (!$ok) { + return $r; + } + #$this->debugLog(join(', ', $r)); + $res = $this->existsAudioClip($r['sessid'], $r['gunid']); + #$this->debugLog($res); + if (PEAR::isError($res)) + return new XML_RPC_Response(0, 805, + "xr_existsAudioClip: ".$res->getMessage(). + " ".$res->getUserInfo() + ); + return new XML_RPC_Response(XML_RPC_encode(array('exists'=>$res))); + } + + /*====================================================== metadata methods */ + /** + * Return all file's metadata as XML string + * + * The XML-RPC name of this method is "locstor.getAudioClip". + * + * The input parameters are an XML-RPC struct with the following + * fields: + *
      + *
    • sessid : string - session id
    • + *
    • gunid : string - global unique id of AudioCLip
    • + *
    + * + * On success, returns a XML-RPC struct with single field: + *
      + *
    • metadata : string - metadata as XML
    • + *
    + * + * On errors, returns an XML-RPC error response. + * The possible error codes and error message are: + *
      + *
    • 3 - Incorrect parameters passed to method: + * Wanted ... , got ... at param
    • + *
    • 801 - wrong 1st parameter, struct expected.
    • + *
    • 805 - xr_getAudioClip: + * <message from lower layer>
    • + *
    + * + * @param XML_RPC_Message $input + * @return XML_RPC_Response + * @see LocStor::getAudioClip + */ + public function xr_getAudioClip($input) + { + list($ok, $r) = XR_LocStor::xr_getParams($input); + if (!$ok) { + return $r; + } + $res = $this->getAudioClip($r['sessid'], $r['gunid']); + if (PEAR::isError($res)) { + return new XML_RPC_Response(0, 805, + "xr_getAudioClip: ".$res->getMessage()." ".$res->getUserInfo() + ); + } + return new XML_RPC_Response(XML_RPC_encode(array('metadata'=>$res))); + } + + /** + * Update existing audio clip metadata + * + * The XML-RPC name of this method is "locstor.updateAudioClipMetadata". + * + * The input parameters are an XML-RPC struct with the following + * fields: + *
      + *
    • sessid : string - session id
    • + *
    • gunid : string - global unique id of AudioCLip
    • + *
    • metadata : metadata XML string
    • + *
    + * + * On success, returns a XML-RPC struct with single field: + *
      + *
    • status : boolean - TRUE
    • + *
    + * + * On errors, returns an XML-RPC error response. + * The possible error codes and error message are: + *
      + *
    • 3 - Incorrect parameters passed to method: + * Wanted ... , got ... at param
    • + *
    • 801 - wrong 1st parameter, struct expected.
    • + *
    • 805 - xr_updateAudioClipMetadata: + * <message from lower layer>
    • + *
    + * + * @param XML_RPC_Message $input + * @return XML_RPC_Response + * @see LocStor::updateAudioClipMetadata + */ + public function xr_updateAudioClipMetadata($input) + { + list($ok, $r) = XR_LocStor::xr_getParams($input); + if (!$ok) { + return $r; + } + $res = $this->updateAudioClipMetadata( + $r['sessid'], $r['gunid'], $r['metadata'] + ); + if (PEAR::isError($res)) { + return new XML_RPC_Response(0, 805, + "xr_updateAudioClip: ".$res->getMessage(). + " ".$res->getUserInfo() + ); + } + return new XML_RPC_Response(XML_RPC_encode(array('status'=>$res))); + } + + /** + * Search in local metadata database + * + * The XML-RPC name of this method is "locstor.searchMetadata". + * + * The input parameters are an XML-RPC struct with the following + * fields: + *
      + *
    • sessid : string - session id
    • + *
    • criteria : struct, with following fields:
      + *
        + *
      • filetype : string - type of searched files, + * meaningful values: 'audioclip', 'webstream', 'playlist', 'all'
      • + *
      • operator : string - type of conditions join + * (any condition matches / all conditions match), + * meaningful values: 'and', 'or', '' + * (may be empty or ommited only with less then 2 items in + * "conditions" field) + *
      • + *
      • limit : int - limit for result arrays (0 means unlimited)
      • + *
      • offset : int - starting point (0 means without offset)
      • + *
      • orderby : string - metadata category for sorting (optional) + * or array of strings for multicolumn orderby + * [default: dc:creator, dc:source, dc:title] + *
      • + *
      • desc : boolean - flag for descending order (optional) + * or array of boolean for multicolumn orderby + * (it corresponds to elements of orderby field) + * [default: all ascending] + *
      • + *
      • conditions : array of struct with fields: + *
          + *
        • cat : string - metadata category name
        • + *
        • op : string - operator, meaningful values: + * 'full', 'partial', 'prefix', '=', '<', '<=', + * '>', '>='
        • + *
        • val : string - search value
        • + *
        + *
      • + *
      + *
    • + *
    + * + * On success, returns a XML-RPC array of structs with fields: + *
      + *
    • cnt : integer - number of matching gunids + * of files have been found
    • + *
    • results : array of hashes: + *
        + *
      • gunid: string
      • + *
      • type: string - audioclip | playlist | webstream
      • + *
      • title: string - dc:title from metadata
      • + *
      • creator: string - dc:creator from metadata
      • + *
      • source: string - dc:source from metadata
      • + *
      • length: string - dcterms:extent in extent format
      • + *
      + *
    • + *
    + * (cnt value may be greater than size of result array - see limit param) + * + * On errors, returns an XML-RPC error response. + * The possible error codes and error message are: + *
      + *
    • 3 - Incorrect parameters passed to method: + * Wanted ... , got ... at param
    • + *
    • 801 - wrong 1st parameter, struct expected.
    • + *
    • 805 - xr_searchMetadata: + * <message from lower layer>
    • + *
    + * + * @param XML_RPC_Message $input + * @return XML_RPC_Response + * @see LocStor::searchMetadata + * @see BasicStor::localSearch + */ + public function xr_searchMetadata($input) + { + list($ok, $r) = XR_LocStor::xr_getParams($input); + if (!$ok) { + return $r; + } + $res = $this->searchMetadata($r['sessid'], $r['criteria']); + if (PEAR::isError($res)) { + return new XML_RPC_Response(0, 805, + "xr_searchAudioClip: ".$res->getMessage(). + " ".$res->getUserInfo() + ); + } +# return new XML_RPC_Response(XML_RPC_encode($res)); + $xv = new XML_RPC_Value; + $xv->addStruct(array( + 'cnt' => XML_RPC_encode($res['cnt']), + 'results' => + (count($res['results'])==0 + ? new XML_RPC_Value(array(), 'array') + : XML_RPC_encode($res['results']) + ), + )); + return new XML_RPC_Response($xv); + } + + /** + * Return values of specified metadata category + * + * The XML-RPC name of this method is "locstor.browseCategory". + * + * The input parameters are an XML-RPC struct with the following + * fields: + *
      + *
    • sessid : string - session id
    • + *
    • category : string - metadata category name + * with or without namespace prefix (dc:title, author)
    • + *
    • criteria : hash - see searchMetadata method
    • + *
    + * + * On success, returns a XML-RPC struct with the following fields: + *
      + *
    • results : array with found values
    • + *
    • cnt : integer - number of matching values
    • + *
    + * + * On errors, returns an XML-RPC error response. + * The possible error codes and error message are: + *
      + *
    • 3 - Incorrect parameters passed to method: + * Wanted ... , got ... at param
    • + *
    • 801 - wrong 1st parameter, struct expected.
    • + *
    • 805 - xr_browseCategory: + * <message from lower layer>
    • + *
    + * + * @param XML_RPC_Message $input + * @return XML_RPC_Response + * @see LocStor::browseCategory + */ + public function xr_browseCategory($input) + { + list($ok, $r) = XR_LocStor::xr_getParams($input); + if (!$ok) { + return $r; + } + $res = $this->browseCategory( + $r['category'], $r['criteria'], $r['sessid'] + ); + if (PEAR::isError($res)) { + return new XML_RPC_Response(0, 805, + "xr_getAudioClip: ".$res->getMessage()." ".$res->getUserInfo() + + ); + } + $xv = new XML_RPC_Value; + $xv->addStruct(array( + 'cnt' => XML_RPC_encode($res['cnt']), + 'results' => + (count($res['results'])==0 + ? new XML_RPC_Value(array(), 'array') + : XML_RPC_encode($res['results']) + ), + )); + return new XML_RPC_Response($xv); + } + + /* ============================================== methods for preferences */ + /** + * Load user preference value + * + * The XML-RPC name of this method is "locstor.loadPref". + * + * The input parameters are an XML-RPC struct with the following + * fields: + *
      + *
    • sessid : string - session id
    • + *
    • key : string - preference key
    • + *
    + * + * On success, returns a XML-RPC struct with single field: + *
      + *
    • value : string - preference value
    • + *
    + * + * On errors, returns an XML-RPC error response. + * The possible error codes and error message are: + *
      + *
    • 3 - Incorrect parameters passed to method: + * Wanted ... , got ... at param
    • + *
    • 801 - wrong 1st parameter, struct expected.
    • + *
    • 805 - xr_loadPref: + * <message from lower layer>
    • + *
    • 848 - invalid session id.
    • + *
    • 849 - invalid preference key.
    • + *
    + * + * @param XML_RPC_Message $input + * @return XML_RPC_Response + * @see Pref::loadPref + */ + public function xr_loadPref($input) + { + list($ok, $r) = XR_LocStor::xr_getParams($input); + if (!$ok) { + return $r; + } + require_once(dirname(__FILE__).'/../Prefs.php'); + $pr = new Prefs($this); + $res = $pr->loadPref($r['sessid'], $r['key']); + if (PEAR::isError($res)) { + $ec0 = intval($res->getCode()); + $ec = ($ec0 == GBERR_SESS || $ec0 == GBERR_PREF ? 800+$ec0 : 805 ); + return new XML_RPC_Response(0, $ec, + "xr_getAudioClip: ".$res->getMessage()." ".$res->getUserInfo() + + ); + } + return new XML_RPC_Response(XML_RPC_encode(array('value'=>$res))); + } + + /** + * Save user preference value + * + * The XML-RPC name of this method is "locstor.savePref". + * + * The input parameters are an XML-RPC struct with the following + * fields: + *
      + *
    • sessid : string - session id
    • + *
    • key : string - preference key
    • + *
    • value : string - preference value
    • + *
    + * + * On success, returns a XML-RPC struct with single field: + *
      + *
    • status : boolean
    • + *
    + * + * On errors, returns an XML-RPC error response. + * The possible error codes and error message are: + *
      + *
    • 3 - Incorrect parameters passed to method: + * Wanted ... , got ... at param
    • + *
    • 801 - wrong 1st parameter, struct expected.
    • + *
    • 805 - xr_savePref: + * <message from lower layer>
    • + *
    • 848 - invalid session id.
    • + *
    + * + * @param XML_RPC_Message $input + * @return XML_RPC_Response + * @see Pref::savePref + */ + public function xr_savePref($input) + { + list($ok, $r) = XR_LocStor::xr_getParams($input); + if (!$ok) { + return $r; + } + require_once(dirname(__FILE__).'/../Prefs.php'); + $pr = new Prefs($this); + $res = $pr->savePref($r['sessid'], $r['key'], $r['value']); + if (PEAR::isError($res)) { + $ec0 = intval($res->getCode()); + $ec = ($ec0 == GBERR_SESS ? 800+$ec0 : 805 ); + return new XML_RPC_Response(0, $ec, + "xr_getAudioClip: ".$res->getMessage()." ".$res->getUserInfo() + ); + } + return new XML_RPC_Response(XML_RPC_encode(array('status'=>$res))); + } + + /** + * Delete user preference record + * + * The XML-RPC name of this method is "locstor.delPref". + * + * The input parameters are an XML-RPC struct with the following + * fields: + *
      + *
    • sessid : string - session id
    • + *
    • key : string - preference key
    • + *
    + * + * On success, returns a XML-RPC struct with single field: + *
      + *
    • status : boolean
    • + *
    + * + * On errors, returns an XML-RPC error response. + * The possible error codes and error message are: + *
      + *
    • 3 - Incorrect parameters passed to method: + * Wanted ... , got ... at param
    • + *
    • 801 - wrong 1st parameter, struct expected.
    • + *
    • 805 - xr_delPref: + * <message from lower layer>
    • + *
    • 848 - invalid session id.
    • + *
    • 849 - invalid preference key.
    • + *
    + * + * @param XML_RPC_Message $input + * @return XML_RPC_Response + * @see Pref::delPref + */ + public function xr_delPref($input) + { + list($ok, $r) = XR_LocStor::xr_getParams($input); + if (!$ok) { + return $r; + } + require_once(dirname(__FILE__).'/../Prefs.php'); + $pr = new Prefs($this); + $res = $pr->delPref($r['sessid'], $r['key']); + if (PEAR::isError($res)) { + $ec0 = intval($res->getCode()); + $ec = ($ec0 == GBERR_SESS || $ec0 == GBERR_PREF ? 800+$ec0 : 805 ); + return new XML_RPC_Response(0, $ec, + "xr_getAudioClip: ".$res->getMessage()." ".$res->getUserInfo() + ); + } + return new XML_RPC_Response(XML_RPC_encode(array('status'=>$res))); + } + + /** + * Read group preference record + * + * The XML-RPC name of this method is "locstor.loadGroupPref". + * + * The input parameters are an XML-RPC struct with the following + * fields: + *
      + *
    • sessid : string - session id
    • + *
    • group : string - group name
    • + *
    • key : string - preference key
    • + *
    + * + * On success, returns a XML-RPC struct with single field: + *
      + *
    • value : string - preference value
    • + *
    + * + * On errors, returns an XML-RPC error response. + * The possible error codes and error message are: + *
      + *
    • 3 - Incorrect parameters passed to method: + * Wanted ... , got ... at param
    • + *
    • 801 - wrong 1st parameter, struct expected.
    • + *
    • 805 - xr_loadGroupPref: + * <message from lower layer>
    • + *
    • 820 - invalid group name.
    • + *
    • 848 - invalid session id.
    • + *
    • 849 - invalid preference key.
    • + *
    + * + * @param XML_RPC_Message $input + * @return XML_RPC_Response + * @see Pref::loadGroupPref + */ + public function xr_loadGroupPref($input) + { + list($ok, $r) = XR_LocStor::xr_getParams($input); + if (!$ok) { + return $r; + } + require_once(dirname(__FILE__).'/../Prefs.php'); + $pr = new Prefs($this); + $res = $pr->loadGroupPref($r['group'], $r['key']); + if (PEAR::isError($res)) { + $ec0 = intval($res->getCode()); + $ec = ( + $ec0 == GBERR_SESS || $ec0 == GBERR_PREF || $ec0==ALIBERR_NOTGR + ? 800+$ec0 : 805 + ); + return new XML_RPC_Response(0, $ec, + "xr_getAudioClip: ".$res->getMessage()." ".$res->getUserInfo() + + ); + } + return new XML_RPC_Response(XML_RPC_encode(array('value'=>$res))); + } + + /** + * Save group preference record + * + * The XML-RPC name of this method is "locstor.saveGroupPref". + * + * The input parameters are an XML-RPC struct with the following + * fields: + *
      + *
    • sessid : string - session id
    • + *
    • group : string - group name
    • + *
    • key : string - preference key
    • + *
    • value : string - preference value
    • + *
    + * + * On success, returns a XML-RPC struct with single field: + *
      + *
    • status : boolean
    • + *
    + * + * On errors, returns an XML-RPC error response. + * The possible error codes and error message are: + *
      + *
    • 3 - Incorrect parameters passed to method: + * Wanted ... , got ... at param
    • + *
    • 801 - wrong 1st parameter, struct expected.
    • + *
    • 805 - xr_saveGroupPref: + * <message from lower layer>
    • + *
    • 820 - invalid group name.
    • + *
    • 848 - invalid session id.
    • + *
    + * + * @param XML_RPC_Message $input + * @return XML_RPC_Response + * @see Pref::saveGroupPref + */ + public function xr_saveGroupPref($input) + { + list($ok, $r) = XR_LocStor::xr_getParams($input); + if (!$ok) { + return $r; + } + require_once(dirname(__FILE__).'/../Prefs.php'); + $pr = new Prefs($this); + $res = $pr->saveGroupPref($r['sessid'], $r['group'], $r['key'], $r['value']); + if (PEAR::isError($res)) { + $ec0 = intval($res->getCode()); + $ec = ($ec0==GBERR_SESS || $ec0==ALIBERR_NOTGR ? 800+$ec0 : 805 ); + return new XML_RPC_Response(0, $ec, + "xr_getAudioClip: ".$res->getMessage()." ".$res->getUserInfo() + ); + } + return new XML_RPC_Response(XML_RPC_encode(array('status'=>$res))); + } + + /* =============================== remote repository (networking) methods */ + /* ------------------------------------------------------- common methods */ + /** + * Common "check" method for transports + * + * The XML-RPC name of this method is "locstor.getTransportInfo". + * + * The input parameters are an XML-RPC struct with the following + * fields: + *
      + *
    • trtok : string - transport token
    • + *
    + * + * On success, returns a XML-RPC struct with the following fields: + *
      + *
    • trtype: string - audioclip | playlist | search | file
    • + *
    • direction: string - up | down
    • + *
    • state: string - transport state
    • + *
    • expectedsize: int - expected size
    • + *
    • realsize: int - size of transported file
    • + *
    • expectedchsum: string - expected checksum
    • + *
    • realchsum: string - checksum of transported file
    • + *
    • title: string - file title
    • + *
    • errmsg: string - error message from failed transports
    • + *
    + * + * On errors, returns an XML-RPC error response. + * The possible error codes and error message are: + *
      + *
    • 3 - Incorrect parameters passed to method: + * Wanted ... , got ... at param
    • + *
    • 801 - wrong 1st parameter, struct expected.
    • + *
    • 805 - xr_getTransportInfo: + * <message from lower layer>
    • + *
    • 848 - invalid session id.
    • + *
    • 872 - invalid tranport token.
    • + *
    + * + * @param XML_RPC_Message $input + * @return XML_RPC_Response + * @see Transport::getTransportInfo + */ + public function xr_getTransportInfo($input) + { + list($ok, $r) = XR_LocStor::xr_getParams($input); + if (!$ok) { + return $r; + } + require_once('../Transport.php'); + $tr = new Transport($this); + $res = $tr->getTransportInfo($r['trtok']); + if (PEAR::isError($res)) { + $ec0 = intval($res->getCode()); + $ec = ($ec0 == GBERR_SESS || $ec0 == TRERR_TOK ? 800+$ec0 : 805 ); + return new XML_RPC_Response(0, $ec, + "xr_getTransportInfo: ".$res->getMessage()." ".$res->getUserInfo() + ); + } + return new XML_RPC_Response(XML_RPC_encode($res)); + } + + /** + * Turn transports on/off, optionaly return current state. + * + * The XML-RPC name of this method is "locstor.turnOnOffTransports". + * + * The input parameters are an XML-RPC struct with the following + * fields: + *
      + *
    • sessid : string - session id
    • + *
    • onOff: boolean optional + * (if not used, current state is returned)
    • + *
    + * + * On success, returns a XML-RPC struct with the following fields: + *
      + *
    • state : boolean - previous state
    • + *
    + * + * On errors, returns an XML-RPC error response. + * The possible error codes and error message are: + *
      + *
    • 3 - Incorrect parameters passed to method: + * Wanted ... , got ... at param
    • + *
    • 801 - wrong 1st parameter, struct expected.
    • + *
    • 805 - xr_turnOnOffTransports: + * <message from lower layer>
    • + *
    • 848 - invalid session id.
    • + *
    • 872 - invalid tranport token.
    • + *
    + * + * @param XML_RPC_Message $input + * @return XML_RPC_Response + * @see Transport::turnOnOffTransports + */ + public function xr_turnOnOffTransports($input) + { + list($ok, $r) = XR_LocStor::xr_getParams($input); + if (!$ok) { + return $r; + } + require_once('../Transport.php'); + $tr = new Transport($this); + $res = $tr->turnOnOffTransports($r['onOff']); + if (PEAR::isError($res)) { + $ec0 = intval($res->getCode()); + $ec = ($ec0 == GBERR_SESS || $ec0 == TRERR_TOK ? 800+$ec0 : 805 ); + return new XML_RPC_Response(0, $ec, + "xr_turnOnOffTransports: ".$res->getMessage()." ".$res->getUserInfo() + ); + } + return new XML_RPC_Response(XML_RPC_encode(array('state'=>$res))); + } + + /** + * Pause, resume or cancel transport + * + * The XML-RPC name of this method is "locstor.doTransportAction". + * + * The input parameters are an XML-RPC struct with the following + * fields: + *
      + *
    • sessid : string - session id
    • + *
    • trtok : string - transport token
    • + *
    • action: string - pause | resume | cancel + *
    + * + * On success, returns a XML-RPC struct with the following fields: + *
      + *
    • state : string - resulting transport state
    • + *
    + * + * On errors, returns an XML-RPC error response. + * The possible error codes and error message are: + *
      + *
    • 3 - Incorrect parameters passed to method: + * Wanted ... , got ... at param
    • + *
    • 801 - wrong 1st parameter, struct expected.
    • + *
    • 805 - xr_doTransportAction: + * <message from lower layer>
    • + *
    • 848 - invalid session id.
    • + *
    • 872 - invalid tranport token.
    • + *
    + * + * @param XML_RPC_Message $input + * @return XML_RPC_Response + * @see Transport::doTransportAction + */ + public function xr_doTransportAction($input) + { + list($ok, $r) = XR_LocStor::xr_getParams($input); + if (!$ok) { + return $r; + } + require_once('../Transport.php'); + $tr = new Transport($this); + $res = $tr->doTransportAction($r['trtok'], $r['action']); + if (PEAR::isError($res)) { + $ec0 = intval($res->getCode()); + $ec = ($ec0 == GBERR_SESS || $ec0 == TRERR_TOK ? 800+$ec0 : 805 ); + return new XML_RPC_Response(0, $ec, + "xr_doTransportAction: ".$res->getMessage()." ".$res->getUserInfo() + ); + } + return new XML_RPC_Response(XML_RPC_encode(array('state'=>$res))); + } + + /* ------------------------ methods for ls-archive-format file transports */ + /** + * Open async file transfer from local storageServer to network hub, + * file should be ls-archive-format file. + * + * The XML-RPC name of this method is "locstor.uploadFile2Hub". + * + * The input parameters are an XML-RPC struct with the following + * fields: + *
      + *
    • sessid : string - session id
    • + *
    • filePath string - local path to uploaded file
    • + *
    + * + * On success, returns a XML-RPC struct with the following fields: + *
      + *
    • trtok : string - transport token
    • + *
    + * + * On errors, returns an XML-RPC error response. + * The possible error codes and error message are: + *
      + *
    • 3 - Incorrect parameters passed to method: + * Wanted ... , got ... at param
    • + *
    • 801 - wrong 1st parameter, struct expected.
    • + *
    • 805 - xr_uploadFile2Hub: + * <message from lower layer>
    • + *
    • 848 - invalid session id.
    • + *
    • 872 - invalid tranport token.
    • + *
    + * + * @param XML_RPC_Message $input + * @return XML_RPC_Response + * @see Transport::uploadFile2Hub + */ + public function xr_uploadFile2Hub($input) + { + list($ok, $r) = XR_LocStor::xr_getParams($input); + if (!$ok) { + return $r; + } + require_once('../Transport.php'); + $tr = new Transport($this); + $res = $tr->uploadFile2Hub($r['filePath']); // local files on XML-RPC :( + // there should be something as uploadFile2storageServer + if (PEAR::isError($res)) { + $ec0 = intval($res->getCode()); + $ec = ($ec0 == GBERR_SESS || $ec0 == TRERR_TOK ? 800+$ec0 : 805 ); + return new XML_RPC_Response(0, $ec, + "xr_uploadFile2Hub: ".$res->getMessage()." ".$res->getUserInfo() + ); + } + return new XML_RPC_Response(XML_RPC_encode(array('trtok'=>$res))); + } + + /** + * Get list of prepared transfers initiated by hub + * + * The XML-RPC name of this method is "locstor.getHubInitiatedTransfers". + * + * The input parameters are an XML-RPC struct with the following + * fields: + *
      + *
    • sessid : string - session id
    • + *
    + * + * On success, returns a XML-RPC struct with the following fields: + *
      + *
    • results : array of structs with fields: + *
        + *
      • trtok : string - transport token
      • + *
      • ... ?
      • + *
      + *
    • + *
    + * + * On errors, returns an XML-RPC error response. + * The possible error codes and error message are: + *
      + *
    • 3 - Incorrect parameters passed to method: + * Wanted ... , got ... at param
    • + *
    • 801 - wrong 1st parameter, struct expected.
    • + *
    • 805 - xr_getHubInitiatedTransfers: + * <message from lower layer>
    • + *
    • 848 - invalid session id.
    • + *
    • 872 - invalid tranport token.
    • + *
    + * + * @param XML_RPC_Message $input + * @return XML_RPC_Response + * @see Transport::getHubInitiatedTransfers + */ + public function xr_getHubInitiatedTransfers($input) + { + list($ok, $r) = XR_LocStor::xr_getParams($input); + if (!$ok) { + return $r; + } + require_once('../Transport.php'); + $tr = new Transport($this); + $res = $tr->getHubInitiatedTransfers(); + if (PEAR::isError($res)) { + $ec0 = intval($res->getCode()); + $ec = ($ec0 == GBERR_SESS || $ec0 == TRERR_TOK ? 800+$ec0 : 805 ); + return new XML_RPC_Response(0, $ec, + "xr_getHubInitiatedTransfers: ".$res->getMessage()." ".$res->getUserInfo() + ); + } + return new XML_RPC_Response(XML_RPC_encode($res)); + } + + /** + * Start of download initiated by hub + * + * The XML-RPC name of this method is "locstor.startHubInitiatedTransfer". + * + * The input parameters are an XML-RPC struct with the following + * fields: + *
      + *
    • trtok : string - transport token obtained from + * the getHubInitiatedTransfers method
    • + *
    + * + * On success, returns a XML-RPC struct with the following fields: + *
      + *
    • trtok : string - transport token
    • + *
    + * + * On errors, returns an XML-RPC error response. + * The possible error codes and error message are: + *
      + *
    • 3 - Incorrect parameters passed to method: + * Wanted ... , got ... at param
    • + *
    • 801 - wrong 1st parameter, struct expected.
    • + *
    • 805 - xr_startHubInitiatedTransfer: + * <message from lower layer>
    • + *
    • 848 - invalid session id.
    • + *
    • 872 - invalid tranport token.
    • + *
    + * + * @param XML_RPC_Message $input + * @return XML_RPC_Response + * @see Transport::startHubInitiatedTransfer + */ + public function xr_startHubInitiatedTransfer($input) + { + list($ok, $r) = XR_LocStor::xr_getParams($input); + if (!$ok) { + return $r; + } + require_once('../Transport.php'); + $tr = new Transport($this); + $res = $tr->startHubInitiatedTransfer($r['trtok']); + if (PEAR::isError($res)) { + $ec0 = intval($res->getCode()); + $ec = ($ec0 == GBERR_SESS || $ec0 == TRERR_TOK ? 800+$ec0 : 805 ); + return new XML_RPC_Response(0, $ec, + "xr_startHubInitiatedTransfer: ".$res->getMessage()." ".$res->getUserInfo() + ); + } + return new XML_RPC_Response(XML_RPC_encode(array('trtok'=>$res))); + } + + /* ------------- special methods for audioClip/webstream object transport */ + + /** + * Start upload of audioclip or playlist from local storageServer to hub + * + * The XML-RPC name of this method is "locstor.upload2Hub". + * + * The input parameters are an XML-RPC struct with the following + * fields: + *
      + *
    • sessid : string - session id
    • + *
    • gunid: string - global unique id of object being transported + *
    • + *
    + * + * On success, returns a XML-RPC struct with the following fields: + *
      + *
    • trtok : string - transport token
    • + *
    + * + * On errors, returns an XML-RPC error response. + * The possible error codes and error message are: + *
      + *
    • 3 - Incorrect parameters passed to method: + * Wanted ... , got ... at param
    • + *
    • 801 - wrong 1st parameter, struct expected.
    • + *
    • 805 - xr_upload2Hub: + * <message from lower layer>
    • + *
    • 848 - invalid session id.
    • + *
    • 872 - invalid tranport token.
    • + *
    + * + * @param XML_RPC_Message $input + * @return XML_RPC_Response + * @see Transport::upload2Hub + */ + public function xr_upload2Hub($input) + { + list($ok, $r) = XR_LocStor::xr_getParams($input); + if (!$ok) { + return $r; + } + require_once('../Transport.php'); + $tr = new Transport($this); + $res = $tr->upload2Hub($r['gunid']); + if (PEAR::isError($res)) { + $ec0 = intval($res->getCode()); + $ec = ($ec0 == GBERR_SESS || $ec0 == TRERR_TOK ? 800+$ec0 : 805 ); + return new XML_RPC_Response(0, $ec, + "xr_upload2Hub: ".$res->getMessage()." ".$res->getUserInfo() + ); + } + return new XML_RPC_Response(XML_RPC_encode(array('trtok'=>$res))); + } + + /** + * Start download of audioclip or playlist from hub to local storageServer + * + * The XML-RPC name of this method is "locstor.downloadFromHub". + * + * The input parameters are an XML-RPC struct with the following + * fields: + *
      + *
    • sessid : string - session id
    • + *
    • gunid: string - global unique id of object being transported + *
    • + *
    + * + * On success, returns a XML-RPC struct with the following fields: + *
      + *
    • trtok : string - transport token
    • + *
    + * + * On errors, returns an XML-RPC error response. + * The possible error codes and error message are: + *
      + *
    • 3 - Incorrect parameters passed to method: + * Wanted ... , got ... at param
    • + *
    • 801 - wrong 1st parameter, struct expected.
    • + *
    • 805 - xr_downloadFromHub: + * <message from lower layer>
    • + *
    • 848 - invalid session id.
    • + *
    • 872 - invalid tranport token.
    • + *
    + * + * @param XML_RPC_Message $input + * @return XML_RPC_Response + * @see Transport::downloadFromHub + */ + public function xr_downloadFromHub($input) + { + list($ok, $par) = XR_LocStor::xr_getParams($input); + if (!$ok) { + return $par; + } + require_once('../Transport.php'); + $tr = new Transport($this); + $uid = Alib::GetSessUserId($par['sessid']); + $res = $tr->downloadFromHub($uid, $par['gunid']); + if (PEAR::isError($res)) { + $ec0 = intval($res->getCode()); + $ec = ($ec0 == GBERR_SESS || $ec0 == TRERR_TOK ? 800+$ec0 : 805 ); + return new XML_RPC_Response(0, $ec, + "xr_downloadFromHub: ".$res->getMessage()." ".$res->getUserInfo() + ); + } + return new XML_RPC_Response(XML_RPC_encode(array('trtok'=>$res))); + } + + /* ------------------------------------------------ global-search methods */ + /** + * Start search job on network hub + * + * The XML-RPC name of this method is "locstor.globalSearch". + * + * The input parameters are an XML-RPC struct with the following + * fields: + *
      + *
    • sessid : string - session id
    • + *
    • criteria : hash, LS criteria format - see searchMetadata method + *
    • + *
    + * + * On success, returns a XML-RPC struct with the following fields: + *
      + *
    • trtok : string - transport token
    • + *
    + * + * On errors, returns an XML-RPC error response. + * The possible error codes and error message are: + *
      + *
    • 3 - Incorrect parameters passed to method: + * Wanted ... , got ... at param
    • + *
    • 801 - wrong 1st parameter, struct expected.
    • + *
    • 805 - xr_globalSearch: + * <message from lower layer>
    • + *
    • 848 - invalid session id.
    • + *
    • 874 - invalid hub connection configuration.
    • + *
    • 872 - invalid tranport token.
    • + *
    + * + * @param XML_RPC_Message $input + * @return XML_RPC_Response + * @see Transport::globalSearch + */ +// public function xr_globalSearch($input) +// { +// list($ok, $r) = XR_LocStor::xr_getParams($input); +// if (!$ok) { +// return $r; +// } +// require_once('../Transport.php'); +// $tr = new Transport($this); +// $res = $tr->globalSearch($r['criteria']); +// if (PEAR::isError($res)) { +// $ec0 = intval($res->getCode()); +// $ec = ($ec0 == GBERR_SESS || $ec0 == TRERR_TOK ? 800+$ec0 : 805 ); +// return new XML_RPC_Response(0, $ec, +// "xr_globalSearch: ".$res->getMessage()." ".$res->getUserInfo() +// ); +// } +// return new XML_RPC_Response(XML_RPC_encode(array('trtok'=>$res))); +// } + + /** + * Get results from search job on network hub. + * (returns error if not finished) + * + * The XML-RPC name of this method is "locstor.getSearchResults". + * + * The input parameters are an XML-RPC struct with the following + * fields: + *
      + *
    • trtok : string - transport token
    • + *
    + * + * On success, returns a XML-RPC array of structs with fields: + *
      + *
    • cnt : integer - number of matching gunids + * of files have been found
    • + *
    • results : array of hashes: + *
        + *
      • gunid: string
      • + *
      • type: string - audioclip | playlist | webstream
      • + *
      • title: string - dc:title from metadata
      • + *
      • creator: string - dc:creator from metadata
      • + *
      • source: string - dc:source from metadata
      • + *
      • length: string - dcterms:extent in extent format
      • + *
      + *
    • + *
    + * (cnt value may be greater than size of result array - see limit param) + * + * On errors, returns an XML-RPC error response. + * The possible error codes and error message are: + *
      + *
    • 3 - Incorrect parameters passed to method: + * Wanted ... , got ... at param
    • + *
    • 801 - wrong 1st parameter, struct expected.
    • + *
    • 805 - xr_getSearchResults: + * <message from lower layer>
    • + *
    • 848 - invalid session id.
    • + *
    • 872 - invalid tranport token.
    • + *
    • 873 - not finished.
    • + *
    + * + * @param XML_RPC_Message $input + * @return XML_RPC_Response + * @see Transport::getSearchResults + */ +// public function xr_getSearchResults($input) +// { +// list($ok, $r) = XR_LocStor::xr_getParams($input); +// if (!$ok) { +// return $r; +// } +// require_once('../Transport.php'); +// $tr = new Transport($this); +// $res = $tr->getSearchResults($r['trtok']); +// if (PEAR::isError($res)) { +// $ec0 = intval($res->getCode()); +// $ec = ( +// $ec0 == GBERR_SESS || $ec0 == TRERR_TOK || $ec0 == TRERR_NOTFIN +// ? 800+$ec0 : 805 ); +// return new XML_RPC_Response(0, $ec, +// "xr_getSearchResults: ".$res->getMessage()." ".$res->getUserInfo() +// ); +// } +// return new XML_RPC_Response(XML_RPC_encode($res)); +// } + + /** + * OBSOLETE + * Starts upload audioclip to remote archive + * + * The XML-RPC name of this method is "locstor.uploadToArchive". + * + * The input parameters are an XML-RPC struct with the following + * fields: + *
      + *
    • sessid : string - session id
    • + *
    • gunid : string - global unique id
    • + *
    + * + * On success, returns a XML-RPC struct with single field: + *
      + *
    • trtok : string - transport token
    • + *
    + * + * On errors, returns an XML-RPC error response. + * The possible error codes and error message are: + *
      + *
    • 3 - Incorrect parameters passed to method: + * Wanted ... , got ... at param
    • + *
    • 801 - wrong 1st parameter, struct expected.
    • + *
    • 805 - xr_uploadToArchive: + * <message from lower layer>
    • + *
    • 848 - invalid session id.
    • + *
    + * + * @param XML_RPC_Message $input + * @return XML_RPC_Response + * @see Transport::uploadToArchive + */ +// public function xr_uploadToArchive($input) +// { +// list($ok, $r) = XR_LocStor::xr_getParams($input); +// if (!$ok) { +// return $r; +// } +// require_once(dirname(__FILE__).'/../Transport.php'); +// $tr = new Transport($this); +// $res = $tr->uploadToArchive($r['gunid'], $r['sessid']); +// if (PEAR::isError($res)) { +// $ec0 = intval($res->getCode()); +// $ec = ($ec0 == GBERR_SESS ? 800+$ec0 : 805 ); +// return new XML_RPC_Response(0, $ec, +// "xr_getAudioClip: ".$res->getMessage()." ".$res->getUserInfo() +// ); +// } +// return new XML_RPC_Response(XML_RPC_encode(array('trtok'=>$res))); +// } + + /** + * OBSOLETE + * Starts download audioclip from remote archive + * + * The XML-RPC name of this method is "locstor.downloadFromArchive". + * + * The input parameters are an XML-RPC struct with the following + * fields: + *
      + *
    • sessid : string - session id
    • + *
    • gunid : string - global unique id
    • + *
    + * + * On success, returns a XML-RPC struct with single field: + *
      + *
    • trtok : string - transport token
    • + *
    + * + * On errors, returns an XML-RPC error response. + * The possible error codes and error message are: + *
      + *
    • 3 - Incorrect parameters passed to method: + * Wanted ... , got ... at param
    • + *
    • 801 - wrong 1st parameter, struct expected.
    • + *
    • 805 - xr_downloadFromArchive: + * <message from lower layer>
    • + *
    • 848 - invalid session id.
    • + *
    + * + * @param XML_RPC_Message $input + * @return XML_RPC_Response + * @see Transport::downloadFromArchive + */ +// public function xr_downloadFromArchive($input) +// { +// list($ok, $r) = XR_LocStor::xr_getParams($input); +// if (!$ok) { +// return $r; +// } +// require_once(dirname(__FILE__).'/../Transport.php'); +// $tr = new Transport($this); +// $res = $tr->downloadFromArchive($r['gunid'], $r['sessid']); +// if (PEAR::isError($res)) { +// $ec0 = intval($res->getCode()); +// $ec = ($ec0 == GBERR_SESS ? 800+$ec0 : 805 ); +// return new XML_RPC_Response(0, $ec, +// "xr_getAudioClip: ".$res->getMessage()." ".$res->getUserInfo() +// ); +// } +// return new XML_RPC_Response(XML_RPC_encode(array('trtok'=>$res))); +// } + + /* ================================================ methods for debugging */ + /** + * Reset storageServer for debugging. + * + * The XML-RPC name of this method is "locstor.resetStorage". + * + * The input parameters are an empty XML-RPC struct, + * or struct with the following optional fields: + *
      + *
    • loadSampleData : boolean - load sample data? (default: true) + *
    • + *
    • invalidateSessionIds : boolean - invalidate active session IDs? + * (default: false) + *
    • + *
    + * + * On success, returns the same result as searchMetadata with filetype + * 'all' and no conditions, ordered by filetype and dc:title + * i.e. XML-RPC array of structs with fields: + *
      + *
    • cnt : integer - number of inserted files
    • + *
    • results : array of hashes: + *
        + *
      • gunid: string
      • + *
      • type: string - audioclip | playlist | webstream
      • + *
      • title: string - dc:title from metadata
      • + *
      • creator: string - dc:creator from metadata
      • + *
      • source: string - dc:source from metadata
      • + *
      • length: string - dcterms:extent in extent format
      • + *
      + *
    • + *
    + * + * On errors, returns an XML-RPC error response. + * The possible error codes and error message are: + *
      + *
    • 3 - Incorrect parameters passed to method: + * Wanted ... , got ... at param
    • + *
    • 801 - wrong 1st parameter, struct expected.
    • + *
    • 805 - xr_resetStorage: + * <message from lower layer>
    • + *
    + * + * @param XML_RPC_Message $input + * @return XML_RPC_Response + * @see LocStor::getAudioClip + */ +// public function xr_resetStorage($input) +// { +// list($ok, $r) = XR_LocStor::xr_getParams($input); +// if (!$ok) { +// return $r; +// } +// $res = $this->resetStorage( +// isset($r['loadSampleData']) ? $r['loadSampleData'] : TRUE, +// !(isset($r['invalidateSessionIds']) ? $r['invalidateSessionIds'] : FALSE) +// ); +// if (PEAR::isError($res)) { +// return new XML_RPC_Response(0, 805, +// "xr_getAudioClip: ".$res->getMessage()." ".$res->getUserInfo() +// ); +// } +// return new XML_RPC_Response(XML_RPC_encode($res)); +// } + + /** + * Test XMLRPC - strupper and return given string, + * also return loginname of logged user + * - debug method only + * + * @param XML_RPC_Message $input + * @return XML_RPC_Response + */ + public function xr_test($input) + { + list($ok, $r) = XR_LocStor::xr_getParams($input); + if (!$ok) { + return $r; + } + return new XML_RPC_Response(XML_RPC_encode(array( + 'str'=>strtoupper($r['teststring']), + 'login' => Alib::GetSessLogin($r['sessid']), + 'sessid'=>$r['sessid'] + ))); + } + + /** + * Open writable URL for put method - debug method only + * + * @param XML_RPC_Message $input + * @return XML_RPC_Response + */ + public function xr_openPut($input) + { + list($ok, $r) = XR_LocStor::xr_getParams($input); + if (!$ok) { + return $r; + } + $res = $this->bsOpenPut(); + if (PEAR::isError($res)) { + return new XML_RPC_Response(0, 805, + "xr_getAudioClip: ".$res->getMessage()." ".$res->getUserInfo() + ); + } + return new XML_RPC_Response(XML_RPC_encode($res)); + } + + + /** + * Close writable URL - debug method only + * + * @param XML_RPC_Message $input + * @return XML_RPC_Response + */ + public function xr_closePut($input) + { + list($ok, $r) = XR_LocStor::xr_getParams($input); + if (!$ok) { + return $r; + } + $res = $this->bsClosePut($r['token'], $r['chsum']); + if (PEAR::isError($res)) { + return new XML_RPC_Response(0, 805, + "xr_getAudioClip: ".$res->getMessage()." ".$res->getUserInfo() + ); + } + return new XML_RPC_Response(XML_RPC_encode(array( + 'fname'=>$res['fname'], + 'owner'=>$res['owner'], + ))); + } + + /** + * Simple ping method - return string + * + * @param XML_RPC_Message $input + * @return XML_RPC_Response + */ + function xr_ping($input) + { + list($ok, $r) = XR_LocStor::xr_getParams($input); + if (!$ok) { + return $r; + } + $res = date("Ymd-H:i:s")." -- reply from remote node: {$r['par']}"; + return new XML_RPC_Response(XML_RPC_encode($res)); + } + + + /** + * @param XML_RPC_Message $input + * @return XML_RPC_Response + */ + function xr_uploadOpen($input) + { + list($ok, $r) = XR_LocStor::xr_getParams($input); + if (!$ok) { + return $r; + } + $res = $this->uploadOpen($r['sessid'], $r['chsum']); + if (PEAR::isError($res)) + return new XML_RPC_Response(0, 803, + "xr_uploadOpen: ".$res->getMessage(). + " ".$res->getUserInfo() + ); + return new XML_RPC_Response(XML_RPC_encode($res)); + } + + + /** + * Check state of file upload + * + * @param XML_RPC_Message $input + * @return XML_RPC_Response + */ + function xr_uploadCheck($input) + { + list($ok, $r) = XR_LocStor::xr_getParams($input); + if (!$ok) { + return $r; + } + $res = $this->uploadCheck($r['token']); + if (PEAR::isError($res)) + return new XML_RPC_Response(0, 803, + "xr_uploadCheck: ".$res->getMessage(). + " ".$res->getUserInfo() + ); + return new XML_RPC_Response(XML_RPC_encode($res)); + } + + + /** + * @param XML_RPC_Message $input + * @return XML_RPC_Response + */ + function xr_uploadClose($input) + { + list($ok, $r) = XR_LocStor::xr_getParams($input); + if (!$ok) { + return $r; + } + $res = $this->uploadClose($r['token'], $r['trtype'], $r['pars']); + if (PEAR::isError($res)) { + $code = 803; + // Special case for duplicate file - give back + // different error code so we can display nice user message. + if ($res->getCode() == GBERR_GUNID) { + $code = 888; + } + return new XML_RPC_Response(0, $code, + "xr_uploadClose: ".$res->getMessage(). + " ".$res->getUserInfo() + ); + } + return new XML_RPC_Response(XML_RPC_encode($res)); + } + + + /** + * @param XML_RPC_Message $input + * @return XML_RPC_Response + */ + function xr_downloadOpen($input) + { + list($ok, $r) = XR_LocStor::xr_getParams($input); + if (!$ok) { + return $r; + } + $res = $this->downloadOpen($r['sessid'], $r['trtype'], $r['pars']); + if (PEAR::isError($res)) + return new XML_RPC_Response(0, 803, + "xr_downloadOpen: ".$res->getMessage(). + " ".$res->getUserInfo() + ); + return new XML_RPC_Response(XML_RPC_encode($res)); + } + + + /** + * @param XML_RPC_Message $input + * @return XML_RPC_Response + */ + function xr_downloadClose($input) + { + list($ok, $r) = XR_LocStor::xr_getParams($input); + if (!$ok) { + return $r; + } + $res = $this->downloadClose($r['token'], $r['trtype']); + if (PEAR::isError($res)) + return new XML_RPC_Response(0, 803, + "xr_downloadClose: ".$res->getMessage(). + " ".$res->getUserInfo() + ); + return new XML_RPC_Response(XML_RPC_encode($res)); + } + + + /** + * @param XML_RPC_Message $input + * @return XML_RPC_Response + */ + function xr_prepareHubInitiatedTransfer($input) + { + list($ok, $r) = XR_LocStor::xr_getParams($input); + if (!$ok) { + return $r; + } + foreach (array('trtype'=>NULL, 'direction'=>'up', 'pars'=>array()) as $k => $dv) { + if (!isset($r[$k])) { + $r[$k] = $dv; + } + } + $res = $this->prepareHubInitiatedTransfer( + $r['target'], $r['trtype'], $r['direction'], $r['pars']); + if (PEAR::isError($res)) + return new XML_RPC_Response(0, 803, + "xr_prepareHubInitiatedTransfer: ".$res->getMessage(). + " ".$res->getUserInfo() + ); + return new XML_RPC_Response(XML_RPC_encode($res)); + } + + + /** + * @param XML_RPC_Message $input + * @return XML_RPC_Response + */ + function xr_listHubInitiatedTransfers($input) + { + list($ok, $r) = XR_LocStor::xr_getParams($input); + if (!$ok) { + return $r; + } + foreach (array('target'=>NULL, 'direction'=>NULL, 'trtok'=>NULL) as $k=>$dv) { + if (!isset($r[$k])) { + $r[$k] = $dv; + } + } + $res = $this->listHubInitiatedTransfers( + $r['target'], $r['direction'], $r['trtok']); + if (PEAR::isError($res)) + return new XML_RPC_Response(0, 803, + "xr_listHubInitiatedTransfers: ".$res->getMessage(). + " ".$res->getUserInfo() + ); + return new XML_RPC_Response(XML_RPC_encode($res)); + } + + + /** + * @param XML_RPC_Message $input + * @return XML_RPC_Response + */ + function xr_setHubInitiatedTransfer($input) + { + list($ok, $r) = XR_LocStor::xr_getParams($input); + if (!$ok) { + return $r; + } + $res = $this->setHubInitiatedTransfer( + $r['target'], $r['trtok'], $r['state']); + if (PEAR::isError($res)) + return new XML_RPC_Response(0, 803, + "xr_setHubInitiatedTransfer: ".$res->getMessage(). + " ".$res->getUserInfo() + ); + return new XML_RPC_Response(XML_RPC_encode($res)); + } + + /* ==================================================== "private" methods */ + /** + * Check and convert struct of parameters + * + * @param XML_RPC_Message $input + * @return array + * Array of two items: first item is boolean, indicating + * successful decode. + * On success, the second param is an array of values. + * On failure, the second param is an XML_RPC_Response object. + */ + protected static function xr_getParams($input) + { + $p = $input->getParam(0); + if (isset($p) && ($p->scalartyp()=="struct")) { + $r = XML_RPC_decode($p); + return array(TRUE, $r); + } else { + return array(FALSE, new XML_RPC_Response(0, 801, "wrong 1st parameter, struct expected." )); + } + } + +} // class XR_LocStor + +?> \ No newline at end of file diff --git a/application/models/xmlrpc/index.php b/application/models/xmlrpc/index.php new file mode 100644 index 000000000..e1ee867a4 --- /dev/null +++ b/application/models/xmlrpc/index.php @@ -0,0 +1,4 @@ + \ No newline at end of file diff --git a/application/models/xmlrpc/put.php b/application/models/xmlrpc/put.php new file mode 100644 index 000000000..650a5880a --- /dev/null +++ b/application/models/xmlrpc/put.php @@ -0,0 +1,99 @@ + + *
  • token : string, put token returned by appropriate + * XMLRPC call
  • + * + * + * On success, returns HTTP return code 200. + * + * On errors, returns HTTP return code >200 + * The possible error codes are: + *
      + *
    • 400 - Incorrect parameters passed to method
    • + *
    • 403 - Access denied
    • + *
    • 500 - Application error
    • + *
    + * + * @see XR_LocStor + * @package Campcaster + * @subpackage storageServer + */ + +define('USE_FLOCK', TRUE); + +require_once(dirname(__FILE__).'/../../conf.php'); +require_once('DB.php'); +require_once(dirname(__FILE__).'/../LocStor.php'); + +PEAR::setErrorHandling(PEAR_ERROR_RETURN); +$CC_DBC = DB::connect($CC_CONFIG['dsn'], TRUE); +$CC_DBC->setFetchMode(DB_FETCHMODE_ASSOC); +$gb = new LocStor(); + +function http_error($code, $err) +{ + header("HTTP/1.1 $code"); + header("Content-type: text/plain; charset=UTF-8"); + echo "$err\r\n"; + flush(); + exit; +} + +if (preg_match("|^[0-9a-fA-F]{16}$|", $_REQUEST['token'])) { + $token = $_REQUEST['token']; +} else { + http_error(400, "Error on token parameter. ({$_REQUEST['token']})"); +} + +$tc = BasicStor::bsCheckToken($token, 'put'); +if (PEAR::isError($tc)) { + http_error(500, $ex->getMessage()); +} +if (!$tc) { + http_error(403, "put.php: Token not valid ($token)."); +} + +header("Content-type: text/plain"); + +$destfile = $CC_CONFIG['accessDir']."/{$token}"; + +/* PUT data comes in on the input stream */ +$putdata = @fopen("php://input", "r") or + http_error(500, "put.php: Can't read input"); + +/* Open a file for writing */ +$fp = @fopen($destfile, "ab") or + http_error(500, "put.php: Can't write to destination file (token=$token)"); + +if ( USE_FLOCK ) { + // lock the file + $lockres = flock($fp,LOCK_EX+LOCK_NB); + if ($lockres !== TRUE) { + http_error(409, "put.php: file locked (token=$token)"); + } +} + +/* Read the data 1 KB at a time and write to the file */ +while ($data = fread($putdata, 1024)){ + fwrite($fp, $data); +} + +if ( USE_FLOCK ) { + // unlock the file + flock($fp,LOCK_UN); +} + +/* Close the streams */ +fclose($fp); +fclose($putdata); + +header("HTTP/1.1 200"); +?> \ No newline at end of file diff --git a/application/models/xmlrpc/schedulerPhpClient.php b/application/models/xmlrpc/schedulerPhpClient.php new file mode 100644 index 000000000..d3fce621a --- /dev/null +++ b/application/models/xmlrpc/schedulerPhpClient.php @@ -0,0 +1,511 @@ + + *
  • m
  • full method name (include optional prefix) + *
  • p
  • array of input parameter names + *
  • t
  • array of input parameter types + *
  • r
  • array of result element names (not used there at present) + *
  • e
  • array of error codes/messages (not used there at present) + * + */ +$mdefs = array( + "listMethods" => array('m'=>"system.listMethods", 'p'=>NULL, 't'=>NULL), + "AddAudioClipToPlaylistMethod" => array( + 'm'=>'addAudioClipToPlaylist', + 'p'=>array('sessionId'/*string*/, 'playlistId'/*string*/, 'audioClipId'/*string*/, 'relativeOffset'/*int*/, 'clipStart'/*int*/, 'clipEnd'/*int*/, 'clipLength'/*int*/), + 't'=>array('string', 'string', 'string', 'int', 'int', 'int', 'int'), + 'r'=>array('playlistElementId'/*string*/), + 'e'=>array( + '301'=>'invalid argument format', + '302'=>'missing playlist ID argument', + '303'=>'missing audio clip ID argument', + '304'=>'missing relative offset argument', + '305'=>'playlist not found', + '306'=>'playlist has not been opened for editing', + '307'=>'audio clip does not exist', + '308'=>'two audio clips at the same relative offset', + '320'=>'missing session ID argument', + ) + ), + "CreatePlaylistMethod" => array( + 'm'=>'createPlaylist', + 'p'=>array('sessionId'/*string*/), + 't'=>array('string'), + 'r'=>array('playlist'/*string*/), + 'e'=>array( + '201'=>'invalid argument format', + '202'=>'could not create playlist', + '220'=>'missing session ID argument', + ) + ), + "DeletePlaylistMethod" => array( + 'm'=>'deletePlaylist', + 'p'=>array('sessionId'/*string*/, 'playlistId'/*string*/), + 't'=>array('string', 'string'), + 'r'=>array(), + 'e'=>array( + '901'=>'invalid argument format', + '902'=>'missing playlist ID argument', + '903'=>'playlist not found', + '904'=>'playlist is locked', + '905'=>'playlist could not be deleted', + '920'=>'missing session ID argument', + ) + ), + "DisplayAudioClipMethod" => array( + 'm'=>'displayAudioClip', + 'p'=>array('sessionId'/*string*/, 'audioClipId'/*string*/), + 't'=>array('string', 'string'), + 'r'=>array('audioClip'/*string*/), + 'e'=>array( + '601'=>'invalid argument format', + '602'=>'argument is not an audio clip ID', + '603'=>'audio clip not found', + '620'=>'missing session ID argument', + ) + ), + "DisplayAudioClipsMethod" => array( + 'm'=>'displayAudioClips', + 'p'=>array('sessionId'/*string*/), + 't'=>array('string'), + 'r'=>array(array('audioClip'/*string*/)), + 'e'=>array( + '1801'=>'invalid argument format', + '1802'=>'XML-RPC error', + '1820'=>'missing session ID argument', + ) + ), + "DisplayPlaylistMethod" => array( + 'm'=>'displayPlaylist', + 'p'=>array('sessionId'/*string*/, 'playlistId'/*string*/), + 't'=>array('string', 'string'), + 'r'=>array('playlist'/*string*/), + 'e'=>array( + '1001'=>'invalid argument format', + '1002'=>'argument is not a playlist ID', + '1003'=>'playlist not found', + '1020'=>'missing session ID argument', + ) + ), + "DisplayPlaylistsMethod" => array( + 'm'=>'displayPlaylists', + 'p'=>array('sessionId'/*string*/), + 't'=>array('string'), + 'r'=>array(array('playlist'/*string*/)), + 'e'=>array( + '1701'=>'invalid argument format', + '1702'=>'XML-RPC error', + '1720'=>'missing session ID argument', + ) + ), + "DisplayScheduleMethod" => array( + 'm'=>'displaySchedule', + 'p'=>array('sessionId'/*string*/, 'from'/*datetime*/, 'to'/*datetime*/), + 't'=>array('string', 'dateTime.iso8601', 'dateTime.iso8601'), + 'r'=>array(array('id'/*int*/, 'playlistId'/*string*/, 'start'/*datetime*/, 'end'/*datetime*/)), + 'e'=>array( + '1101'=>'invalid argument format', + '1102'=>"missing or invalid 'from' argument", + '1103'=>"missing or invalid 'to' argument", + '1120'=>'missing session ID argument', + ) + ), + "GeneratePlayReportMethod" => array( + 'm'=>'generatePlayReport', + 'p'=>array('sessionId'/*string*/, 'from'/*datetime*/, 'to'/*datetime*/), + 't'=>array('string', 'dateTime.iso8601', 'dateTime.iso8601'), + 'r'=>array(array('audioClipId'/*string*/, 'timestamp'/*datetime*/)), + 'e'=>array( + '1501'=>'invalid argument format', + '1502'=>"missing or invalid 'from' argument", + '1503'=>"missing or invalid 'to' argument", + '1520'=>'missing session ID argument', + ) + ), + "GetSchedulerTimeMethod" => array( + 'm'=>'getSchedulerTime', + 'p'=>array(), + 't'=>array(), + 'r'=>array('schedulerTime'/*datetime*/), + 'e'=>array( +) + ), + "GetVersionMethod" => array( + 'm'=>'getVersion', + 'p'=>array(), + 't'=>array(), + 'r'=>array('version'/*string*/), + 'e'=>array() + ), + "LoginMethod" => array( + 'm'=>'login', + 'p'=>array('login'/*string*/, 'password'/*string*/), + 't'=>array('string', 'string'), + 'r'=>array('sessionId'/*string*/), + 'e'=>array( + '2001'=>'invalid argument format', + '2002'=>'missing login argument', + '2003'=>'missing password argument', + '2004'=>'the authentication server reported an error', + ) + ), + "LogoutMethod" => array( + 'm'=>'logout', + 'p'=>array('sessionId'/*string*/), + 't'=>array('string'), + 'r'=>array(), + 'e'=>array( + '2101'=>'invalid argument format', + '2120'=>'missing session ID argument', + '2104'=>'the authentication server reported an error', + ) + ), + "OpenPlaylistForEditingMethod" => array( + 'm'=>'openPlaylistForEditing', + 'p'=>array('sessionId'/*string*/, 'playlistId'/*string*/), + 't'=>array('string', 'string'), + 'r'=>array('playlist'/*string*/), + 'e'=>array( + '101'=>'invalid argument format', + '102'=>'argument is not a playlist ID', + '104'=>'could not open playlist for editing', + '120'=>'missing session ID argument', + ) + ), + "RemoveAudioClipFromPlaylistMethod" => array( + 'm'=>'removeAudioClipFromPlaylist', + 'p'=>array('sessionId'/*string*/, 'playlistId'/*string*/, 'playlistElementId'/*string*/), + 't'=>array('string', 'string', 'string'), + 'r'=>array(), + 'e'=>array( + '401'=>'invalid argument format', + '402'=>'missing playlist ID argument', + '403'=>'missing relative offset argument', + '404'=>'playlist does not exist', + '405'=>'playlist has not been opened for editing', + '406'=>'no audio clip at the specified relative offset', + '420'=>'missing session ID argument', + ) + ), + "RemoveFromScheduleMethod" => array( + 'm'=>'removeFromSchedule', + 'p'=>array('sessionId'/*string*/, 'scheduleEntryId'/*string*/), + 't'=>array('string', 'string'), + 'r'=>array(), + 'e'=>array( + '1201'=>'invalid argument format', + '1202'=>'missing schedule entry ID argument', + '1203'=>'schedule entry not found', + '1220'=>'missing session ID argument', + ) + ), + "RescheduleMethod" => array( + 'm'=>'reschedule', + 'p'=>array('sessionId'/*string*/, 'scheduleEntryId'/*string*/, 'playtime'/*datetime*/), + 't'=>array('string', 'string', 'dateTime.iso8601'), + 'r'=>array(), + 'e'=>array( + '1301'=>'invalid argument format', + '1302'=>'missing schedule entry ID argument', + '1303'=>'missing playtime argument', + '1304'=>'schedule entry not found', + '1305'=>'could not reschedule entry', + '1320'=>'missing session ID argument', + ) + ), +// "ResetStorageMethod" => array( +// 'm'=>'resetStorage', +// 'p'=>array(), +// 't'=>array(), +// 'r'=>array(), +// 'e'=>array('3001'=>'storage client reported an error'), +// ), + "RevertEditedPlaylistMethod" => array( + 'm'=>'revertEditedPlaylist', + 'p'=>array('sessionId'/*string*/, 'playlistId'/*string*/), + 't'=>array('string', 'string'), + 'r'=>array(), + 'e'=>array( + '801'=>'invalid argument format', + '802'=>'argument is not a playlist ID', + '803'=>'playlist not found', + '804'=>'could not revert playlist', + '820'=>'missing session ID argument', + ) + ), + "SavePlaylistMethod" => array( + 'm'=>'savePlaylist', + 'p'=>array('sessionId'/*string*/, 'playlistId'/*string*/), + 't'=>array('string', 'string'), + 'r'=>array(), + 'e'=>array( + '701'=>'invalid argument format', + '702'=>'argument is not a playlist ID', + '703'=>'playlist not found', + '705'=>'could not save playlist', + '720'=>'missing session ID argument', + ) + ), + "UpdateFadeInFadeOutMethod" => array( + 'm'=>'updateFadeInFadeOut', + 'p'=>array('sessionId'/*string*/, 'playlistId'/*string*/, 'playlistElementId'/*string*/, 'fadeIn'/*int*/, 'fadeOut'/*int*/), + 't'=>array('string', 'string', 'string', 'int', 'int'), + 'r'=>array(), + 'e'=>array( + '1601'=>'invalid argument format', + '1602'=>'missing playlist ID argument', + '1603'=>'missing playlist element ID argument', + '1604'=>'missing fade in argument', + '1605'=>'missing fade out argument', + '1606'=>'playlist does not exist', + '1607'=>'playlist has not been opened for editing', + '1608'=>'error executing setFadeInfo() method', + '1620'=>'missing session ID argument', + ) + ), + "UploadPlaylistMethod" => array( + 'm'=>'uploadPlaylist', + 'p'=>array('sessionId'/*string*/, 'playlistId'/*string*/, 'playtime'/*datetime*/), + 't'=>array('string', 'string', 'dateTime.iso8601'), + 'r'=>array('scheduleEntryId'/*string*/), + 'e'=>array( + '1401'=>'invalid argument format', + '1402'=>'missing playlist ID argument', + '1403'=>'missing playtime argument', + '1404'=>'playlist not found', + '1405'=>'timeframe not available', + '1406'=>'could not schedule playlist', + '1420'=>'missing session ID argument', + ) + ), + "ValidatePlaylistMethod" => array( + 'm'=>'validatePlaylist', + 'p'=>array('sessionId'/*string*/, 'playlistId'/*string*/), + 't'=>array('string', 'string'), + 'r'=>array('valid'/*bool*/), + 'e'=>array( + '501'=>'invalid argument format', + '502'=>'missing playlist ID argument', + '503'=>'playlist does not exist', + '504'=>'playlist has not been opened for editing', + '520'=>'missing session ID argument', + ) + ), + "LoginGB" => array( + 'm'=>'locstor.login', + 'p'=>array('login'/*string*/, 'pass'/*string*/), + 't'=>array('string', 'string'), + 'r'=>array('sessid'/*string*/), + 'e'=>array( + '2001'=>'invalid argument format', + '2002'=>'missing login argument', + '2003'=>'missing password argument', + '2004'=>'the authentication server reported an error', + ) + ), + "LogoutGB" => array( + 'm'=>'locstor.logout', + 'p'=>array('sessid'/*string*/), + 't'=>array('string'), + 'r'=>array('status'/*boolean*/), + 'e'=>array( + '2001'=>'invalid argument format', + '2002'=>'missing login argument', + '2003'=>'missing password argument', + '2004'=>'the authentication server reported an error', + ) + ), +); + +/* ======================================================== class definitions */ + +class SchedulerPhpClient { + /** + * Array with methods description + * @var array + */ + private $mdefs = array(); + + /** + * XMLRPC client object reference + */ + private $client = NULL; + + /** + * Verbosity flag + */ + private $verbose = FALSE; + + /** + * XMLRPC debug flag + */ + private $debug = 0; + + /** + * Constructor - please DON'T CALL IT, use factory method instead + * + * @param DB $dbc + * @param array $mdefs + * hash array with methods description + * @param array $config + * hash array with configuration + * @param int $debug + * XMLRPC debug flag + * @param boolean $verbose + * verbosity flag + */ + public function __construct($mdefs, $debug=0, $verbose=FALSE) + { + global $CC_DBC, $CC_CONFIG; + $this->mdefs = $mdefs; + $this->debug = $debug; + $this->verbose = $verbose; + $confPrefix = "scheduler"; + //$confPrefix = "storage"; + $serverPath = + "http://{$CC_CONFIG["{$confPrefix}UrlHost"]}:{$CC_CONFIG["{$confPrefix}UrlPort"]}". + "{$CC_CONFIG["{$confPrefix}UrlPath"]}/{$CC_CONFIG["{$confPrefix}XMLRPC"]}"; + //$serverPath = "http://localhost:80/campcasterStorageServerCVS/xmlrpc/xrLocStor.php"; + if ($this->verbose) { + echo "serverPath: $serverPath\n"; + } + $url = parse_url($serverPath); + $this->client = new XML_RPC_Client($url['path'], $url['host'], $url['port']); + } + + + /** + * Factory, create object instance + * + * In fact it doesn't create instance of SchedulerPhpClient, but + * dynamically extend this class with set of methods based on $mdefs array + * (using eval function) and instantiate resulting class + * SchedulerPhpClientCore instead. + * Each new method in this subclass accepts parameters according to $mdefs + * array, call wrapper callMethod(methodname, parameters) and return its + * result. + * + * @param array $mdefs + * hash array with methods description + * @param int $debug + * XMLRPC debug flag + * @param boolean $verbose + * verbosity flag + * @return SchedulerPhpClientCore + */ + function &factory($mdefs, $debug=0, $verbose=FALSE) + { + global $CC_DBC, $CC_CONFIG; + $f = ''; + foreach ($mdefs as $fn => $farr) { + $f .= + ' function '.$fn.'(){'."\n". + ' $pars = func_get_args();'."\n". + ' $r = $this->callMethod("'.$fn.'", $pars);'."\n". + ' return $r;'."\n". + ' }'."\n"; + } + $e = + "class SchedulerPhpClientCore extends SchedulerPhpClient{\n". + "$f\n". + "}\n"; +# echo $e; + if (FALSE === eval($e)) { + return $CC_DBC->raiseError("Eval failed"); + } + $spc = new SchedulerPhpClientCore($mdefs, $debug, $verbose); + return $spc; + } + + + /** + * XMLRPC methods wrapper + * Encode XMLRPC request message, send it, receive and decode response. + * + * @param string $method + * method name + * @param array $gettedPars + * returned by func_get_args() in called method + * @return array + * PHP hash with response + */ + function callMethod($method, $gettedPars) + { + $parr = array(); + $XML_RPC_val = new XML_RPC_Value; + foreach ($this->mdefs[$method]['p'] as $i => $p) { + $parr[$p] = new XML_RPC_Value; + $parr[$p]->addScalar($gettedPars[$i], $this->mdefs[$method]['t'][$i]); + } + $XML_RPC_val->addStruct($parr); + $fullmethod = $this->mdefs[$method]['m']; + $msg = new XML_RPC_Message($fullmethod, array($XML_RPC_val)); + if ($this->verbose) { + echo "parr:\n"; + var_dump($parr); + echo "message:\n"; + echo $msg->serialize()."\n"; + } + $this->client->setDebug($this->debug); + $res = $this->client->send($msg); + if ($res->faultCode() > 0) { + return PEAR::raiseError( + "SchedulerPhpClient::$method:".$res->faultString()." ". + $res->faultCode()."\n", $res->faultCode(), + PEAR_ERROR_RETURN + ); + } + if ($this->verbose) { + echo "result:\n"; + echo $res->serialize(); + } + $val = $res->value(); + $resp = XML_RPC_decode($res->value()); + return $resp; + } + +} // class SchedulerPhpClient + +/* ======================================================== class definitions */ + +/** + * Example of use: + * + * / + + +// db object handling: +$CC_DBC = DB::connect($CC_CONFIG['dsn'], TRUE); +$CC_DBC->setFetchMode(DB_FETCHMODE_ASSOC); +$CC_DBC->setErrorHandling(PEAR_ERROR_RETURN); + +// scheduler client instantiation: +$spc = SchedulerPhpClient::factory($mdefs); +#$spc = SchedulerPhpClient::factory($mdefs, 0, TRUE); +if(PEAR::isError($spc)){ echo $spc->getMessage."\n"; exit; } + +// call of chosen function by name according to key values in $mdefs array: +// (for testing on storageServer XMLRPC I've changes confPrefix in +// SchedulerPhpClient constructor from 'scheduler' to 'storage' value) +#$r = $spc->LoginGB('root', 'q'); var_dump($r); +#$r = $spc->LogoutGB(''); var_dump($r); + +#$r = $spc->DisplayScheduleMethod($this->Base->sessid, '2005-01-01 00:00:00.000000', '2005-02-01 00:00:00.000000'); var_dump($r); +#$r = $spc->DisplayScheduleMethod('dummySessionId2-1681692777', '2005-01-01 00:00:00.000000', '2005-02-01 00:00:00.000000'); var_dump($r); +$r = $spc->DisplayScheduleMethod($this->Base->sessid, '20040101T00:00:00', '20050401T00:00:00'); var_dump($r); +#$r = $spc->LoginMethod('root', 'q'); var_dump($r); +#$r = $spc->LogoutMethod('dummySessionId3-1714636915'); var_dump($r); +#$r = $spc->listMethods(); var_dump($r); +#$r = $spc->GetSchedulerTimeMethod(); var_dump($r); +================= */ + +?> \ No newline at end of file diff --git a/application/models/xmlrpc/simpleGet.php b/application/models/xmlrpc/simpleGet.php new file mode 100644 index 000000000..ebb0cf8dc --- /dev/null +++ b/application/models/xmlrpc/simpleGet.php @@ -0,0 +1,128 @@ + + *
  • sessid : string, session ID
  • + *
  • id : string, global unique ID of requested file
  • + * + * + * On success, returns HTTP return code 200 and requested file. + * + * On errors, returns HTTP return code >200 + * The possible error codes are: + *
      + *
    • 400 - Incorrect parameters passed to method
    • + *
    • 403 - Access denied
    • + *
    • 404 - File not found
    • + *
    • 500 - Application error
    • + *
    + * + */ + +require_once(dirname(__FILE__).'/../../conf.php'); +require_once('DB.php'); +require_once(dirname(__FILE__).'/../LocStor.php'); +require_once(dirname(__FILE__).'/../MetaData.php'); + +$CC_DBC = DB::connect($CC_CONFIG['dsn'], TRUE); +$CC_DBC->setErrorHandling(PEAR_ERROR_RETURN); +$CC_DBC->setFetchMode(DB_FETCHMODE_ASSOC); + +$locStor = new LocStor(); + +function http_error($code, $err) +{ + header("HTTP/1.1 $code"); + header("Content-type: text/plain; charset=UTF-8"); + echo "$err\r\n"; + exit; +} + +/** + * This function encodes an filename to + * be transferred via HTTP header. + * + * @param string $p_string utf8 filename + * @return string HTTP header encoded filename + */ +function sg_2hexstring($p_string) +{ + for ($x=0; $x < strlen($p_string); $x++) { + $return .= '%' . bin2hex($p_string[$x]); + } + return $return; +} + +// parameter checking: +if (preg_match("|^[0-9a-fA-F]{32}$|", $_REQUEST['sessid'])) { + $sessid = $_REQUEST['sessid']; +} else { + http_error(400, "Error on sessid parameter. ({$_REQUEST['sessid']})"); +} +if (preg_match("|^[0-9a-fA-F]{16}$|", $_REQUEST['id'])) { + $gunid = $_REQUEST['id']; +} else { + http_error(400, "Error on id parameter. ({$_REQUEST['id']})"); +} + +// stored file recall: +$ac = StoredFile::RecallByGunid($gunid); +if (PEAR::isError($ac)) { + switch ($ac->getCode()) { + case GBERR_DENY: + http_error(403, "403 ".$ac->getMessage()); + case GBERR_FILENEX: + case GBERR_FOBJNEX: + http_error(404, "404 File not found"); + default: + http_error(500, "500 ".$ac->getMessage()); + } +} +$lid = BasicStor::IdFromGunid($gunid); +if (PEAR::isError($lid)) { + http_error(500, $lid->getMessage()); +} +if (($res = BasicStor::Authorize('read', $lid, $sessid)) !== TRUE) { + http_error(403, "403 Access denied"); +} +$ftype = BasicStor::GetObjType($lid); +if (PEAR::isError($ftype)) { + http_error(500, $ftype->getMessage()); +} +switch ($ftype) { + case "audioclip": + $realFname = $ac->getRealFileName(); + $mime = $ac->getMime(); + $md = new MetaData($ac->getGunId(), null); + $fileName = $md->getMetadataValue('dc:title').'.'.$ac->getFileExtension(); + header("Content-type: $mime"); + header("Content-length: ".filesize($realFname)); + header("Content-Disposition: attachment; filename*=".sg_2hexstring($fileName).";"); + readfile($realFname); + break; + case "webstream": + $url = $locStor->bsGetMetadataValue($lid, 'ls:url'); + if (empty($url)) { + http_error(500, "Unable to get ls:url value"); + } + $txt = "Location: $url"; + header($txt); + // echo "$txt\n"; + break; + case "playlist"; + // $md = $locStor->bsGetMetadata($ac->getId(), $sessid); + $md = $locStor->getAudioClip($sessid, $gunid); + // header("Content-type: text/xml"); + header("Content-type: application/smil"); + echo $md; + break; + default: + // var_dump($ftype); + http_error(500, "500 Unknown ftype ($ftype)"); +} +?> \ No newline at end of file diff --git a/application/models/xmlrpc/urldecode b/application/models/xmlrpc/urldecode new file mode 100755 index 000000000..172c8f73c --- /dev/null +++ b/application/models/xmlrpc/urldecode @@ -0,0 +1,7 @@ +#!/usr/bin/php -q + \ No newline at end of file diff --git a/application/models/xmlrpc/xrLocStor.php b/application/models/xmlrpc/xrLocStor.php new file mode 100644 index 000000000..fb9acf702 --- /dev/null +++ b/application/models/xmlrpc/xrLocStor.php @@ -0,0 +1,172 @@ + + + + + + +faultCode +804 + + +faultString +"); +ini_set("error_append_string", " + + + + +"); +header("Content-type: text/xml"); + +/* ================================================================= includes */ +require_once(dirname(__FILE__).'/../../conf.php'); +require_once('DB.php'); +require_once("XML/RPC/Server.php"); +require_once('XR_LocStor.php'); + +/* ============================================ setting default error handler */ +function errHndl($errno, $errmsg, $filename, $linenum, $vars) +{ + switch ($errno) { + case E_WARNING: + case E_NOTICE: + case E_USER_WARNING: + case E_USER_NOTICE: + return; + break; + default: + $xr = new XML_RPC_Response(0, 805, + htmlspecialchars("ERROR:xrLocStor: $errno $errmsg ($filename:$linenum)")); + header("Content-type: text/xml"); + echo $xr->serialize(); + exit($errno); + } +} +$old_error_handler = set_error_handler("errHndl", E_ALL); + +/* ============================================================= runable code */ +$CC_DBC = DB::connect($CC_CONFIG['dsn'], TRUE); +if (PEAR::isError($CC_DBC)) { + trigger_error("DB::connect: ".$CC_DBC->getMessage()." ".$CC_DBC->getUserInfo(),E_USER_ERROR); +} +$CC_DBC->setErrorHandling(PEAR_ERROR_RETURN); +$CC_DBC->setFetchMode(DB_FETCHMODE_ASSOC); + +$locStor = new XR_LocStor(); + +$methods = array( + 'test' => 'Tests toupper and checks sessid, params: '. + 'teststring, sessid.', + 'getVersion' => 'Get version of Campcaster.', +// 'authenticate' => 'Checks authentication.', + 'login' => 'Login to storage.', + 'logout' => 'Logout from storage.', + 'existsAudioClip' => 'Checks if an audio clip with the specified '. + 'id is stored in local storage.', + 'storeAudioClipOpen' => 'Open channel to store a new audio clip '. + 'or replace an existing one.', + 'storeAudioClipClose' => 'Close channel to store a new audio clip'. + ' or replace an existing one.', + 'downloadRawAudioDataOpen'=> 'Create and return downloadable URL'. + 'for audio file', + 'downloadRawAudioDataClose'=>'Discard downloadable URL for audio file', + 'downloadMetadataOpen' => 'Create and return downloadable URL'. + 'for metadata', + 'downloadMetadataClose' => 'Discard downloadable URL for metadata', + 'openPut' => 'openPut', + 'closePut' => 'closePut', + 'deleteAudioClip' => 'Delete an existing Audio clip.', + 'updateAudioClipMetadata' => 'Update the metadata of an Audio clip '. + 'stored in Local storage.', + 'searchMetadata' => 'Search through the metadata of stored '. + 'files, return all matching clip ids.', + 'browseCategory' =>'Return values of specified metadata category.', + 'accessRawAudioData' => 'Get access to raw audio data.', + 'releaseRawAudioData' => 'Release access to raw audio data.', + 'getAudioClip' => 'Return the contents of an Audio clip.', +// 'resetStorage' => 'Reset storageServer for debugging.', + 'storeWebstream' => 'Store audio stream identified by URL', + + 'createPlaylist' => 'Create a new Playlist metafile.', + 'editPlaylist' => 'Open a Playlist metafile for editing.', + 'savePlaylist' => 'Save a Playlist metafile.', + 'revertEditedPlaylist' => 'RollBack playlist changes to the locked state.', + 'deletePlaylist' => 'Delete a Playlist metafile.', + 'accessPlaylist' => 'Open readable URL to a Playlist metafile.', + 'releasePlaylist' => 'Release readable URL from accessPlaylist.', + 'existsPlaylist' => 'Check whether a Playlist exists.', + 'playlistIsAvailable' => 'Check whether a Playlist is available '. + 'for editing.', + 'exportPlaylistOpen' => 'Create a tarfile with playlist export.', + 'exportPlaylistClose' => 'Close playlist export.', + 'importPlaylistOpen' => 'Open writable handle for playlist import.', + 'importPlaylistClose' => 'Close import-handle and import playlist.', + + 'renderPlaylistToFileOpen' => 'Render playlist to ogg file (open handle)', + 'renderPlaylistToFileCheck' => 'Render playlist to ogg file (check results)', + 'renderPlaylistToFileClose' => 'Render playlist to ogg file (close handle)', + + 'renderPlaylistToStorageOpen' => 'Render playlist to storage media clip (open handle)', + 'renderPlaylistToStorageCheck' => 'Render playlist to storage media clip (check results)', + + 'renderPlaylistToRSSOpen' => 'Render playlist to RSS file (open handle)', + 'renderPlaylistToRSSCheck' => 'Render playlist to RSS file (check results)', + 'renderPlaylistToRSSClose' => 'Render playlist to RSS file (close handle)', + + 'createBackupOpen' => 'Create backup of storage (open handle)', + 'createBackupCheck' => 'Create backup of storage (check results)', + 'createBackupClose' => 'Create backup of storage (close handle)', + + 'restoreBackupOpen' => 'Restore a backup file (open handle)', + 'restoreBackupClosePut' => 'Restore a backup file (close PUT handle)', + 'restoreBackupCheck' => 'Restore a backup file (check results)', + 'restoreBackupClose' => 'Restore a backup file (close handle)', + + 'loadPref' => 'Load user preference value.', + 'savePref' => 'Save user preference value.', + 'delPref' => 'Delete user preference record.', + 'loadGroupPref' => 'Read group preference record.', + 'saveGroupPref' => 'Delete user preference record.', + + 'getTransportInfo' => 'Common "check" method and info getter for transports', + 'turnOnOffTransports' => 'Turn transports on/off, optionaly return current state', + 'doTransportAction' => 'Pause, resume or cancel transport', + 'uploadFile2Hub' => 'Open async file transfer from local storageServer to network hub', + 'getHubInitiatedTransfers' => 'Get list of prepared transfers initiated by hub', + 'startHubInitiatedTransfer' => 'Start of download initiated by hub', + 'upload2Hub' => 'Start upload of audioclip or playlist from local storageServer to hub', + 'downloadFromHub' => 'Start download of audioclip or playlist from hub to local storageServer', +// 'globalSearch' => 'Start search job on network hub', +// 'getSearchResults' => 'Get results from search job on network hub', + + 'uploadOpen' => 'Open file-layer upload', + 'uploadCheck' => 'Check the checksum of uploaded file', + 'uploadClose' => 'Close file-layer upload', + 'downloadOpen' => 'Open file-layer download', +// 'downloadCheck' => 'Check the checksum of downloaded file', + 'downloadClose' => 'Close file-layer download', + 'prepareHubInitiatedTransfer' => 'Prepare hub initiated transfer', + 'listHubInitiatedTransfers' => 'List hub initiated transfers', + 'setHubInitiatedTransfer' => 'Set state of hub initiated transfers', + 'ping' => 'Echo request', +); + +$defs = array(); +foreach ($methods as $method => $description) { + $defs["locstor.$method"] = array( + "function" => array(&$locStor, "xr_$method"), + // NOTE: the way this signature is set up, every function must take at least one parameter! + "signature" => array( + array($GLOBALS['XML_RPC_Struct'], $GLOBALS['XML_RPC_Struct']) + ), + "docstring" => $description + ); +} + +$s = new XML_RPC_Server($defs); + +?> \ No newline at end of file diff --git a/application/models/xmlrpc/xr_cli_test.php b/application/models/xmlrpc/xr_cli_test.php new file mode 100644 index 000000000..c282be12b --- /dev/null +++ b/application/models/xmlrpc/xr_cli_test.php @@ -0,0 +1,368 @@ +debug = 1; + echo "ServerPath: $serverPath\n"; + echo "Host: {$url['host']}, path: {$url['path']}\n"; + echo "Method: $method\n"; + echo "Parameters:\n"; + //var_dump($pars); +} + +$infos = array( + "listMethods" => array('m'=>"system.listMethods", 'p'=>NULL), + "methodHelp" => array('m'=>"system.methodHelp", 'p'=>0), + "methodSignature" => array('m'=>"system.methodSignature", 'p'=>0), + "test" => array('m'=>"locstor.test", 'p'=>array('sessid', 'teststring')), + "ping" => array('m'=>"locstor.ping", 'p'=>array("par")), + "getVersion" => array('m'=>"locstor.getVersion", 'p'=>array("str"), 'r'=>'version'), + "authenticate" => array('m'=>"locstor.authenticate", 'p'=>array('login', 'pass'), 'r'=>'authenticate'), + "login" => array('m'=>"locstor.login", 'p'=>array('login', 'pass'), 'r'=>'sessid'), + "logout" => array('m'=>"locstor.logout", 'p'=>array('sessid'), 'r'=>'status'), + "storeAudioClipOpen" => array('m'=>"locstor.storeAudioClipOpen", + 'p'=>array('sessid', 'gunid', 'metadata', 'fname', 'chsum'), + 'r'=>array('url', 'token') + ), + "storeAudioClipClose" => array('m'=>"locstor.storeAudioClipClose", + 'p'=>array('sessid', 'token'), 'r'=>'gunid'), + "accessRawAudioData" => array('m'=>"locstor.accessRawAudioData", + 'p'=>array('sessid', 'gunid'), 'r'=>array('url', 'token')), + "releaseRawAudioData" => array('m'=>"locstor.releaseRawAudioData", + 'p'=>array('token'), 'r'=>'status'), + "downloadRawAudioDataOpen" => + array('m'=>"locstor.downloadRawAudioDataOpen", + 'p'=>array('sessid', 'gunid'), 'r'=>array('url', 'token')), + "downloadRawAudioDataClose" => + array('m'=>"locstor.downloadRawAudioDataClose", + 'p'=>array('sessid', 'token'), 'r'=>'gunid'), + "downloadMetadataOpen" => array('m'=>"locstor.downloadMetadataOpen", + 'p'=>array('sessid', 'gunid'), 'r'=>array('url', 'token')), + "downloadMetadataClose" => array('m'=>"locstor.downloadMetadataClose", + 'p'=>array('sessid', 'token'), 'r'=>'gunid'), + + "deleteAudioClip" => + array('m'=>"locstor.deleteAudioClip", + 'p'=>array('sessid', 'gunid', 'forced'), 'r'=>'status'), + "existsAudioClip" => array('m'=>"locstor.existsAudioClip", + 'p'=>array('sessid', 'gunid'), 'r'=>'exists'), + "getAudioClip" => array('m'=>"locstor.getAudioClip", + 'p'=>array('sessid', 'gunid'), 'r'=>'metadata'), + "updateAudioClipMetadata" => array('m'=>"locstor.updateAudioClipMetadata", + 'p'=>array('sessid', 'gunid', 'metadata'), 'r'=>'status'), + "searchMetadata" => array('m'=>"locstor.searchMetadata", 'p'=>NULL), + "browseCategory" => array('m'=>"locstor.browseCategory", 'p'=>NULL), +// "resetStorage" => array('m'=>"locstor.resetStorage", +// 'p'=>array()), +# 'p'=>array('loadSampleData', 'invalidateSessionIds')), + "storeWebstream" => array('m'=>"locstor.storeWebstream", + 'p'=>array('sessid', 'gunid', 'metadata', 'fname', 'url'), + 'r'=>array('gunid') + ), + + "createPlaylist" => array('m'=>"locstor.createPlaylist", + 'p'=>array('sessid', 'plid', 'fname'), 'r'=>'plid'), + "editPlaylist" => array('m'=>"locstor.editPlaylist", + 'p'=>array('sessid', 'plid'), 'r'=>array('url', 'token')), + "savePlaylist" => array('m'=>"locstor.savePlaylist", + 'p'=>array('sessid', 'token', 'newPlaylist'), 'r'=>'plid'), + "revertEditedPlaylist" => array('m'=>"locstor.revertEditedPlaylist", + 'p'=>array('sessid', 'token'), 'r'=>'plid'), + "deletePlaylist" => array('m'=>"locstor.deletePlaylist", + 'p'=>array('sessid', 'plid', 'forced'), 'r'=>'status'), + "accessPlaylist" => array('m'=>"locstor.accessPlaylist", + 'p'=>array('sessid', 'plid'), 'r'=>array('url', 'token')), + "releasePlaylist" => array('m'=>"locstor.releasePlaylist", + 'p'=>array('token'), 'r'=>'plid'), + "existsPlaylist" => array('m'=>"locstor.existsPlaylist", + 'p'=>array('sessid', 'plid'), 'r'=>'exists'), + "playlistIsAvailable" => array('m'=>"locstor.playlistIsAvailable", + 'p'=>array('sessid', 'plid'), 'r'=>array('available', 'ownerid', 'ownerlogin')), + + "exportPlaylistOpen" => array('m'=>"locstor.exportPlaylistOpen", + 'p'=>array('sessid', 'plids', 'type', 'standalone'), + 'r'=>array('url', 'token')), + "exportPlaylistClose" => array('m'=>"locstor.exportPlaylistClose", + 'p'=>array('token'), 'r'=>array('status')), + "importPlaylistOpen" => array('m'=>"locstor.importPlaylistOpen", + 'p'=>array('sessid', 'chsum'), 'r'=>array('url', 'token')), + "importPlaylistClose" => array('m'=>"locstor.importPlaylistClose", + 'p'=>array('token'), 'r'=>array('gunid')), + + "renderPlaylistToFileOpen" => array('m'=>"locstor.renderPlaylistToFileOpen", + 'p'=>array('sessid', 'plid'), + 'r'=>array('token')), + "renderPlaylistToFileCheck" => array('m'=>"locstor.renderPlaylistToFileCheck", + 'p'=>array('token'), 'r'=>array('status', 'url')), + "renderPlaylistToFileClose" => array('m'=>"locstor.renderPlaylistToFileClose", + 'p'=>array('token'), 'r'=>array('status')), + "renderPlaylistToStorageOpen" => array('m'=>"locstor.renderPlaylistToStorageOpen", + 'p'=>array('sessid', 'plid'), + 'r'=>array('token')), + "renderPlaylistToStorageCheck" => array('m'=>"locstor.renderPlaylistToStorageCheck", + 'p'=>array('token'), 'r'=>array('status', 'gunid')), + "renderPlaylistToRSSOpen" => array('m'=>"locstor.renderPlaylistToRSSOpen", + 'p'=>array('sessid', 'plid'), + 'r'=>array('token')), + "renderPlaylistToRSSCheck" => array('m'=>"locstor.renderPlaylistToRSSCheck", + 'p'=>array('token'), 'r'=>array('status', 'url')), + "renderPlaylistToRSSClose" => array('m'=>"locstor.renderPlaylistToRSSClose", + 'p'=>array('token'), 'r'=>array('status')), + + "loadPref" => array('m'=>"locstor.loadPref", + 'p'=>array('sessid', 'key'), 'r'=>'value'), + "savePref" => array('m'=>"locstor.savePref", + 'p'=>array('sessid', 'key', 'value'), 'r'=>'status'), + "delPref" => array('m'=>"locstor.delPref", + 'p'=>array('sessid', 'key'), 'r'=>'status'), + "loadGroupPref" => array('m'=>"locstor.loadGroupPref", + 'p'=>array('sessid', 'group', 'key'), 'r'=>'value'), + "saveGroupPref" => array('m'=>"locstor.saveGroupPref", + 'p'=>array('sessid', 'group', 'key', 'value'), 'r'=>'status'), + + "getTransportInfo" => array('m'=>"locstor.getTransportInfo", + 'p'=>array('trtok'), + 'r'=>array('state', 'realsize', 'expectedsize', 'realsum', 'expectedsum')), + "turnOnOffTransports" => array('m'=>"locstor.turnOnOffTransports", + 'p'=>array('sessid', 'onOff'), 'r'=>array('state')), + "doTransportAction" => array('m'=>"locstor.doTransportAction", + 'p'=>array('sessid', 'trtok', 'action'), 'r'=>array('state')), + "uploadFile2Hub" => array('m'=>"locstor.uploadFile2Hub", + 'p'=>array('sessid', 'filePath'), 'r'=>array('trtok')), + "getHubInitiatedTransfers" => array('m'=>"locstor.getHubInitiatedTransfers", + 'p'=>array('sessid'), 'r'=>array()), + "startHubInitiatedTransfer" => array('m'=>"locstor.startHubInitiatedTransfer", + 'p'=>array('trtok'), 'r'=>array()), + "upload2Hub" => array('m'=>"locstor.upload2Hub", + 'p'=>array('sessid', 'gunid'), 'r'=>array('trtok')), + "downloadFromHub" => array('m'=>"locstor.downloadFromHub", + 'p'=>array('sessid', 'gunid'), 'r'=>array('trtok')), +// "globalSearch" => array('m'=>"locstor.globalSearch", +// 'p'=>array('sessid', 'criteria'), 'r'=>array('trtok')), +// "getSearchResults" => array('m'=>"locstor.getSearchResults", +// 'p'=>array('trtok')), + + "createBackupOpen" => array('m'=>"locstor.createBackupOpen", + 'p'=>array('sessid', 'criteria'), 'r'=>array('token')), + "createBackupCheck" => array('m'=>"locstor.createBackupCheck", +# 'p'=>array('token'), 'r'=>array('status', 'url', 'metafile', 'faultString')), + 'p'=>array('token'), 'r'=>array('status', 'url', 'tmpfile')), + "createBackupClose" => array('m'=>"locstor.createBackupClose", + 'p'=>array('token'), 'r'=>array('status')), + "restoreBackupOpen" => array('m'=>"locstor.restoreBackupOpen", + 'p'=>array('sessid', 'chsum'), 'r'=>array('url', 'token')), + "restoreBackupClosePut" => array('m'=>"locstor.restoreBackupClosePut", + 'p'=>array('sessid', 'token'), 'r'=>array('token')), + "restoreBackupCheck" => array('m'=>"locstor.restoreBackupCheck", + 'p'=>array('token'), 'r'=>array('status', 'faultString')), + "restoreBackupClose" => array('m'=>"locstor.restoreBackupClose", + 'p'=>array('token'), 'r'=>array('status')), + +/* + "uploadToArchive" => array('m'=>"locstor.uploadToArchive", + 'p'=>array('sessid', 'gunid'), 'r'=>'trtok'), + "downloadFromArchive" => array('m'=>"locstor.downloadFromArchive", + 'p'=>array('sessid', 'gunid'), 'r'=>'trtok'), +*/ + + "openPut" => array('m'=>"locstor.openPut", 'p'=>array()), + "closePut" => array('m'=>"locstor.closePut", 'p'=>array()), +); + + +switch ($method) { + case "searchMetadata": +// case "globalSearch": + case "createBackupOpen": + $parr = array( + 'sessid'=>$pars[0], + 'criteria'=>array( + 'filetype'=>'audioclip', + 'operator'=>'and', + 'limit'=> 0, + 'offset'=> 0, + 'conditions'=>array( + array('cat'=>$pars[1], 'op'=>'partial', 'val'=>$pars[2]) + ) + ), + ); + break; + case "browseCategory": + $parr = array( + 'sessid'=>$pars[0], + 'category'=>$pars[1], + 'criteria'=>array( + 'filetype'=>'audioclip', + 'operator'=>'and', + 'limit'=> 0, + 'offset'=> 0, + 'conditions'=>array( + array('cat'=>$pars[2], 'op'=>'partial', 'val'=>$pars[3]) + ) + ), + ); + break; +// case "resetStorage": +// $parr = array( +// 'loadSampleData'=>(boolean)$pars[0], +// 'invalidateSessionIds'=>(boolean)$pars[1], +// ); +// break; + default: + $pinfo = $infos[$method]['p']; + if (is_null($pinfo)) { + $parr = NULL; + } elseif(!is_array($pinfo)) { + $parr = $pars[0]; + #echo "pinfo not null and not array.\n"; exit; + } elseif(count($pinfo) == 0) { + $parr = (object)array(); + } else { + $parr = array(); $i=0; + foreach($pinfo as $it){ + if(isset($pars[$i])) $parr[$it] = $pars[$i]; + $i++; + } + } +} // switch + +$fullmethod = $infos[$method]['m']; +if (is_array($options)) { + $msg = new XML_RPC_Message($fullmethod, array(XML_RPC_encode($options))); +} else { + $msg = new XML_RPC_Message($fullmethod); +} +//$msg = new XML_RPC_Message($fullmethod, array(XML_RPC_encode($parr))); + +if ($verbose) { + echo "parr:\n"; + var_dump($parr); + echo "message:\n"; + echo $msg->serialize()."\n"; +} + +#$client->setDebug(1); +$res = $client->send($msg); +if ($res->faultCode() > 0) { + echo "xr_cli_test.php: ".$res->faultString()." ".$res->faultCode()."\n"; +# echo var_export($res); + exit(1); +} + +if ($verbose) { + echo "result:\n"; + echo $res->serialize(); +} + +$resp = XML_RPC_decode($res->value()); +if (isset($infos[$method]['r'])) { + $pom = $infos[$method]['r']; + if (is_array($pom)) { + foreach ($pom as $k => $it) { + $pom[$k] = $resp[$it]; + } + echo join(' ', $pom)."\n"; + } else { + switch ($pom) { + case "status": + case "exists": + echo ($resp[$pom]=='1' ? "TRUE" : "FALSE" )."\n"; + break; + default: + echo "{$resp[$pom]}\n"; + } + } +} else { + switch ($method) { + case "searchMetadata": +// case "getSearchResults": + $acCnt = 0; + $acGunids = array(); + $plCnt = 0; + $plGunids = array(); + $fld = (isset($options['category']) ? $options['category'] : 'gunid' ); + foreach ($resp['results'] as $k => $v) { + if ($v['type']=='audioclip') { + $acCnt++; + $acGunids[] = $v[$fld]; + } + if ($v['type']=='playlist') { + $plCnt++; + $plGunids[] = $v[$fld]; + } + } + echo "AC({$acCnt}): ". + join(", ", $acGunids). + " | PL({$plCnt}): ". + join(", ", $plGunids). + "\n"; + break; + case "browseCategory": + echo "RES({$resp['cnt']}): ". + join(", ", $resp['results']). + "\n"; + break; + default: + print_r($resp); + echo "\n"; + } +} + +?> \ No newline at end of file diff --git a/application/models/xmlrpc/xr_web_test.php b/application/models/xmlrpc/xr_web_test.php new file mode 100644 index 000000000..34537170c --- /dev/null +++ b/application/models/xmlrpc/xr_web_test.php @@ -0,0 +1,373 @@ +'.htmlspecialchars($p_printValue)."\n"; + echo $str; + return $selected; +} // fn camp_html_select_option + + +$serverPath = + "http://{$CC_CONFIG['storageUrlHost']}:{$CC_CONFIG['storageUrlPort']}". + "{$CC_CONFIG['storageUrlPath']}/{$CC_CONFIG['storageXMLRPC']}"; +$serverPath = camp_session_get("storageserver_xmlrpc_path", $serverPath); +$f_selectedMethod = camp_session_get("f_selectedMethod", "listMethods"); +$url = parse_url($serverPath); +$client = new XML_RPC_Client($url['path'], $url['host']); + +$methodDefs = array( + "listMethods" => array('m'=>"system.listMethods", 'p'=>NULL), + "methodHelp" => array('m'=>"system.methodHelp", 'p'=>0), + "methodSignature" => array('m'=>"system.methodSignature", 'p'=>0), + "test" => array('m'=>"locstor.test", 'p'=>array('sessid', 'teststring')), + "getVersion" => array('m'=>"locstor.getVersion", 'p'=>array(), 'r'=>'version'), + "authenticate" => array('m'=>"locstor.authenticate", 'p'=>array('login', 'pass'), 'r'=>'authenticate'), + "login" => array('m'=>"locstor.login", 'p'=>array('login', 'pass'), 'r'=>'sessid'), + "logout" => array('m'=>"locstor.logout", 'p'=>array('sessid'), 'r'=>'status'), + "storeAudioClipOpen" => array('m'=>"locstor.storeAudioClipOpen", + 'p'=>array('sessid', 'gunid', 'metadata', 'fname', 'chsum'), + 'r'=>array('url', 'token') + ), + "storeAudioClipClose" => array('m'=>"locstor.storeAudioClipClose", + 'p'=>array('sessid', 'token'), 'r'=>'gunid'), + "accessRawAudioData" => array('m'=>"locstor.accessRawAudioData", + 'p'=>array('sessid', 'gunid'), 'r'=>array('url', 'token')), + "releaseRawAudioData" => array('m'=>"locstor.releaseRawAudioData", + 'p'=>array('token'), 'r'=>'status'), + "downloadRawAudioDataOpen" => + array('m'=>"locstor.downloadRawAudioDataOpen", + 'p'=>array('sessid', 'gunid'), 'r'=>array('url', 'token')), + "downloadRawAudioDataClose" => + array('m'=>"locstor.downloadRawAudioDataClose", + 'p'=>array('sessid', 'token'), 'r'=>'gunid'), + "downloadMetadataOpen" => array('m'=>"locstor.downloadMetadataOpen", + 'p'=>array('sessid', 'gunid'), 'r'=>array('url', 'token')), + "downloadMetadataClose" => array('m'=>"locstor.downloadMetadataClose", + 'p'=>array('sessid', 'token'), 'r'=>'gunid'), + + "deleteAudioClip" => + array('m'=>"locstor.deleteAudioClip", + 'p'=>array('sessid', 'gunid', 'forced'), 'r'=>'status'), + "existsAudioClip" => array('m'=>"locstor.existsAudioClip", + 'p'=>array('sessid', 'gunid'), 'r'=>'exists'), + "getAudioClip" => array('m'=>"locstor.getAudioClip", + 'p'=>array('sessid', 'gunid'), 'r'=>'metadata'), + "updateAudioClipMetadata" => array('m'=>"locstor.updateAudioClipMetadata", + 'p'=>array('sessid', 'gunid', 'metadata'), 'r'=>'status'), + "searchMetadata" => array('m'=>"locstor.searchMetadata", 'p'=>NULL), + "browseCategory" => array('m'=>"locstor.browseCategory", 'p'=>NULL), +// "resetStorage" => array('m'=>"locstor.resetStorage", +// 'p'=>array()), + "storeWebstream" => array('m'=>"locstor.storeWebstream", + 'p'=>array('sessid', 'gunid', 'metadata', 'fname', 'url'), + 'r'=>array('gunid') + ), + + "createPlaylist" => array('m'=>"locstor.createPlaylist", + 'p'=>array('sessid', 'plid', 'fname'), 'r'=>'plid'), + "editPlaylist" => array('m'=>"locstor.editPlaylist", + 'p'=>array('sessid', 'plid'), 'r'=>array('url', 'token')), + "savePlaylist" => array('m'=>"locstor.savePlaylist", + 'p'=>array('sessid', 'token', 'newPlaylist'), 'r'=>'plid'), + "revertEditedPlaylist" => array('m'=>"locstor.revertEditedPlaylist", + 'p'=>array('sessid', 'token'), 'r'=>'plid'), + "deletePlaylist" => array('m'=>"locstor.deletePlaylist", + 'p'=>array('sessid', 'plid', 'forced'), 'r'=>'status'), + "accessPlaylist" => array('m'=>"locstor.accessPlaylist", + 'p'=>array('sessid', 'plid'), 'r'=>array('url', 'token')), + "releasePlaylist" => array('m'=>"locstor.releasePlaylist", + 'p'=>array('token'), 'r'=>'plid'), + "existsPlaylist" => array('m'=>"locstor.existsPlaylist", + 'p'=>array('sessid', 'plid'), 'r'=>'exists'), + "playlistIsAvailable" => array('m'=>"locstor.playlistIsAvailable", + 'p'=>array('sessid', 'plid'), 'r'=>array('available', 'ownerid', 'ownerlogin')), + + "exportPlaylistOpen" => array('m'=>"locstor.exportPlaylistOpen", + 'p'=>array('sessid', 'plids', 'type', 'standalone'), + 'r'=>array('url', 'token')), + "exportPlaylistClose" => array('m'=>"locstor.exportPlaylistClose", + 'p'=>array('token'), 'r'=>array('status')), + "importPlaylistOpen" => array('m'=>"locstor.importPlaylistOpen", + 'p'=>array('sessid', 'chsum'), 'r'=>array('url', 'token')), + "importPlaylistClose" => array('m'=>"locstor.importPlaylistClose", + 'p'=>array('token'), 'r'=>array('gunid')), + + "renderPlaylistToFileOpen" => array('m'=>"locstor.renderPlaylistToFileOpen", + 'p'=>array('sessid', 'plid'), + 'r'=>array('token')), + "renderPlaylistToFileCheck" => array('m'=>"locstor.renderPlaylistToFileCheck", + 'p'=>array('token'), 'r'=>array('status', 'url')), + "renderPlaylistToFileClose" => array('m'=>"locstor.renderPlaylistToFileClose", + 'p'=>array('token'), 'r'=>array('status')), + "renderPlaylistToStorageOpen" => array('m'=>"locstor.renderPlaylistToStorageOpen", + 'p'=>array('sessid', 'plid'), + 'r'=>array('token')), + "renderPlaylistToStorageCheck" => array('m'=>"locstor.renderPlaylistToStorageCheck", + 'p'=>array('token'), 'r'=>array('status', 'gunid')), + "renderPlaylistToRSSOpen" => array('m'=>"locstor.renderPlaylistToRSSOpen", + 'p'=>array('sessid', 'plid'), + 'r'=>array('token')), + "renderPlaylistToRSSCheck" => array('m'=>"locstor.renderPlaylistToRSSCheck", + 'p'=>array('token'), 'r'=>array('status', 'url')), + "renderPlaylistToRSSClose" => array('m'=>"locstor.renderPlaylistToRSSClose", + 'p'=>array('token'), 'r'=>array('status')), + + "loadPref" => array('m'=>"locstor.loadPref", + 'p'=>array('sessid', 'key'), 'r'=>'value'), + "savePref" => array('m'=>"locstor.savePref", + 'p'=>array('sessid', 'key', 'value'), 'r'=>'status'), + "delPref" => array('m'=>"locstor.delPref", + 'p'=>array('sessid', 'key'), 'r'=>'status'), + "loadGroupPref" => array('m'=>"locstor.loadGroupPref", + 'p'=>array('sessid', 'group', 'key'), 'r'=>'value'), + "saveGroupPref" => array('m'=>"locstor.saveGroupPref", + 'p'=>array('sessid', 'group', 'key', 'value'), 'r'=>'status'), + + "getTransportInfo" => array('m'=>"locstor.getTransportInfo", + 'p'=>array('trtok'), + 'r'=>array('state', 'realsize', 'expectedsize', 'realsum', 'expectedsum')), + "turnOnOffTransports" => array('m'=>"locstor.turnOnOffTransports", + 'p'=>array('sessid', 'onOff'), 'r'=>array('state')), + "doTransportAction" => array('m'=>"locstor.doTransportAction", + 'p'=>array('sessid', 'trtok', 'action'), 'r'=>array('state')), + "uploadFile2Hub" => array('m'=>"locstor.uploadFile2Hub", + 'p'=>array('sessid', 'filePath'), 'r'=>array('trtok')), + "getHubInitiatedTransfers" => array('m'=>"locstor.getHubInitiatedTransfers", + 'p'=>array('sessid'), 'r'=>array()), + "startHubInitiatedTransfer" => array('m'=>"locstor.startHubInitiatedTransfer", + 'p'=>array('trtok'), 'r'=>array()), + "upload2Hub" => array('m'=>"locstor.upload2Hub", + 'p'=>array('sessid', 'gunid'), 'r'=>array('trtok')), + "downloadFromHub" => array('m'=>"locstor.downloadFromHub", + 'p'=>array('sessid', 'gunid'), 'r'=>array('trtok')), +// "globalSearch" => array('m'=>"locstor.globalSearch", +// 'p'=>array('sessid', 'criteria'), 'r'=>array('trtok')), +// "getSearchResults" => array('m'=>"locstor.getSearchResults", +// 'p'=>array('trtok')), + + "createBackupOpen" => array('m'=>"locstor.createBackupOpen", + 'p'=>array('sessid', 'criteria'), 'r'=>array('token')), + "createBackupCheck" => array('m'=>"locstor.createBackupCheck", +# 'p'=>array('token'), 'r'=>array('status', 'url', 'metafile', 'faultString')), + 'p'=>array('token'), 'r'=>array('status', 'url', 'tmpfile')), + "createBackupClose" => array('m'=>"locstor.createBackupClose", + 'p'=>array('token'), 'r'=>array('status')), + "restoreBackupOpen" => array('m'=>"locstor.restoreBackupOpen", + 'p'=>array('sessid', 'chsum'), 'r'=>array('url', 'token')), + "restoreBackupClosePut" => array('m'=>"locstor.restoreBackupClosePut", + 'p'=>array('sessid', 'token'), 'r'=>array('token')), + "restoreBackupCheck" => array('m'=>"locstor.restoreBackupCheck", + 'p'=>array('token'), 'r'=>array('status', 'faultString')), + "restoreBackupClose" => array('m'=>"locstor.restoreBackupClose", + 'p'=>array('token'), 'r'=>array('status')), + "openPut" => array('m'=>"locstor.openPut", 'p'=>array()), + "closePut" => array('m'=>"locstor.closePut", 'p'=>array()), +); + +if (isset($_REQUEST['go_button'])) { + // Get the parameters + $methodParams = $methodDefs[$f_selectedMethod]['p']; + foreach ($methodParams as $methodParamName) { + $inputParamName = "param_".$methodParamName; + $xmlParameters[$methodParamName] = $_REQUEST[$inputParamName]; + $_SESSION[$inputParamName] = $_REQUEST[$inputParamName]; + } + + // Create the XML-RPC message + $actualMethod = $methodDefs[$f_selectedMethod]['m']; + $msg = new XML_RPC_Message($actualMethod, array(XML_RPC_encode($xmlParameters))); + $sentMessage = $msg->serialize(); + + // Send it + $sendResult = $client->send($msg); + if ($sendResult->faultCode() > 0) { + $errorMsg = "xr_cli_test.php: ".$sendResult->faultString()." ".$sendResult->faultCode()."\n"; + } else { + // If successful + $xmlResponse = XML_RPC_decode($sendResult->value()); + + // Special case state handling + switch ($f_selectedMethod) { + case "login": + // Remember the login session ID so we can use it to call + // other methods. + $loggedIn = true; + $_SESSION['xmlrpc_session_id'] = $xmlResponse['sessid']; + break; + case "logout": + unset($_SESSION['xmlrpc_session_id']); + break; + case "storeAudioClipOpen": + $_SESSION['xmlrpc_token'] = $xmlResponse['token']; + $_SESSION['xmlrpc_put_url'] = $xmlResponse['url']; + break; + } + + if (isset($methodDefs[$method]['r'])) { + $expectedResult = $methodDefs[$method]['r']; + if (is_array($expectedResult)) { + foreach ($expectedResult as $resultName) { + $actualResults[$resultName] = $xmlResponse[$resultName]; + } + echo join(' ', $actualResults)."\n"; + } else { + switch ($expectedResult) { + case "status": + case "exists": + echo ($xmlResponse[$expectedResult]=='1' ? "TRUE" : "FALSE" )."\n"; + break; + default: + echo "{$xmlResponse[$expectedResult]}\n"; + } + } + } else { + switch ($method) { + case "searchMetadata": +// case "getSearchResults": + $acCnt = 0; + $acGunids = array(); + $plCnt = 0; + $plGunids = array(); + $fld = (isset($options['category']) ? $options['category'] : 'gunid' ); + foreach ($xmlResponse['results'] as $k => $v) { + if ($v['type']=='audioclip') { + $acCnt++; + $acGunids[] = $v[$fld]; + } + if ($v['type']=='playlist') { + $plCnt++; + $plGunids[] = $v[$fld]; + } + } + echo "AC({$acCnt}): ". + join(", ", $acGunids). + " | PL({$plCnt}): ". + join(", ", $plGunids). + "\n"; + break; + case "browseCategory": + echo "RES({$xmlResponse['cnt']}): ". + join(", ", $xmlResponse['results']). + "\n"; + break; + default: + //print_r($xmlResponse); + } + } + } +} +?> + + +
    +StorageServer path :
    +Method: + +
    +Parameters: +"; +} else { + echo ""; + foreach ($methodParams as $methodParamName) { + $value = ""; + if ($methodParamName == "sessid" && isset($_SESSION['xmlrpc_session_id'])) { + $value = $_SESSION['xmlrpc_session_id']; + } elseif ($methodParamName == "token" && isset($_SESSION['xmlrpc_token'])) { + $value = $_SESSION['xmlrpc_token']; + } elseif (isset($_SESSION["param_".$methodParamName])) { + $value = $_SESSION["param_".$methodParamName]; + } + echo ""; + echo ""; ?> + "; +} +?> +
    + + + + +
    "; +} +if (isset($sentMessage)) { + ?> + Sent message:
    + +
    + + Error:
    + +
    + + Response:
    + +
    + + + diff --git a/application/views/scripts/error/denied.phtml b/application/views/scripts/error/denied.phtml new file mode 100644 index 000000000..5a133fec6 --- /dev/null +++ b/application/views/scripts/error/denied.phtml @@ -0,0 +1 @@ +

    View script for controller Error and script/action name denied
    \ No newline at end of file diff --git a/application/views/scripts/error/error.phtml b/application/views/scripts/error/error.phtml new file mode 100644 index 000000000..1997506cb --- /dev/null +++ b/application/views/scripts/error/error.phtml @@ -0,0 +1,28 @@ + + + + + Zend Framework Default Application + + +

    An error occurred

    +

    message ?>

    + + exception)): ?> + +

    Exception information:

    +

    + Message: exception->getMessage() ?> +

    + +

    Stack trace:

    +
    exception->getTraceAsString() ?>
    +  
    + +

    Request Parameters:

    +
    request->getParams(), true) ?>
    +  
    + + + + diff --git a/application/views/scripts/index/display.phtml b/application/views/scripts/index/display.phtml new file mode 100644 index 000000000..d2cfe08b7 --- /dev/null +++ b/application/views/scripts/index/display.phtml @@ -0,0 +1 @@ +

    View script for controller Index and script/action name display
    \ No newline at end of file diff --git a/application/views/scripts/index/index.phtml b/application/views/scripts/index/index.phtml new file mode 100644 index 000000000..e69de29bb diff --git a/application/views/scripts/index/main.phtml b/application/views/scripts/index/main.phtml new file mode 100644 index 000000000..6d10563ec --- /dev/null +++ b/application/views/scripts/index/main.phtml @@ -0,0 +1 @@ +

    View script for controller Index and script/action name main
    \ No newline at end of file diff --git a/application/views/scripts/index/newfield.phtml b/application/views/scripts/index/newfield.phtml new file mode 100644 index 000000000..d1935f51f --- /dev/null +++ b/application/views/scripts/index/newfield.phtml @@ -0,0 +1 @@ +

    View script for controller Index and script/action name newfield
    \ No newline at end of file diff --git a/application/views/scripts/library/contents.ajax.phtml b/application/views/scripts/library/contents.ajax.phtml new file mode 100644 index 000000000..080b0a341 --- /dev/null +++ b/application/views/scripts/library/contents.ajax.phtml @@ -0,0 +1 @@ +partialLoop('library/libraryTablePartial.phtml', $this->files) ?> diff --git a/application/views/scripts/library/contents.phtml b/application/views/scripts/library/contents.phtml new file mode 100644 index 000000000..4cee3f9bb --- /dev/null +++ b/application/views/scripts/library/contents.phtml @@ -0,0 +1,22 @@ +files) { +?> +
    $methodParamName
    + + + + + + + +partialLoop('library/libraryTablePartial.phtml', $this->files); +?> +
    TitleArtistAlbumTrackLength
    + + diff --git a/application/views/scripts/library/context-menu.phtml b/application/views/scripts/library/context-menu.phtml new file mode 100644 index 000000000..260fb3b3d --- /dev/null +++ b/application/views/scripts/library/context-menu.phtml @@ -0,0 +1,3 @@ +
      +partialLoop('library/contextMenuPartial.phtml', $this->menu) ?> +
    diff --git a/application/views/scripts/library/contextMenuPartial.phtml b/application/views/scripts/library/contextMenuPartial.phtml new file mode 100644 index 000000000..31b484496 --- /dev/null +++ b/application/views/scripts/library/contextMenuPartial.phtml @@ -0,0 +1 @@ +
  • text ?>
  • diff --git a/application/views/scripts/library/delete.phtml b/application/views/scripts/library/delete.phtml new file mode 100644 index 000000000..5cbb9a545 --- /dev/null +++ b/application/views/scripts/library/delete.phtml @@ -0,0 +1 @@ +

    View script for controller Library and script/action name delete
    \ No newline at end of file diff --git a/application/views/scripts/library/index.phtml b/application/views/scripts/library/index.phtml new file mode 100644 index 000000000..e69de29bb diff --git a/application/views/scripts/library/libraryTablePartial.phtml b/application/views/scripts/library/libraryTablePartial.phtml new file mode 100644 index 000000000..8cb982cf5 --- /dev/null +++ b/application/views/scripts/library/libraryTablePartial.phtml @@ -0,0 +1,8 @@ + + + track_title ?> + artist_name ?> + album_title ?> + track_number ?> + length ?> + diff --git a/application/views/scripts/library/search.phtml b/application/views/scripts/library/search.phtml new file mode 100644 index 000000000..5fb9621a7 --- /dev/null +++ b/application/views/scripts/library/search.phtml @@ -0,0 +1 @@ +

    View script for controller Library and script/action name search
    \ No newline at end of file diff --git a/application/views/scripts/login/index.phtml b/application/views/scripts/login/index.phtml new file mode 100644 index 000000000..c9df1898e --- /dev/null +++ b/application/views/scripts/login/index.phtml @@ -0,0 +1,3 @@ +form; +echo $this->errorMessage; diff --git a/application/views/scripts/login/logout.phtml b/application/views/scripts/login/logout.phtml new file mode 100644 index 000000000..35e6f2b8a --- /dev/null +++ b/application/views/scripts/login/logout.phtml @@ -0,0 +1 @@ +

    View script for controller Login and script/action name logout
    \ No newline at end of file diff --git a/application/views/scripts/playlist/add-item.phtml b/application/views/scripts/playlist/add-item.phtml new file mode 100644 index 000000000..dcba31552 --- /dev/null +++ b/application/views/scripts/playlist/add-item.phtml @@ -0,0 +1 @@ +

    View script for controller Playlist and script/action name addItem
    \ No newline at end of file diff --git a/application/views/scripts/playlist/delete-active.phtml b/application/views/scripts/playlist/delete-active.phtml new file mode 100644 index 000000000..61f99953e --- /dev/null +++ b/application/views/scripts/playlist/delete-active.phtml @@ -0,0 +1 @@ +

    View script for controller Playlist and script/action name deleteActive
    \ No newline at end of file diff --git a/application/views/scripts/playlist/delete-item.ajax.phtml b/application/views/scripts/playlist/delete-item.ajax.phtml new file mode 100644 index 000000000..08da3f559 --- /dev/null +++ b/application/views/scripts/playlist/delete-item.ajax.phtml @@ -0,0 +1,8 @@ +playlistcontents)) { + echo $this->partialLoop('playlist/playlistEditorTable.phtml', $this->playlistcontents); + } + else { + echo '
    Empty playlist
    '; + } +?> diff --git a/application/views/scripts/playlist/delete-item.phtml b/application/views/scripts/playlist/delete-item.phtml new file mode 100644 index 000000000..f1a489d89 --- /dev/null +++ b/application/views/scripts/playlist/delete-item.phtml @@ -0,0 +1 @@ +

    View script for controller Playlist and script/action name deleteItem
    \ No newline at end of file diff --git a/application/views/scripts/playlist/delete.phtml b/application/views/scripts/playlist/delete.phtml new file mode 100644 index 000000000..c4e7b62a1 --- /dev/null +++ b/application/views/scripts/playlist/delete.phtml @@ -0,0 +1 @@ +

    View script for controller Playlist and script/action name delete
    \ No newline at end of file diff --git a/application/views/scripts/playlist/edit.phtml b/application/views/scripts/playlist/edit.phtml new file mode 100644 index 000000000..63bab601c --- /dev/null +++ b/application/views/scripts/playlist/edit.phtml @@ -0,0 +1,35 @@ +
    +
    + + Title + Creator + Length + Cue In + Cue Out + Playlength +
    +
    +
      + playlistcontents)) { + echo $this->partialLoop('playlist/playlistEditorTable.phtml', $this->playlistcontents); + } + else { + echo '
    • Empty playlist
    • '; + } + ?> +
    +
    +
    + diff --git a/application/views/scripts/playlist/index.phtml b/application/views/scripts/playlist/index.phtml new file mode 100644 index 000000000..df687565d --- /dev/null +++ b/application/views/scripts/playlist/index.phtml @@ -0,0 +1,15 @@ + + + diff --git a/application/views/scripts/playlist/metadata.phtml b/application/views/scripts/playlist/metadata.phtml new file mode 100644 index 000000000..41276c253 --- /dev/null +++ b/application/views/scripts/playlist/metadata.phtml @@ -0,0 +1,4 @@ +form->setAction($this->url()); +echo $this->form; diff --git a/application/views/scripts/playlist/move-item.ajax.phtml b/application/views/scripts/playlist/move-item.ajax.phtml new file mode 100644 index 000000000..08da3f559 --- /dev/null +++ b/application/views/scripts/playlist/move-item.ajax.phtml @@ -0,0 +1,8 @@ +playlistcontents)) { + echo $this->partialLoop('playlist/playlistEditorTable.phtml', $this->playlistcontents); + } + else { + echo '
    Empty playlist
    '; + } +?> diff --git a/application/views/scripts/playlist/move-item.phtml b/application/views/scripts/playlist/move-item.phtml new file mode 100644 index 000000000..b739285c1 --- /dev/null +++ b/application/views/scripts/playlist/move-item.phtml @@ -0,0 +1 @@ +

    View script for controller Playlist and script/action name moveItem
    \ No newline at end of file diff --git a/application/views/scripts/playlist/new.phtml b/application/views/scripts/playlist/new.phtml new file mode 100644 index 000000000..07e36af17 --- /dev/null +++ b/application/views/scripts/playlist/new.phtml @@ -0,0 +1 @@ +

    View script for controller Playlist and script/action name new
    \ No newline at end of file diff --git a/application/views/scripts/playlist/playlistEditorTable.phtml b/application/views/scripts/playlist/playlistEditorTable.phtml new file mode 100644 index 000000000..da27d1adc --- /dev/null +++ b/application/views/scripts/playlist/playlistEditorTable.phtml @@ -0,0 +1,31 @@ + + +
  • +
    + Fade in: fadein ?> +
    + + + + + CcFiles['track_title'] ?> + + + CcFiles['artist_name'] ?> + + + CcFiles['length'] ?> + + + cuein ?> + + + cueout ?> + + + cliplength ?> + +
    + Fade out: fadeout ?> +
    +
  • diff --git a/application/views/scripts/playlist/set-cue.phtml b/application/views/scripts/playlist/set-cue.phtml new file mode 100644 index 000000000..197caa1f4 --- /dev/null +++ b/application/views/scripts/playlist/set-cue.phtml @@ -0,0 +1 @@ +

    View script for controller Playlist and script/action name setCue
    \ No newline at end of file diff --git a/application/views/scripts/playlist/set-fade.phtml b/application/views/scripts/playlist/set-fade.phtml new file mode 100644 index 000000000..379c433ff --- /dev/null +++ b/application/views/scripts/playlist/set-fade.phtml @@ -0,0 +1 @@ +

    View script for controller Playlist and script/action name setFade
    \ No newline at end of file diff --git a/application/views/scripts/plupload/index.phtml b/application/views/scripts/plupload/index.phtml new file mode 100644 index 000000000..ced9919a1 --- /dev/null +++ b/application/views/scripts/plupload/index.phtml @@ -0,0 +1 @@ +

    View script for controller Plupload and script/action name index
    \ No newline at end of file diff --git a/application/views/scripts/plupload/plupload.phtml b/application/views/scripts/plupload/plupload.phtml new file mode 100644 index 000000000..92c98cc93 --- /dev/null +++ b/application/views/scripts/plupload/plupload.phtml @@ -0,0 +1,6 @@ +
    +
    +
    +
    +
    +
    diff --git a/application/views/scripts/plupload/upload.phtml b/application/views/scripts/plupload/upload.phtml new file mode 100644 index 000000000..69977aa4c --- /dev/null +++ b/application/views/scripts/plupload/upload.phtml @@ -0,0 +1 @@ +

    View script for controller Library and script/action name upload
    \ No newline at end of file diff --git a/application/views/scripts/schedule/add-show-dialog.phtml b/application/views/scripts/schedule/add-show-dialog.phtml new file mode 100644 index 000000000..3c4a8ff95 --- /dev/null +++ b/application/views/scripts/schedule/add-show-dialog.phtml @@ -0,0 +1 @@ +

    View script for controller Schedule and script/action name addShowDialog
    \ No newline at end of file diff --git a/application/views/scripts/schedule/event-feed.phtml b/application/views/scripts/schedule/event-feed.phtml new file mode 100644 index 000000000..09f20573d --- /dev/null +++ b/application/views/scripts/schedule/event-feed.phtml @@ -0,0 +1 @@ +

    View script for controller Schedule and script/action name eventFeed
    \ No newline at end of file diff --git a/application/views/scripts/schedule/index.phtml b/application/views/scripts/schedule/index.phtml new file mode 100644 index 000000000..7cbbc9bd8 --- /dev/null +++ b/application/views/scripts/schedule/index.phtml @@ -0,0 +1,2 @@ +
    Add Show
    +
    diff --git a/application/views/scripts/search/display.phtml b/application/views/scripts/search/display.phtml new file mode 100644 index 000000000..5dd4dc8b8 --- /dev/null +++ b/application/views/scripts/search/display.phtml @@ -0,0 +1,29 @@ +form->setAction($this->url()); +echo $this->form; +?> +Add +Submit + +files) { +?> + + + + + + + + +partialLoop('library/libraryTablePartial.phtml', $this->files); +?> +
    TitleArtistAlbumTrackLength
    +No Results"; + } +?> diff --git a/application/views/scripts/search/index.phtml b/application/views/scripts/search/index.phtml new file mode 100644 index 000000000..dee2538d1 --- /dev/null +++ b/application/views/scripts/search/index.phtml @@ -0,0 +1 @@ +

    View script for controller Forms and script/action name index
    \ No newline at end of file diff --git a/application/views/scripts/search/newfield.ajax.phtml b/application/views/scripts/search/newfield.ajax.phtml new file mode 100644 index 000000000..1dd0f7359 --- /dev/null +++ b/application/views/scripts/search/newfield.ajax.phtml @@ -0,0 +1 @@ +field; ?> diff --git a/application/views/scripts/search/newfield.phtml b/application/views/scripts/search/newfield.phtml new file mode 100644 index 000000000..e0cbb61b3 --- /dev/null +++ b/application/views/scripts/search/newfield.phtml @@ -0,0 +1 @@ +

    View script for controller Forms and script/action name newfield
    \ No newline at end of file diff --git a/build/build.properties b/build/build.properties new file mode 100644 index 000000000..a3f567089 --- /dev/null +++ b/build/build.properties @@ -0,0 +1,22 @@ +project.home = /home/naomiaro/campcaster-refactor/campcaster +project.build = ${project.home}/build + +#Database driver +propel.database = pgsql +propel.database.url = pgsql:host=localhost dbname=campcaster user=campcaster password=campcaster + +#Project name +propel.project = campcaster + +propel.schema.dir = ${project.build} +propel.conf.dir = ${project.build} + +# set the directories for the generated output, i.e. the data object classes, a +# PHP file with the configuration data and the SQL files +propel.output.dir = ${project.home} +propel.php.dir = ${propel.output.dir}/application/models +propel.phpconf.dir = ${propel.output.dir}/application/configs +propel.sql.dir = ${project.build}/sql + +# set the name for the configuration file +propel.runtime.phpconf.file = propel-config.php diff --git a/build/runtime-conf.xml b/build/runtime-conf.xml new file mode 100644 index 000000000..4c83aa481 --- /dev/null +++ b/build/runtime-conf.xml @@ -0,0 +1,13 @@ + + + + + + pgsql + + pgsql:host=localhost;port=5432;dbname=campcaster;user=campcaster;password=campcaster + + + + + diff --git a/build/schema.xml b/build/schema.xml new file mode 100644 index 000000000..8588586ee --- /dev/null +++ b/build/schema.xml @@ -0,0 +1,279 @@ + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + +
    + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + +
    + + + + + + + + + + + + + + +
    + + + + + + + + + +
    + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    diff --git a/build/sql/schema.sql b/build/sql/schema.sql new file mode 100644 index 000000000..8f997d217 --- /dev/null +++ b/build/sql/schema.sql @@ -0,0 +1,411 @@ + +----------------------------------------------------------------------------- +-- cc_access +----------------------------------------------------------------------------- + +DROP TABLE "cc_access" CASCADE; + + +CREATE TABLE "cc_access" +( + "id" serial NOT NULL, + "gunid" CHAR(32), + "token" INT8, + "chsum" CHAR(32) default '' NOT NULL, + "ext" VARCHAR(128) default '' NOT NULL, + "type" VARCHAR(20) default '' NOT NULL, + "parent" INT8, + "owner" INTEGER, + "ts" TIMESTAMP, + PRIMARY KEY ("id") +); + +COMMENT ON TABLE "cc_access" IS ''; + + +SET search_path TO public; +CREATE INDEX "cc_access_gunid_idx" ON "cc_access" ("gunid"); + +CREATE INDEX "cc_access_parent_idx" ON "cc_access" ("parent"); + +CREATE INDEX "cc_access_token_idx" ON "cc_access" ("token"); + +----------------------------------------------------------------------------- +-- cc_backup +----------------------------------------------------------------------------- + +DROP TABLE "cc_backup" CASCADE; + + +CREATE TABLE "cc_backup" +( + "token" VARCHAR(64) NOT NULL, + "sessionid" VARCHAR(64) NOT NULL, + "status" VARCHAR(32) NOT NULL, + "fromtime" TIMESTAMP NOT NULL, + "totime" TIMESTAMP NOT NULL, + PRIMARY KEY ("token") +); + +COMMENT ON TABLE "cc_backup" IS ''; + + +SET search_path TO public; +----------------------------------------------------------------------------- +-- cc_files +----------------------------------------------------------------------------- + +DROP TABLE "cc_files" CASCADE; + + +CREATE TABLE "cc_files" +( + "id" serial NOT NULL, + "gunid" CHAR(32) NOT NULL, + "name" VARCHAR(255) default '' NOT NULL, + "mime" VARCHAR(255) default '' NOT NULL, + "ftype" VARCHAR(128) default '' NOT NULL, + "filepath" TEXT default '', + "state" VARCHAR(128) default 'empty' NOT NULL, + "currentlyaccessing" INTEGER default 0 NOT NULL, + "editedby" INTEGER, + "mtime" TIMESTAMP(6), + "md5" CHAR(32), + "track_title" VARCHAR(512), + "artist_name" VARCHAR(512), + "bit_rate" VARCHAR(32), + "sample_rate" VARCHAR(32), + "format" VARCHAR(128), + "length" TIME, + "album_title" VARCHAR(512), + "genre" VARCHAR(64), + "comments" TEXT, + "year" VARCHAR(16), + "track_number" INTEGER, + "channels" INTEGER, + "url" VARCHAR(1024), + "bpm" VARCHAR(8), + "rating" VARCHAR(8), + "encoded_by" VARCHAR(255), + "disc_number" VARCHAR(8), + "mood" VARCHAR(64), + "label" VARCHAR(512), + "composer" VARCHAR(512), + "encoder" VARCHAR(64), + "checksum" VARCHAR(256), + "lyrics" TEXT, + "orchestra" VARCHAR(512), + "conductor" VARCHAR(512), + "lyricist" VARCHAR(512), + "original_lyricist" VARCHAR(512), + "radio_station_name" VARCHAR(512), + "info_url" VARCHAR(512), + "artist_url" VARCHAR(512), + "audio_source_url" VARCHAR(512), + "radio_station_url" VARCHAR(512), + "buy_this_url" VARCHAR(512), + "isrc_number" VARCHAR(512), + "catalog_number" VARCHAR(512), + "original_artist" VARCHAR(512), + "copyright" VARCHAR(512), + "report_datetime" VARCHAR(32), + "report_location" VARCHAR(512), + "report_organization" VARCHAR(512), + "subject" VARCHAR(512), + "contributor" VARCHAR(512), + "language" VARCHAR(512), + PRIMARY KEY ("id"), + CONSTRAINT "cc_files_gunid_idx" UNIQUE ("gunid") +); + +COMMENT ON TABLE "cc_files" IS ''; + + +SET search_path TO public; +CREATE INDEX "cc_files_md5_idx" ON "cc_files" ("md5"); + +CREATE INDEX "cc_files_name_idx" ON "cc_files" ("name"); + +----------------------------------------------------------------------------- +-- cc_perms +----------------------------------------------------------------------------- + +DROP TABLE "cc_perms" CASCADE; + + +CREATE TABLE "cc_perms" +( + "permid" INTEGER NOT NULL, + "subj" INTEGER, + "action" VARCHAR(20), + "obj" INTEGER, + "type" CHAR(1), + PRIMARY KEY ("permid"), + CONSTRAINT "cc_perms_all_idx" UNIQUE ("subj","action","obj"), + CONSTRAINT "cc_perms_permid_idx" UNIQUE ("permid") +); + +COMMENT ON TABLE "cc_perms" IS ''; + + +SET search_path TO public; +CREATE INDEX "cc_perms_subj_obj_idx" ON "cc_perms" ("subj","obj"); + +----------------------------------------------------------------------------- +-- cc_show +----------------------------------------------------------------------------- + +DROP TABLE "cc_show" CASCADE; + + +CREATE TABLE "cc_show" +( + "id" serial NOT NULL, + "name" VARCHAR(255) default '' NOT NULL, + "first_show" DATE NOT NULL, + "last_show" DATE, + "start_time" TIME NOT NULL, + "end_time" TIME NOT NULL, + "repeats" INT2 NOT NULL, + "day" INT2 NOT NULL, + "description" VARCHAR(512), + PRIMARY KEY ("id") +); + +COMMENT ON TABLE "cc_show" IS ''; + + +SET search_path TO public; +----------------------------------------------------------------------------- +-- cc_playlist +----------------------------------------------------------------------------- + +DROP TABLE "cc_playlist" CASCADE; + + +CREATE TABLE "cc_playlist" +( + "id" serial NOT NULL, + "name" VARCHAR(255) default '' NOT NULL, + "state" VARCHAR(128) default 'empty' NOT NULL, + "currentlyaccessing" INTEGER default 0 NOT NULL, + "editedby" INTEGER, + "mtime" TIMESTAMP(6), + "creator" VARCHAR(32), + "description" VARCHAR(512), + PRIMARY KEY ("id") +); + +COMMENT ON TABLE "cc_playlist" IS ''; + + +SET search_path TO public; +----------------------------------------------------------------------------- +-- cc_playlistcontents +----------------------------------------------------------------------------- + +DROP TABLE "cc_playlistcontents" CASCADE; + + +CREATE TABLE "cc_playlistcontents" +( + "id" serial NOT NULL, + "playlist_id" INTEGER, + "file_id" INTEGER, + "position" INTEGER, + "cliplength" TIME default '00:00:00', + "cuein" TIME default '00:00:00', + "cueout" TIME default '00:00:00', + "fadein" TIME default '00:00:00', + "fadeout" TIME default '00:00:00', + PRIMARY KEY ("id") +); + +COMMENT ON TABLE "cc_playlistcontents" IS ''; + + +SET search_path TO public; +----------------------------------------------------------------------------- +-- cc_pref +----------------------------------------------------------------------------- + +DROP TABLE "cc_pref" CASCADE; + + +CREATE TABLE "cc_pref" +( + "id" serial NOT NULL, + "subjid" INTEGER, + "keystr" VARCHAR(255), + "valstr" TEXT, + PRIMARY KEY ("id"), + CONSTRAINT "cc_pref_id_idx" UNIQUE ("id"), + CONSTRAINT "cc_pref_subj_key_idx" UNIQUE ("subjid","keystr") +); + +COMMENT ON TABLE "cc_pref" IS ''; + + +SET search_path TO public; +CREATE INDEX "cc_pref_subjid_idx" ON "cc_pref" ("subjid"); + +----------------------------------------------------------------------------- +-- cc_schedule +----------------------------------------------------------------------------- + +DROP TABLE "cc_schedule" CASCADE; + + +CREATE TABLE "cc_schedule" +( + "id" INT8 NOT NULL, + "playlist_id" INTEGER NOT NULL, + "starts" TIMESTAMP NOT NULL, + "ends" TIMESTAMP NOT NULL, + "group_id" INTEGER, + "file_id" INTEGER, + "clip_length" TIME default '00:00:00', + "fade_in" TIME default '00:00:00', + "fade_out" TIME default '00:00:00', + "cue_in" TIME default '00:00:00', + "cue_out" TIME default '00:00:00', + PRIMARY KEY ("id") +); + +COMMENT ON TABLE "cc_schedule" IS ''; + + +SET search_path TO public; +----------------------------------------------------------------------------- +-- cc_sess +----------------------------------------------------------------------------- + +DROP TABLE "cc_sess" CASCADE; + + +CREATE TABLE "cc_sess" +( + "sessid" CHAR(32) NOT NULL, + "userid" INTEGER, + "login" VARCHAR(255), + "ts" TIMESTAMP, + PRIMARY KEY ("sessid") +); + +COMMENT ON TABLE "cc_sess" IS ''; + + +SET search_path TO public; +CREATE INDEX "cc_sess_login_idx" ON "cc_sess" ("login"); + +CREATE INDEX "cc_sess_userid_idx" ON "cc_sess" ("userid"); + +----------------------------------------------------------------------------- +-- cc_smemb +----------------------------------------------------------------------------- + +DROP TABLE "cc_smemb" CASCADE; + + +CREATE TABLE "cc_smemb" +( + "id" INTEGER NOT NULL, + "uid" INTEGER default 0 NOT NULL, + "gid" INTEGER default 0 NOT NULL, + "level" INTEGER default 0 NOT NULL, + "mid" INTEGER, + PRIMARY KEY ("id"), + CONSTRAINT "cc_smemb_id_idx" UNIQUE ("id") +); + +COMMENT ON TABLE "cc_smemb" IS ''; + + +SET search_path TO public; +----------------------------------------------------------------------------- +-- cc_subjs +----------------------------------------------------------------------------- + +DROP TABLE "cc_subjs" CASCADE; + + +CREATE TABLE "cc_subjs" +( + "id" INTEGER NOT NULL, + "login" VARCHAR(255) default '' NOT NULL, + "pass" VARCHAR(255) default '' NOT NULL, + "type" CHAR(1) default 'U' NOT NULL, + "realname" VARCHAR(255) default '' NOT NULL, + "lastlogin" TIMESTAMP, + "lastfail" TIMESTAMP, + PRIMARY KEY ("id"), + CONSTRAINT "cc_subjs_id_idx" UNIQUE ("id"), + CONSTRAINT "cc_subjs_login_idx" UNIQUE ("login") +); + +COMMENT ON TABLE "cc_subjs" IS ''; + + +SET search_path TO public; +----------------------------------------------------------------------------- +-- cc_trans +----------------------------------------------------------------------------- + +DROP TABLE "cc_trans" CASCADE; + + +CREATE TABLE "cc_trans" +( + "id" serial NOT NULL, + "trtok" CHAR(16) NOT NULL, + "direction" VARCHAR(128) NOT NULL, + "state" VARCHAR(128) NOT NULL, + "trtype" VARCHAR(128) NOT NULL, + "lock" CHAR(1) default 'N' NOT NULL, + "target" VARCHAR(255), + "rtrtok" CHAR(16), + "mdtrtok" CHAR(16), + "gunid" CHAR(32), + "pdtoken" INT8, + "url" VARCHAR(255), + "localfile" VARCHAR(255), + "fname" VARCHAR(255), + "title" VARCHAR(255), + "expectedsum" CHAR(32), + "realsum" CHAR(32), + "expectedsize" INTEGER, + "realsize" INTEGER, + "uid" INTEGER, + "errmsg" VARCHAR(255), + "jobpid" INTEGER, + "start" TIMESTAMP, + "ts" TIMESTAMP, + PRIMARY KEY ("id"), + CONSTRAINT "cc_trans_id_idx" UNIQUE ("id"), + CONSTRAINT "cc_trans_token_idx" UNIQUE ("pdtoken"), + CONSTRAINT "cc_trans_trtok_idx" UNIQUE ("trtok") +); + +COMMENT ON TABLE "cc_trans" IS ''; + + +SET search_path TO public; +CREATE INDEX "cc_trans_gunid_idx" ON "cc_trans" ("gunid"); + +CREATE INDEX "cc_trans_state_idx" ON "cc_trans" ("state"); + +ALTER TABLE "cc_access" ADD CONSTRAINT "cc_access_owner_fkey" FOREIGN KEY ("owner") REFERENCES "cc_subjs" ("id"); + +ALTER TABLE "cc_files" ADD CONSTRAINT "cc_files_editedby_fkey" FOREIGN KEY ("editedby") REFERENCES "cc_subjs" ("id"); + +ALTER TABLE "cc_perms" ADD CONSTRAINT "cc_perms_subj_fkey" FOREIGN KEY ("subj") REFERENCES "cc_subjs" ("id") ON DELETE CASCADE; + +ALTER TABLE "cc_playlist" ADD CONSTRAINT "cc_playlist_editedby_fkey" FOREIGN KEY ("editedby") REFERENCES "cc_subjs" ("id"); + +ALTER TABLE "cc_playlistcontents" ADD CONSTRAINT "cc_playlistcontents_file_id_fkey" FOREIGN KEY ("file_id") REFERENCES "cc_files" ("id") ON DELETE CASCADE; + +ALTER TABLE "cc_playlistcontents" ADD CONSTRAINT "cc_playlistcontents_playlist_id_fkey" FOREIGN KEY ("playlist_id") REFERENCES "cc_playlist" ("id") ON DELETE CASCADE; + +ALTER TABLE "cc_pref" ADD CONSTRAINT "cc_pref_subjid_fkey" FOREIGN KEY ("subjid") REFERENCES "cc_subjs" ("id") ON DELETE CASCADE; + +ALTER TABLE "cc_sess" ADD CONSTRAINT "cc_sess_userid_fkey" FOREIGN KEY ("userid") REFERENCES "cc_subjs" ("id") ON DELETE CASCADE; diff --git a/build/sql/sqldb.map b/build/sql/sqldb.map new file mode 100644 index 000000000..b57650420 --- /dev/null +++ b/build/sql/sqldb.map @@ -0,0 +1,2 @@ +# Sqlfile -> Database map +schema.sql=campcaster diff --git a/docs/README.txt b/docs/README.txt new file mode 100644 index 000000000..1e3c6df4d --- /dev/null +++ b/docs/README.txt @@ -0,0 +1,30 @@ +README +====== + +This directory should be used to place project specfic documentation including +but not limited to project notes, generated API/phpdoc documentation, or +manual files generated or hand written. Ideally, this directory would remain +in your development environment only and should not be deployed with your +application to it's final production location. + + +Setting Up Your VHOST +===================== + +The following is a sample VHOST you might want to consider for your project. + + + DocumentRoot "/home/naomiaro/campcaster-refactor/campcaster/public" + ServerName campcaster.local + + # This should be omitted in the production environment + SetEnv APPLICATION_ENV development + + + Options Indexes MultiViews FollowSymLinks + AllowOverride All + Order allow,deny + Allow from all + + + diff --git a/library/Zend/Acl.php b/library/Zend/Acl.php new file mode 100644 index 000000000..3a0b8b313 --- /dev/null +++ b/library/Zend/Acl.php @@ -0,0 +1,1164 @@ + array( + 'allRoles' => array( + 'allPrivileges' => array( + 'type' => self::TYPE_DENY, + 'assert' => null + ), + 'byPrivilegeId' => array() + ), + 'byRoleId' => array() + ), + 'byResourceId' => array() + ); + + /** + * Adds a Role having an identifier unique to the registry + * + * The $parents parameter may be a reference to, or the string identifier for, + * a Role existing in the registry, or $parents may be passed as an array of + * these - mixing string identifiers and objects is ok - to indicate the Roles + * from which the newly added Role will directly inherit. + * + * In order to resolve potential ambiguities with conflicting rules inherited + * from different parents, the most recently added parent takes precedence over + * parents that were previously added. In other words, the first parent added + * will have the least priority, and the last parent added will have the + * highest priority. + * + * @param Zend_Acl_Role_Interface $role + * @param Zend_Acl_Role_Interface|string|array $parents + * @uses Zend_Acl_Role_Registry::add() + * @return Zend_Acl Provides a fluent interface + */ + public function addRole($role, $parents = null) + { + if (is_string($role)) { + $role = new Zend_Acl_Role($role); + } + + if (!$role instanceof Zend_Acl_Role_Interface) { + require_once 'Zend/Acl/Exception.php'; + throw new Zend_Acl_Exception('addRole() expects $role to be of type Zend_Acl_Role_Interface'); + } + + + $this->_getRoleRegistry()->add($role, $parents); + + return $this; + } + + /** + * Returns the identified Role + * + * The $role parameter can either be a Role or Role identifier. + * + * @param Zend_Acl_Role_Interface|string $role + * @uses Zend_Acl_Role_Registry::get() + * @return Zend_Acl_Role_Interface + */ + public function getRole($role) + { + return $this->_getRoleRegistry()->get($role); + } + + /** + * Returns true if and only if the Role exists in the registry + * + * The $role parameter can either be a Role or a Role identifier. + * + * @param Zend_Acl_Role_Interface|string $role + * @uses Zend_Acl_Role_Registry::has() + * @return boolean + */ + public function hasRole($role) + { + return $this->_getRoleRegistry()->has($role); + } + + /** + * Returns true if and only if $role inherits from $inherit + * + * Both parameters may be either a Role or a Role identifier. If + * $onlyParents is true, then $role must inherit directly from + * $inherit in order to return true. By default, this method looks + * through the entire inheritance DAG to determine whether $role + * inherits from $inherit through its ancestor Roles. + * + * @param Zend_Acl_Role_Interface|string $role + * @param Zend_Acl_Role_Interface|string $inherit + * @param boolean $onlyParents + * @uses Zend_Acl_Role_Registry::inherits() + * @return boolean + */ + public function inheritsRole($role, $inherit, $onlyParents = false) + { + return $this->_getRoleRegistry()->inherits($role, $inherit, $onlyParents); + } + + /** + * Removes the Role from the registry + * + * The $role parameter can either be a Role or a Role identifier. + * + * @param Zend_Acl_Role_Interface|string $role + * @uses Zend_Acl_Role_Registry::remove() + * @return Zend_Acl Provides a fluent interface + */ + public function removeRole($role) + { + $this->_getRoleRegistry()->remove($role); + + if ($role instanceof Zend_Acl_Role_Interface) { + $roleId = $role->getRoleId(); + } else { + $roleId = $role; + } + + foreach ($this->_rules['allResources']['byRoleId'] as $roleIdCurrent => $rules) { + if ($roleId === $roleIdCurrent) { + unset($this->_rules['allResources']['byRoleId'][$roleIdCurrent]); + } + } + foreach ($this->_rules['byResourceId'] as $resourceIdCurrent => $visitor) { + if (array_key_exists('byRoleId', $visitor)) { + foreach ($visitor['byRoleId'] as $roleIdCurrent => $rules) { + if ($roleId === $roleIdCurrent) { + unset($this->_rules['byResourceId'][$resourceIdCurrent]['byRoleId'][$roleIdCurrent]); + } + } + } + } + + return $this; + } + + /** + * Removes all Roles from the registry + * + * @uses Zend_Acl_Role_Registry::removeAll() + * @return Zend_Acl Provides a fluent interface + */ + public function removeRoleAll() + { + $this->_getRoleRegistry()->removeAll(); + + foreach ($this->_rules['allResources']['byRoleId'] as $roleIdCurrent => $rules) { + unset($this->_rules['allResources']['byRoleId'][$roleIdCurrent]); + } + foreach ($this->_rules['byResourceId'] as $resourceIdCurrent => $visitor) { + foreach ($visitor['byRoleId'] as $roleIdCurrent => $rules) { + unset($this->_rules['byResourceId'][$resourceIdCurrent]['byRoleId'][$roleIdCurrent]); + } + } + + return $this; + } + + /** + * Adds a Resource having an identifier unique to the ACL + * + * The $parent parameter may be a reference to, or the string identifier for, + * the existing Resource from which the newly added Resource will inherit. + * + * @param Zend_Acl_Resource_Interface|string $resource + * @param Zend_Acl_Resource_Interface|string $parent + * @throws Zend_Acl_Exception + * @return Zend_Acl Provides a fluent interface + */ + public function addResource($resource, $parent = null) + { + if (is_string($resource)) { + $resource = new Zend_Acl_Resource($resource); + } + + if (!$resource instanceof Zend_Acl_Resource_Interface) { + require_once 'Zend/Acl/Exception.php'; + throw new Zend_Acl_Exception('addResource() expects $resource to be of type Zend_Acl_Resource_Interface'); + } + + $resourceId = $resource->getResourceId(); + + if ($this->has($resourceId)) { + require_once 'Zend/Acl/Exception.php'; + throw new Zend_Acl_Exception("Resource id '$resourceId' already exists in the ACL"); + } + + $resourceParent = null; + + if (null !== $parent) { + try { + if ($parent instanceof Zend_Acl_Resource_Interface) { + $resourceParentId = $parent->getResourceId(); + } else { + $resourceParentId = $parent; + } + $resourceParent = $this->get($resourceParentId); + } catch (Zend_Acl_Exception $e) { + throw new Zend_Acl_Exception("Parent Resource id '$resourceParentId' does not exist", 0, $e); + } + $this->_resources[$resourceParentId]['children'][$resourceId] = $resource; + } + + $this->_resources[$resourceId] = array( + 'instance' => $resource, + 'parent' => $resourceParent, + 'children' => array() + ); + + return $this; + } + + /** + * Adds a Resource having an identifier unique to the ACL + * + * The $parent parameter may be a reference to, or the string identifier for, + * the existing Resource from which the newly added Resource will inherit. + * + * @deprecated in version 1.9.1 and will be available till 2.0. New code + * should use addResource() instead. + * + * @param Zend_Acl_Resource_Interface $resource + * @param Zend_Acl_Resource_Interface|string $parent + * @throws Zend_Acl_Exception + * @return Zend_Acl Provides a fluent interface + */ + public function add(Zend_Acl_Resource_Interface $resource, $parent = null) + { + return $this->addResource($resource, $parent); + } + + /** + * Returns the identified Resource + * + * The $resource parameter can either be a Resource or a Resource identifier. + * + * @param Zend_Acl_Resource_Interface|string $resource + * @throws Zend_Acl_Exception + * @return Zend_Acl_Resource_Interface + */ + public function get($resource) + { + if ($resource instanceof Zend_Acl_Resource_Interface) { + $resourceId = $resource->getResourceId(); + } else { + $resourceId = (string) $resource; + } + + if (!$this->has($resource)) { + require_once 'Zend/Acl/Exception.php'; + throw new Zend_Acl_Exception("Resource '$resourceId' not found"); + } + + return $this->_resources[$resourceId]['instance']; + } + + /** + * Returns true if and only if the Resource exists in the ACL + * + * The $resource parameter can either be a Resource or a Resource identifier. + * + * @param Zend_Acl_Resource_Interface|string $resource + * @return boolean + */ + public function has($resource) + { + if ($resource instanceof Zend_Acl_Resource_Interface) { + $resourceId = $resource->getResourceId(); + } else { + $resourceId = (string) $resource; + } + + return isset($this->_resources[$resourceId]); + } + + /** + * Returns true if and only if $resource inherits from $inherit + * + * Both parameters may be either a Resource or a Resource identifier. If + * $onlyParent is true, then $resource must inherit directly from + * $inherit in order to return true. By default, this method looks + * through the entire inheritance tree to determine whether $resource + * inherits from $inherit through its ancestor Resources. + * + * @param Zend_Acl_Resource_Interface|string $resource + * @param Zend_Acl_Resource_Interface|string $inherit + * @param boolean $onlyParent + * @throws Zend_Acl_Resource_Registry_Exception + * @return boolean + */ + public function inherits($resource, $inherit, $onlyParent = false) + { + try { + $resourceId = $this->get($resource)->getResourceId(); + $inheritId = $this->get($inherit)->getResourceId(); + } catch (Zend_Acl_Exception $e) { + throw new Zend_Acl_Exception($e->getMessage(), $e->getCode(), $e); + } + + if (null !== $this->_resources[$resourceId]['parent']) { + $parentId = $this->_resources[$resourceId]['parent']->getResourceId(); + if ($inheritId === $parentId) { + return true; + } else if ($onlyParent) { + return false; + } + } else { + return false; + } + + while (null !== $this->_resources[$parentId]['parent']) { + $parentId = $this->_resources[$parentId]['parent']->getResourceId(); + if ($inheritId === $parentId) { + return true; + } + } + + return false; + } + + /** + * Removes a Resource and all of its children + * + * The $resource parameter can either be a Resource or a Resource identifier. + * + * @param Zend_Acl_Resource_Interface|string $resource + * @throws Zend_Acl_Exception + * @return Zend_Acl Provides a fluent interface + */ + public function remove($resource) + { + try { + $resourceId = $this->get($resource)->getResourceId(); + } catch (Zend_Acl_Exception $e) { + throw new Zend_Acl_Exception($e->getMessage(), $e->getCode(), $e); + } + + $resourcesRemoved = array($resourceId); + if (null !== ($resourceParent = $this->_resources[$resourceId]['parent'])) { + unset($this->_resources[$resourceParent->getResourceId()]['children'][$resourceId]); + } + foreach ($this->_resources[$resourceId]['children'] as $childId => $child) { + $this->remove($childId); + $resourcesRemoved[] = $childId; + } + + foreach ($resourcesRemoved as $resourceIdRemoved) { + foreach ($this->_rules['byResourceId'] as $resourceIdCurrent => $rules) { + if ($resourceIdRemoved === $resourceIdCurrent) { + unset($this->_rules['byResourceId'][$resourceIdCurrent]); + } + } + } + + unset($this->_resources[$resourceId]); + + return $this; + } + + /** + * Removes all Resources + * + * @return Zend_Acl Provides a fluent interface + */ + public function removeAll() + { + foreach ($this->_resources as $resourceId => $resource) { + foreach ($this->_rules['byResourceId'] as $resourceIdCurrent => $rules) { + if ($resourceId === $resourceIdCurrent) { + unset($this->_rules['byResourceId'][$resourceIdCurrent]); + } + } + } + + $this->_resources = array(); + + return $this; + } + + /** + * Adds an "allow" rule to the ACL + * + * @param Zend_Acl_Role_Interface|string|array $roles + * @param Zend_Acl_Resource_Interface|string|array $resources + * @param string|array $privileges + * @param Zend_Acl_Assert_Interface $assert + * @uses Zend_Acl::setRule() + * @return Zend_Acl Provides a fluent interface + */ + public function allow($roles = null, $resources = null, $privileges = null, Zend_Acl_Assert_Interface $assert = null) + { + return $this->setRule(self::OP_ADD, self::TYPE_ALLOW, $roles, $resources, $privileges, $assert); + } + + /** + * Adds a "deny" rule to the ACL + * + * @param Zend_Acl_Role_Interface|string|array $roles + * @param Zend_Acl_Resource_Interface|string|array $resources + * @param string|array $privileges + * @param Zend_Acl_Assert_Interface $assert + * @uses Zend_Acl::setRule() + * @return Zend_Acl Provides a fluent interface + */ + public function deny($roles = null, $resources = null, $privileges = null, Zend_Acl_Assert_Interface $assert = null) + { + return $this->setRule(self::OP_ADD, self::TYPE_DENY, $roles, $resources, $privileges, $assert); + } + + /** + * Removes "allow" permissions from the ACL + * + * @param Zend_Acl_Role_Interface|string|array $roles + * @param Zend_Acl_Resource_Interface|string|array $resources + * @param string|array $privileges + * @uses Zend_Acl::setRule() + * @return Zend_Acl Provides a fluent interface + */ + public function removeAllow($roles = null, $resources = null, $privileges = null) + { + return $this->setRule(self::OP_REMOVE, self::TYPE_ALLOW, $roles, $resources, $privileges); + } + + /** + * Removes "deny" restrictions from the ACL + * + * @param Zend_Acl_Role_Interface|string|array $roles + * @param Zend_Acl_Resource_Interface|string|array $resources + * @param string|array $privileges + * @uses Zend_Acl::setRule() + * @return Zend_Acl Provides a fluent interface + */ + public function removeDeny($roles = null, $resources = null, $privileges = null) + { + return $this->setRule(self::OP_REMOVE, self::TYPE_DENY, $roles, $resources, $privileges); + } + + /** + * Performs operations on ACL rules + * + * The $operation parameter may be either OP_ADD or OP_REMOVE, depending on whether the + * user wants to add or remove a rule, respectively: + * + * OP_ADD specifics: + * + * A rule is added that would allow one or more Roles access to [certain $privileges + * upon] the specified Resource(s). + * + * OP_REMOVE specifics: + * + * The rule is removed only in the context of the given Roles, Resources, and privileges. + * Existing rules to which the remove operation does not apply would remain in the + * ACL. + * + * The $type parameter may be either TYPE_ALLOW or TYPE_DENY, depending on whether the + * rule is intended to allow or deny permission, respectively. + * + * The $roles and $resources parameters may be references to, or the string identifiers for, + * existing Resources/Roles, or they may be passed as arrays of these - mixing string identifiers + * and objects is ok - to indicate the Resources and Roles to which the rule applies. If either + * $roles or $resources is null, then the rule applies to all Roles or all Resources, respectively. + * Both may be null in order to work with the default rule of the ACL. + * + * The $privileges parameter may be used to further specify that the rule applies only + * to certain privileges upon the Resource(s) in question. This may be specified to be a single + * privilege with a string, and multiple privileges may be specified as an array of strings. + * + * If $assert is provided, then its assert() method must return true in order for + * the rule to apply. If $assert is provided with $roles, $resources, and $privileges all + * equal to null, then a rule having a type of: + * + * TYPE_ALLOW will imply a type of TYPE_DENY, and + * + * TYPE_DENY will imply a type of TYPE_ALLOW + * + * when the rule's assertion fails. This is because the ACL needs to provide expected + * behavior when an assertion upon the default ACL rule fails. + * + * @param string $operation + * @param string $type + * @param Zend_Acl_Role_Interface|string|array $roles + * @param Zend_Acl_Resource_Interface|string|array $resources + * @param string|array $privileges + * @param Zend_Acl_Assert_Interface $assert + * @throws Zend_Acl_Exception + * @uses Zend_Acl_Role_Registry::get() + * @uses Zend_Acl::get() + * @return Zend_Acl Provides a fluent interface + */ + public function setRule($operation, $type, $roles = null, $resources = null, $privileges = null, + Zend_Acl_Assert_Interface $assert = null) + { + // ensure that the rule type is valid; normalize input to uppercase + $type = strtoupper($type); + if (self::TYPE_ALLOW !== $type && self::TYPE_DENY !== $type) { + require_once 'Zend/Acl/Exception.php'; + throw new Zend_Acl_Exception("Unsupported rule type; must be either '" . self::TYPE_ALLOW . "' or '" + . self::TYPE_DENY . "'"); + } + + // ensure that all specified Roles exist; normalize input to array of Role objects or null + if (!is_array($roles)) { + $roles = array($roles); + } else if (0 === count($roles)) { + $roles = array(null); + } + $rolesTemp = $roles; + $roles = array(); + foreach ($rolesTemp as $role) { + if (null !== $role) { + $roles[] = $this->_getRoleRegistry()->get($role); + } else { + $roles[] = null; + } + } + unset($rolesTemp); + + // ensure that all specified Resources exist; normalize input to array of Resource objects or null + if (!is_array($resources)) { + $resources = array($resources); + } else if (0 === count($resources)) { + $resources = array(null); + } + $resourcesTemp = $resources; + $resources = array(); + foreach ($resourcesTemp as $resource) { + if (null !== $resource) { + $resources[] = $this->get($resource); + } else { + $resources[] = null; + } + } + unset($resourcesTemp); + + // normalize privileges to array + if (null === $privileges) { + $privileges = array(); + } else if (!is_array($privileges)) { + $privileges = array($privileges); + } + + switch ($operation) { + + // add to the rules + case self::OP_ADD: + foreach ($resources as $resource) { + foreach ($roles as $role) { + $rules =& $this->_getRules($resource, $role, true); + if (0 === count($privileges)) { + $rules['allPrivileges']['type'] = $type; + $rules['allPrivileges']['assert'] = $assert; + if (!isset($rules['byPrivilegeId'])) { + $rules['byPrivilegeId'] = array(); + } + } else { + foreach ($privileges as $privilege) { + $rules['byPrivilegeId'][$privilege]['type'] = $type; + $rules['byPrivilegeId'][$privilege]['assert'] = $assert; + } + } + } + } + break; + + // remove from the rules + case self::OP_REMOVE: + foreach ($resources as $resource) { + foreach ($roles as $role) { + $rules =& $this->_getRules($resource, $role); + if (null === $rules) { + continue; + } + if (0 === count($privileges)) { + if (null === $resource && null === $role) { + if ($type === $rules['allPrivileges']['type']) { + $rules = array( + 'allPrivileges' => array( + 'type' => self::TYPE_DENY, + 'assert' => null + ), + 'byPrivilegeId' => array() + ); + } + continue; + } + + if (isset($rules['allPrivileges']['type']) && + $type === $rules['allPrivileges']['type']) + { + unset($rules['allPrivileges']); + } + } else { + foreach ($privileges as $privilege) { + if (isset($rules['byPrivilegeId'][$privilege]) && + $type === $rules['byPrivilegeId'][$privilege]['type']) + { + unset($rules['byPrivilegeId'][$privilege]); + } + } + } + } + } + break; + + default: + require_once 'Zend/Acl/Exception.php'; + throw new Zend_Acl_Exception("Unsupported operation; must be either '" . self::OP_ADD . "' or '" + . self::OP_REMOVE . "'"); + } + + return $this; + } + + /** + * Returns true if and only if the Role has access to the Resource + * + * The $role and $resource parameters may be references to, or the string identifiers for, + * an existing Resource and Role combination. + * + * If either $role or $resource is null, then the query applies to all Roles or all Resources, + * respectively. Both may be null to query whether the ACL has a "blacklist" rule + * (allow everything to all). By default, Zend_Acl creates a "whitelist" rule (deny + * everything to all), and this method would return false unless this default has + * been overridden (i.e., by executing $acl->allow()). + * + * If a $privilege is not provided, then this method returns false if and only if the + * Role is denied access to at least one privilege upon the Resource. In other words, this + * method returns true if and only if the Role is allowed all privileges on the Resource. + * + * This method checks Role inheritance using a depth-first traversal of the Role registry. + * The highest priority parent (i.e., the parent most recently added) is checked first, + * and its respective parents are checked similarly before the lower-priority parents of + * the Role are checked. + * + * @param Zend_Acl_Role_Interface|string $role + * @param Zend_Acl_Resource_Interface|string $resource + * @param string $privilege + * @uses Zend_Acl::get() + * @uses Zend_Acl_Role_Registry::get() + * @return boolean + */ + public function isAllowed($role = null, $resource = null, $privilege = null) + { + // reset role & resource to null + $this->_isAllowedRole = null; + $this->_isAllowedResource = null; + $this->_isAllowedPrivilege = null; + + if (null !== $role) { + // keep track of originally called role + $this->_isAllowedRole = $role; + $role = $this->_getRoleRegistry()->get($role); + if (!$this->_isAllowedRole instanceof Zend_Acl_Role_Interface) { + $this->_isAllowedRole = $role; + } + } + + if (null !== $resource) { + // keep track of originally called resource + $this->_isAllowedResource = $resource; + $resource = $this->get($resource); + if (!$this->_isAllowedResource instanceof Zend_Acl_Resource_Interface) { + $this->_isAllowedResource = $resource; + } + } + + if (null === $privilege) { + // query on all privileges + do { + // depth-first search on $role if it is not 'allRoles' pseudo-parent + if (null !== $role && null !== ($result = $this->_roleDFSAllPrivileges($role, $resource, $privilege))) { + return $result; + } + + // look for rule on 'allRoles' psuedo-parent + if (null !== ($rules = $this->_getRules($resource, null))) { + foreach ($rules['byPrivilegeId'] as $privilege => $rule) { + if (self::TYPE_DENY === ($ruleTypeOnePrivilege = $this->_getRuleType($resource, null, $privilege))) { + return false; + } + } + if (null !== ($ruleTypeAllPrivileges = $this->_getRuleType($resource, null, null))) { + return self::TYPE_ALLOW === $ruleTypeAllPrivileges; + } + } + + // try next Resource + $resource = $this->_resources[$resource->getResourceId()]['parent']; + + } while (true); // loop terminates at 'allResources' pseudo-parent + } else { + $this->_isAllowedPrivilege = $privilege; + // query on one privilege + do { + // depth-first search on $role if it is not 'allRoles' pseudo-parent + if (null !== $role && null !== ($result = $this->_roleDFSOnePrivilege($role, $resource, $privilege))) { + return $result; + } + + // look for rule on 'allRoles' pseudo-parent + if (null !== ($ruleType = $this->_getRuleType($resource, null, $privilege))) { + return self::TYPE_ALLOW === $ruleType; + } else if (null !== ($ruleTypeAllPrivileges = $this->_getRuleType($resource, null, null))) { + return self::TYPE_ALLOW === $ruleTypeAllPrivileges; + } + + // try next Resource + $resource = $this->_resources[$resource->getResourceId()]['parent']; + + } while (true); // loop terminates at 'allResources' pseudo-parent + } + } + + /** + * Returns the Role registry for this ACL + * + * If no Role registry has been created yet, a new default Role registry + * is created and returned. + * + * @return Zend_Acl_Role_Registry + */ + protected function _getRoleRegistry() + { + if (null === $this->_roleRegistry) { + $this->_roleRegistry = new Zend_Acl_Role_Registry(); + } + return $this->_roleRegistry; + } + + /** + * Performs a depth-first search of the Role DAG, starting at $role, in order to find a rule + * allowing/denying $role access to all privileges upon $resource + * + * This method returns true if a rule is found and allows access. If a rule exists and denies access, + * then this method returns false. If no applicable rule is found, then this method returns null. + * + * @param Zend_Acl_Role_Interface $role + * @param Zend_Acl_Resource_Interface $resource + * @return boolean|null + */ + protected function _roleDFSAllPrivileges(Zend_Acl_Role_Interface $role, Zend_Acl_Resource_Interface $resource = null) + { + $dfs = array( + 'visited' => array(), + 'stack' => array() + ); + + if (null !== ($result = $this->_roleDFSVisitAllPrivileges($role, $resource, $dfs))) { + return $result; + } + + while (null !== ($role = array_pop($dfs['stack']))) { + if (!isset($dfs['visited'][$role->getRoleId()])) { + if (null !== ($result = $this->_roleDFSVisitAllPrivileges($role, $resource, $dfs))) { + return $result; + } + } + } + + return null; + } + + /** + * Visits an $role in order to look for a rule allowing/denying $role access to all privileges upon $resource + * + * This method returns true if a rule is found and allows access. If a rule exists and denies access, + * then this method returns false. If no applicable rule is found, then this method returns null. + * + * This method is used by the internal depth-first search algorithm and may modify the DFS data structure. + * + * @param Zend_Acl_Role_Interface $role + * @param Zend_Acl_Resource_Interface $resource + * @param array $dfs + * @return boolean|null + * @throws Zend_Acl_Exception + */ + protected function _roleDFSVisitAllPrivileges(Zend_Acl_Role_Interface $role, Zend_Acl_Resource_Interface $resource = null, + &$dfs = null) + { + if (null === $dfs) { + /** + * @see Zend_Acl_Exception + */ + require_once 'Zend/Acl/Exception.php'; + throw new Zend_Acl_Exception('$dfs parameter may not be null'); + } + + if (null !== ($rules = $this->_getRules($resource, $role))) { + foreach ($rules['byPrivilegeId'] as $privilege => $rule) { + if (self::TYPE_DENY === ($ruleTypeOnePrivilege = $this->_getRuleType($resource, $role, $privilege))) { + return false; + } + } + if (null !== ($ruleTypeAllPrivileges = $this->_getRuleType($resource, $role, null))) { + return self::TYPE_ALLOW === $ruleTypeAllPrivileges; + } + } + + $dfs['visited'][$role->getRoleId()] = true; + foreach ($this->_getRoleRegistry()->getParents($role) as $roleParentId => $roleParent) { + $dfs['stack'][] = $roleParent; + } + + return null; + } + + /** + * Performs a depth-first search of the Role DAG, starting at $role, in order to find a rule + * allowing/denying $role access to a $privilege upon $resource + * + * This method returns true if a rule is found and allows access. If a rule exists and denies access, + * then this method returns false. If no applicable rule is found, then this method returns null. + * + * @param Zend_Acl_Role_Interface $role + * @param Zend_Acl_Resource_Interface $resource + * @param string $privilege + * @return boolean|null + * @throws Zend_Acl_Exception + */ + protected function _roleDFSOnePrivilege(Zend_Acl_Role_Interface $role, Zend_Acl_Resource_Interface $resource = null, + $privilege = null) + { + if (null === $privilege) { + /** + * @see Zend_Acl_Exception + */ + require_once 'Zend/Acl/Exception.php'; + throw new Zend_Acl_Exception('$privilege parameter may not be null'); + } + + $dfs = array( + 'visited' => array(), + 'stack' => array() + ); + + if (null !== ($result = $this->_roleDFSVisitOnePrivilege($role, $resource, $privilege, $dfs))) { + return $result; + } + + while (null !== ($role = array_pop($dfs['stack']))) { + if (!isset($dfs['visited'][$role->getRoleId()])) { + if (null !== ($result = $this->_roleDFSVisitOnePrivilege($role, $resource, $privilege, $dfs))) { + return $result; + } + } + } + + return null; + } + + /** + * Visits an $role in order to look for a rule allowing/denying $role access to a $privilege upon $resource + * + * This method returns true if a rule is found and allows access. If a rule exists and denies access, + * then this method returns false. If no applicable rule is found, then this method returns null. + * + * This method is used by the internal depth-first search algorithm and may modify the DFS data structure. + * + * @param Zend_Acl_Role_Interface $role + * @param Zend_Acl_Resource_Interface $resource + * @param string $privilege + * @param array $dfs + * @return boolean|null + * @throws Zend_Acl_Exception + */ + protected function _roleDFSVisitOnePrivilege(Zend_Acl_Role_Interface $role, Zend_Acl_Resource_Interface $resource = null, + $privilege = null, &$dfs = null) + { + if (null === $privilege) { + /** + * @see Zend_Acl_Exception + */ + require_once 'Zend/Acl/Exception.php'; + throw new Zend_Acl_Exception('$privilege parameter may not be null'); + } + + if (null === $dfs) { + /** + * @see Zend_Acl_Exception + */ + require_once 'Zend/Acl/Exception.php'; + throw new Zend_Acl_Exception('$dfs parameter may not be null'); + } + + if (null !== ($ruleTypeOnePrivilege = $this->_getRuleType($resource, $role, $privilege))) { + return self::TYPE_ALLOW === $ruleTypeOnePrivilege; + } else if (null !== ($ruleTypeAllPrivileges = $this->_getRuleType($resource, $role, null))) { + return self::TYPE_ALLOW === $ruleTypeAllPrivileges; + } + + $dfs['visited'][$role->getRoleId()] = true; + foreach ($this->_getRoleRegistry()->getParents($role) as $roleParentId => $roleParent) { + $dfs['stack'][] = $roleParent; + } + + return null; + } + + /** + * Returns the rule type associated with the specified Resource, Role, and privilege + * combination. + * + * If a rule does not exist or its attached assertion fails, which means that + * the rule is not applicable, then this method returns null. Otherwise, the + * rule type applies and is returned as either TYPE_ALLOW or TYPE_DENY. + * + * If $resource or $role is null, then this means that the rule must apply to + * all Resources or Roles, respectively. + * + * If $privilege is null, then the rule must apply to all privileges. + * + * If all three parameters are null, then the default ACL rule type is returned, + * based on whether its assertion method passes. + * + * @param Zend_Acl_Resource_Interface $resource + * @param Zend_Acl_Role_Interface $role + * @param string $privilege + * @return string|null + */ + protected function _getRuleType(Zend_Acl_Resource_Interface $resource = null, Zend_Acl_Role_Interface $role = null, + $privilege = null) + { + // get the rules for the $resource and $role + if (null === ($rules = $this->_getRules($resource, $role))) { + return null; + } + + // follow $privilege + if (null === $privilege) { + if (isset($rules['allPrivileges'])) { + $rule = $rules['allPrivileges']; + } else { + return null; + } + } else if (!isset($rules['byPrivilegeId'][$privilege])) { + return null; + } else { + $rule = $rules['byPrivilegeId'][$privilege]; + } + + // check assertion first + if ($rule['assert']) { + $assertion = $rule['assert']; + $assertionValue = $assertion->assert( + $this, + ($this->_isAllowedRole instanceof Zend_Acl_Role_Interface) ? $this->_isAllowedRole : $role, + ($this->_isAllowedResource instanceof Zend_Acl_Resource_Interface) ? $this->_isAllowedResource : $resource, + $this->_isAllowedPrivilege + ); + } + + if (null === $rule['assert'] || $assertionValue) { + return $rule['type']; + } else if (null !== $resource || null !== $role || null !== $privilege) { + return null; + } else if (self::TYPE_ALLOW === $rule['type']) { + return self::TYPE_DENY; + } else { + return self::TYPE_ALLOW; + } + } + + /** + * Returns the rules associated with a Resource and a Role, or null if no such rules exist + * + * If either $resource or $role is null, this means that the rules returned are for all Resources or all Roles, + * respectively. Both can be null to return the default rule set for all Resources and all Roles. + * + * If the $create parameter is true, then a rule set is first created and then returned to the caller. + * + * @param Zend_Acl_Resource_Interface $resource + * @param Zend_Acl_Role_Interface $role + * @param boolean $create + * @return array|null + */ + protected function &_getRules(Zend_Acl_Resource_Interface $resource = null, Zend_Acl_Role_Interface $role = null, + $create = false) + { + // create a reference to null + $null = null; + $nullRef =& $null; + + // follow $resource + do { + if (null === $resource) { + $visitor =& $this->_rules['allResources']; + break; + } + $resourceId = $resource->getResourceId(); + if (!isset($this->_rules['byResourceId'][$resourceId])) { + if (!$create) { + return $nullRef; + } + $this->_rules['byResourceId'][$resourceId] = array(); + } + $visitor =& $this->_rules['byResourceId'][$resourceId]; + } while (false); + + + // follow $role + if (null === $role) { + if (!isset($visitor['allRoles'])) { + if (!$create) { + return $nullRef; + } + $visitor['allRoles']['byPrivilegeId'] = array(); + } + return $visitor['allRoles']; + } + $roleId = $role->getRoleId(); + if (!isset($visitor['byRoleId'][$roleId])) { + if (!$create) { + return $nullRef; + } + $visitor['byRoleId'][$roleId]['byPrivilegeId'] = array(); + } + return $visitor['byRoleId'][$roleId]; + } + + + /** + * @return array of registered roles (Deprecated) + * @deprecated Deprecated since version 1.10 (December 2009) + */ + public function getRegisteredRoles() + { + trigger_error('The method getRegisteredRoles() was deprecated as of ' + . 'version 1.0, and may be removed. You\'re encouraged ' + . 'to use getRoles() instead.'); + + return $this->_getRoleRegistry()->getRoles(); + } + + /** + * @return array of registered roles + */ + public function getRoles() + { + return array_keys($this->_getRoleRegistry()->getRoles()); + } + + /** + * @return array of registered resources + */ + public function getResources() + { + return array_keys($this->_resources); + } + +} + diff --git a/library/Zend/Acl/Assert/Interface.php b/library/Zend/Acl/Assert/Interface.php new file mode 100644 index 000000000..e456798cd --- /dev/null +++ b/library/Zend/Acl/Assert/Interface.php @@ -0,0 +1,64 @@ +_resourceId = (string) $resourceId; + } + + /** + * Defined by Zend_Acl_Resource_Interface; returns the Resource identifier + * + * @return string + */ + public function getResourceId() + { + return $this->_resourceId; + } + + /** + * Defined by Zend_Acl_Resource_Interface; returns the Resource identifier + * Proxies to getResourceId() + * + * @return string + */ + public function __toString() + { + return $this->getResourceId(); + } +} diff --git a/library/Zend/Acl/Resource/Interface.php b/library/Zend/Acl/Resource/Interface.php new file mode 100644 index 000000000..9720594d6 --- /dev/null +++ b/library/Zend/Acl/Resource/Interface.php @@ -0,0 +1,37 @@ +_roleId = (string) $roleId; + } + + /** + * Defined by Zend_Acl_Role_Interface; returns the Role identifier + * + * @return string + */ + public function getRoleId() + { + return $this->_roleId; + } + + /** + * Defined by Zend_Acl_Role_Interface; returns the Role identifier + * Proxies to getRoleId() + * + * @return string + */ + public function __toString() + { + return $this->getRoleId(); + } +} diff --git a/library/Zend/Acl/Role/Interface.php b/library/Zend/Acl/Role/Interface.php new file mode 100644 index 000000000..65d6d84ff --- /dev/null +++ b/library/Zend/Acl/Role/Interface.php @@ -0,0 +1,37 @@ +getRoleId(); + + if ($this->has($roleId)) { + /** + * @see Zend_Acl_Role_Registry_Exception + */ + require_once 'Zend/Acl/Role/Registry/Exception.php'; + throw new Zend_Acl_Role_Registry_Exception("Role id '$roleId' already exists in the registry"); + } + + $roleParents = array(); + + if (null !== $parents) { + if (!is_array($parents)) { + $parents = array($parents); + } + /** + * @see Zend_Acl_Role_Registry_Exception + */ + require_once 'Zend/Acl/Role/Registry/Exception.php'; + foreach ($parents as $parent) { + try { + if ($parent instanceof Zend_Acl_Role_Interface) { + $roleParentId = $parent->getRoleId(); + } else { + $roleParentId = $parent; + } + $roleParent = $this->get($roleParentId); + } catch (Zend_Acl_Role_Registry_Exception $e) { + throw new Zend_Acl_Role_Registry_Exception("Parent Role id '$roleParentId' does not exist", 0, $e); + } + $roleParents[$roleParentId] = $roleParent; + $this->_roles[$roleParentId]['children'][$roleId] = $role; + } + } + + $this->_roles[$roleId] = array( + 'instance' => $role, + 'parents' => $roleParents, + 'children' => array() + ); + + return $this; + } + + /** + * Returns the identified Role + * + * The $role parameter can either be a Role or a Role identifier. + * + * @param Zend_Acl_Role_Interface|string $role + * @throws Zend_Acl_Role_Registry_Exception + * @return Zend_Acl_Role_Interface + */ + public function get($role) + { + if ($role instanceof Zend_Acl_Role_Interface) { + $roleId = $role->getRoleId(); + } else { + $roleId = (string) $role; + } + + if (!$this->has($role)) { + /** + * @see Zend_Acl_Role_Registry_Exception + */ + require_once 'Zend/Acl/Role/Registry/Exception.php'; + throw new Zend_Acl_Role_Registry_Exception("Role '$roleId' not found"); + } + + return $this->_roles[$roleId]['instance']; + } + + /** + * Returns true if and only if the Role exists in the registry + * + * The $role parameter can either be a Role or a Role identifier. + * + * @param Zend_Acl_Role_Interface|string $role + * @return boolean + */ + public function has($role) + { + if ($role instanceof Zend_Acl_Role_Interface) { + $roleId = $role->getRoleId(); + } else { + $roleId = (string) $role; + } + + return isset($this->_roles[$roleId]); + } + + /** + * Returns an array of an existing Role's parents + * + * The array keys are the identifiers of the parent Roles, and the values are + * the parent Role instances. The parent Roles are ordered in this array by + * ascending priority. The highest priority parent Role, last in the array, + * corresponds with the parent Role most recently added. + * + * If the Role does not have any parents, then an empty array is returned. + * + * @param Zend_Acl_Role_Interface|string $role + * @uses Zend_Acl_Role_Registry::get() + * @return array + */ + public function getParents($role) + { + $roleId = $this->get($role)->getRoleId(); + + return $this->_roles[$roleId]['parents']; + } + + /** + * Returns true if and only if $role inherits from $inherit + * + * Both parameters may be either a Role or a Role identifier. If + * $onlyParents is true, then $role must inherit directly from + * $inherit in order to return true. By default, this method looks + * through the entire inheritance DAG to determine whether $role + * inherits from $inherit through its ancestor Roles. + * + * @param Zend_Acl_Role_Interface|string $role + * @param Zend_Acl_Role_Interface|string $inherit + * @param boolean $onlyParents + * @throws Zend_Acl_Role_Registry_Exception + * @return boolean + */ + public function inherits($role, $inherit, $onlyParents = false) + { + /** + * @see Zend_Acl_Role_Registry_Exception + */ + require_once 'Zend/Acl/Role/Registry/Exception.php'; + try { + $roleId = $this->get($role)->getRoleId(); + $inheritId = $this->get($inherit)->getRoleId(); + } catch (Zend_Acl_Role_Registry_Exception $e) { + throw new Zend_Acl_Role_Registry_Exception($e->getMessage(), $e->getCode(), $e); + } + + $inherits = isset($this->_roles[$roleId]['parents'][$inheritId]); + + if ($inherits || $onlyParents) { + return $inherits; + } + + foreach ($this->_roles[$roleId]['parents'] as $parentId => $parent) { + if ($this->inherits($parentId, $inheritId)) { + return true; + } + } + + return false; + } + + /** + * Removes the Role from the registry + * + * The $role parameter can either be a Role or a Role identifier. + * + * @param Zend_Acl_Role_Interface|string $role + * @throws Zend_Acl_Role_Registry_Exception + * @return Zend_Acl_Role_Registry Provides a fluent interface + */ + public function remove($role) + { + /** + * @see Zend_Acl_Role_Registry_Exception + */ + require_once 'Zend/Acl/Role/Registry/Exception.php'; + try { + $roleId = $this->get($role)->getRoleId(); + } catch (Zend_Acl_Role_Registry_Exception $e) { + throw new Zend_Acl_Role_Registry_Exception($e->getMessage(), $e->getCode(), $e); + } + + foreach ($this->_roles[$roleId]['children'] as $childId => $child) { + unset($this->_roles[$childId]['parents'][$roleId]); + } + foreach ($this->_roles[$roleId]['parents'] as $parentId => $parent) { + unset($this->_roles[$parentId]['children'][$roleId]); + } + + unset($this->_roles[$roleId]); + + return $this; + } + + /** + * Removes all Roles from the registry + * + * @return Zend_Acl_Role_Registry Provides a fluent interface + */ + public function removeAll() + { + $this->_roles = array(); + + return $this; + } + + public function getRoles() + { + return $this->_roles; + } + +} diff --git a/library/Zend/Acl/Role/Registry/Exception.php b/library/Zend/Acl/Role/Registry/Exception.php new file mode 100644 index 000000000..c900fd301 --- /dev/null +++ b/library/Zend/Acl/Role/Registry/Exception.php @@ -0,0 +1,36 @@ +_acl = new Zend_Acl(); + $xml = simplexml_load_file($rolefile); +/* +Roles file format: + + + + + + + + +*/ + foreach($xml->role as $role) { + $this->_acl->addRole(new Zend_Acl_Role((string)$role["id"])); + foreach($role->user as $user) { + $this->_users[(string)$user["name"]] = array("password" => (string)$user["password"], + "role" => (string)$role["id"]); + } + } + } + + /** + * Get ACL with roles from XML file + * + * @return Zend_Acl + */ + public function getAcl() + { + return $this->_acl; + } + + /** + * Perform authentication + * + * @throws Zend_Auth_Adapter_Exception + * @return Zend_Auth_Result + * @see Zend_Auth_Adapter_Interface#authenticate() + */ + public function authenticate() + { + if (empty($this->_username) || + empty($this->_password)) { + /** + * @see Zend_Auth_Adapter_Exception + */ + require_once 'Zend/Auth/Adapter/Exception.php'; + throw new Zend_Auth_Adapter_Exception('Username/password should be set'); + } + + if(!isset($this->_users[$this->_username])) { + return new Zend_Auth_Result(Zend_Auth_Result::FAILURE_IDENTITY_NOT_FOUND, + null, + array('Username not found') + ); + } + + $user = $this->_users[$this->_username]; + if($user["password"] != $this->_password) { + return new Zend_Auth_Result(Zend_Auth_Result::FAILURE_CREDENTIAL_INVALID, + null, + array('Authentication failed') + ); + } + + $id = new stdClass(); + $id->role = $user["role"]; + $id->name = $this->_username; + return new Zend_Auth_Result(Zend_Auth_Result::SUCCESS, $id); + } +} diff --git a/library/Zend/Amf/Adobe/DbInspector.php b/library/Zend/Amf/Adobe/DbInspector.php new file mode 100644 index 000000000..82c3c8d65 --- /dev/null +++ b/library/Zend/Amf/Adobe/DbInspector.php @@ -0,0 +1,103 @@ +describeTable('Pdo_Mysql', + * array( + * 'host' => '127.0.0.1', + * 'username' => 'webuser', + * 'password' => 'xxxxxxxx', + * 'dbname' => 'test' + * ), + * 'mytable' + * ); + * + * @param string $dbType Database adapter type for Zend_Db + * @param array|object $dbDescription Adapter-specific connection settings + * @param string $tableName Table name + * @return array Table description + * @see Zend_Db::describeTable() + * @see Zend_Db::factory() + */ + public function describeTable($dbType, $dbDescription, $tableName) + { + $db = $this->_connect($dbType, $dbDescription); + return $db->describeTable($tableName); + } + + /** + * Test database connection + * + * @param string $dbType Database adapter type for Zend_Db + * @param array|object $dbDescription Adapter-specific connection settings + * @return bool + * @see Zend_Db::factory() + */ + public function connect($dbType, $dbDescription) + { + $db = $this->_connect($dbType, $dbDescription); + $db->listTables(); + return true; + } + + /** + * Get the list of database tables + * + * @param string $dbType Database adapter type for Zend_Db + * @param array|object $dbDescription Adapter-specific connection settings + * @return array List of the tables + */ + public function getTables($dbType, $dbDescription) + { + $db = $this->_connect($dbType, $dbDescription); + return $db->listTables(); + } +} diff --git a/library/Zend/Amf/Adobe/Introspector.php b/library/Zend/Amf/Adobe/Introspector.php new file mode 100644 index 000000000..0e0268970 --- /dev/null +++ b/library/Zend/Amf/Adobe/Introspector.php @@ -0,0 +1,313 @@ +_xml = new DOMDocument('1.0', 'utf-8'); + } + + /** + * Create XML definition on an AMF service class + * + * @param string $serviceClass Service class name + * @param array $options invocation options + * @return string XML with service class introspection + */ + public function introspect($serviceClass, $options = array()) + { + $this->_options = $options; + + if (strpbrk($serviceClass, '\\/<>')) { + return $this->_returnError('Invalid service name'); + } + + // Transform com.foo.Bar into com_foo_Bar + $serviceClass = str_replace('.' , '_', $serviceClass); + + // Introspect! + if (!class_exists($serviceClass)) { + require_once 'Zend/Loader.php'; + Zend_Loader::loadClass($serviceClass, $this->_getServicePath()); + } + + $serv = $this->_xml->createElement('service-description'); + $serv->setAttribute('xmlns', 'http://ns.adobe.com/flex/service-description/2008'); + + $this->_types = $this->_xml->createElement('types'); + $this->_ops = $this->_xml->createElement('operations'); + + $r = Zend_Server_Reflection::reflectClass($serviceClass); + $this->_addService($r, $this->_ops); + + $serv->appendChild($this->_types); + $serv->appendChild($this->_ops); + $this->_xml->appendChild($serv); + + return $this->_xml->saveXML(); + } + + /** + * Authentication handler + * + * @param Zend_Acl $acl + * @return unknown_type + */ + public function initAcl(Zend_Acl $acl) + { + return false; // we do not need auth for this class + } + + /** + * Generate map of public class attributes + * + * @param string $typename type name + * @param DOMElement $typexml target XML element + * @return void + */ + protected function _addClassAttributes($typename, DOMElement $typexml) + { + // Do not try to autoload here because _phpTypeToAS should + // have already attempted to load this class + if (!class_exists($typename, false)) { + return; + } + + $rc = new Zend_Reflection_Class($typename); + foreach ($rc->getProperties() as $prop) { + if (!$prop->isPublic()) { + continue; + } + + $propxml = $this->_xml->createElement('property'); + $propxml->setAttribute('name', $prop->getName()); + + $type = $this->_registerType($this->_getPropertyType($prop)); + $propxml->setAttribute('type', $type); + + $typexml->appendChild($propxml); + } + } + + /** + * Build XML service description from reflection class + * + * @param Zend_Server_Reflection_Class $refclass + * @param DOMElement $target target XML element + * @return void + */ + protected function _addService(Zend_Server_Reflection_Class $refclass, DOMElement $target) + { + foreach ($refclass->getMethods() as $method) { + if (!$method->isPublic() + || $method->isConstructor() + || ('__' == substr($method->name, 0, 2)) + ) { + continue; + } + + foreach ($method->getPrototypes() as $proto) { + $op = $this->_xml->createElement('operation'); + $op->setAttribute('name', $method->getName()); + + $rettype = $this->_registerType($proto->getReturnType()); + $op->setAttribute('returnType', $rettype); + + foreach ($proto->getParameters() as $param) { + $arg = $this->_xml->createElement('argument'); + $arg->setAttribute('name', $param->getName()); + + $type = $param->getType(); + if ($type == 'mixed' && ($pclass = $param->getClass())) { + $type = $pclass->getName(); + } + + $ptype = $this->_registerType($type); + $arg->setAttribute('type', $ptype); + + if($param->isDefaultValueAvailable()) { + $arg->setAttribute('defaultvalue', $param->getDefaultValue()); + } + + $op->appendChild($arg); + } + + $target->appendChild($op); + } + } + } + + /** + * Extract type of the property from DocBlock + * + * @param Zend_Reflection_Property $prop reflection property object + * @return string Property type + */ + protected function _getPropertyType(Zend_Reflection_Property $prop) + { + $docBlock = $prop->getDocComment(); + + if (!$docBlock) { + return 'Unknown'; + } + + if (!$docBlock->hasTag('var')) { + return 'Unknown'; + } + + $tag = $docBlock->getTag('var'); + return trim($tag->getDescription()); + } + + /** + * Get the array of service directories + * + * @return array Service class directories + */ + protected function _getServicePath() + { + if (isset($this->_options['server'])) { + return $this->_options['server']->getDirectory(); + } + + if (isset($this->_options['directories'])) { + return $this->_options['directories']; + } + + return array(); + } + + /** + * Map from PHP type name to AS type name + * + * @param string $typename PHP type name + * @return string AS type name + */ + protected function _phpTypeToAS($typename) + { + if (class_exists($typename)) { + $vars = get_class_vars($typename); + + if (isset($vars['_explicitType'])) { + return $vars['_explicitType']; + } + } + + if (false !== ($asname = Zend_Amf_Parse_TypeLoader::getMappedClassName($typename))) { + return $asname; + } + + return $typename; + } + + /** + * Register new type on the system + * + * @param string $typename type name + * @return string New type name + */ + protected function _registerType($typename) + { + // Known type - return its AS name + if (isset($this->_typesMap[$typename])) { + return $this->_typesMap[$typename]; + } + + // Standard types + if (in_array($typename, array('void', 'null', 'mixed', 'unknown_type'))) { + return 'Unknown'; + } + + if (in_array($typename, array('int', 'integer', 'bool', 'boolean', 'float', 'string', 'object', 'Unknown', 'stdClass', 'array'))) { + return $typename; + } + + // Resolve and store AS name + $asTypeName = $this->_phpTypeToAS($typename); + $this->_typesMap[$typename] = $asTypeName; + + // Create element for the name + $typeEl = $this->_xml->createElement('type'); + $typeEl->setAttribute('name', $asTypeName); + $this->_addClassAttributes($typename, $typeEl); + $this->_types->appendChild($typeEl); + + return $asTypeName; + } + + /** + * Return error with error message + * + * @param string $msg Error message + * @return string + */ + protected function _returnError($msg) + { + return 'ERROR: $msg'; + } +} diff --git a/library/Zend/Amf/Auth/Abstract.php b/library/Zend/Amf/Auth/Abstract.php new file mode 100644 index 000000000..227e8301f --- /dev/null +++ b/library/Zend/Amf/Auth/Abstract.php @@ -0,0 +1,42 @@ +_username = $username; + $this->_password = $password; + } +} diff --git a/library/Zend/Amf/Constants.php b/library/Zend/Amf/Constants.php new file mode 100644 index 000000000..f3bc4ee9c --- /dev/null +++ b/library/Zend/Amf/Constants.php @@ -0,0 +1,87 @@ +_stream->readByte(); + } + + switch($typeMarker) { + // number + case Zend_Amf_Constants::AMF0_NUMBER: + return $this->_stream->readDouble(); + + // boolean + case Zend_Amf_Constants::AMF0_BOOLEAN: + return (boolean) $this->_stream->readByte(); + + // string + case Zend_Amf_Constants::AMF0_STRING: + return $this->_stream->readUTF(); + + // object + case Zend_Amf_Constants::AMF0_OBJECT: + return $this->readObject(); + + // null + case Zend_Amf_Constants::AMF0_NULL: + return null; + + // undefined + case Zend_Amf_Constants::AMF0_UNDEFINED: + return null; + + // Circular references are returned here + case Zend_Amf_Constants::AMF0_REFERENCE: + return $this->readReference(); + + // mixed array with numeric and string keys + case Zend_Amf_Constants::AMF0_MIXEDARRAY: + return $this->readMixedArray(); + + // array + case Zend_Amf_Constants::AMF0_ARRAY: + return $this->readArray(); + + // date + case Zend_Amf_Constants::AMF0_DATE: + return $this->readDate(); + + // longString strlen(string) > 2^16 + case Zend_Amf_Constants::AMF0_LONGSTRING: + return $this->_stream->readLongUTF(); + + //internal AS object, not supported + case Zend_Amf_Constants::AMF0_UNSUPPORTED: + return null; + + // XML + case Zend_Amf_Constants::AMF0_XML: + return $this->readXmlString(); + + // typed object ie Custom Class + case Zend_Amf_Constants::AMF0_TYPEDOBJECT: + return $this->readTypedObject(); + + //AMF3-specific + case Zend_Amf_Constants::AMF0_AMF3: + return $this->readAmf3TypeMarker(); + + default: + require_once 'Zend/Amf/Exception.php'; + throw new Zend_Amf_Exception('Unsupported marker type: ' . $typeMarker); + } + } + + /** + * Read AMF objects and convert to PHP objects + * + * Read the name value pair objects form the php message and convert them to + * a php object class. + * + * Called when the marker type is 3. + * + * @param array|null $object + * @return object + */ + public function readObject($object = null) + { + if ($object === null) { + $object = array(); + } + + while (true) { + $key = $this->_stream->readUTF(); + $typeMarker = $this->_stream->readByte(); + if ($typeMarker != Zend_Amf_Constants::AMF0_OBJECTTERM ){ + //Recursivly call readTypeMarker to get the types of properties in the object + $object[$key] = $this->readTypeMarker($typeMarker); + } else { + //encountered AMF object terminator + break; + } + } + $this->_reference[] = $object; + return (object) $object; + } + + /** + * Read reference objects + * + * Used to gain access to the private array of reference objects. + * Called when marker type is 7. + * + * @return object + * @throws Zend_Amf_Exception for invalid reference keys + */ + public function readReference() + { + $key = $this->_stream->readInt(); + if (!array_key_exists($key, $this->_reference)) { + require_once 'Zend/Amf/Exception.php'; + throw new Zend_Amf_Exception('Invalid reference key: '. $key); + } + return $this->_reference[$key]; + } + + /** + * Reads an array with numeric and string indexes. + * + * Called when marker type is 8 + * + * @todo As of Flash Player 9 there is not support for mixed typed arrays + * so we handle this as an object. With the introduction of vectors + * in Flash Player 10 this may need to be reconsidered. + * @return array + */ + public function readMixedArray() + { + $length = $this->_stream->readLong(); + return $this->readObject(); + } + + /** + * Converts numerically indexed actiosncript arrays into php arrays. + * + * Called when marker type is 10 + * + * @return array + */ + public function readArray() + { + $length = $this->_stream->readLong(); + $array = array(); + while ($length--) { + $array[] = $this->readTypeMarker(); + } + return $array; + } + + /** + * Convert AS Date to Zend_Date + * + * @return Zend_Date + */ + public function readDate() + { + // get the unix time stamp. Not sure why ActionScript does not use + // milliseconds + $timestamp = floor($this->_stream->readDouble() / 1000); + + // The timezone offset is never returned to the server; it is always 0, + // so read and ignore. + $offset = $this->_stream->readInt(); + + require_once 'Zend/Date.php'; + $date = new Zend_Date($timestamp); + return $date; + } + + /** + * Convert XML to SimpleXml + * If user wants DomDocument they can use dom_import_simplexml + * + * @return SimpleXml Object + */ + public function readXmlString() + { + $string = $this->_stream->readLongUTF(); + return simplexml_load_string($string); + } + + /** + * Read Class that is to be mapped to a server class. + * + * Commonly used for Value Objects on the server + * + * @todo implement Typed Class mapping + * @return object|array + * @throws Zend_Amf_Exception if unable to load type + */ + public function readTypedObject() + { + require_once 'Zend/Amf/Parse/TypeLoader.php'; + // get the remote class name + $className = $this->_stream->readUTF(); + $loader = Zend_Amf_Parse_TypeLoader::loadType($className); + $returnObject = new $loader(); + $properties = get_object_vars($this->readObject()); + foreach($properties as $key=>$value) { + if($key) { + $returnObject->$key = $value; + } + } + if($returnObject instanceof Zend_Amf_Value_Messaging_ArrayCollection) { + $returnObject = get_object_vars($returnObject); + } + return $returnObject; + } + + /** + * AMF3 data type encountered load AMF3 Deserializer to handle + * type markers. + * + * @return string + */ + public function readAmf3TypeMarker() + { + require_once 'Zend/Amf/Parse/Amf3/Deserializer.php'; + $deserializer = new Zend_Amf_Parse_Amf3_Deserializer($this->_stream); + $this->_objectEncoding = Zend_Amf_Constants::AMF3_OBJECT_ENCODING; + return $deserializer->readTypeMarker(); + } + + /** + * Return the object encoding to check if an AMF3 object + * is going to be return. + * + * @return int + */ + public function getObjectEncoding() + { + return $this->_objectEncoding; + } +} diff --git a/library/Zend/Amf/Parse/Amf0/Serializer.php b/library/Zend/Amf/Parse/Amf0/Serializer.php new file mode 100644 index 000000000..aea853da1 --- /dev/null +++ b/library/Zend/Amf/Parse/Amf0/Serializer.php @@ -0,0 +1,349 @@ +writeObjectReference($data, $markerType) ) { + + // Write the Type Marker to denote the following action script data type + $this->_stream->writeByte($markerType); + switch($markerType) { + case Zend_Amf_Constants::AMF0_NUMBER: + $this->_stream->writeDouble($data); + break; + case Zend_Amf_Constants::AMF0_BOOLEAN: + $this->_stream->writeByte($data); + break; + case Zend_Amf_Constants::AMF0_STRING: + $this->_stream->writeUTF($data); + break; + case Zend_Amf_Constants::AMF0_OBJECT: + $this->writeObject($data); + break; + case Zend_Amf_Constants::AMF0_NULL: + break; + case Zend_Amf_Constants::AMF0_REFERENCE: + $this->_stream->writeInt($data); + break; + case Zend_Amf_Constants::AMF0_MIXEDARRAY: + // Write length of numeric keys as zero. + $this->_stream->writeLong(0); + $this->writeObject($data); + break; + case Zend_Amf_Constants::AMF0_ARRAY: + $this->writeArray($data); + break; + case Zend_Amf_Constants::AMF0_DATE: + $this->writeDate($data); + break; + case Zend_Amf_Constants::AMF0_LONGSTRING: + $this->_stream->writeLongUTF($data); + break; + case Zend_Amf_Constants::AMF0_TYPEDOBJECT: + $this->writeTypedObject($data); + break; + case Zend_Amf_Constants::AMF0_AMF3: + $this->writeAmf3TypeMarker($data); + break; + default: + require_once 'Zend/Amf/Exception.php'; + throw new Zend_Amf_Exception("Unknown Type Marker: " . $markerType); + } + } + } else { + if(is_resource($data)) { + $data = Zend_Amf_Parse_TypeLoader::handleResource($data); + } + switch (true) { + case (is_int($data) || is_float($data)): + $markerType = Zend_Amf_Constants::AMF0_NUMBER; + break; + case (is_bool($data)): + $markerType = Zend_Amf_Constants::AMF0_BOOLEAN; + break; + case (is_string($data) && (strlen($data) > 65536)): + $markerType = Zend_Amf_Constants::AMF0_LONGSTRING; + break; + case (is_string($data)): + $markerType = Zend_Amf_Constants::AMF0_STRING; + break; + case (is_object($data)): + if (($data instanceof DateTime) || ($data instanceof Zend_Date)) { + $markerType = Zend_Amf_Constants::AMF0_DATE; + } else { + + if($className = $this->getClassName($data)){ + //Object is a Typed object set classname + $markerType = Zend_Amf_Constants::AMF0_TYPEDOBJECT; + $this->_className = $className; + } else { + // Object is a generic classname + $markerType = Zend_Amf_Constants::AMF0_OBJECT; + } + break; + } + break; + case (null === $data): + $markerType = Zend_Amf_Constants::AMF0_NULL; + break; + case (is_array($data)): + // check if it is an associative array + $i = 0; + foreach (array_keys($data) as $key) { + // check if it contains non-integer keys + if (!is_numeric($key) || intval($key) != $key) { + $markerType = Zend_Amf_Constants::AMF0_OBJECT; + break; + // check if it is a sparse indexed array + } else if ($key != $i) { + $markerType = Zend_Amf_Constants::AMF0_MIXEDARRAY; + break; + } + $i++; + } + // Dealing with a standard numeric array + if(!$markerType){ + $markerType = Zend_Amf_Constants::AMF0_ARRAY; + break; + } + break; + default: + require_once 'Zend/Amf/Exception.php'; + throw new Zend_Amf_Exception('Unsupported data type: ' . gettype($data)); + } + + $this->writeTypeMarker($data, $markerType); + } + return $this; + } + + /** + * Check if the given object is in the reference table, write the reference if it exists, + * otherwise add the object to the reference table + * + * @param mixed $object object to check for reference + * @param $markerType AMF type of the object to write + * @return Boolean true, if the reference was written, false otherwise + */ + protected function writeObjectReference($object, $markerType){ + if( $markerType == Zend_Amf_Constants::AMF0_OBJECT || + $markerType == Zend_Amf_Constants::AMF0_MIXEDARRAY || + $markerType == Zend_Amf_Constants::AMF0_ARRAY || + $markerType == Zend_Amf_Constants::AMF0_TYPEDOBJECT ) { + + $ref = array_search($object, $this->_referenceObjects,true); + //handle object reference + if($ref !== false){ + $this->writeTypeMarker($ref,Zend_Amf_Constants::AMF0_REFERENCE); + return true; + } + + $this->_referenceObjects[] = $object; + } + + return false; + } + + /** + * Write a PHP array with string or mixed keys. + * + * @param object $data + * @return Zend_Amf_Parse_Amf0_Serializer + */ + public function writeObject($object) + { + // Loop each element and write the name of the property. + foreach ($object as $key => $value) { + // skip variables starting with an _ private transient + if( $key[0] == "_") continue; + $this->_stream->writeUTF($key); + $this->writeTypeMarker($value); + } + + // Write the end object flag + $this->_stream->writeInt(0); + $this->_stream->writeByte(Zend_Amf_Constants::AMF0_OBJECTTERM); + return $this; + } + + /** + * Write a standard numeric array to the output stream. If a mixed array + * is encountered call writeTypeMarker with mixed array. + * + * @param array $array + * @return Zend_Amf_Parse_Amf0_Serializer + */ + public function writeArray($array) + { + $length = count($array); + if (!$length < 0) { + // write the length of the array + $this->_stream->writeLong(0); + } else { + // Write the length of the numeric array + $this->_stream->writeLong($length); + for ($i=0; $i<$length; $i++) { + $value = isset($array[$i]) ? $array[$i] : null; + $this->writeTypeMarker($value); + } + } + return $this; + } + + /** + * Convert the DateTime into an AMF Date + * + * @param DateTime|Zend_Date $data + * @return Zend_Amf_Parse_Amf0_Serializer + */ + public function writeDate($data) + { + if ($data instanceof DateTime) { + $dateString = $data->format('U'); + } elseif ($data instanceof Zend_Date) { + $dateString = $data->toString('U'); + } else { + require_once 'Zend/Amf/Exception.php'; + throw new Zend_Amf_Exception('Invalid date specified; must be a DateTime or Zend_Date object'); + } + $dateString *= 1000; + + // Make the conversion and remove milliseconds. + $this->_stream->writeDouble($dateString); + + // Flash does not respect timezone but requires it. + $this->_stream->writeInt(0); + + return $this; + } + + /** + * Write a class mapped object to the output stream. + * + * @param object $data + * @return Zend_Amf_Parse_Amf0_Serializer + */ + public function writeTypedObject($data) + { + $this->_stream->writeUTF($this->_className); + $this->writeObject($data); + return $this; + } + + /** + * Encountered and AMF3 Type Marker use AMF3 serializer. Once AMF3 is + * encountered it will not return to AMf0. + * + * @param string $data + * @return Zend_Amf_Parse_Amf0_Serializer + */ + public function writeAmf3TypeMarker($data) + { + require_once 'Zend/Amf/Parse/Amf3/Serializer.php'; + $serializer = new Zend_Amf_Parse_Amf3_Serializer($this->_stream); + $serializer->writeTypeMarker($data); + return $this; + } + + /** + * Find if the class name is a class mapped name and return the + * respective classname if it is. + * + * @param object $object + * @return false|string $className + */ + protected function getClassName($object) + { + require_once 'Zend/Amf/Parse/TypeLoader.php'; + //Check to see if the object is a typed object and we need to change + $className = ''; + switch (true) { + // the return class mapped name back to actionscript class name. + case Zend_Amf_Parse_TypeLoader::getMappedClassName(get_class($object)): + $className = Zend_Amf_Parse_TypeLoader::getMappedClassName(get_class($object)); + break; + // Check to see if the user has defined an explicit Action Script type. + case isset($object->_explicitType): + $className = $object->_explicitType; + break; + // Check if user has defined a method for accessing the Action Script type + case method_exists($object, 'getASClassName'): + $className = $object->getASClassName(); + break; + // No return class name is set make it a generic object + case ($object instanceof stdClass): + $className = ''; + break; + // By default, use object's class name + default: + $className = get_class($object); + break; + } + if(!$className == '') { + return $className; + } else { + return false; + } + } +} diff --git a/library/Zend/Amf/Parse/Amf3/Deserializer.php b/library/Zend/Amf/Parse/Amf3/Deserializer.php new file mode 100644 index 000000000..e974173f8 --- /dev/null +++ b/library/Zend/Amf/Parse/Amf3/Deserializer.php @@ -0,0 +1,424 @@ +_stream->readByte(); + } + + switch($typeMarker) { + case Zend_Amf_Constants::AMF3_UNDEFINED: + return null; + case Zend_Amf_Constants::AMF3_NULL: + return null; + case Zend_Amf_Constants::AMF3_BOOLEAN_FALSE: + return false; + case Zend_Amf_Constants::AMF3_BOOLEAN_TRUE: + return true; + case Zend_Amf_Constants::AMF3_INTEGER: + return $this->readInteger(); + case Zend_Amf_Constants::AMF3_NUMBER: + return $this->_stream->readDouble(); + case Zend_Amf_Constants::AMF3_STRING: + return $this->readString(); + case Zend_Amf_Constants::AMF3_DATE: + return $this->readDate(); + case Zend_Amf_Constants::AMF3_ARRAY: + return $this->readArray(); + case Zend_Amf_Constants::AMF3_OBJECT: + return $this->readObject(); + case Zend_Amf_Constants::AMF3_XML: + case Zend_Amf_Constants::AMF3_XMLSTRING: + return $this->readXmlString(); + case Zend_Amf_Constants::AMF3_BYTEARRAY: + return $this->readString(); + default: + require_once 'Zend/Amf/Exception.php'; + throw new Zend_Amf_Exception('Unsupported type marker: ' . $typeMarker); + } + } + + /** + * Read and deserialize an integer + * + * AMF 3 represents smaller integers with fewer bytes using the most + * significant bit of each byte. The worst case uses 32-bits + * to represent a 29-bit number, which is what we would have + * done with no compression. + * - 0x00000000 - 0x0000007F : 0xxxxxxx + * - 0x00000080 - 0x00003FFF : 1xxxxxxx 0xxxxxxx + * - 0x00004000 - 0x001FFFFF : 1xxxxxxx 1xxxxxxx 0xxxxxxx + * - 0x00200000 - 0x3FFFFFFF : 1xxxxxxx 1xxxxxxx 1xxxxxxx xxxxxxxx + * - 0x40000000 - 0xFFFFFFFF : throw range exception + * + * 0x04 -> integer type code, followed by up to 4 bytes of data. + * + * Parsing integers on OSFlash for the AMF3 integer data format: + * @link http://osflash.org/amf3/parsing_integers + * @return int|float + */ + public function readInteger() + { + $count = 1; + $intReference = $this->_stream->readByte(); + $result = 0; + while ((($intReference & 0x80) != 0) && $count < 4) { + $result <<= 7; + $result |= ($intReference & 0x7f); + $intReference = $this->_stream->readByte(); + $count++; + } + if ($count < 4) { + $result <<= 7; + $result |= $intReference; + } else { + // Use all 8 bits from the 4th byte + $result <<= 8; + $result |= $intReference; + + // Check if the integer should be negative + if (($result & 0x10000000) != 0) { + //and extend the sign bit + $result |= ~0xFFFFFFF; + } + } + return $result; + } + + /** + * Read and deserialize a string + * + * Strings can be sent as a reference to a previously + * occurring String by using an index to the implicit string reference table. + * Strings are encoding using UTF-8 - however the header may either + * describe a string literal or a string reference. + * + * - string = 0x06 string-data + * - string-data = integer-data [ modified-utf-8 ] + * - modified-utf-8 = *OCTET + * + * @return String + */ + public function readString() + { + $stringReference = $this->readInteger(); + + //Check if this is a reference string + if (($stringReference & 0x01) == 0) { + // reference string + $stringReference = $stringReference >> 1; + if ($stringReference >= count($this->_referenceStrings)) { + require_once 'Zend/Amf/Exception.php'; + throw new Zend_Amf_Exception('Undefined string reference: ' . $stringReference); + } + // reference string found + return $this->_referenceStrings[$stringReference]; + } + + $length = $stringReference >> 1; + if ($length) { + $string = $this->_stream->readBytes($length); + $this->_referenceStrings[] = $string; + } else { + $string = ""; + } + return $string; + } + + /** + * Read and deserialize a date + * + * Data is the number of milliseconds elapsed since the epoch + * of midnight, 1st Jan 1970 in the UTC time zone. + * Local time zone information is not sent to flash. + * + * - date = 0x08 integer-data [ number-data ] + * + * @return Zend_Date + */ + public function readDate() + { + $dateReference = $this->readInteger(); + if (($dateReference & 0x01) == 0) { + $dateReference = $dateReference >> 1; + if ($dateReference>=count($this->_referenceObjects)) { + require_once 'Zend/Amf/Exception.php'; + throw new Zend_Amf_Exception('Undefined date reference: ' . $dateReference); + } + return $this->_referenceObjects[$dateReference]; + } + + $timestamp = floor($this->_stream->readDouble() / 1000); + + require_once 'Zend/Date.php'; + $dateTime = new Zend_Date((int) $timestamp); + $this->_referenceObjects[] = $dateTime; + return $dateTime; + } + + /** + * Read amf array to PHP array + * + * - array = 0x09 integer-data ( [ 1OCTET *amf3-data ] | [OCTET *amf3-data 1] | [ OCTET *amf-data ] ) + * + * @return array + */ + public function readArray() + { + $arrayReference = $this->readInteger(); + if (($arrayReference & 0x01)==0){ + $arrayReference = $arrayReference >> 1; + if ($arrayReference>=count($this->_referenceObjects)) { + require_once 'Zend/Amf/Exception.php'; + throw new Zend_Amf_Exception('Unknow array reference: ' . $arrayReference); + } + return $this->_referenceObjects[$arrayReference]; + } + + // Create a holder for the array in the reference list + $data = array(); + $this->_referenceObjects[] =& $data; + $key = $this->readString(); + + // Iterating for string based keys. + while ($key != '') { + $data[$key] = $this->readTypeMarker(); + $key = $this->readString(); + } + + $arrayReference = $arrayReference >>1; + + //We have a dense array + for ($i=0; $i < $arrayReference; $i++) { + $data[] = $this->readTypeMarker(); + } + + return $data; + } + + /** + * Read an object from the AMF stream and convert it into a PHP object + * + * @todo Rather than using an array of traitsInfo create Zend_Amf_Value_TraitsInfo + * @return object|array + */ + public function readObject() + { + $traitsInfo = $this->readInteger(); + $storedObject = ($traitsInfo & 0x01)==0; + $traitsInfo = $traitsInfo >> 1; + + // Check if the Object is in the stored Objects reference table + if ($storedObject) { + $ref = $traitsInfo; + if (!isset($this->_referenceObjects[$ref])) { + require_once 'Zend/Amf/Exception.php'; + throw new Zend_Amf_Exception('Unknown Object reference: ' . $ref); + } + $returnObject = $this->_referenceObjects[$ref]; + } else { + // Check if the Object is in the stored Definitions reference table + $storedClass = ($traitsInfo & 0x01) == 0; + $traitsInfo = $traitsInfo >> 1; + if ($storedClass) { + $ref = $traitsInfo; + if (!isset($this->_referenceDefinitions[$ref])) { + require_once 'Zend/Amf/Exception.php'; + throw new Zend_Amf_Exception('Unknows Definition reference: '. $ref); + } + // Populate the reference attributes + $className = $this->_referenceDefinitions[$ref]['className']; + $encoding = $this->_referenceDefinitions[$ref]['encoding']; + $propertyNames = $this->_referenceDefinitions[$ref]['propertyNames']; + } else { + // The class was not in the reference tables. Start reading rawdata to build traits. + // Create a traits table. Zend_Amf_Value_TraitsInfo would be ideal + $className = $this->readString(); + $encoding = $traitsInfo & 0x03; + $propertyNames = array(); + $traitsInfo = $traitsInfo >> 2; + } + + // We now have the object traits defined in variables. Time to go to work: + if (!$className) { + // No class name generic object + $returnObject = new stdClass(); + } else { + // Defined object + // Typed object lookup against registered classname maps + if ($loader = Zend_Amf_Parse_TypeLoader::loadType($className)) { + $returnObject = new $loader(); + } else { + //user defined typed object + require_once 'Zend/Amf/Exception.php'; + throw new Zend_Amf_Exception('Typed object not found: '. $className . ' '); + } + } + + // Add the Object to the reference table + $this->_referenceObjects[] = $returnObject; + + $properties = array(); // clear value + // Check encoding types for additional processing. + switch ($encoding) { + case (Zend_Amf_Constants::ET_EXTERNAL): + // Externalizable object such as {ArrayCollection} and {ObjectProxy} + if (!$storedClass) { + $this->_referenceDefinitions[] = array( + 'className' => $className, + 'encoding' => $encoding, + 'propertyNames' => $propertyNames, + ); + } + $returnObject->externalizedData = $this->readTypeMarker(); + break; + case (Zend_Amf_Constants::ET_DYNAMIC): + // used for Name-value encoding + if (!$storedClass) { + $this->_referenceDefinitions[] = array( + 'className' => $className, + 'encoding' => $encoding, + 'propertyNames' => $propertyNames, + ); + } + // not a reference object read name value properties from byte stream + do { + $property = $this->readString(); + if ($property != "") { + $propertyNames[] = $property; + $properties[$property] = $this->readTypeMarker(); + } + } while ($property !=""); + break; + default: + // basic property list object. + if (!$storedClass) { + $count = $traitsInfo; // Number of properties in the list + for($i=0; $i< $count; $i++) { + $propertyNames[] = $this->readString(); + } + // Add a reference to the class. + $this->_referenceDefinitions[] = array( + 'className' => $className, + 'encoding' => $encoding, + 'propertyNames' => $propertyNames, + ); + } + foreach ($propertyNames as $property) { + $properties[$property] = $this->readTypeMarker(); + } + break; + } + + // Add properties back to the return object. + foreach($properties as $key=>$value) { + if($key) { + $returnObject->$key = $value; + } + } + + + } + + if($returnObject instanceof Zend_Amf_Value_Messaging_ArrayCollection) { + if(isset($returnObject->externalizedData)) { + $returnObject = $returnObject->externalizedData; + } else { + $returnObject = get_object_vars($returnObject); + } + } + + return $returnObject; + } + + /** + * Convert XML to SimpleXml + * If user wants DomDocument they can use dom_import_simplexml + * + * @return SimpleXml Object + */ + public function readXmlString() + { + $xmlReference = $this->readInteger(); + $length = $xmlReference >> 1; + $string = $this->_stream->readBytes($length); + return simplexml_load_string($string); + } +} diff --git a/library/Zend/Amf/Parse/Amf3/Serializer.php b/library/Zend/Amf/Parse/Amf3/Serializer.php new file mode 100644 index 000000000..031ce7c4f --- /dev/null +++ b/library/Zend/Amf/Parse/Amf3/Serializer.php @@ -0,0 +1,507 @@ +_stream->writeByte($markerType); + + switch ($markerType) { + case Zend_Amf_Constants::AMF3_NULL: + break; + case Zend_Amf_Constants::AMF3_BOOLEAN_FALSE: + break; + case Zend_Amf_Constants::AMF3_BOOLEAN_TRUE: + break; + case Zend_Amf_Constants::AMF3_INTEGER: + $this->writeInteger($data); + break; + case Zend_Amf_Constants::AMF3_NUMBER: + $this->_stream->writeDouble($data); + break; + case Zend_Amf_Constants::AMF3_STRING: + $this->writeString($data); + break; + case Zend_Amf_Constants::AMF3_DATE: + $this->writeDate($data); + break; + case Zend_Amf_Constants::AMF3_ARRAY: + $this->writeArray($data); + break; + case Zend_Amf_Constants::AMF3_OBJECT: + $this->writeObject($data); + break; + case Zend_Amf_Constants::AMF3_BYTEARRAY: + $this->writeByteArray($data); + break; + case Zend_Amf_Constants::AMF3_XMLSTRING; + $this->writeXml($data); + break; + default: + require_once 'Zend/Amf/Exception.php'; + throw new Zend_Amf_Exception('Unknown Type Marker: ' . $markerType); + } + } else { + // Detect Type Marker + if(is_resource($data)) { + $data = Zend_Amf_Parse_TypeLoader::handleResource($data); + } + switch (true) { + case (null === $data): + $markerType = Zend_Amf_Constants::AMF3_NULL; + break; + case (is_bool($data)): + if ($data){ + $markerType = Zend_Amf_Constants::AMF3_BOOLEAN_TRUE; + } else { + $markerType = Zend_Amf_Constants::AMF3_BOOLEAN_FALSE; + } + break; + case (is_int($data)): + if (($data > 0xFFFFFFF) || ($data < -268435456)) { + $markerType = Zend_Amf_Constants::AMF3_NUMBER; + } else { + $markerType = Zend_Amf_Constants::AMF3_INTEGER; + } + break; + case (is_float($data)): + $markerType = Zend_Amf_Constants::AMF3_NUMBER; + break; + case (is_string($data)): + $markerType = Zend_Amf_Constants::AMF3_STRING; + break; + case (is_array($data)): + $markerType = Zend_Amf_Constants::AMF3_ARRAY; + break; + case (is_object($data)): + // Handle object types. + if (($data instanceof DateTime) || ($data instanceof Zend_Date)) { + $markerType = Zend_Amf_Constants::AMF3_DATE; + } else if ($data instanceof Zend_Amf_Value_ByteArray) { + $markerType = Zend_Amf_Constants::AMF3_BYTEARRAY; + } else if (($data instanceof DOMDocument) || ($data instanceof SimpleXMLElement)) { + $markerType = Zend_Amf_Constants::AMF3_XMLSTRING; + } else { + $markerType = Zend_Amf_Constants::AMF3_OBJECT; + } + break; + default: + require_once 'Zend/Amf/Exception.php'; + throw new Zend_Amf_Exception('Unsupported data type: ' . gettype($data)); + } + $this->writeTypeMarker($data, $markerType); + } + } + + /** + * Write an AMF3 integer + * + * @param int|float $data + * @return Zend_Amf_Parse_Amf3_Serializer + */ + public function writeInteger($int) + { + if (($int & 0xffffff80) == 0) { + $this->_stream->writeByte($int & 0x7f); + return $this; + } + + if (($int & 0xffffc000) == 0 ) { + $this->_stream->writeByte(($int >> 7 ) | 0x80); + $this->_stream->writeByte($int & 0x7f); + return $this; + } + + if (($int & 0xffe00000) == 0) { + $this->_stream->writeByte(($int >> 14 ) | 0x80); + $this->_stream->writeByte(($int >> 7 ) | 0x80); + $this->_stream->writeByte($int & 0x7f); + return $this; + } + + $this->_stream->writeByte(($int >> 22 ) | 0x80); + $this->_stream->writeByte(($int >> 15 ) | 0x80); + $this->_stream->writeByte(($int >> 8 ) | 0x80); + $this->_stream->writeByte($int & 0xff); + return $this; + } + + /** + * Send string to output stream, without trying to reference it. + * The string is prepended with strlen($string) << 1 | 0x01 + * + * @param string $string + * @return Zend_Amf_Parse_Amf3_Serializer + */ + protected function writeBinaryString($string){ + $ref = strlen($string) << 1 | 0x01; + $this->writeInteger($ref); + $this->_stream->writeBytes($string); + + return $this; + } + + /** + * Send string to output stream + * + * @param string $string + * @return Zend_Amf_Parse_Amf3_Serializer + */ + public function writeString($string) + { + $len = strlen($string); + if(!$len){ + $this->writeInteger(0x01); + return $this; + } + + $ref = array_search($string, $this->_referenceStrings, true); + if($ref === false){ + $this->_referenceStrings[] = $string; + $this->writeBinaryString($string); + } else { + $ref <<= 1; + $this->writeInteger($ref); + } + + return $this; + } + + /** + * Send ByteArray to output stream + * + * @param string|Zend_Amf_Value_ByteArray $data + * @return Zend_Amf_Parse_Amf3_Serializer + */ + public function writeByteArray($data){ + if($this->writeObjectReference($data)){ + return $this; + } + + if(is_string($data)) { + //nothing to do + } else if ($data instanceof Zend_Amf_Value_ByteArray) { + $data = $data->getData(); + } else { + require_once 'Zend/Amf/Exception.php'; + throw new Zend_Amf_Exception('Invalid ByteArray specified; must be a string or Zend_Amf_Value_ByteArray'); + } + + $this->writeBinaryString($data); + + return $this; + } + + /** + * Send xml to output stream + * + * @param DOMDocument|SimpleXMLElement $xml + * @return Zend_Amf_Parse_Amf3_Serializer + */ + public function writeXml($xml) + { + if($this->writeObjectReference($xml)){ + return $this; + } + + if(is_string($xml)) { + //nothing to do + } else if ($xml instanceof DOMDocument) { + $xml = $xml->saveXml(); + } else if ($xml instanceof SimpleXMLElement) { + $xml = $xml->asXML(); + } else { + require_once 'Zend/Amf/Exception.php'; + throw new Zend_Amf_Exception('Invalid xml specified; must be a DOMDocument or SimpleXMLElement'); + } + + $this->writeBinaryString($xml); + + return $this; + } + + /** + * Convert DateTime/Zend_Date to AMF date + * + * @param DateTime|Zend_Date $date + * @return Zend_Amf_Parse_Amf3_Serializer + */ + public function writeDate($date) + { + if($this->writeObjectReference($date)){ + return $this; + } + + if ($date instanceof DateTime) { + $dateString = $date->format('U') * 1000; + } elseif ($date instanceof Zend_Date) { + $dateString = $date->toString('U') * 1000; + } else { + require_once 'Zend/Amf/Exception.php'; + throw new Zend_Amf_Exception('Invalid date specified; must be a string DateTime or Zend_Date object'); + } + + $this->writeInteger(0x01); + // write time to stream minus milliseconds + $this->_stream->writeDouble($dateString); + return $this; + } + + /** + * Write a PHP array back to the amf output stream + * + * @param array $array + * @return Zend_Amf_Parse_Amf3_Serializer + */ + public function writeArray(array $array) + { + // arrays aren't reference here but still counted + $this->_referenceObjects[] = $array; + + // have to seperate mixed from numberic keys. + $numeric = array(); + $string = array(); + foreach ($array as $key => $value) { + if (is_int($key)) { + $numeric[] = $value; + } else { + $string[$key] = $value; + } + } + + // write the preamble id of the array + $length = count($numeric); + $id = ($length << 1) | 0x01; + $this->writeInteger($id); + + //Write the mixed type array to the output stream + foreach($string as $key => $value) { + $this->writeString($key) + ->writeTypeMarker($value); + } + $this->writeString(''); + + // Write the numeric array to ouput stream + foreach($numeric as $value) { + $this->writeTypeMarker($value); + } + return $this; + } + + /** + * Check if the given object is in the reference table, write the reference if it exists, + * otherwise add the object to the reference table + * + * @param mixed $object object to check for reference + * @return Boolean true, if the reference was written, false otherwise + */ + protected function writeObjectReference($object) + { + $ref = array_search($object, $this->_referenceObjects,true); + //quickly handle object references + if($ref !== false){ + $ref <<= 1; + $this->writeInteger($ref); + return true; + } + $this->_referenceObjects[] = $object; + return false; + } + + /** + * Write object to ouput stream + * + * @param mixed $data + * @return Zend_Amf_Parse_Amf3_Serializer + */ + public function writeObject($object) + { + if($this->writeObjectReference($object)){ + return $this; + } + + $className = ''; + + //Check to see if the object is a typed object and we need to change + switch (true) { + // the return class mapped name back to actionscript class name. + case ($className = Zend_Amf_Parse_TypeLoader::getMappedClassName(get_class($object))): + break; + + // Check to see if the user has defined an explicit Action Script type. + case isset($object->_explicitType): + $className = $object->_explicitType; + break; + + // Check if user has defined a method for accessing the Action Script type + case method_exists($object, 'getASClassName'): + $className = $object->getASClassName(); + break; + + // No return class name is set make it a generic object + case ($object instanceof stdClass): + $className = ''; + break; + + // By default, use object's class name + default: + $className = get_class($object); + break; + } + + $writeTraits = true; + + //check to see, if we have a corresponding definition + if(array_key_exists($className, $this->_referenceDefinitions)){ + $traitsInfo = $this->_referenceDefinitions[$className]['id']; + $encoding = $this->_referenceDefinitions[$className]['encoding']; + $propertyNames = $this->_referenceDefinitions[$className]['propertyNames']; + + $traitsInfo = ($traitsInfo << 2) | 0x01; + + $writeTraits = false; + } else { + $propertyNames = array(); + + if($className == ''){ + //if there is no className, we interpret the class as dynamic without any sealed members + $encoding = Zend_Amf_Constants::ET_DYNAMIC; + } else { + $encoding = Zend_Amf_Constants::ET_PROPLIST; + + foreach($object as $key => $value) { + if( $key[0] != "_") { + $propertyNames[] = $key; + } + } + } + + $this->_referenceDefinitions[$className] = array( + 'id' => count($this->_referenceDefinitions), + 'encoding' => $encoding, + 'propertyNames' => $propertyNames, + ); + + $traitsInfo = Zend_Amf_Constants::AMF3_OBJECT_ENCODING; + $traitsInfo |= $encoding << 2; + $traitsInfo |= (count($propertyNames) << 4); + } + + $this->writeInteger($traitsInfo); + + if($writeTraits){ + $this->writeString($className); + foreach ($propertyNames as $value) { + $this->writeString($value); + } + } + + try { + switch($encoding) { + case Zend_Amf_Constants::ET_PROPLIST: + //Write the sealed values to the output stream. + foreach ($propertyNames as $key) { + $this->writeTypeMarker($object->$key); + } + break; + case Zend_Amf_Constants::ET_DYNAMIC: + //Write the sealed values to the output stream. + foreach ($propertyNames as $key) { + $this->writeTypeMarker($object->$key); + } + + //Write remaining properties + foreach($object as $key => $value){ + if(!in_array($key,$propertyNames) && $key[0] != "_"){ + $this->writeString($key); + $this->writeTypeMarker($value); + } + } + + //Write an empty string to end the dynamic part + $this->writeString(''); + break; + case Zend_Amf_Constants::ET_EXTERNAL: + require_once 'Zend/Amf/Exception.php'; + throw new Zend_Amf_Exception('External Object Encoding not implemented'); + break; + default: + require_once 'Zend/Amf/Exception.php'; + throw new Zend_Amf_Exception('Unknown Object Encoding type: ' . $encoding); + } + } catch (Exception $e) { + require_once 'Zend/Amf/Exception.php'; + throw new Zend_Amf_Exception('Unable to writeObject output: ' . $e->getMessage(), 0, $e); + } + + return $this; + } +} diff --git a/library/Zend/Amf/Parse/Deserializer.php b/library/Zend/Amf/Parse/Deserializer.php new file mode 100644 index 000000000..4f697bb1f --- /dev/null +++ b/library/Zend/Amf/Parse/Deserializer.php @@ -0,0 +1,65 @@ +_stream = $stream; + } + + /** + * Checks for AMF marker types and calls the appropriate methods + * for deserializing those marker types. Markers are the data type of + * the following value. + * + * @param int $typeMarker + * @return mixed Whatever the data type is of the marker in php + */ + public abstract function readTypeMarker($markerType = null); +} diff --git a/library/Zend/Amf/Parse/InputStream.php b/library/Zend/Amf/Parse/InputStream.php new file mode 100644 index 000000000..03dff2f6b --- /dev/null +++ b/library/Zend/Amf/Parse/InputStream.php @@ -0,0 +1,39 @@ + Value is Mysql type (exact string) => PHP type + */ + static public $fieldTypes = array( + "int" => "int", + "timestamp" => "int", + "year" => "int", + "real" => "float", + ); + /** + * Parse resource into array + * + * @param resource $resource + * @return array + */ + public function parse($resource) { + $result = array(); + $fieldcnt = mysql_num_fields($resource); + $fields_transform = array(); + for($i=0;$i<$fieldcnt;$i++) { + $type = mysql_field_type($resource, $i); + if(isset(self::$fieldTypes[$type])) { + $fields_transform[mysql_field_name($resource, $i)] = self::$fieldTypes[$type]; + } + } + + while($row = mysql_fetch_object($resource)) { + foreach($fields_transform as $fieldname => $fieldtype) { + settype($row->$fieldname, $fieldtype); + } + $result[] = $row; + } + return $result; + } +} diff --git a/library/Zend/Amf/Parse/Resource/MysqliResult.php b/library/Zend/Amf/Parse/Resource/MysqliResult.php new file mode 100644 index 000000000..dc99849fd --- /dev/null +++ b/library/Zend/Amf/Parse/Resource/MysqliResult.php @@ -0,0 +1,128 @@ + "MYSQLI_TYPE_DECIMAL", + 1 => "MYSQLI_TYPE_TINYINT", + 2 => "MYSQLI_TYPE_SMALLINT", + 3 => "MYSQLI_TYPE_INTEGER", + 4 => "MYSQLI_TYPE_FLOAT", + 5 => "MYSQLI_TYPE_DOUBLE", + 7 => "MYSQLI_TYPE_TIMESTAMP", + 8 => "MYSQLI_TYPE_BIGINT", + 9 => "MYSQLI_TYPE_MEDIUMINT", + 10 => "MYSQLI_TYPE_DATE", + 11 => "MYSQLI_TYPE_TIME", + 12 => "MYSQLI_TYPE_DATETIME", + 13 => "MYSQLI_TYPE_YEAR", + 14 => "MYSQLI_TYPE_DATE", + 16 => "MYSQLI_TYPE_BIT", + 246 => "MYSQLI_TYPE_DECIMAL", + 247 => "MYSQLI_TYPE_ENUM", + 248 => "MYSQLI_TYPE_SET", + 249 => "MYSQLI_TYPE_TINYBLOB", + 250 => "MYSQLI_TYPE_MEDIUMBLOB", + 251 => "MYSQLI_TYPE_LONGBLOB", + 252 => "MYSQLI_TYPE_BLOB", + 253 => "MYSQLI_TYPE_VARCHAR", + 254 => "MYSQLI_TYPE_CHAR", + 255 => "MYSQLI_TYPE_GEOMETRY", + ); + + // Build an associative array for a type look up + static $mysqli_to_php = array( + "MYSQLI_TYPE_DECIMAL" => 'float', + "MYSQLI_TYPE_NEWDECIMAL" => 'float', + "MYSQLI_TYPE_BIT" => 'integer', + "MYSQLI_TYPE_TINYINT" => 'integer', + "MYSQLI_TYPE_SMALLINT" => 'integer', + "MYSQLI_TYPE_MEDIUMINT" => 'integer', + "MYSQLI_TYPE_BIGINT" => 'integer', + "MYSQLI_TYPE_INTEGER" => 'integer', + "MYSQLI_TYPE_FLOAT" => 'float', + "MYSQLI_TYPE_DOUBLE" => 'float', + "MYSQLI_TYPE_NULL" => 'null', + "MYSQLI_TYPE_TIMESTAMP" => 'string', + "MYSQLI_TYPE_INT24" => 'integer', + "MYSQLI_TYPE_DATE" => 'string', + "MYSQLI_TYPE_TIME" => 'string', + "MYSQLI_TYPE_DATETIME" => 'string', + "MYSQLI_TYPE_YEAR" => 'string', + "MYSQLI_TYPE_NEWDATE" => 'string', + "MYSQLI_TYPE_ENUM" => 'string', + "MYSQLI_TYPE_SET" => 'string', + "MYSQLI_TYPE_TINYBLOB" => 'object', + "MYSQLI_TYPE_MEDIUMBLOB" => 'object', + "MYSQLI_TYPE_LONGBLOB" => 'object', + "MYSQLI_TYPE_BLOB" => 'object', + "MYSQLI_TYPE_CHAR" => 'string', + "MYSQLI_TYPE_VARCHAR" => 'string', + "MYSQLI_TYPE_GEOMETRY" => 'object', + "MYSQLI_TYPE_BIT" => 'integer', + ); + + /** + * Parse resource into array + * + * @param resource $resource + * @return array + */ + public function parse($resource) { + + $result = array(); + $fieldcnt = mysqli_num_fields($resource); + + + $fields_transform = array(); + + for($i=0;$i<$fieldcnt;$i++) { + $finfo = mysqli_fetch_field_direct($resource, $i); + + if(isset(self::$mysqli_type[$finfo->type])) { + $fields_transform[$finfo->name] = self::$mysqli_to_php[self::$mysqli_type[$finfo->type]]; + } + } + + while($row = mysqli_fetch_assoc($resource)) { + foreach($fields_transform as $fieldname => $fieldtype) { + settype($row[$fieldname], $fieldtype); + } + $result[] = $row; + } + return $result; + } +} diff --git a/library/Zend/Amf/Parse/Resource/Stream.php b/library/Zend/Amf/Parse/Resource/Stream.php new file mode 100644 index 000000000..2e116c410 --- /dev/null +++ b/library/Zend/Amf/Parse/Resource/Stream.php @@ -0,0 +1,42 @@ +_stream = $stream; + } + + /** + * Find the PHP object type and convert it into an AMF object type + * + * @param mixed $content + * @param int $markerType + * @return void + */ + public abstract function writeTypeMarker($content, $markerType=null); +} diff --git a/library/Zend/Amf/Parse/TypeLoader.php b/library/Zend/Amf/Parse/TypeLoader.php new file mode 100644 index 000000000..85aab3212 --- /dev/null +++ b/library/Zend/Amf/Parse/TypeLoader.php @@ -0,0 +1,231 @@ + 'Zend_Amf_Value_Messaging_AcknowledgeMessage', + 'flex.messaging.messages.ErrorMessage' => 'Zend_Amf_Value_Messaging_AsyncMessage', + 'flex.messaging.messages.CommandMessage' => 'Zend_Amf_Value_Messaging_CommandMessage', + 'flex.messaging.messages.ErrorMessage' => 'Zend_Amf_Value_Messaging_ErrorMessage', + 'flex.messaging.messages.RemotingMessage' => 'Zend_Amf_Value_Messaging_RemotingMessage', + 'flex.messaging.io.ArrayCollection' => 'Zend_Amf_Value_Messaging_ArrayCollection', + ); + + /** + * @var array Default class map + */ + protected static $_defaultClassMap = array( + 'flex.messaging.messages.AcknowledgeMessage' => 'Zend_Amf_Value_Messaging_AcknowledgeMessage', + 'flex.messaging.messages.ErrorMessage' => 'Zend_Amf_Value_Messaging_AsyncMessage', + 'flex.messaging.messages.CommandMessage' => 'Zend_Amf_Value_Messaging_CommandMessage', + 'flex.messaging.messages.ErrorMessage' => 'Zend_Amf_Value_Messaging_ErrorMessage', + 'flex.messaging.messages.RemotingMessage' => 'Zend_Amf_Value_Messaging_RemotingMessage', + 'flex.messaging.io.ArrayCollection' => 'Zend_Amf_Value_Messaging_ArrayCollection', + ); + + /** + * @var Zend_Loader_PluginLoader_Interface + */ + protected static $_resourceLoader = null; + + + /** + * Load the mapped class type into a callback. + * + * @param string $className + * @return object|false + */ + public static function loadType($className) + { + $class = self::getMappedClassName($className); + if(!$class) { + $class = str_replace('.', '_', $className); + } + if (!class_exists($class)) { + return "stdClass"; + } + return $class; + } + + /** + * Looks up the supplied call name to its mapped class name + * + * @param string $className + * @return string + */ + public static function getMappedClassName($className) + { + $mappedName = array_search($className, self::$classMap); + + if ($mappedName) { + return $mappedName; + } + + $mappedName = array_search($className, array_flip(self::$classMap)); + + if ($mappedName) { + return $mappedName; + } + + return false; + } + + /** + * Map PHP class names to ActionScript class names + * + * Allows users to map the class names of there action script classes + * to the equivelent php class name. Used in deserialization to load a class + * and serialiation to set the class name of the returned object. + * + * @param string $asClassName + * @param string $phpClassName + * @return void + */ + public static function setMapping($asClassName, $phpClassName) + { + self::$classMap[$asClassName] = $phpClassName; + } + + /** + * Reset type map + * + * @return void + */ + public static function resetMap() + { + self::$classMap = self::$_defaultClassMap; + } + + /** + * Set loader for resource type handlers + * + * @param Zend_Loader_PluginLoader_Interface $loader + */ + public static function setResourceLoader(Zend_Loader_PluginLoader_Interface $loader) + { + self::$_resourceLoader = $loader; + } + + /** + * Add directory to the list of places where to look for resource handlers + * + * @param string $prefix + * @param string $dir + */ + public static function addResourceDirectory($prefix, $dir) + { + if(self::$_resourceLoader) { + self::$_resourceLoader->addPrefixPath($prefix, $dir); + } + } + + /** + * Get plugin class that handles this resource + * + * @param resource $resource Resource type + * @return string Class name + */ + public static function getResourceParser($resource) + { + if(self::$_resourceLoader) { + $type = preg_replace("/[^A-Za-z0-9_]/", " ", get_resource_type($resource)); + $type = str_replace(" ","", ucwords($type)); + return self::$_resourceLoader->load($type); + } + return false; + } + + /** + * Convert resource to a serializable object + * + * @param resource $resource + * @return mixed + */ + public static function handleResource($resource) + { + if(!self::$_resourceLoader) { + require_once 'Zend/Amf/Exception.php'; + throw new Zend_Amf_Exception('Unable to handle resources - resource plugin loader not set'); + } + try { + while(is_resource($resource)) { + $resclass = self::getResourceParser($resource); + if(!$resclass) { + require_once 'Zend/Amf/Exception.php'; + throw new Zend_Amf_Exception('Can not serialize resource type: '. get_resource_type($resource)); + } + $parser = new $resclass(); + if(is_callable(array($parser, 'parse'))) { + $resource = $parser->parse($resource); + } else { + require_once 'Zend/Amf/Exception.php'; + throw new Zend_Amf_Exception("Could not call parse() method on class $resclass"); + } + } + return $resource; + } catch(Zend_Amf_Exception $e) { + throw new Zend_Amf_Exception($e->getMessage(), $e->getCode(), $e); + } catch(Exception $e) { + require_once 'Zend/Amf/Exception.php'; + throw new Zend_Amf_Exception('Can not serialize resource type: '. get_resource_type($resource), 0, $e); + } + } +} diff --git a/library/Zend/Amf/Request.php b/library/Zend/Amf/Request.php new file mode 100644 index 000000000..23e58bf24 --- /dev/null +++ b/library/Zend/Amf/Request.php @@ -0,0 +1,251 @@ +_inputStream = new Zend_Amf_Parse_InputStream($request); + $this->_deserializer = new Zend_Amf_Parse_Amf0_Deserializer($this->_inputStream); + $this->readMessage($this->_inputStream); + return $this; + } + + /** + * Takes the raw AMF input stream and converts it into valid PHP objects + * + * @param Zend_Amf_Parse_InputStream + * @return Zend_Amf_Request + */ + public function readMessage(Zend_Amf_Parse_InputStream $stream) + { + $clientVersion = $stream->readUnsignedShort(); + if (($clientVersion != Zend_Amf_Constants::AMF0_OBJECT_ENCODING) + && ($clientVersion != Zend_Amf_Constants::AMF3_OBJECT_ENCODING) + && ($clientVersion != Zend_Amf_Constants::FMS_OBJECT_ENCODING) + ) { + require_once 'Zend/Amf/Exception.php'; + throw new Zend_Amf_Exception('Unknown Player Version ' . $clientVersion); + } + + $this->_bodies = array(); + $this->_headers = array(); + $headerCount = $stream->readInt(); + + // Iterate through the AMF envelope header + while ($headerCount--) { + $this->_headers[] = $this->readHeader(); + } + + // Iterate through the AMF envelope body + $bodyCount = $stream->readInt(); + while ($bodyCount--) { + $this->_bodies[] = $this->readBody(); + } + + return $this; + } + + /** + * Deserialize a message header from the input stream. + * + * A message header is structured as: + * - NAME String + * - MUST UNDERSTAND Boolean + * - LENGTH Int + * - DATA Object + * + * @return Zend_Amf_Value_MessageHeader + */ + public function readHeader() + { + $name = $this->_inputStream->readUTF(); + $mustRead = (bool)$this->_inputStream->readByte(); + $length = $this->_inputStream->readLong(); + + try { + $data = $this->_deserializer->readTypeMarker(); + } catch (Exception $e) { + require_once 'Zend/Amf/Exception.php'; + throw new Zend_Amf_Exception('Unable to parse ' . $name . ' header data: ' . $e->getMessage() . ' '. $e->getLine(), 0, $e); + } + + $header = new Zend_Amf_Value_MessageHeader($name, $mustRead, $data, $length); + return $header; + } + + /** + * Deserialize a message body from the input stream + * + * @return Zend_Amf_Value_MessageBody + */ + public function readBody() + { + $targetURI = $this->_inputStream->readUTF(); + $responseURI = $this->_inputStream->readUTF(); + $length = $this->_inputStream->readLong(); + + try { + $data = $this->_deserializer->readTypeMarker(); + } catch (Exception $e) { + require_once 'Zend/Amf/Exception.php'; + throw new Zend_Amf_Exception('Unable to parse ' . $targetURI . ' body data ' . $e->getMessage(), 0, $e); + } + + // Check for AMF3 objectEncoding + if ($this->_deserializer->getObjectEncoding() == Zend_Amf_Constants::AMF3_OBJECT_ENCODING) { + /* + * When and AMF3 message is sent to the server it is nested inside + * an AMF0 array called Content. The following code gets the object + * out of the content array and sets it as the message data. + */ + if(is_array($data) && $data[0] instanceof Zend_Amf_Value_Messaging_AbstractMessage){ + $data = $data[0]; + } + + // set the encoding so we return our message in AMF3 + $this->_objectEncoding = Zend_Amf_Constants::AMF3_OBJECT_ENCODING; + } + + $body = new Zend_Amf_Value_MessageBody($targetURI, $responseURI, $data); + return $body; + } + + /** + * Return an array of the body objects that were found in the amf request. + * + * @return array {target, response, length, content} + */ + public function getAmfBodies() + { + return $this->_bodies; + } + + /** + * Accessor to private array of message bodies. + * + * @param Zend_Amf_Value_MessageBody $message + * @return Zend_Amf_Request + */ + public function addAmfBody(Zend_Amf_Value_MessageBody $message) + { + $this->_bodies[] = $message; + return $this; + } + + /** + * Return an array of headers that were found in the amf request. + * + * @return array {operation, mustUnderstand, length, param} + */ + public function getAmfHeaders() + { + return $this->_headers; + } + + /** + * Return the either 0 or 3 for respect AMF version + * + * @return int + */ + public function getObjectEncoding() + { + return $this->_objectEncoding; + } + + /** + * Set the object response encoding + * + * @param mixed $int + * @return Zend_Amf_Request + */ + public function setObjectEncoding($int) + { + $this->_objectEncoding = $int; + return $this; + } +} diff --git a/library/Zend/Amf/Request/Http.php b/library/Zend/Amf/Request/Http.php new file mode 100644 index 000000000..48fb5413a --- /dev/null +++ b/library/Zend/Amf/Request/Http.php @@ -0,0 +1,80 @@ +_rawRequest = $amfRequest; + $this->initialize($amfRequest); + } else { + echo '

    Zend Amf Endpoint

    ' ; + } + } + + /** + * Retrieve raw AMF Request + * + * @return string + */ + public function getRawRequest() + { + return $this->_rawRequest; + } +} diff --git a/library/Zend/Amf/Response.php b/library/Zend/Amf/Response.php new file mode 100644 index 000000000..be77a91ca --- /dev/null +++ b/library/Zend/Amf/Response.php @@ -0,0 +1,194 @@ +_outputStream = new Zend_Amf_Parse_OutputStream(); + $this->writeMessage($this->_outputStream); + return $this; + } + + /** + * Serialize the PHP data types back into Actionscript and + * create and AMF stream. + * + * @param Zend_Amf_Parse_OutputStream $stream + * @return Zend_Amf_Response + */ + public function writeMessage(Zend_Amf_Parse_OutputStream $stream) + { + $objectEncoding = $this->_objectEncoding; + + //Write encoding to start of stream. Preamble byte is written of two byte Unsigned Short + $stream->writeByte(0x00); + $stream->writeByte($objectEncoding); + + // Loop through the AMF Headers that need to be returned. + $headerCount = count($this->_headers); + $stream->writeInt($headerCount); + foreach ($this->getAmfHeaders() as $header) { + $serializer = new Zend_Amf_Parse_Amf0_Serializer($stream); + $stream->writeUTF($header->name); + $stream->writeByte($header->mustRead); + $stream->writeLong(Zend_Amf_Constants::UNKNOWN_CONTENT_LENGTH); + $serializer->writeTypeMarker($header->data); + } + + // loop through the AMF bodies that need to be returned. + $bodyCount = count($this->_bodies); + $stream->writeInt($bodyCount); + foreach ($this->_bodies as $body) { + $serializer = new Zend_Amf_Parse_Amf0_Serializer($stream); + $stream->writeUTF($body->getTargetURI()); + $stream->writeUTF($body->getResponseURI()); + $stream->writeLong(Zend_Amf_Constants::UNKNOWN_CONTENT_LENGTH); + if($this->_objectEncoding == Zend_Amf_Constants::AMF0_OBJECT_ENCODING) { + $serializer->writeTypeMarker($body->getData()); + } else { + // Content is AMF3 + $serializer->writeTypeMarker($body->getData(),Zend_Amf_Constants::AMF0_AMF3); + } + } + + return $this; + } + + /** + * Return the output stream content + * + * @return string The contents of the output stream + */ + public function getResponse() + { + return $this->_outputStream->getStream(); + } + + /** + * Return the output stream content + * + * @return string + */ + public function __toString() + { + return $this->getResponse(); + } + + /** + * Add an AMF body to be sent to the Flash Player + * + * @param Zend_Amf_Value_MessageBody $body + * @return Zend_Amf_Response + */ + public function addAmfBody(Zend_Amf_Value_MessageBody $body) + { + $this->_bodies[] = $body; + return $this; + } + + /** + * Return an array of AMF bodies to be serialized + * + * @return array + */ + public function getAmfBodies() + { + return $this->_bodies; + } + + /** + * Add an AMF Header to be sent back to the flash player + * + * @param Zend_Amf_Value_MessageHeader $header + * @return Zend_Amf_Response + */ + public function addAmfHeader(Zend_Amf_Value_MessageHeader $header) + { + $this->_headers[] = $header; + return $this; + } + + /** + * Retrieve attached AMF message headers + * + * @return array Array of Zend_Amf_Value_MessageHeader objects + */ + public function getAmfHeaders() + { + return $this->_headers; + } + + /** + * Set the AMF encoding that will be used for serialization + * + * @param int $encoding + * @return Zend_Amf_Response + */ + public function setObjectEncoding($encoding) + { + $this->_objectEncoding = $encoding; + return $this; + } +} diff --git a/library/Zend/Amf/Response/Http.php b/library/Zend/Amf/Response/Http.php new file mode 100644 index 000000000..c6e306daf --- /dev/null +++ b/library/Zend/Amf/Response/Http.php @@ -0,0 +1,50 @@ + method pairs + * @var array + */ + protected $_table = array(); + + /** + * + * @var bool session flag; whether or not to add a session to each response. + */ + protected $_session = false; + + /** + * Namespace allows all AMF calls to not clobber other php session variables + * @var Zend_Session_NameSpace default session namespace zend_amf + */ + protected $_sesionNamespace = 'zend_amf'; + + /** + * Set the default session.name if php_ + * @var string + */ + protected $_sessionName = 'PHPSESSID'; + + /** + * Authentication handler object + * + * @var Zend_Amf_Auth_Abstract + */ + protected $_auth; + /** + * ACL handler object + * + * @var Zend_Acl + */ + protected $_acl; + /** + * The server constructor + */ + public function __construct() + { + Zend_Amf_Parse_TypeLoader::setResourceLoader(new Zend_Loader_PluginLoader(array("Zend_Amf_Parse_Resource" => "Zend/Amf/Parse/Resource"))); + } + + /** + * Set authentication adapter + * + * @param Zend_Amf_Auth_Abstract $auth + * @return Zend_Amf_Server + */ + public function setAuth(Zend_Amf_Auth_Abstract $auth) + { + $this->_auth = $auth; + return $this; + } + /** + * Get authentication adapter + * + * @return Zend_Amf_Auth_Abstract + */ + public function getAuth() + { + return $this->_auth; + } + + /** + * Set ACL adapter + * + * @param Zend_Acl $acl + * @return Zend_Amf_Server + */ + public function setAcl(Zend_Acl $acl) + { + $this->_acl = $acl; + return $this; + } + /** + * Get ACL adapter + * + * @return Zend_Acl + */ + public function getAcl() + { + return $this->_acl; + } + + /** + * Set production flag + * + * @param bool $flag + * @return Zend_Amf_Server + */ + public function setProduction($flag) + { + $this->_production = (bool) $flag; + return $this; + } + + /** + * Whether or not the server is in production + * + * @return bool + */ + public function isProduction() + { + return $this->_production; + } + + /** + * @param namespace of all incoming sessions defaults to Zend_Amf + * @return Zend_Amf_Server + */ + public function setSession($namespace = 'Zend_Amf') + { + require_once 'Zend/Session.php'; + $this->_session = true; + $this->_sesionNamespace = new Zend_Session_Namespace($namespace); + return $this; + } + + /** + * Whether of not the server is using sessions + * @return bool + */ + public function isSession() + { + return $this->_session; + } + + /** + * Check if the ACL allows accessing the function or method + * + * @param string|object $object Object or class being accessed + * @param string $function Function or method being accessed + * @return unknown_type + */ + protected function _checkAcl($object, $function) + { + if(!$this->_acl) { + return true; + } + if($object) { + $class = is_object($object)?get_class($object):$object; + if(!$this->_acl->has($class)) { + require_once 'Zend/Acl/Resource.php'; + $this->_acl->add(new Zend_Acl_Resource($class)); + } + $call = array($object, "initAcl"); + if(is_callable($call) && !call_user_func($call, $this->_acl)) { + // if initAcl returns false, no ACL check + return true; + } + } else { + $class = null; + } + + $auth = Zend_Auth::getInstance(); + if($auth->hasIdentity()) { + $role = $auth->getIdentity()->role; + } else { + if($this->_acl->hasRole(Zend_Amf_Constants::GUEST_ROLE)) { + $role = Zend_Amf_Constants::GUEST_ROLE; + } else { + require_once 'Zend/Amf/Server/Exception.php'; + throw new Zend_Amf_Server_Exception("Unauthenticated access not allowed"); + } + } + if($this->_acl->isAllowed($role, $class, $function)) { + return true; + } else { + require_once 'Zend/Amf/Server/Exception.php'; + throw new Zend_Amf_Server_Exception("Access not allowed"); + } + } + + /** + * Get PluginLoader for the Server + * + * @return Zend_Loader_PluginLoader + */ + protected function getLoader() + { + if(empty($this->_loader)) { + require_once 'Zend/Loader/PluginLoader.php'; + $this->_loader = new Zend_Loader_PluginLoader(); + } + return $this->_loader; + } + + /** + * Loads a remote class or method and executes the function and returns + * the result + * + * @param string $method Is the method to execute + * @param mixed $param values for the method + * @return mixed $response the result of executing the method + * @throws Zend_Amf_Server_Exception + */ + protected function _dispatch($method, $params = null, $source = null) + { + if($source) { + if(($mapped = Zend_Amf_Parse_TypeLoader::getMappedClassName($source)) !== false) { + $source = $mapped; + } + } + $qualifiedName = empty($source) ? $method : $source.".".$method; + + if (!isset($this->_table[$qualifiedName])) { + // if source is null a method that was not defined was called. + if ($source) { + $className = str_replace(".", "_", $source); + if(class_exists($className, false) && !isset($this->_classAllowed[$className])) { + require_once 'Zend/Amf/Server/Exception.php'; + throw new Zend_Amf_Server_Exception('Can not call "' . $className . '" - use setClass()'); + } + try { + $this->getLoader()->load($className); + } catch (Exception $e) { + require_once 'Zend/Amf/Server/Exception.php'; + throw new Zend_Amf_Server_Exception('Class "' . $className . '" does not exist: '.$e->getMessage(), 0, $e); + } + // Add the new loaded class to the server. + $this->setClass($className, $source); + } else { + require_once 'Zend/Amf/Server/Exception.php'; + throw new Zend_Amf_Server_Exception('Method "' . $method . '" does not exist'); + } + } + + $info = $this->_table[$qualifiedName]; + $argv = $info->getInvokeArguments(); + + if (0 < count($argv)) { + $params = array_merge($params, $argv); + } + + if ($info instanceof Zend_Server_Reflection_Function) { + $func = $info->getName(); + $this->_checkAcl(null, $func); + $return = call_user_func_array($func, $params); + } elseif ($info instanceof Zend_Server_Reflection_Method) { + // Get class + $class = $info->getDeclaringClass()->getName(); + if ('static' == $info->isStatic()) { + // for some reason, invokeArgs() does not work the same as + // invoke(), and expects the first argument to be an object. + // So, using a callback if the method is static. + $this->_checkAcl($class, $info->getName()); + $return = call_user_func_array(array($class, $info->getName()), $params); + } else { + // Object methods + try { + $object = $info->getDeclaringClass()->newInstance(); + } catch (Exception $e) { + require_once 'Zend/Amf/Server/Exception.php'; + throw new Zend_Amf_Server_Exception('Error instantiating class ' . $class . ' to invoke method ' . $info->getName() . ': '.$e->getMessage(), 621, $e); + } + $this->_checkAcl($object, $info->getName()); + $return = $info->invokeArgs($object, $params); + } + } else { + require_once 'Zend/Amf/Server/Exception.php'; + throw new Zend_Amf_Server_Exception('Method missing implementation ' . get_class($info)); + } + + return $return; + } + + /** + * Handles each of the 11 different command message types. + * + * A command message is a flex.messaging.messages.CommandMessage + * + * @see Zend_Amf_Value_Messaging_CommandMessage + * @param Zend_Amf_Value_Messaging_CommandMessage $message + * @return Zend_Amf_Value_Messaging_AcknowledgeMessage + */ + protected function _loadCommandMessage(Zend_Amf_Value_Messaging_CommandMessage $message) + { + require_once 'Zend/Amf/Value/Messaging/AcknowledgeMessage.php'; + switch($message->operation) { + case Zend_Amf_Value_Messaging_CommandMessage::DISCONNECT_OPERATION : + case Zend_Amf_Value_Messaging_CommandMessage::CLIENT_PING_OPERATION : + $return = new Zend_Amf_Value_Messaging_AcknowledgeMessage($message); + break; + case Zend_Amf_Value_Messaging_CommandMessage::LOGIN_OPERATION : + $data = explode(':', base64_decode($message->body)); + $userid = $data[0]; + $password = isset($data[1])?$data[1]:""; + if(empty($userid)) { + require_once 'Zend/Amf/Server/Exception.php'; + throw new Zend_Amf_Server_Exception('Login failed: username not supplied'); + } + if(!$this->_handleAuth($userid, $password)) { + require_once 'Zend/Amf/Server/Exception.php'; + throw new Zend_Amf_Server_Exception('Authentication failed'); + } + $return = new Zend_Amf_Value_Messaging_AcknowledgeMessage($message); + break; + case Zend_Amf_Value_Messaging_CommandMessage::LOGOUT_OPERATION : + if($this->_auth) { + Zend_Auth::getInstance()->clearIdentity(); + } + $return = new Zend_Amf_Value_Messaging_AcknowledgeMessage($message); + break; + default : + require_once 'Zend/Amf/Server/Exception.php'; + throw new Zend_Amf_Server_Exception('CommandMessage::' . $message->operation . ' not implemented'); + break; + } + return $return; + } + + /** + * Create appropriate error message + * + * @param int $objectEncoding Current AMF encoding + * @param string $message Message that was being processed when error happened + * @param string $description Error description + * @param mixed $detail Detailed data about the error + * @param int $code Error code + * @param int $line Error line + * @return Zend_Amf_Value_Messaging_ErrorMessage|array + */ + protected function _errorMessage($objectEncoding, $message, $description, $detail, $code, $line) + { + $return = null; + switch ($objectEncoding) { + case Zend_Amf_Constants::AMF0_OBJECT_ENCODING : + return array ( + 'description' => ($this->isProduction ()) ? '' : $description, + 'detail' => ($this->isProduction ()) ? '' : $detail, + 'line' => ($this->isProduction ()) ? 0 : $line, + 'code' => $code + ); + case Zend_Amf_Constants::AMF3_OBJECT_ENCODING : + require_once 'Zend/Amf/Value/Messaging/ErrorMessage.php'; + $return = new Zend_Amf_Value_Messaging_ErrorMessage ( $message ); + $return->faultString = $this->isProduction () ? '' : $description; + $return->faultCode = $code; + $return->faultDetail = $this->isProduction () ? '' : $detail; + break; + } + return $return; + } + + /** + * Handle AMF authentication + * + * @param string $userid + * @param string $password + * @return boolean + */ + protected function _handleAuth( $userid, $password) + { + if (!$this->_auth) { + return true; + } + $this->_auth->setCredentials($userid, $password); + $auth = Zend_Auth::getInstance(); + $result = $auth->authenticate($this->_auth); + if ($result->isValid()) { + if (!$this->isSession()) { + $this->setSession(); + } + return true; + } else { + // authentication failed, good bye + require_once 'Zend/Amf/Server/Exception.php'; + throw new Zend_Amf_Server_Exception( + "Authentication failed: " . join("\n", + $result->getMessages()), $result->getCode()); + } + + } + + /** + * Takes the deserialized AMF request and performs any operations. + * + * @todo should implement and SPL observer pattern for custom AMF headers + * @todo DescribeService support + * @param Zend_Amf_Request $request + * @return Zend_Amf_Response + * @throws Zend_Amf_server_Exception|Exception + */ + protected function _handle(Zend_Amf_Request $request) + { + // Get the object encoding of the request. + $objectEncoding = $request->getObjectEncoding(); + + // create a response object to place the output from the services. + $response = $this->getResponse(); + + // set response encoding + $response->setObjectEncoding($objectEncoding); + + $responseBody = $request->getAmfBodies(); + + $handleAuth = false; + if ($this->_auth) { + $headers = $request->getAmfHeaders(); + if (isset($headers[Zend_Amf_Constants::CREDENTIALS_HEADER]) && + isset($headers[Zend_Amf_Constants::CREDENTIALS_HEADER]->userid)) { + $handleAuth = true; + } + } + + // Iterate through each of the service calls in the AMF request + foreach($responseBody as $body) + { + try { + if ($handleAuth) { + if ($this->_handleAuth( + $headers[Zend_Amf_Constants::CREDENTIALS_HEADER]->userid, + $headers[Zend_Amf_Constants::CREDENTIALS_HEADER]->password)) { + // use RequestPersistentHeader to clear credentials + $response->addAmfHeader( + new Zend_Amf_Value_MessageHeader( + Zend_Amf_Constants::PERSISTENT_HEADER, + false, + new Zend_Amf_Value_MessageHeader( + Zend_Amf_Constants::CREDENTIALS_HEADER, + false, null))); + $handleAuth = false; + } + } + + if ($objectEncoding == Zend_Amf_Constants::AMF0_OBJECT_ENCODING) { + // AMF0 Object Encoding + $targetURI = $body->getTargetURI(); + $message = ''; + + // Split the target string into its values. + $source = substr($targetURI, 0, strrpos($targetURI, '.')); + + if ($source) { + // Break off method name from namespace into source + $method = substr(strrchr($targetURI, '.'), 1); + $return = $this->_dispatch($method, $body->getData(), $source); + } else { + // Just have a method name. + $return = $this->_dispatch($targetURI, $body->getData()); + } + } else { + // AMF3 read message type + $message = $body->getData(); + if ($message instanceof Zend_Amf_Value_Messaging_CommandMessage) { + // async call with command message + $return = $this->_loadCommandMessage($message); + } elseif ($message instanceof Zend_Amf_Value_Messaging_RemotingMessage) { + require_once 'Zend/Amf/Value/Messaging/AcknowledgeMessage.php'; + $return = new Zend_Amf_Value_Messaging_AcknowledgeMessage($message); + $return->body = $this->_dispatch($message->operation, $message->body, $message->source); + } else { + // Amf3 message sent with netConnection + $targetURI = $body->getTargetURI(); + + // Split the target string into its values. + $source = substr($targetURI, 0, strrpos($targetURI, '.')); + + if ($source) { + // Break off method name from namespace into source + $method = substr(strrchr($targetURI, '.'), 1); + $return = $this->_dispatch($method, $body->getData(), $source); + } else { + // Just have a method name. + $return = $this->_dispatch($targetURI, $body->getData()); + } + } + } + $responseType = Zend_AMF_Constants::RESULT_METHOD; + } catch (Exception $e) { + $return = $this->_errorMessage($objectEncoding, $message, + $e->getMessage(), $e->getTraceAsString(),$e->getCode(), $e->getLine()); + $responseType = Zend_AMF_Constants::STATUS_METHOD; + } + + $responseURI = $body->getResponseURI() . $responseType; + $newBody = new Zend_Amf_Value_MessageBody($responseURI, null, $return); + $response->addAmfBody($newBody); + } + // Add a session header to the body if session is requested. + if($this->isSession()) { + $currentID = session_id(); + $joint = "?"; + if(isset($_SERVER['QUERY_STRING'])) { + if(!strpos($_SERVER['QUERY_STRING'], $currentID) !== FALSE) { + if(strrpos($_SERVER['QUERY_STRING'], "?") !== FALSE) { + $joint = "&"; + } + } + } + + // create a new AMF message header with the session id as a variable. + $sessionValue = $joint . $this->_sessionName . "=" . $currentID; + $sessionHeader = new Zend_Amf_Value_MessageHeader(Zend_Amf_Constants::URL_APPEND_HEADER, false, $sessionValue); + $response->addAmfHeader($sessionHeader); + } + + // serialize the response and return serialized body. + $response->finalize(); + } + + /** + * Handle an AMF call from the gateway. + * + * @param null|Zend_Amf_Request $request Optional + * @return Zend_Amf_Response + */ + public function handle($request = null) + { + // Check if request was passed otherwise get it from the server + if (is_null($request) || !$request instanceof Zend_Amf_Request) { + $request = $this->getRequest(); + } else { + $this->setRequest($request); + } + if ($this->isSession()) { + // Check if a session is being sent from the amf call + if (isset($_COOKIE[$this->_sessionName])) { + session_id($_COOKIE[$this->_sessionName]); + } + } + + // Check for errors that may have happend in deserialization of Request. + try { + // Take converted PHP objects and handle service call. + // Serialize to Zend_Amf_response for output stream + $this->_handle($request); + $response = $this->getResponse(); + } catch (Exception $e) { + // Handle any errors in the serialization and service calls. + require_once 'Zend/Amf/Server/Exception.php'; + throw new Zend_Amf_Server_Exception('Handle error: ' . $e->getMessage() . ' ' . $e->getLine(), 0, $e); + } + + // Return the Amf serialized output string + return $response; + } + + /** + * Set request object + * + * @param string|Zend_Amf_Request $request + * @return Zend_Amf_Server + */ + public function setRequest($request) + { + if (is_string($request) && class_exists($request)) { + $request = new $request(); + if (!$request instanceof Zend_Amf_Request) { + require_once 'Zend/Amf/Server/Exception.php'; + throw new Zend_Amf_Server_Exception('Invalid request class'); + } + } elseif (!$request instanceof Zend_Amf_Request) { + require_once 'Zend/Amf/Server/Exception.php'; + throw new Zend_Amf_Server_Exception('Invalid request object'); + } + $this->_request = $request; + return $this; + } + + /** + * Return currently registered request object + * + * @return null|Zend_Amf_Request + */ + public function getRequest() + { + if (null === $this->_request) { + require_once 'Zend/Amf/Request/Http.php'; + $this->setRequest(new Zend_Amf_Request_Http()); + } + + return $this->_request; + } + + /** + * Public access method to private Zend_Amf_Server_Response reference + * + * @param string|Zend_Amf_Server_Response $response + * @return Zend_Amf_Server + */ + public function setResponse($response) + { + if (is_string($response) && class_exists($response)) { + $response = new $response(); + if (!$response instanceof Zend_Amf_Response) { + require_once 'Zend/Amf/Server/Exception.php'; + throw new Zend_Amf_Server_Exception('Invalid response class'); + } + } elseif (!$response instanceof Zend_Amf_Response) { + require_once 'Zend/Amf/Server/Exception.php'; + throw new Zend_Amf_Server_Exception('Invalid response object'); + } + $this->_response = $response; + return $this; + } + + /** + * get a reference to the Zend_Amf_response instance + * + * @return Zend_Amf_Server_Response + */ + public function getResponse() + { + if (null === ($response = $this->_response)) { + require_once 'Zend/Amf/Response/Http.php'; + $this->setResponse(new Zend_Amf_Response_Http()); + } + return $this->_response; + } + + /** + * Attach a class or object to the server + * + * Class may be either a class name or an instantiated object. Reflection + * is done on the class or object to determine the available public + * methods, and each is attached to the server as and available method. If + * a $namespace has been provided, that namespace is used to prefix + * AMF service call. + * + * @param string|object $class + * @param string $namespace Optional + * @param mixed $arg Optional arguments to pass to a method + * @return Zend_Amf_Server + * @throws Zend_Amf_Server_Exception on invalid input + */ + public function setClass($class, $namespace = '', $argv = null) + { + if (is_string($class) && !class_exists($class)){ + require_once 'Zend/Amf/Server/Exception.php'; + throw new Zend_Amf_Server_Exception('Invalid method or class'); + } elseif (!is_string($class) && !is_object($class)) { + require_once 'Zend/Amf/Server/Exception.php'; + throw new Zend_Amf_Server_Exception('Invalid method or class; must be a classname or object'); + } + + $argv = null; + if (2 < func_num_args()) { + $argv = array_slice(func_get_args(), 2); + } + + // Use the class name as the name space by default. + + if ($namespace == '') { + $namespace = is_object($class) ? get_class($class) : $class; + } + + $this->_classAllowed[is_object($class) ? get_class($class) : $class] = true; + + $this->_methods[] = Zend_Server_Reflection::reflectClass($class, $argv, $namespace); + $this->_buildDispatchTable(); + + return $this; + } + + /** + * Attach a function to the server + * + * Additional arguments to pass to the function at dispatch may be passed; + * any arguments following the namespace will be aggregated and passed at + * dispatch time. + * + * @param string|array $function Valid callback + * @param string $namespace Optional namespace prefix + * @return Zend_Amf_Server + * @throws Zend_Amf_Server_Exception + */ + public function addFunction($function, $namespace = '') + { + if (!is_string($function) && !is_array($function)) { + require_once 'Zend/Amf/Server/Exception.php'; + throw new Zend_Amf_Server_Exception('Unable to attach function'); + } + + $argv = null; + if (2 < func_num_args()) { + $argv = array_slice(func_get_args(), 2); + } + + $function = (array) $function; + foreach ($function as $func) { + if (!is_string($func) || !function_exists($func)) { + require_once 'Zend/Amf/Server/Exception.php'; + throw new Zend_Amf_Server_Exception('Unable to attach function'); + } + $this->_methods[] = Zend_Server_Reflection::reflectFunction($func, $argv, $namespace); + } + + $this->_buildDispatchTable(); + return $this; + } + + + /** + * Creates an array of directories in which services can reside. + * TODO: add support for prefixes? + * + * @param string $dir + */ + public function addDirectory($dir) + { + $this->getLoader()->addPrefixPath("", $dir); + } + + /** + * Returns an array of directories that can hold services. + * + * @return array + */ + public function getDirectory() + { + return $this->getLoader()->getPaths(""); + } + + /** + * (Re)Build the dispatch table + * + * The dispatch table consists of a an array of method name => + * Zend_Server_Reflection_Function_Abstract pairs + * + * @return void + */ + protected function _buildDispatchTable() + { + $table = array(); + foreach ($this->_methods as $key => $dispatchable) { + if ($dispatchable instanceof Zend_Server_Reflection_Function_Abstract) { + $ns = $dispatchable->getNamespace(); + $name = $dispatchable->getName(); + $name = empty($ns) ? $name : $ns . '.' . $name; + + if (isset($table[$name])) { + require_once 'Zend/Amf/Server/Exception.php'; + throw new Zend_Amf_Server_Exception('Duplicate method registered: ' . $name); + } + $table[$name] = $dispatchable; + continue; + } + + if ($dispatchable instanceof Zend_Server_Reflection_Class) { + foreach ($dispatchable->getMethods() as $method) { + $ns = $method->getNamespace(); + $name = $method->getName(); + $name = empty($ns) ? $name : $ns . '.' . $name; + + if (isset($table[$name])) { + require_once 'Zend/Amf/Server/Exception.php'; + throw new Zend_Amf_Server_Exception('Duplicate method registered: ' . $name); + } + $table[$name] = $method; + continue; + } + } + } + $this->_table = $table; + } + + + + /** + * Raise a server fault + * + * Unimplemented + * + * @param string|Exception $fault + * @return void + */ + public function fault($fault = null, $code = 404) + { + } + + /** + * Returns a list of registered methods + * + * Returns an array of dispatchables (Zend_Server_Reflection_Function, + * _Method, and _Class items). + * + * @return array + */ + public function getFunctions() + { + return $this->_table; + } + + /** + * Set server persistence + * + * Unimplemented + * + * @param mixed $mode + * @return void + */ + public function setPersistence($mode) + { + } + + /** + * Load server definition + * + * Unimplemented + * + * @param array $definition + * @return void + */ + public function loadFunctions($definition) + { + } + + /** + * Map ActionScript classes to PHP classes + * + * @param string $asClass + * @param string $phpClass + * @return Zend_Amf_Server + */ + public function setClassMap($asClass, $phpClass) + { + require_once 'Zend/Amf/Parse/TypeLoader.php'; + Zend_Amf_Parse_TypeLoader::setMapping($asClass, $phpClass); + return $this; + } + + /** + * List all available methods + * + * Returns an array of method names. + * + * @return array + */ + public function listMethods() + { + return array_keys($this->_table); + } +} diff --git a/library/Zend/Amf/Server/Exception.php b/library/Zend/Amf/Server/Exception.php new file mode 100644 index 000000000..c970707b2 --- /dev/null +++ b/library/Zend/Amf/Server/Exception.php @@ -0,0 +1,37 @@ +_stream = $stream; + $this->_needle = 0; + $this->_streamLength = strlen($stream); + $this->_bigEndian = (pack('l', 1) === "\x00\x00\x00\x01"); + } + + /** + * Returns the current stream + * + * @return string + */ + public function getStream() + { + return $this->_stream; + } + + /** + * Read the number of bytes in a row for the length supplied. + * + * @todo Should check that there are enough bytes left in the stream we are about to read. + * @param int $length + * @return string + * @throws Zend_Amf_Exception for buffer underrun + */ + public function readBytes($length) + { + if (($length + $this->_needle) > $this->_streamLength) { + require_once 'Zend/Amf/Exception.php'; + throw new Zend_Amf_Exception('Buffer underrun at needle position: ' . $this->_needle . ' while requesting length: ' . $length); + } + $bytes = substr($this->_stream, $this->_needle, $length); + $this->_needle+= $length; + return $bytes; + } + + /** + * Write any length of bytes to the stream + * + * Usually a string. + * + * @param string $bytes + * @return Zend_Amf_Util_BinaryStream + */ + public function writeBytes($bytes) + { + $this->_stream.= $bytes; + return $this; + } + + /** + * Reads a signed byte + * + * @return int Value is in the range of -128 to 127. + */ + public function readByte() + { + if (($this->_needle + 1) > $this->_streamLength) { + require_once 'Zend/Amf/Exception.php'; + throw new Zend_Amf_Exception('Buffer underrun at needle position: ' . $this->_needle . ' while requesting length: ' . $length); + } + + return ord($this->_stream{$this->_needle++}); + } + + /** + * Writes the passed string into a signed byte on the stream. + * + * @param string $stream + * @return Zend_Amf_Util_BinaryStream + */ + public function writeByte($stream) + { + $this->_stream.= pack('c', $stream); + return $this; + } + + /** + * Reads a signed 32-bit integer from the data stream. + * + * @return int Value is in the range of -2147483648 to 2147483647 + */ + public function readInt() + { + return ($this->readByte() << 8) + $this->readByte(); + } + + /** + * Write an the integer to the output stream as a 32 bit signed integer + * + * @param int $stream + * @return Zend_Amf_Util_BinaryStream + */ + public function writeInt($stream) + { + $this->_stream.= pack('n', $stream); + return $this; + } + + /** + * Reads a UTF-8 string from the data stream + * + * @return string A UTF-8 string produced by the byte representation of characters + */ + public function readUtf() + { + $length = $this->readInt(); + return $this->readBytes($length); + } + + /** + * Wite a UTF-8 string to the outputstream + * + * @param string $stream + * @return Zend_Amf_Util_BinaryStream + */ + public function writeUtf($stream) + { + $this->writeInt(strlen($stream)); + $this->_stream.= $stream; + return $this; + } + + + /** + * Read a long UTF string + * + * @return string + */ + public function readLongUtf() + { + $length = $this->readLong(); + return $this->readBytes($length); + } + + /** + * Write a long UTF string to the buffer + * + * @param string $stream + * @return Zend_Amf_Util_BinaryStream + */ + public function writeLongUtf($stream) + { + $this->writeLong(strlen($stream)); + $this->_stream.= $stream; + } + + /** + * Read a long numeric value + * + * @return double + */ + public function readLong() + { + return ($this->readByte() << 24) + ($this->readByte() << 16) + ($this->readByte() << 8) + $this->readByte(); + } + + /** + * Write long numeric value to output stream + * + * @param int|string $stream + * @return Zend_Amf_Util_BinaryStream + */ + public function writeLong($stream) + { + $this->_stream.= pack('N', $stream); + return $this; + } + + /** + * Read a 16 bit unsigned short. + * + * @todo This could use the unpack() w/ S,n, or v + * @return double + */ + public function readUnsignedShort() + { + $byte1 = $this->readByte(); + $byte2 = $this->readByte(); + return (($byte1 << 8) | $byte2); + } + + /** + * Reads an IEEE 754 double-precision floating point number from the data stream. + * + * @return double Floating point number + */ + public function readDouble() + { + $bytes = substr($this->_stream, $this->_needle, 8); + $this->_needle+= 8; + + if (!$this->_bigEndian) { + $bytes = strrev($bytes); + } + + $double = unpack('dflt', $bytes); + return $double['flt']; + } + + /** + * Writes an IEEE 754 double-precision floating point number from the data stream. + * + * @param string|double $stream + * @return Zend_Amf_Util_BinaryStream + */ + public function writeDouble($stream) + { + $stream = pack('d', $stream); + if (!$this->_bigEndian) { + $stream = strrev($stream); + } + $this->_stream.= $stream; + return $this; + } + +} diff --git a/library/Zend/Amf/Value/ByteArray.php b/library/Zend/Amf/Value/ByteArray.php new file mode 100644 index 000000000..f6e9de74b --- /dev/null +++ b/library/Zend/Amf/Value/ByteArray.php @@ -0,0 +1,58 @@ +_data = $data; + } + + /** + * Return the byte stream + * + * @return string + */ + public function getData() + { + return $this->_data; + } +} diff --git a/library/Zend/Amf/Value/MessageBody.php b/library/Zend/Amf/Value/MessageBody.php new file mode 100644 index 000000000..e366291d4 --- /dev/null +++ b/library/Zend/Amf/Value/MessageBody.php @@ -0,0 +1,182 @@ + + * This Message structure defines how a local client would + * invoke a method/operation on a remote server. Additionally, + * the response from the Server is structured identically. + * + * @package Zend_Amf + * @subpackage Value + * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Amf_Value_MessageBody +{ + /** + * A string describing which operation, function, or method + * is to be remotley invoked. + * @var string + */ + protected $_targetUri = ""; + + /** + * Universal Resource Identifier that uniquely targets the originator's + * Object that should receive the server's response. The server will + * use this path specification to target the "OnResult()" or "onStatus()" + * handlers within the client. For Flash, it specifies an ActionScript + * Object path only. The NetResponse object pointed to by the Response Uri + * contains the connection state information. Passing/specifying this + * provides a convenient mechanism for the client/server to share access + * to an object that is managing the state of the shared connection. + * + * Since the server will use this field in the event of an error, + * this field is required even if a successful server request would + * not be expected to return a value to the client. + * + * @var string + */ + protected $_responseUri = ""; + + /** + * Contains the actual data associated with the operation. It contains + * the client's parameter data that is passed to the server's operation/method. + * When serializing a root level data type or a parameter list array, no + * name field is included. That is, the data is anonomously represented + * as "Type Marker"/"Value" pairs. When serializing member data, the data is + * represented as a series of "Name"/"Type"/"Value" combinations. + * + * For server generated responses, it may contain any ActionScript + * data/objects that the server was expected to provide. + * + * @var string + */ + protected $_data; + + /** + * Constructor + * + * @param string $targetUri + * @param string $responseUri + * @param string $data + * @return void + */ + public function __construct($targetUri, $responseUri, $data) + { + $this->setTargetUri($targetUri); + $this->setResponseUri($responseUri); + $this->setData($data); + } + + /** + * Retrieve target Uri + * + * @return string + */ + public function getTargetUri() + { + return $this->_targetUri; + } + + /** + * Set target Uri + * + * @param string $targetUri + * @return Zend_Amf_Value_MessageBody + */ + public function setTargetUri($targetUri) + { + if (null === $targetUri) { + $targetUri = ''; + } + $this->_targetUri = (string) $targetUri; + return $this; + } + + /** + * Get target Uri + * + * @return string + */ + public function getResponseUri() + { + return $this->_responseUri; + } + + /** + * Set response Uri + * + * @param string $responseUri + * @return Zend_Amf_Value_MessageBody + */ + public function setResponseUri($responseUri) + { + if (null === $responseUri) { + $responseUri = ''; + } + $this->_responseUri = $responseUri; + return $this; + } + + /** + * Retrieve response data + * + * @return string + */ + public function getData() + { + return $this->_data; + } + + /** + * Set response data + * + * @param mixed $data + * @return Zend_Amf_Value_MessageBody + */ + public function setData($data) + { + $this->_data = $data; + return $this; + } + + /** + * Set reply method + * + * @param string $methodName + * @return Zend_Amf_Value_MessageBody + */ + public function setReplyMethod($methodName) + { + if (!preg_match('#^[/?]#', $methodName)) { + $this->_targetUri = rtrim($this->_targetUri, '/') . '/'; + } + $this->_targetUri = $this->_targetUri . $methodName; + return $this; + } +} diff --git a/library/Zend/Amf/Value/MessageHeader.php b/library/Zend/Amf/Value/MessageHeader.php new file mode 100644 index 000000000..124b2b3bd --- /dev/null +++ b/library/Zend/Amf/Value/MessageHeader.php @@ -0,0 +1,81 @@ +name = $name; + $this->mustRead = (bool) $mustRead; + $this->data = $data; + if (null !== $length) { + $this->length = (int) $length; + } + } +} diff --git a/library/Zend/Amf/Value/Messaging/AbstractMessage.php b/library/Zend/Amf/Value/Messaging/AbstractMessage.php new file mode 100644 index 000000000..1974405a5 --- /dev/null +++ b/library/Zend/Amf/Value/Messaging/AbstractMessage.php @@ -0,0 +1,92 @@ +clientId = $this->generateId(); + $this->destination = null; + $this->messageId = $this->generateId(); + $this->timestamp = time().'00'; + $this->timeToLive = 0; + $this->headers = new STDClass(); + $this->body = null; + + // correleate the two messages + if ($message && isset($message->messageId)) { + $this->correlationId = $message->messageId; + } + } +} diff --git a/library/Zend/Amf/Value/Messaging/ArrayCollection.php b/library/Zend/Amf/Value/Messaging/ArrayCollection.php new file mode 100644 index 000000000..a862c67c0 --- /dev/null +++ b/library/Zend/Amf/Value/Messaging/ArrayCollection.php @@ -0,0 +1,35 @@ +body + * of the message. + */ + const LOGIN_OPERATION = 8; + + /** + * This operation is used to log the user out of the current channel, and + * will invalidate the server session if the channel is HTTP based. + */ + const LOGOUT_OPERATION = 9; + + /** + * This operation is used to indicate that the client's subscription to a + * remote destination has been invalidated. + */ + const SESSION_INVALIDATE_OPERATION = 10; + + /** + * This operation is used by the MultiTopicConsumer to subscribe/unsubscribe + * from multiple subtopics/selectors in the same message. + */ + const MULTI_SUBSCRIBE_OPERATION = 11; + + /** + * This operation is used to indicate that a channel has disconnected + */ + const DISCONNECT_OPERATION = 12; + + /** + * This is the default operation for new CommandMessage instances. + */ + const UNKNOWN_OPERATION = 10000; + + /** + * The operation to execute for messages of this type + * @var int + */ + public $operation = self::UNKNOWN_OPERATION; +} diff --git a/library/Zend/Amf/Value/Messaging/ErrorMessage.php b/library/Zend/Amf/Value/Messaging/ErrorMessage.php new file mode 100644 index 000000000..93da0e929 --- /dev/null +++ b/library/Zend/Amf/Value/Messaging/ErrorMessage.php @@ -0,0 +1,67 @@ +clientId = $this->generateId(); + $this->destination = null; + $this->messageId = $this->generateId(); + $this->timestamp = time().'00'; + $this->timeToLive = 0; + $this->headers = new stdClass(); + $this->body = null; + } +} diff --git a/library/Zend/Amf/Value/TraitsInfo.php b/library/Zend/Amf/Value/TraitsInfo.php new file mode 100644 index 000000000..ecd6efab5 --- /dev/null +++ b/library/Zend/Amf/Value/TraitsInfo.php @@ -0,0 +1,154 @@ +_className = $className; + $this->_dynamic = $dynamic; + $this->_externalizable = $externalizable; + $this->_properties = $properties; + } + + /** + * Test if the class is a dynamic class + * + * @return boolean + */ + public function isDynamic() + { + return $this->_dynamic; + } + + /** + * Test if class is externalizable + * + * @return boolean + */ + public function isExternalizable() + { + return $this->_externalizable; + } + + /** + * Return the number of properties in the class + * + * @return int + */ + public function length() + { + return count($this->_properties); + } + + /** + * Return the class name + * + * @return string + */ + public function getClassName() + { + return $this->_className; + } + + /** + * Add an additional property + * + * @param string $name + * @return Zend_Amf_Value_TraitsInfo + */ + public function addProperty($name) + { + $this->_properties[] = $name; + return $this; + } + + /** + * Add all properties of the class. + * + * @param array $props + * @return Zend_Amf_Value_TraitsInfo + */ + public function addAllProperties(array $props) + { + $this->_properties = $props; + return $this; + } + + /** + * Get the property at a given index + * + * @param int $index + * @return string + */ + public function getProperty($index) + { + return $this->_properties[(int) $index]; + } + + /** + * Return all properties of the class. + * + * @return array + */ + public function getAllProperties() + { + return $this->_properties; + } +} diff --git a/library/Zend/Application.php b/library/Zend/Application.php new file mode 100644 index 000000000..35739a3fc --- /dev/null +++ b/library/Zend/Application.php @@ -0,0 +1,405 @@ +_environment = (string) $environment; + + require_once 'Zend/Loader/Autoloader.php'; + $this->_autoloader = Zend_Loader_Autoloader::getInstance(); + + if (null !== $options) { + if (is_string($options)) { + $options = $this->_loadConfig($options); + } elseif ($options instanceof Zend_Config) { + $options = $options->toArray(); + } elseif (!is_array($options)) { + throw new Zend_Application_Exception('Invalid options provided; must be location of config file, a config object, or an array'); + } + + $this->setOptions($options); + } + } + + /** + * Retrieve current environment + * + * @return string + */ + public function getEnvironment() + { + return $this->_environment; + } + + /** + * Retrieve autoloader instance + * + * @return Zend_Loader_Autoloader + */ + public function getAutoloader() + { + return $this->_autoloader; + } + + /** + * Set application options + * + * @param array $options + * @throws Zend_Application_Exception When no bootstrap path is provided + * @throws Zend_Application_Exception When invalid bootstrap information are provided + * @return Zend_Application + */ + public function setOptions(array $options) + { + if (!empty($options['config'])) { + if (is_array($options['config'])) { + $_options = array(); + foreach ($options['config'] as $tmp) { + $_options = $this->mergeOptions($_options, $this->_loadConfig($tmp)); + } + $options = $this->mergeOptions($_options, $options); + } else { + $options = $this->mergeOptions($this->_loadConfig($options['config']), $options); + } + } + + $this->_options = $options; + + $options = array_change_key_case($options, CASE_LOWER); + + $this->_optionKeys = array_keys($options); + + if (!empty($options['phpsettings'])) { + $this->setPhpSettings($options['phpsettings']); + } + + if (!empty($options['includepaths'])) { + $this->setIncludePaths($options['includepaths']); + } + + if (!empty($options['autoloadernamespaces'])) { + $this->setAutoloaderNamespaces($options['autoloadernamespaces']); + } + + if (!empty($options['autoloaderzfpath'])) { + $autoloader = $this->getAutoloader(); + if (method_exists($autoloader, 'setZfPath')) { + $zfPath = $options['autoloaderzfpath']; + $zfVersion = !empty($options['autoloaderzfversion']) + ? $options['autoloaderzfversion'] + : 'latest'; + $autoloader->setZfPath($zfPath, $zfVersion); + } + } + + if (!empty($options['bootstrap'])) { + $bootstrap = $options['bootstrap']; + + if (is_string($bootstrap)) { + $this->setBootstrap($bootstrap); + } elseif (is_array($bootstrap)) { + if (empty($bootstrap['path'])) { + throw new Zend_Application_Exception('No bootstrap path provided'); + } + + $path = $bootstrap['path']; + $class = null; + + if (!empty($bootstrap['class'])) { + $class = $bootstrap['class']; + } + + $this->setBootstrap($path, $class); + } else { + throw new Zend_Application_Exception('Invalid bootstrap information provided'); + } + } + + return $this; + } + + /** + * Retrieve application options (for caching) + * + * @return array + */ + public function getOptions() + { + return $this->_options; + } + + /** + * Is an option present? + * + * @param string $key + * @return bool + */ + public function hasOption($key) + { + return in_array(strtolower($key), $this->_optionKeys); + } + + /** + * Retrieve a single option + * + * @param string $key + * @return mixed + */ + public function getOption($key) + { + if ($this->hasOption($key)) { + $options = $this->getOptions(); + $options = array_change_key_case($options, CASE_LOWER); + return $options[strtolower($key)]; + } + return null; + } + + /** + * Merge options recursively + * + * @param array $array1 + * @param mixed $array2 + * @return array + */ + public function mergeOptions(array $array1, $array2 = null) + { + if (is_array($array2)) { + foreach ($array2 as $key => $val) { + if (is_array($array2[$key])) { + $array1[$key] = (array_key_exists($key, $array1) && is_array($array1[$key])) + ? $this->mergeOptions($array1[$key], $array2[$key]) + : $array2[$key]; + } else { + $array1[$key] = $val; + } + } + } + return $array1; + } + + /** + * Set PHP configuration settings + * + * @param array $settings + * @param string $prefix Key prefix to prepend to array values (used to map . separated INI values) + * @return Zend_Application + */ + public function setPhpSettings(array $settings, $prefix = '') + { + foreach ($settings as $key => $value) { + $key = empty($prefix) ? $key : $prefix . $key; + if (is_scalar($value)) { + ini_set($key, $value); + } elseif (is_array($value)) { + $this->setPhpSettings($value, $key . '.'); + } + } + + return $this; + } + + /** + * Set include path + * + * @param array $paths + * @return Zend_Application + */ + public function setIncludePaths(array $paths) + { + $path = implode(PATH_SEPARATOR, $paths); + set_include_path($path . PATH_SEPARATOR . get_include_path()); + return $this; + } + + /** + * Set autoloader namespaces + * + * @param array $namespaces + * @return Zend_Application + */ + public function setAutoloaderNamespaces(array $namespaces) + { + $autoloader = $this->getAutoloader(); + + foreach ($namespaces as $namespace) { + $autoloader->registerNamespace($namespace); + } + + return $this; + } + + /** + * Set bootstrap path/class + * + * @param string $path + * @param string $class + * @return Zend_Application + */ + public function setBootstrap($path, $class = null) + { + // setOptions() can potentially send a null value; specify default + // here + if (null === $class) { + $class = 'Bootstrap'; + } + + if (!class_exists($class, false)) { + require_once $path; + if (!class_exists($class, false)) { + throw new Zend_Application_Exception('Bootstrap class not found'); + } + } + $this->_bootstrap = new $class($this); + + if (!$this->_bootstrap instanceof Zend_Application_Bootstrap_Bootstrapper) { + throw new Zend_Application_Exception('Bootstrap class does not implement Zend_Application_Bootstrap_Bootstrapper'); + } + + return $this; + } + + /** + * Get bootstrap object + * + * @return Zend_Application_Bootstrap_BootstrapAbstract + */ + public function getBootstrap() + { + if (null === $this->_bootstrap) { + $this->_bootstrap = new Zend_Application_Bootstrap_Bootstrap($this); + } + return $this->_bootstrap; + } + + /** + * Bootstrap application + * + * @param null|string|array $resource + * @return Zend_Application + */ + public function bootstrap($resource = null) + { + $this->getBootstrap()->bootstrap($resource); + return $this; + } + + /** + * Run the application + * + * @return void + */ + public function run() + { + $this->getBootstrap()->run(); + } + + /** + * Load configuration file of options + * + * @param string $file + * @throws Zend_Application_Exception When invalid configuration file is provided + * @return array + */ + protected function _loadConfig($file) + { + $environment = $this->getEnvironment(); + $suffix = strtolower(pathinfo($file, PATHINFO_EXTENSION)); + + switch ($suffix) { + case 'ini': + $config = new Zend_Config_Ini($file, $environment); + break; + + case 'xml': + $config = new Zend_Config_Xml($file, $environment); + break; + + case 'php': + case 'inc': + $config = include $file; + if (!is_array($config)) { + throw new Zend_Application_Exception('Invalid configuration file provided; PHP file does not return array value'); + } + return $config; + break; + + default: + throw new Zend_Application_Exception('Invalid configuration file provided; unknown config type'); + } + + return $config->toArray(); + } +} diff --git a/library/Zend/Application/Bootstrap/Bootstrap.php b/library/Zend/Application/Bootstrap/Bootstrap.php new file mode 100644 index 000000000..b51ef29bd --- /dev/null +++ b/library/Zend/Application/Bootstrap/Bootstrap.php @@ -0,0 +1,156 @@ +hasOption('resourceloader')) { + $this->setOptions(array( + 'resourceloader' => $application->getOption('resourceloader') + )); + } + $this->getResourceLoader(); + + if (!$this->hasPluginResource('FrontController')) { + $this->registerPluginResource('FrontController'); + } + } + + /** + * Run the application + * + * Checks to see that we have a default controller directory. If not, an + * exception is thrown. + * + * If so, it registers the bootstrap with the 'bootstrap' parameter of + * the front controller, and dispatches the front controller. + * + * @return mixed + * @throws Zend_Application_Bootstrap_Exception + */ + public function run() + { + $front = $this->getResource('FrontController'); + $default = $front->getDefaultModule(); + if (null === $front->getControllerDirectory($default)) { + throw new Zend_Application_Bootstrap_Exception( + 'No default controller directory registered with front controller' + ); + } + + $front->setParam('bootstrap', $this); + $response = $front->dispatch(); + if ($front->returnResponse()) { + return $response; + } + } + + /** + * Set module resource loader + * + * @param Zend_Loader_Autoloader_Resource $loader + * @return Zend_Application_Module_Bootstrap + */ + public function setResourceLoader(Zend_Loader_Autoloader_Resource $loader) + { + $this->_resourceLoader = $loader; + return $this; + } + + /** + * Retrieve module resource loader + * + * @return Zend_Loader_Autoloader_Resource + */ + public function getResourceLoader() + { + if ((null === $this->_resourceLoader) + && (false !== ($namespace = $this->getAppNamespace())) + ) { + $r = new ReflectionClass($this); + $path = $r->getFileName(); + $this->setResourceLoader(new Zend_Application_Module_Autoloader(array( + 'namespace' => $namespace, + 'basePath' => dirname($path), + ))); + } + return $this->_resourceLoader; + } + + /** + * Get application namespace (used for module autoloading) + * + * @return string + */ + public function getAppNamespace() + { + return $this->_appNamespace; + } + + /** + * Set application namespace (for module autoloading) + * + * @param string + * @return Zend_Application_Bootstrap_Bootstrap + */ + public function setAppNamespace($value) + { + $this->_appNamespace = (string) $value; + return $this; + } +} diff --git a/library/Zend/Application/Bootstrap/BootstrapAbstract.php b/library/Zend/Application/Bootstrap/BootstrapAbstract.php new file mode 100644 index 000000000..18172aab2 --- /dev/null +++ b/library/Zend/Application/Bootstrap/BootstrapAbstract.php @@ -0,0 +1,768 @@ +setApplication($application); + $options = $application->getOptions(); + $this->setOptions($options); + } + + /** + * Set class state + * + * @param array $options + * @return Zend_Application_Bootstrap_BootstrapAbstract + */ + public function setOptions(array $options) + { + $this->_options = $this->mergeOptions($this->_options, $options); + + $options = array_change_key_case($options, CASE_LOWER); + $this->_optionKeys = array_merge($this->_optionKeys, array_keys($options)); + + $methods = get_class_methods($this); + foreach ($methods as $key => $method) { + $methods[$key] = strtolower($method); + } + + if (array_key_exists('pluginpaths', $options)) { + $pluginLoader = $this->getPluginLoader(); + + foreach ($options['pluginpaths'] as $prefix => $path) { + $pluginLoader->addPrefixPath($prefix, $path); + } + unset($options['pluginpaths']); + } + + foreach ($options as $key => $value) { + $method = 'set' . strtolower($key); + + if (in_array($method, $methods)) { + $this->$method($value); + } elseif ('resources' == $key) { + foreach ($value as $resource => $resourceOptions) { + $this->registerPluginResource($resource, $resourceOptions); + } + } + } + return $this; + } + + /** + * Get current options from bootstrap + * + * @return array + */ + public function getOptions() + { + return $this->_options; + } + + /** + * Is an option present? + * + * @param string $key + * @return bool + */ + public function hasOption($key) + { + return in_array($key, $this->_optionKeys); + } + + /** + * Retrieve a single option + * + * @param string $key + * @return mixed + */ + public function getOption($key) + { + if ($this->hasOption($key)) { + $options = $this->getOptions(); + $options = array_change_key_case($options, CASE_LOWER); + return $options[strtolower($key)]; + } + return null; + } + + /** + * Merge options recursively + * + * @param array $array1 + * @param mixed $array2 + * @return array + */ + public function mergeOptions(array $array1, $array2 = null) + { + if (is_array($array2)) { + foreach ($array2 as $key => $val) { + if (is_array($array2[$key])) { + $array1[$key] = (array_key_exists($key, $array1) && is_array($array1[$key])) + ? $this->mergeOptions($array1[$key], $array2[$key]) + : $array2[$key]; + } else { + $array1[$key] = $val; + } + } + } + return $array1; + } + + /** + * Get class resources (as resource/method pairs) + * + * Uses get_class_methods() by default, reflection on prior to 5.2.6, + * as a bug prevents the usage of get_class_methods() there. + * + * @return array + */ + public function getClassResources() + { + if (null === $this->_classResources) { + if (version_compare(PHP_VERSION, '5.2.6') === -1) { + $class = new ReflectionObject($this); + $classMethods = $class->getMethods(); + $methodNames = array(); + + foreach ($classMethods as $method) { + $methodNames[] = $method->getName(); + } + } else { + $methodNames = get_class_methods($this); + } + + $this->_classResources = array(); + foreach ($methodNames as $method) { + if (5 < strlen($method) && '_init' === substr($method, 0, 5)) { + $this->_classResources[strtolower(substr($method, 5))] = $method; + } + } + } + + return $this->_classResources; + } + + /** + * Get class resource names + * + * @return array + */ + public function getClassResourceNames() + { + $resources = $this->getClassResources(); + return array_keys($resources); + } + + /** + * Register a new resource plugin + * + * @param string|Zend_Application_Resource_Resource $resource + * @param mixed $options + * @return Zend_Application_Bootstrap_BootstrapAbstract + * @throws Zend_Application_Bootstrap_Exception When invalid resource is provided + */ + public function registerPluginResource($resource, $options = null) + { + if ($resource instanceof Zend_Application_Resource_Resource) { + $resource->setBootstrap($this); + $pluginName = $this->_resolvePluginResourceName($resource); + $this->_pluginResources[$pluginName] = $resource; + return $this; + } + + if (!is_string($resource)) { + throw new Zend_Application_Bootstrap_Exception('Invalid resource provided to ' . __METHOD__); + } + + $this->_pluginResources[$resource] = $options; + return $this; + } + + /** + * Unregister a resource from the bootstrap + * + * @param string|Zend_Application_Resource_Resource $resource + * @return Zend_Application_Bootstrap_BootstrapAbstract + * @throws Zend_Application_Bootstrap_Exception When unknown resource type is provided + */ + public function unregisterPluginResource($resource) + { + if ($resource instanceof Zend_Application_Resource_Resource) { + if ($index = array_search($resource, $this->_pluginResources, true)) { + unset($this->_pluginResources[$index]); + } + return $this; + } + + if (!is_string($resource)) { + throw new Zend_Application_Bootstrap_Exception('Unknown resource type provided to ' . __METHOD__); + } + + $resource = strtolower($resource); + if (array_key_exists($resource, $this->_pluginResources)) { + unset($this->_pluginResources[$resource]); + } + + return $this; + } + + /** + * Is the requested plugin resource registered? + * + * @param string $resource + * @return bool + */ + public function hasPluginResource($resource) + { + return (null !== $this->getPluginResource($resource)); + } + + /** + * Get a registered plugin resource + * + * @param string $resourceName + * @return Zend_Application_Resource_Resource + */ + public function getPluginResource($resource) + { + if (array_key_exists(strtolower($resource), $this->_pluginResources)) { + $resource = strtolower($resource); + if (!$this->_pluginResources[$resource] instanceof Zend_Application_Resource_Resource) { + $resourceName = $this->_loadPluginResource($resource, $this->_pluginResources[$resource]); + if (!$resourceName) { + throw new Zend_Application_Bootstrap_Exception(sprintf('Unable to resolve plugin "%s"; no corresponding plugin with that name', $resource)); + } + $resource = $resourceName; + } + return $this->_pluginResources[$resource]; + } + + foreach ($this->_pluginResources as $plugin => $spec) { + if ($spec instanceof Zend_Application_Resource_Resource) { + $pluginName = $this->_resolvePluginResourceName($spec); + if (0 === strcasecmp($resource, $pluginName)) { + unset($this->_pluginResources[$plugin]); + $this->_pluginResources[$pluginName] = $spec; + return $spec; + } + continue; + } + + if (false !== $pluginName = $this->_loadPluginResource($plugin, $spec)) { + if (0 === strcasecmp($resource, $pluginName)) { + return $this->_pluginResources[$pluginName]; + } + } + + if (class_exists($plugin)) { //@SEE ZF-7550 + $spec = (array) $spec; + $spec['bootstrap'] = $this; + $instance = new $plugin($spec); + $pluginName = $this->_resolvePluginResourceName($instance); + unset($this->_pluginResources[$plugin]); + $this->_pluginResources[$pluginName] = $instance; + + if (0 === strcasecmp($resource, $pluginName)) { + return $instance; + } + } + } + + return null; + } + + /** + * Retrieve all plugin resources + * + * @return array + */ + public function getPluginResources() + { + foreach (array_keys($this->_pluginResources) as $resource) { + $this->getPluginResource($resource); + } + return $this->_pluginResources; + } + + /** + * Retrieve plugin resource names + * + * @return array + */ + public function getPluginResourceNames() + { + $this->getPluginResources(); + return array_keys($this->_pluginResources); + } + + /** + * Set plugin loader for loading resources + * + * @param Zend_Loader_PluginLoader_Interface $loader + * @return Zend_Application_Bootstrap_BootstrapAbstract + */ + public function setPluginLoader(Zend_Loader_PluginLoader_Interface $loader) + { + $this->_pluginLoader = $loader; + return $this; + } + + /** + * Get the plugin loader for resources + * + * @return Zend_Loader_PluginLoader_Interface + */ + public function getPluginLoader() + { + if ($this->_pluginLoader === null) { + $options = array( + 'Zend_Application_Resource' => 'Zend/Application/Resource' + ); + + $this->_pluginLoader = new Zend_Loader_PluginLoader($options); + } + + return $this->_pluginLoader; + } + + /** + * Set application/parent bootstrap + * + * @param Zend_Application|Zend_Application_Bootstrap_Bootstrapper $application + * @return Zend_Application_Bootstrap_BootstrapAbstract + */ + public function setApplication($application) + { + if (($application instanceof Zend_Application) + || ($application instanceof Zend_Application_Bootstrap_Bootstrapper) + ) { + if ($application === $this) { + throw new Zend_Application_Bootstrap_Exception('Cannot set application to same object; creates recursion'); + } + $this->_application = $application; + } else { + throw new Zend_Application_Bootstrap_Exception('Invalid application provided to bootstrap constructor (received "' . get_class($application) . '" instance)'); + } + return $this; + } + + /** + * Retrieve parent application instance + * + * @return Zend_Application|Zend_Application_Bootstrap_Bootstrapper + */ + public function getApplication() + { + return $this->_application; + } + + /** + * Retrieve application environment + * + * @return string + */ + public function getEnvironment() + { + if (null === $this->_environment) { + $this->_environment = $this->getApplication()->getEnvironment(); + } + return $this->_environment; + } + + /** + * Set resource container + * + * By default, if a resource callback has a non-null return value, this + * value will be stored in a container using the resource name as the + * key. + * + * Containers must be objects, and must allow setting public properties. + * + * @param object $container + * @return Zend_Application_Bootstrap_BootstrapAbstract + */ + public function setContainer($container) + { + if (!is_object($container)) { + throw new Zend_Application_Bootstrap_Exception('Resource containers must be objects'); + } + $this->_container = $container; + return $this; + } + + /** + * Retrieve resource container + * + * @return object + */ + public function getContainer() + { + if (null === $this->_container) { + $this->setContainer(new Zend_Registry()); + } + return $this->_container; + } + + /** + * Determine if a resource has been stored in the container + * + * During bootstrap resource initialization, you may return a value. If + * you do, it will be stored in the {@link setContainer() container}. + * You can use this method to determine if a value was stored. + * + * @param string $name + * @return bool + */ + public function hasResource($name) + { + $resource = strtolower($name); + $container = $this->getContainer(); + return isset($container->{$resource}); + } + + /** + * Retrieve a resource from the container + * + * During bootstrap resource initialization, you may return a value. If + * you do, it will be stored in the {@link setContainer() container}. + * You can use this method to retrieve that value. + * + * If no value was returned, this will return a null value. + * + * @param string $name + * @return null|mixed + */ + public function getResource($name) + { + $resource = strtolower($name); + $container = $this->getContainer(); + if ($this->hasResource($resource)) { + return $container->{$resource}; + } + return null; + } + + /** + * Implement PHP's magic to retrieve a ressource + * in the bootstrap + * + * @param string $prop + * @return null|mixed + */ + public function __get($prop) + { + return $this->getResource($prop); + } + + /** + * Implement PHP's magic to ask for the + * existence of a ressource in the bootstrap + * + * @param string $prop + * @return bool + */ + public function __isset($prop) + { + return $this->hasResource($prop); + } + + /** + * Bootstrap individual, all, or multiple resources + * + * Marked as final to prevent issues when subclassing and naming the + * child class 'Bootstrap' (in which case, overriding this method + * would result in it being treated as a constructor). + * + * If you need to override this functionality, override the + * {@link _bootstrap()} method. + * + * @param null|string|array $resource + * @return Zend_Application_Bootstrap_BootstrapAbstract + * @throws Zend_Application_Bootstrap_Exception When invalid argument was passed + */ + final public function bootstrap($resource = null) + { + $this->_bootstrap($resource); + return $this; + } + + /** + * Overloading: intercept calls to bootstrap() methods + * + * @param string $method + * @param array $args + * @return void + * @throws Zend_Application_Bootstrap_Exception On invalid method name + */ + public function __call($method, $args) + { + if (9 < strlen($method) && 'bootstrap' === substr($method, 0, 9)) { + $resource = substr($method, 9); + return $this->bootstrap($resource); + } + + throw new Zend_Application_Bootstrap_Exception('Invalid method "' . $method . '"'); + } + + /** + * Bootstrap implementation + * + * This method may be overridden to provide custom bootstrapping logic. + * It is the sole method called by {@link bootstrap()}. + * + * @param null|string|array $resource + * @return void + * @throws Zend_Application_Bootstrap_Exception When invalid argument was passed + */ + protected function _bootstrap($resource = null) + { + if (null === $resource) { + foreach ($this->getClassResourceNames() as $resource) { + $this->_executeResource($resource); + } + + foreach ($this->getPluginResourceNames() as $resource) { + $this->_executeResource($resource); + } + } elseif (is_string($resource)) { + $this->_executeResource($resource); + } elseif (is_array($resource)) { + foreach ($resource as $r) { + $this->_executeResource($r); + } + } else { + throw new Zend_Application_Bootstrap_Exception('Invalid argument passed to ' . __METHOD__); + } + } + + /** + * Execute a resource + * + * Checks to see if the resource has already been run. If not, it searches + * first to see if a local method matches the resource, and executes that. + * If not, it checks to see if a plugin resource matches, and executes that + * if found. + * + * Finally, if not found, it throws an exception. + * + * @param string $resource + * @return void + * @throws Zend_Application_Bootstrap_Exception When resource not found + */ + protected function _executeResource($resource) + { + $resourceName = strtolower($resource); + + if (in_array($resourceName, $this->_run)) { + return; + } + + if (isset($this->_started[$resourceName]) && $this->_started[$resourceName]) { + throw new Zend_Application_Bootstrap_Exception('Circular resource dependency detected'); + } + + $classResources = $this->getClassResources(); + if (array_key_exists($resourceName, $classResources)) { + $this->_started[$resourceName] = true; + $method = $classResources[$resourceName]; + $return = $this->$method(); + unset($this->_started[$resourceName]); + $this->_markRun($resourceName); + + if (null !== $return) { + $this->getContainer()->{$resourceName} = $return; + } + + return; + } + + if ($this->hasPluginResource($resource)) { + $this->_started[$resourceName] = true; + $plugin = $this->getPluginResource($resource); + $return = $plugin->init(); + unset($this->_started[$resourceName]); + $this->_markRun($resourceName); + + if (null !== $return) { + $this->getContainer()->{$resourceName} = $return; + } + + return; + } + + throw new Zend_Application_Bootstrap_Exception('Resource matching "' . $resource . '" not found'); + } + + /** + * Load a plugin resource + * + * @param string $resource + * @param array|object|null $options + * @return string|false + */ + protected function _loadPluginResource($resource, $options) + { + $options = (array) $options; + $options['bootstrap'] = $this; + $className = $this->getPluginLoader()->load(strtolower($resource), false); + + if (!$className) { + return false; + } + + $instance = new $className($options); + + unset($this->_pluginResources[$resource]); + + if (isset($instance->_explicitType)) { + $resource = $instance->_explicitType; + } + $resource = strtolower($resource); + $this->_pluginResources[$resource] = $instance; + + return $resource; + } + + /** + * Mark a resource as having run + * + * @param string $resource + * @return void + */ + protected function _markRun($resource) + { + if (!in_array($resource, $this->_run)) { + $this->_run[] = $resource; + } + } + + /** + * Resolve a plugin resource name + * + * Uses, in order of preference + * - $_explicitType property of resource + * - Short name of resource (if a matching prefix path is found) + * - class name (if none of the above are true) + * + * The name is then cast to lowercase. + * + * @param Zend_Application_Resource_Resource $resource + * @return string + */ + protected function _resolvePluginResourceName($resource) + { + if (isset($resource->_explicitType)) { + $pluginName = $resource->_explicitType; + } else { + $className = get_class($resource); + $pluginName = $className; + $loader = $this->getPluginLoader(); + foreach ($loader->getPaths() as $prefix => $paths) { + if (0 === strpos($className, $prefix)) { + $pluginName = substr($className, strlen($prefix)); + $pluginName = trim($pluginName, '_'); + break; + } + } + } + $pluginName = strtolower($pluginName); + return $pluginName; + } +} diff --git a/library/Zend/Application/Bootstrap/Bootstrapper.php b/library/Zend/Application/Bootstrap/Bootstrapper.php new file mode 100644 index 000000000..09e077e4f --- /dev/null +++ b/library/Zend/Application/Bootstrap/Bootstrapper.php @@ -0,0 +1,94 @@ +initDefaultResourceTypes(); + } + + /** + * Initialize default resource types for module resource classes + * + * @return void + */ + public function initDefaultResourceTypes() + { + $basePath = $this->getBasePath(); + $this->addResourceTypes(array( + 'dbtable' => array( + 'namespace' => 'Model_DbTable', + 'path' => 'models/DbTable', + ), + 'mappers' => array( + 'namespace' => 'Model_Mapper', + 'path' => 'models/mappers', + ), + 'form' => array( + 'namespace' => 'Form', + 'path' => 'forms', + ), + 'model' => array( + 'namespace' => 'Model', + 'path' => 'models', + ), + 'plugin' => array( + 'namespace' => 'Plugin', + 'path' => 'plugins', + ), + 'service' => array( + 'namespace' => 'Service', + 'path' => 'services', + ), + 'viewhelper' => array( + 'namespace' => 'View_Helper', + 'path' => 'views/helpers', + ), + 'viewfilter' => array( + 'namespace' => 'View_Filter', + 'path' => 'views/filters', + ), + )); + $this->setDefaultResourceType('model'); + } +} diff --git a/library/Zend/Application/Module/Bootstrap.php b/library/Zend/Application/Module/Bootstrap.php new file mode 100644 index 000000000..a02546558 --- /dev/null +++ b/library/Zend/Application/Module/Bootstrap.php @@ -0,0 +1,128 @@ +setApplication($application); + + // Use same plugin loader as parent bootstrap + if ($application instanceof Zend_Application_Bootstrap_ResourceBootstrapper) { + $this->setPluginLoader($application->getPluginLoader()); + } + + $key = strtolower($this->getModuleName()); + if ($application->hasOption($key)) { + // Don't run via setOptions() to prevent duplicate initialization + $this->setOptions($application->getOption($key)); + } + + if ($application->hasOption('resourceloader')) { + $this->setOptions(array( + 'resourceloader' => $application->getOption('resourceloader') + )); + } + $this->initResourceLoader(); + + // ZF-6545: ensure front controller resource is loaded + if (!$this->hasPluginResource('FrontController')) { + $this->registerPluginResource('FrontController'); + } + + // ZF-6545: prevent recursive registration of modules + if ($this->hasPluginResource('modules')) { + $this->unregisterPluginResource('modules'); + } + } + + /** + * Ensure resource loader is loaded + * + * @return void + */ + public function initResourceLoader() + { + $this->getResourceLoader(); + } + + /** + * Get default application namespace + * + * Proxies to {@link getModuleName()}, and returns the current module + * name + * + * @return string + */ + public function getAppNamespace() + { + return $this->getModuleName(); + } + + /** + * Retrieve module name + * + * @return string + */ + public function getModuleName() + { + if (empty($this->_moduleName)) { + $class = get_class($this); + if (preg_match('/^([a-z][a-z0-9]*)_/i', $class, $matches)) { + $prefix = $matches[1]; + } else { + $prefix = $class; + } + $this->_moduleName = $prefix; + } + return $this->_moduleName; + } +} diff --git a/library/Zend/Application/Resource/Cachemanager.php b/library/Zend/Application/Resource/Cachemanager.php new file mode 100644 index 000000000..7444b3d2a --- /dev/null +++ b/library/Zend/Application/Resource/Cachemanager.php @@ -0,0 +1,73 @@ +getCacheManager(); + } + + /** + * Retrieve Zend_Cache_Manager instance + * + * @return Zend_Cache_Manager + */ + public function getCacheManager() + { + if (null === $this->_manager) { + $this->_manager = new Zend_Cache_Manager; + + $options = $this->getOptions(); + foreach ($options as $key => $value) { + if ($this->_manager->hasCacheTemplate($key)) { + $this->_manager->setTemplateOptions($key, $value); + } else { + $this->_manager->setCacheTemplate($key, $value); + } + } + } + + return $this->_manager; + } +} diff --git a/library/Zend/Application/Resource/Db.php b/library/Zend/Application/Resource/Db.php new file mode 100644 index 000000000..348b5e81e --- /dev/null +++ b/library/Zend/Application/Resource/Db.php @@ -0,0 +1,161 @@ +_adapter = $adapter; + return $this; + } + + /** + * Adapter type to use + * + * @return string + */ + public function getAdapter() + { + return $this->_adapter; + } + + /** + * Set the adapter params + * + * @param $adapter string + * @return Zend_Application_Resource_Db + */ + public function setParams(array $params) + { + $this->_params = $params; + return $this; + } + + /** + * Adapter parameters + * + * @return array + */ + public function getParams() + { + return $this->_params; + } + + /** + * Set whether to use this as default table adapter + * + * @param boolean $defaultTableAdapter + * @return Zend_Application_Resource_Db + */ + public function setIsDefaultTableAdapter($isDefaultTableAdapter) + { + $this->_isDefaultTableAdapter = $isDefaultTableAdapter; + return $this; + } + + /** + * Is this adapter the default table adapter? + * + * @return void + */ + public function isDefaultTableAdapter() + { + return $this->_isDefaultTableAdapter; + } + + /** + * Retrieve initialized DB connection + * + * @return null|Zend_Db_Adapter_Interface + */ + public function getDbAdapter() + { + if ((null === $this->_db) + && (null !== ($adapter = $this->getAdapter())) + ) { + $this->_db = Zend_Db::factory($adapter, $this->getParams()); + } + return $this->_db; + } + + /** + * Defined by Zend_Application_Resource_Resource + * + * @return Zend_Db_Adapter_Abstract|null + */ + public function init() + { + if (null !== ($db = $this->getDbAdapter())) { + if ($this->isDefaultTableAdapter()) { + Zend_Db_Table::setDefaultAdapter($db); + } + return $db; + } + } +} diff --git a/library/Zend/Application/Resource/Dojo.php b/library/Zend/Application/Resource/Dojo.php new file mode 100644 index 000000000..a54bb3db5 --- /dev/null +++ b/library/Zend/Application/Resource/Dojo.php @@ -0,0 +1,76 @@ +getDojo(); + } + + /** + * Retrieve Dojo View Helper + * + * @return Zend_Dojo_View_Dojo_Container + */ + public function getDojo() + { + if (null === $this->_dojo) { + $this->getBootstrap()->bootstrap('view'); + $view = $this->getBootstrap()->view; + + Zend_Dojo::enableView($view); + $view->dojo()->setOptions($this->getOptions()); + + $this->_dojo = $view->dojo(); + } + + return $this->_dojo; + } +} diff --git a/library/Zend/Application/Resource/Exception.php b/library/Zend/Application/Resource/Exception.php new file mode 100644 index 000000000..91b9d2465 --- /dev/null +++ b/library/Zend/Application/Resource/Exception.php @@ -0,0 +1,35 @@ +getFrontController(); + + foreach ($this->getOptions() as $key => $value) { + switch (strtolower($key)) { + case 'controllerdirectory': + if (is_string($value)) { + $front->setControllerDirectory($value); + } elseif (is_array($value)) { + foreach ($value as $module => $directory) { + $front->addControllerDirectory($directory, $module); + } + } + break; + + case 'modulecontrollerdirectoryname': + $front->setModuleControllerDirectoryName($value); + break; + + case 'moduledirectory': + $front->addModuleDirectory($value); + break; + + case 'defaultcontrollername': + $front->setDefaultControllerName($value); + break; + + case 'defaultaction': + $front->setDefaultAction($value); + break; + + case 'defaultmodule': + $front->setDefaultModule($value); + break; + + case 'baseurl': + if (!empty($value)) { + $front->setBaseUrl($value); + } + break; + + case 'params': + $front->setParams($value); + break; + + case 'plugins': + foreach ((array) $value as $pluginClass) { + $stackIndex = null; + if(is_array($pluginClass)) { + $pluginClass = array_change_key_case($pluginClass, CASE_LOWER); + if(isset($pluginClass['class'])) + { + if(isset($pluginClass['stackindex'])) { + $stackIndex = $pluginClass['stackindex']; + } + + $pluginClass = $pluginClass['class']; + } + } + + $plugin = new $pluginClass(); + $front->registerPlugin($plugin, $stackIndex); + } + break; + + case 'returnresponse': + $front->returnResponse((bool) $value); + break; + + case 'throwexceptions': + $front->throwExceptions((bool) $value); + break; + + case 'actionhelperpaths': + if (is_array($value)) { + foreach ($value as $helperPrefix => $helperPath) { + Zend_Controller_Action_HelperBroker::addPath($helperPath, $helperPrefix); + } + } + break; + + default: + $front->setParam($key, $value); + break; + } + } + + if (null !== ($bootstrap = $this->getBootstrap())) { + $this->getBootstrap()->frontController = $front; + } + + return $front; + } + + /** + * Retrieve front controller instance + * + * @return Zend_Controller_Front + */ + public function getFrontController() + { + if (null === $this->_front) { + $this->_front = Zend_Controller_Front::getInstance(); + } + return $this->_front; + } +} diff --git a/library/Zend/Application/Resource/Layout.php b/library/Zend/Application/Resource/Layout.php new file mode 100644 index 000000000..fb4a19b35 --- /dev/null +++ b/library/Zend/Application/Resource/Layout.php @@ -0,0 +1,70 @@ +getBootstrap()->bootstrap('FrontController'); + return $this->getLayout(); + } + + /** + * Retrieve layout object + * + * @return Zend_Layout + */ + public function getLayout() + { + if (null === $this->_layout) { + $this->_layout = Zend_Layout::startMvc($this->getOptions()); + } + return $this->_layout; + } +} diff --git a/library/Zend/Application/Resource/Locale.php b/library/Zend/Application/Resource/Locale.php new file mode 100644 index 000000000..39943d38c --- /dev/null +++ b/library/Zend/Application/Resource/Locale.php @@ -0,0 +1,89 @@ +getLocale(); + } + + + /** + * Retrieve locale object + * + * @return Zend_Locale + */ + public function getLocale() + { + if (null === $this->_locale) { + $options = $this->getOptions(); + if(!isset($options['default'])) { + $this->_locale = new Zend_Locale(); + } elseif(!isset($options['force']) || + (bool) $options['force'] == false) + { + // Don't force any locale, just go for auto detection + Zend_Locale::setDefault($options['default']); + $this->_locale = new Zend_Locale(); + } else { + $this->_locale = new Zend_Locale($options['default']); + } + + $key = (isset($options['registry_key']) && !is_numeric($options['registry_key'])) + ? $options['registry_key'] + : self::DEFAULT_REGISTRY_KEY; + Zend_Registry::set($key, $this->_locale); + } + + return $this->_locale; + } +} diff --git a/library/Zend/Application/Resource/Log.php b/library/Zend/Application/Resource/Log.php new file mode 100644 index 000000000..48a5575d0 --- /dev/null +++ b/library/Zend/Application/Resource/Log.php @@ -0,0 +1,78 @@ +getLog(); + } + + /** + * Attach logger + * + * @param Zend_Log $log + * @return Zend_Application_Resource_Log + */ + public function setLog(Zend_Log $log) + { + $this->_log = $log; + return $this; + } + + public function getLog() + { + if (null === $this->_log) { + $options = $this->getOptions(); + $log = Zend_Log::factory($options); + $this->setLog($log); + } + return $this->_log; + } +} diff --git a/library/Zend/Application/Resource/Mail.php b/library/Zend/Application/Resource/Mail.php new file mode 100644 index 000000000..ba5b359a4 --- /dev/null +++ b/library/Zend/Application/Resource/Mail.php @@ -0,0 +1,146 @@ +getMail(); + } + + /** + * + * @return Zend_Mail_Transport_Abstract|null + */ + public function getMail() + { + if (null === $this->_transport) { + $options = $this->getOptions(); + foreach($options as $key => $option) { + $options[strtolower($key)] = $option; + } + $this->setOptions($options); + + if(isset($options['transport']) && + !is_numeric($options['transport'])) + { + $this->_transport = $this->_setupTransport($options['transport']); + if(!isset($options['transport']['register']) || + $options['transport']['register'] == '1' || + (isset($options['transport']['register']) && + !is_numeric($options['transport']['register']) && + (bool) $options['transport']['register'] == true)) + { + Zend_Mail::setDefaultTransport($this->_transport); + } + } + + $this->_setDefaults('from'); + $this->_setDefaults('replyTo'); + } + + return $this->_transport; + } + + protected function _setDefaults($type) { + $key = strtolower('default' . $type); + $options = $this->getOptions(); + + if(isset($options[$key]['email']) && + !is_numeric($options[$key]['email'])) + { + $method = array('Zend_Mail', 'setDefault' . ucfirst($type)); + if(isset($options[$key]['name']) && + !is_numeric($options[$key]['name'])) + { + call_user_func($method, $options[$key]['email'], + $options[$key]['name']); + } else { + call_user_func($method, $options[$key]['email']); + } + } + } + + protected function _setupTransport($options) + { + if(!isset($options['type'])) { + $options['type'] = 'sendmail'; + } + + $transportName = $options['type']; + if(!Zend_Loader_Autoloader::autoload($transportName)) + { + $transportName = ucfirst(strtolower($transportName)); + + if(!Zend_Loader_Autoloader::autoload($transportName)) + { + $transportName = 'Zend_Mail_Transport_' . $transportName; + if(!Zend_Loader_Autoloader::autoload($transportName)) { + throw new Zend_Application_Resource_Exception( + "Specified Mail Transport '{$transportName}'" + . 'could not be found' + ); + } + } + } + + unset($options['type']); + + switch($transportName) { + case 'Zend_Mail_Transport_Smtp': + if(!isset($options['host'])) { + throw new Zend_Application_Resource_Exception( + 'A host is necessary for smtp transport,' + .' but none was given'); + } + + $transport = new $transportName($options['host'], $options); + break; + case 'Zend_Mail_Transport_Sendmail': + default: + $transport = new $transportName($options); + break; + } + + return $transport; + } +} diff --git a/library/Zend/Application/Resource/Modules.php b/library/Zend/Application/Resource/Modules.php new file mode 100644 index 000000000..f8dd962d5 --- /dev/null +++ b/library/Zend/Application/Resource/Modules.php @@ -0,0 +1,138 @@ +_bootstraps = new ArrayObject(array(), ArrayObject::ARRAY_AS_PROPS); + parent::__construct($options); + } + + /** + * Initialize modules + * + * @return array + * @throws Zend_Application_Resource_Exception When bootstrap class was not found + */ + public function init() + { + $bootstrap = $this->getBootstrap(); + $bootstrap->bootstrap('FrontController'); + $front = $bootstrap->getResource('FrontController'); + + $modules = $front->getControllerDirectory(); + $default = $front->getDefaultModule(); + $curBootstrapClass = get_class($bootstrap); + foreach ($modules as $module => $moduleDirectory) { + $bootstrapClass = $this->_formatModuleName($module) . '_Bootstrap'; + if (!class_exists($bootstrapClass, false)) { + $bootstrapPath = dirname($moduleDirectory) . '/Bootstrap.php'; + if (file_exists($bootstrapPath)) { + $eMsgTpl = 'Bootstrap file found for module "%s" but bootstrap class "%s" not found'; + include_once $bootstrapPath; + if (($default != $module) + && !class_exists($bootstrapClass, false) + ) { + throw new Zend_Application_Resource_Exception(sprintf( + $eMsgTpl, $module, $bootstrapClass + )); + } elseif ($default == $module) { + if (!class_exists($bootstrapClass, false)) { + $bootstrapClass = 'Bootstrap'; + if (!class_exists($bootstrapClass, false)) { + throw new Zend_Application_Resource_Exception(sprintf( + $eMsgTpl, $module, $bootstrapClass + )); + } + } + } + } else { + continue; + } + } + + if ($bootstrapClass == $curBootstrapClass) { + // If the found bootstrap class matches the one calling this + // resource, don't re-execute. + continue; + } + + $moduleBootstrap = new $bootstrapClass($bootstrap); + $moduleBootstrap->bootstrap(); + $this->_bootstraps[$module] = $moduleBootstrap; + } + + return $this->_bootstraps; + } + + /** + * Get bootstraps that have been run + * + * @return ArrayObject + */ + public function getExecutedBootstraps() + { + return $this->_bootstraps; + } + + /** + * Format a module name to the module class prefix + * + * @param string $name + * @return string + */ + protected function _formatModuleName($name) + { + $name = strtolower($name); + $name = str_replace(array('-', '.'), ' ', $name); + $name = ucwords($name); + $name = str_replace(' ', '', $name); + return $name; + } +} diff --git a/library/Zend/Application/Resource/Multidb.php b/library/Zend/Application/Resource/Multidb.php new file mode 100644 index 000000000..226fbd743 --- /dev/null +++ b/library/Zend/Application/Resource/Multidb.php @@ -0,0 +1,168 @@ + + * resources.multidb.db1.adapter = "pdo_mysql" + * resources.multidb.db1.host = "localhost" + * resources.multidb.db1.username = "webuser" + * resources.multidb.db1.password = "XXXX" + * resources.multidb.db1.dbname = "db1" + * resources.multidb.db1.default = true + * + * resources.multidb.db2.adapter = "pdo_pgsql" + * resources.multidb.db2.host = "example.com" + * resources.multidb.db2.username = "dba" + * resources.multidb.db2.password = "notthatpublic" + * resources.multidb.db2.dbname = "db2" + *
    + * + * @category Zend + * @package Zend_Application + * @subpackage Resource + * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Application_Resource_Multidb extends Zend_Application_Resource_ResourceAbstract +{ + /** + * Associative array containing all configured db's + * + * @var array + */ + protected $_dbs = array(); + + /** + * An instance of the default db, if set + * + * @var null|Zend_Db_Adapter_Abstract + */ + protected $_defaultDb; + + /** + * Initialize the Database Connections (instances of Zend_Db_Table_Abstract) + * + * @return Zend_Application_Resource_Multidb + */ + public function init() + { + $options = $this->getOptions(); + + foreach ($options as $id => $params) { + $adapter = $params['adapter']; + $default = isset($params['default'])?(int)$params['default']:false; + unset($params['adapter'], $params['default']); + + $this->_dbs[$id] = Zend_Db::factory($adapter, $params); + + if ($default + // For consistency with the Db Resource Plugin + || (isset($params['isDefaultTableAdapter']) + && $params['isDefaultTableAdapter'] == true) + ) { + $this->_setDefault($this->_dbs[$id]); + } + } + + return $this; + } + + /** + * Determine if the given db(identifier) is the default db. + * + * @param string|Zend_Db_Adapter_Abstract $db The db to determine whether it's set as default + * @return boolean True if the given parameter is configured as default. False otherwise + */ + public function isDefault($db) + { + if(!$db instanceof Zend_Db_Adapter_Abstract) { + $db = $this->getDb($db); + } + + return $db === $this->_defaultDb; + } + + /** + * Retrieve the specified database connection + * + * @param null|string|Zend_Db_Adapter_Abstract $db The adapter to retrieve. + * Null to retrieve the default connection + * @return Zend_Db_Adapter_Abstract + * @throws Zend_Application_Resource_Exception if the given parameter could not be found + */ + public function getDb($db = null) + { + if ($db === null) { + return $this->getDefaultDb(); + } + + if (isset($this->_dbs[$db])) { + return $this->_dbs[$db]; + } + + throw new Zend_Application_Resource_Exception( + 'A DB adapter was tried to retrieve, but was not configured' + ); + } + + /** + * Get the default db connection + * + * @param boolean $justPickOne If true, a random (the first one in the stack) + * connection is returned if no default was set. + * If false, null is returned if no default was set. + * @return null|Zend_Db_Adapter_Abstract + */ + public function getDefaultDb($justPickOne = true) + { + if ($this->_defaultDb !== null) { + return $this->_defaultDb; + } + + if ($justPickOne) { + return reset($this->_dbs); // Return first db in db pool + } + + return null; + } + + /** + * Set the default db adapter + * + * @var Zend_Db_Adapter_Abstract $adapter Adapter to set as default + */ + protected function _setDefault(Zend_Db_Adapter_Abstract $adapter) + { + Zend_Db_Table::setDefaultAdapter($adapter); + $this->_defaultDb = $adapter; + } +} diff --git a/library/Zend/Application/Resource/Navigation.php b/library/Zend/Application/Resource/Navigation.php new file mode 100644 index 000000000..566b20aa0 --- /dev/null +++ b/library/Zend/Application/Resource/Navigation.php @@ -0,0 +1,123 @@ +_container) { + $options = $this->getOptions(); + $pages = isset($options['pages']) ? $options['pages'] : array(); + $this->_container = new Zend_Navigation($pages); + } + + $this->store(); + return $this->_container; + } + + /** + * Stores navigation container in registry or Navigation view helper + * + * @return void + */ + public function store() + { + $options = $this->getOptions(); + if (isset($options['storage']['registry']) && + $options['storage']['registry'] == true) { + $this->_storeRegistry(); + } else { + $this->_storeHelper(); + } + } + + /** + * Stores navigation container in the registry + * + * @return void + */ + protected function _storeRegistry() + { + $options = $this->getOptions(); + if(isset($options['storage']['registry']['key']) && + !is_numeric($options['storage']['registry']['key'])) // see ZF-7461 + { + $key = $options['storage']['registry']['key']; + } else { + $key = self::DEFAULT_REGISTRY_KEY; + } + + Zend_Registry::set($key,$this->getContainer()); + } + + /** + * Stores navigation container in the Navigation helper + * + * @return void + */ + protected function _storeHelper() + { + $this->getBootstrap()->bootstrap('view'); + $view = $this->getBootstrap()->view; + $view->getHelper('navigation')->navigation($this->getContainer()); + } + + /** + * Returns navigation container + * + * @return Zend_Navigation + */ + public function getContainer() + { + return $this->_container; + } +} diff --git a/library/Zend/Application/Resource/Resource.php b/library/Zend/Application/Resource/Resource.php new file mode 100644 index 000000000..d3246acc3 --- /dev/null +++ b/library/Zend/Application/Resource/Resource.php @@ -0,0 +1,80 @@ +setOptions($options); + } else if ($options instanceof Zend_Config) { + $this->setOptions($options->toArray()); + } + } + + /** + * Set options from array + * + * @param array $options Configuration for resource + * @return Zend_Application_Resource_ResourceAbstract + */ + public function setOptions(array $options) + { + foreach ($options as $key => $value) { + if (in_array(strtolower($key), $this->_skipOptions)) { + continue; + } + + $method = 'set' . strtolower($key); + if (method_exists($this, $method)) { + $this->$method($value); + } + if ('bootstrap' === $key) { + unset($options[$key]); + } + } + + $this->_options = $this->mergeOptions($this->_options, $options); + + return $this; + } + + /** + * Retrieve resource options + * + * @return array + */ + public function getOptions() + { + return $this->_options; + } + + /** + * Merge options recursively + * + * @param array $array1 + * @param mixed $array2 + * @return array + */ + public function mergeOptions(array $array1, $array2 = null) + { + if (is_array($array2)) { + foreach ($array2 as $key => $val) { + if (is_array($array2[$key])) { + $array1[$key] = (array_key_exists($key, $array1) && is_array($array1[$key])) + ? $this->mergeOptions($array1[$key], $array2[$key]) + : $array2[$key]; + } else { + $array1[$key] = $val; + } + } + } + return $array1; + } + + /** + * Set the bootstrap to which the resource is attached + * + * @param Zend_Application_Bootstrap_Bootstrapper $bootstrap + * @return Zend_Application_Resource_Resource + */ + public function setBootstrap(Zend_Application_Bootstrap_Bootstrapper $bootstrap) + { + $this->_bootstrap = $bootstrap; + return $this; + } + + /** + * Retrieve the bootstrap to which the resource is attached + * + * @return null|Zend_Application_Bootstrap_Bootstrapper + */ + public function getBootstrap() + { + return $this->_bootstrap; + } +} diff --git a/library/Zend/Application/Resource/Router.php b/library/Zend/Application/Resource/Router.php new file mode 100644 index 000000000..1a73fc915 --- /dev/null +++ b/library/Zend/Application/Resource/Router.php @@ -0,0 +1,87 @@ +getRouter(); + } + + /** + * Retrieve router object + * + * @return Zend_Controller_Router_Rewrite + */ + public function getRouter() + { + if (null === $this->_router) { + $bootstrap = $this->getBootstrap(); + $bootstrap->bootstrap('FrontController'); + $this->_router = $bootstrap->getContainer()->frontcontroller->getRouter(); + + $options = $this->getOptions(); + if (!isset($options['routes'])) { + $options['routes'] = array(); + } + + if (isset($options['chainNameSeparator'])) { + $this->_router->setChainNameSeparator($options['chainNameSeparator']); + } + + if (isset($options['useRequestParametersAsGlobal'])) { + $this->_router->useRequestParametersAsGlobal($options['useRequestParametersAsGlobal']); + } + + $this->_router->addConfig(new Zend_Config($options['routes'])); + } + + return $this->_router; + } +} diff --git a/library/Zend/Application/Resource/Session.php b/library/Zend/Application/Resource/Session.php new file mode 100644 index 000000000..306b676f2 --- /dev/null +++ b/library/Zend/Application/Resource/Session.php @@ -0,0 +1,118 @@ +_saveHandler = $saveHandler; + return $this; + } + + /** + * Get session save handler + * + * @return Zend_Session_SaveHandler_Interface + */ + public function getSaveHandler() + { + if (!$this->_saveHandler instanceof Zend_Session_SaveHandler_Interface) { + if (is_array($this->_saveHandler)) { + if (!array_key_exists('class', $this->_saveHandler)) { + throw new Zend_Application_Resource_Exception('Session save handler class not provided in options'); + } + $options = array(); + if (array_key_exists('options', $this->_saveHandler)) { + $options = $this->_saveHandler['options']; + } + $this->_saveHandler = $this->_saveHandler['class']; + $this->_saveHandler = new $this->_saveHandler($options); + } elseif (is_string($this->_saveHandler)) { + $this->_saveHandler = new $this->_saveHandler(); + } + + if (!$this->_saveHandler instanceof Zend_Session_SaveHandler_Interface) { + throw new Zend_Application_Resource_Exception('Invalid session save handler'); + } + } + return $this->_saveHandler; + } + + /** + * @return bool + */ + protected function _hasSaveHandler() + { + return ($this->_saveHandler !== null); + } + + /** + * Defined by Zend_Application_Resource_Resource + * + * @return void + */ + public function init() + { + $options = array_change_key_case($this->getOptions(), CASE_LOWER); + if (isset($options['savehandler'])) { + unset($options['savehandler']); + } + + if (count($options) > 0) { + Zend_Session::setOptions($options); + } + + if ($this->_hasSaveHandler()) { + Zend_Session::setSaveHandler($this->getSaveHandler()); + } + } +} diff --git a/library/Zend/Application/Resource/Translate.php b/library/Zend/Application/Resource/Translate.php new file mode 100644 index 000000000..759d5f44f --- /dev/null +++ b/library/Zend/Application/Resource/Translate.php @@ -0,0 +1,105 @@ +getTranslate(); + } + + /** + * Retrieve translate object + * + * @return Zend_Translate + * @throws Zend_Application_Resource_Exception if registry key was used + * already but is no instance of Zend_Translate + */ + public function getTranslate() + { + if (null === $this->_translate) { + $options = $this->getOptions(); + + if (!isset($options['data'])) { + require_once 'Zend/Application/Resource/Exception.php'; + throw new Zend_Application_Resource_Exception('No translation source data provided.'); + } + + $adapter = isset($options['adapter']) ? $options['adapter'] : Zend_Translate::AN_ARRAY; + $locale = isset($options['locale']) ? $options['locale'] : null; + $translateOptions = isset($options['options']) ? $options['options'] : array(); + + $key = (isset($options['registry_key']) && !is_numeric($options['registry_key'])) + ? $options['registry_key'] + : self::DEFAULT_REGISTRY_KEY; + + if(Zend_Registry::isRegistered($key)) { + $translate = Zend_Registry::get($key); + if(!$translate instanceof Zend_Translate) { + require_once 'Zend/Application/Resource/Exception.php'; + throw new Zend_Application_Resource_Exception($key + . ' already registered in registry but is ' + . 'no instance of Zend_Translate'); + } + + $translate->addTranslation($options['data'], $locale, $options); + $this->_translate = $translate; + } else { + $this->_translate = new Zend_Translate( + $adapter, $options['data'], $locale, $translateOptions + ); + + Zend_Registry::set($key, $this->_translate); + } + } + + return $this->_translate; + } +} diff --git a/library/Zend/Application/Resource/View.php b/library/Zend/Application/Resource/View.php new file mode 100644 index 000000000..2550a94ce --- /dev/null +++ b/library/Zend/Application/Resource/View.php @@ -0,0 +1,78 @@ +getView(); + + $viewRenderer = new Zend_Controller_Action_Helper_ViewRenderer(); + $viewRenderer->setView($view); + Zend_Controller_Action_HelperBroker::addHelper($viewRenderer); + return $view; + } + + /** + * Retrieve view object + * + * @return Zend_View + */ + public function getView() + { + if (null === $this->_view) { + $options = $this->getOptions(); + $this->_view = new Zend_View($options); + + if(isset($options['doctype'])) { + $this->_view->doctype()->setDoctype(strtoupper($options['doctype'])); + } + } + return $this->_view; + } +} diff --git a/library/Zend/Auth.php b/library/Zend/Auth.php new file mode 100644 index 000000000..5bd2b96d4 --- /dev/null +++ b/library/Zend/Auth.php @@ -0,0 +1,169 @@ +_storage) { + /** + * @see Zend_Auth_Storage_Session + */ + require_once 'Zend/Auth/Storage/Session.php'; + $this->setStorage(new Zend_Auth_Storage_Session()); + } + + return $this->_storage; + } + + /** + * Sets the persistent storage handler + * + * @param Zend_Auth_Storage_Interface $storage + * @return Zend_Auth Provides a fluent interface + */ + public function setStorage(Zend_Auth_Storage_Interface $storage) + { + $this->_storage = $storage; + return $this; + } + + /** + * Authenticates against the supplied adapter + * + * @param Zend_Auth_Adapter_Interface $adapter + * @return Zend_Auth_Result + */ + public function authenticate(Zend_Auth_Adapter_Interface $adapter) + { + $result = $adapter->authenticate(); + + /** + * ZF-7546 - prevent multiple succesive calls from storing inconsistent results + * Ensure storage has clean state + */ + if ($this->hasIdentity()) { + $this->clearIdentity(); + } + + if ($result->isValid()) { + $this->getStorage()->write($result->getIdentity()); + } + + return $result; + } + + /** + * Returns true if and only if an identity is available from storage + * + * @return boolean + */ + public function hasIdentity() + { + return !$this->getStorage()->isEmpty(); + } + + /** + * Returns the identity from storage or null if no identity is available + * + * @return mixed|null + */ + public function getIdentity() + { + $storage = $this->getStorage(); + + if ($storage->isEmpty()) { + return null; + } + + return $storage->read(); + } + + /** + * Clears the identity from persistent storage + * + * @return void + */ + public function clearIdentity() + { + $this->getStorage()->clear(); + } +} diff --git a/library/Zend/Auth/Adapter/DbTable.php b/library/Zend/Auth/Adapter/DbTable.php new file mode 100644 index 000000000..908423f64 --- /dev/null +++ b/library/Zend/Auth/Adapter/DbTable.php @@ -0,0 +1,487 @@ +_zendDb = $zendDb; + + if (null !== $tableName) { + $this->setTableName($tableName); + } + + if (null !== $identityColumn) { + $this->setIdentityColumn($identityColumn); + } + + if (null !== $credentialColumn) { + $this->setCredentialColumn($credentialColumn); + } + + if (null !== $credentialTreatment) { + $this->setCredentialTreatment($credentialTreatment); + } + } + + /** + * setTableName() - set the table name to be used in the select query + * + * @param string $tableName + * @return Zend_Auth_Adapter_DbTable Provides a fluent interface + */ + public function setTableName($tableName) + { + $this->_tableName = $tableName; + return $this; + } + + /** + * setIdentityColumn() - set the column name to be used as the identity column + * + * @param string $identityColumn + * @return Zend_Auth_Adapter_DbTable Provides a fluent interface + */ + public function setIdentityColumn($identityColumn) + { + $this->_identityColumn = $identityColumn; + return $this; + } + + /** + * setCredentialColumn() - set the column name to be used as the credential column + * + * @param string $credentialColumn + * @return Zend_Auth_Adapter_DbTable Provides a fluent interface + */ + public function setCredentialColumn($credentialColumn) + { + $this->_credentialColumn = $credentialColumn; + return $this; + } + + /** + * setCredentialTreatment() - allows the developer to pass a parameterized string that is + * used to transform or treat the input credential data. + * + * In many cases, passwords and other sensitive data are encrypted, hashed, encoded, + * obscured, or otherwise treated through some function or algorithm. By specifying a + * parameterized treatment string with this method, a developer may apply arbitrary SQL + * upon input credential data. + * + * Examples: + * + * 'PASSWORD(?)' + * 'MD5(?)' + * + * @param string $treatment + * @return Zend_Auth_Adapter_DbTable Provides a fluent interface + */ + public function setCredentialTreatment($treatment) + { + $this->_credentialTreatment = $treatment; + return $this; + } + + /** + * setIdentity() - set the value to be used as the identity + * + * @param string $value + * @return Zend_Auth_Adapter_DbTable Provides a fluent interface + */ + public function setIdentity($value) + { + $this->_identity = $value; + return $this; + } + + /** + * setCredential() - set the credential value to be used, optionally can specify a treatment + * to be used, should be supplied in parameterized form, such as 'MD5(?)' or 'PASSWORD(?)' + * + * @param string $credential + * @return Zend_Auth_Adapter_DbTable Provides a fluent interface + */ + public function setCredential($credential) + { + $this->_credential = $credential; + return $this; + } + + /** + * getDbSelect() - Return the preauthentication Db Select object for userland select query modification + * + * @return Zend_Db_Select + */ + public function getDbSelect() + { + if ($this->_dbSelect == null) { + $this->_dbSelect = $this->_zendDb->select(); + } + + return $this->_dbSelect; + } + + /** + * getResultRowObject() - Returns the result row as a stdClass object + * + * @param string|array $returnColumns + * @param string|array $omitColumns + * @return stdClass|boolean + */ + public function getResultRowObject($returnColumns = null, $omitColumns = null) + { + if (!$this->_resultRow) { + return false; + } + + $returnObject = new stdClass(); + + if (null !== $returnColumns) { + + $availableColumns = array_keys($this->_resultRow); + foreach ( (array) $returnColumns as $returnColumn) { + if (in_array($returnColumn, $availableColumns)) { + $returnObject->{$returnColumn} = $this->_resultRow[$returnColumn]; + } + } + return $returnObject; + + } elseif (null !== $omitColumns) { + + $omitColumns = (array) $omitColumns; + foreach ($this->_resultRow as $resultColumn => $resultValue) { + if (!in_array($resultColumn, $omitColumns)) { + $returnObject->{$resultColumn} = $resultValue; + } + } + return $returnObject; + + } else { + + foreach ($this->_resultRow as $resultColumn => $resultValue) { + $returnObject->{$resultColumn} = $resultValue; + } + return $returnObject; + + } + } + + /** + * authenticate() - defined by Zend_Auth_Adapter_Interface. This method is called to + * attempt an authentication. Previous to this call, this adapter would have already + * been configured with all necessary information to successfully connect to a database + * table and attempt to find a record matching the provided identity. + * + * @throws Zend_Auth_Adapter_Exception if answering the authentication query is impossible + * @return Zend_Auth_Result + */ + public function authenticate() + { + $this->_authenticateSetup(); + $dbSelect = $this->_authenticateCreateSelect(); + $resultIdentities = $this->_authenticateQuerySelect($dbSelect); + + if ( ($authResult = $this->_authenticateValidateResultset($resultIdentities)) instanceof Zend_Auth_Result) { + return $authResult; + } + + $authResult = $this->_authenticateValidateResult(array_shift($resultIdentities)); + return $authResult; + } + + /** + * _authenticateSetup() - This method abstracts the steps involved with + * making sure that this adapter was indeed setup properly with all + * required pieces of information. + * + * @throws Zend_Auth_Adapter_Exception - in the event that setup was not done properly + * @return true + */ + protected function _authenticateSetup() + { + $exception = null; + + if ($this->_tableName == '') { + $exception = 'A table must be supplied for the Zend_Auth_Adapter_DbTable authentication adapter.'; + } elseif ($this->_identityColumn == '') { + $exception = 'An identity column must be supplied for the Zend_Auth_Adapter_DbTable authentication adapter.'; + } elseif ($this->_credentialColumn == '') { + $exception = 'A credential column must be supplied for the Zend_Auth_Adapter_DbTable authentication adapter.'; + } elseif ($this->_identity == '') { + $exception = 'A value for the identity was not provided prior to authentication with Zend_Auth_Adapter_DbTable.'; + } elseif ($this->_credential === null) { + $exception = 'A credential value was not provided prior to authentication with Zend_Auth_Adapter_DbTable.'; + } + + if (null !== $exception) { + /** + * @see Zend_Auth_Adapter_Exception + */ + require_once 'Zend/Auth/Adapter/Exception.php'; + throw new Zend_Auth_Adapter_Exception($exception); + } + + $this->_authenticateResultInfo = array( + 'code' => Zend_Auth_Result::FAILURE, + 'identity' => $this->_identity, + 'messages' => array() + ); + + return true; + } + + /** + * _authenticateCreateSelect() - This method creates a Zend_Db_Select object that + * is completely configured to be queried against the database. + * + * @return Zend_Db_Select + */ + protected function _authenticateCreateSelect() + { + // build credential expression + if (empty($this->_credentialTreatment) || (strpos($this->_credentialTreatment, '?') === false)) { + $this->_credentialTreatment = '?'; + } + + $credentialExpression = new Zend_Db_Expr( + '(CASE WHEN ' . + $this->_zendDb->quoteInto( + $this->_zendDb->quoteIdentifier($this->_credentialColumn, true) + . ' = ' . $this->_credentialTreatment, $this->_credential + ) + . ' THEN 1 ELSE 0 END) AS ' + . $this->_zendDb->quoteIdentifier( + $this->_zendDb->foldCase('zend_auth_credential_match') + ) + ); + + // get select + $dbSelect = clone $this->getDbSelect(); + $dbSelect->from($this->_tableName, array('*', $credentialExpression)) + ->where($this->_zendDb->quoteIdentifier($this->_identityColumn, true) . ' = ?', $this->_identity); + + return $dbSelect; + } + + /** + * _authenticateQuerySelect() - This method accepts a Zend_Db_Select object and + * performs a query against the database with that object. + * + * @param Zend_Db_Select $dbSelect + * @throws Zend_Auth_Adapter_Exception - when an invalid select + * object is encountered + * @return array + */ + protected function _authenticateQuerySelect(Zend_Db_Select $dbSelect) + { + try { + if ($this->_zendDb->getFetchMode() != Zend_DB::FETCH_ASSOC) { + $origDbFetchMode = $this->_zendDb->getFetchMode(); + $this->_zendDb->setFetchMode(Zend_DB::FETCH_ASSOC); + } + $resultIdentities = $this->_zendDb->fetchAll($dbSelect->__toString()); + if (isset($origDbFetchMode)) { + $this->_zendDb->setFetchMode($origDbFetchMode); + unset($origDbFetchMode); + } + } catch (Exception $e) { + /** + * @see Zend_Auth_Adapter_Exception + */ + require_once 'Zend/Auth/Adapter/Exception.php'; + throw new Zend_Auth_Adapter_Exception('The supplied parameters to Zend_Auth_Adapter_DbTable failed to ' + . 'produce a valid sql statement, please check table and column names ' + . 'for validity.', 0, $e); + } + return $resultIdentities; + } + + /** + * _authenticateValidateResultSet() - This method attempts to make + * certain that only one record was returned in the resultset + * + * @param array $resultIdentities + * @return true|Zend_Auth_Result + */ + protected function _authenticateValidateResultSet(array $resultIdentities) + { + + if (count($resultIdentities) < 1) { + $this->_authenticateResultInfo['code'] = Zend_Auth_Result::FAILURE_IDENTITY_NOT_FOUND; + $this->_authenticateResultInfo['messages'][] = 'A record with the supplied identity could not be found.'; + return $this->_authenticateCreateAuthResult(); + } elseif (count($resultIdentities) > 1) { + $this->_authenticateResultInfo['code'] = Zend_Auth_Result::FAILURE_IDENTITY_AMBIGUOUS; + $this->_authenticateResultInfo['messages'][] = 'More than one record matches the supplied identity.'; + return $this->_authenticateCreateAuthResult(); + } + + return true; + } + + /** + * _authenticateValidateResult() - This method attempts to validate that + * the record in the resultset is indeed a record that matched the + * identity provided to this adapter. + * + * @param array $resultIdentity + * @return Zend_Auth_Result + */ + protected function _authenticateValidateResult($resultIdentity) + { + $zendAuthCredentialMatchColumn = $this->_zendDb->foldCase('zend_auth_credential_match'); + + if ($resultIdentity[$zendAuthCredentialMatchColumn] != '1') { + $this->_authenticateResultInfo['code'] = Zend_Auth_Result::FAILURE_CREDENTIAL_INVALID; + $this->_authenticateResultInfo['messages'][] = 'Supplied credential is invalid.'; + return $this->_authenticateCreateAuthResult(); + } + + unset($resultIdentity[$zendAuthCredentialMatchColumn]); + $this->_resultRow = $resultIdentity; + + $this->_authenticateResultInfo['code'] = Zend_Auth_Result::SUCCESS; + $this->_authenticateResultInfo['messages'][] = 'Authentication successful.'; + return $this->_authenticateCreateAuthResult(); + } + + /** + * _authenticateCreateAuthResult() - Creates a Zend_Auth_Result object from + * the information that has been collected during the authenticate() attempt. + * + * @return Zend_Auth_Result + */ + protected function _authenticateCreateAuthResult() + { + return new Zend_Auth_Result( + $this->_authenticateResultInfo['code'], + $this->_authenticateResultInfo['identity'], + $this->_authenticateResultInfo['messages'] + ); + } + +} diff --git a/library/Zend/Auth/Adapter/Digest.php b/library/Zend/Auth/Adapter/Digest.php new file mode 100644 index 000000000..90b79a836 --- /dev/null +++ b/library/Zend/Auth/Adapter/Digest.php @@ -0,0 +1,230 @@ +$methodName($$option); + } + } + } + + /** + * Returns the filename option value or null if it has not yet been set + * + * @return string|null + */ + public function getFilename() + { + return $this->_filename; + } + + /** + * Sets the filename option value + * + * @param mixed $filename + * @return Zend_Auth_Adapter_Digest Provides a fluent interface + */ + public function setFilename($filename) + { + $this->_filename = (string) $filename; + return $this; + } + + /** + * Returns the realm option value or null if it has not yet been set + * + * @return string|null + */ + public function getRealm() + { + return $this->_realm; + } + + /** + * Sets the realm option value + * + * @param mixed $realm + * @return Zend_Auth_Adapter_Digest Provides a fluent interface + */ + public function setRealm($realm) + { + $this->_realm = (string) $realm; + return $this; + } + + /** + * Returns the username option value or null if it has not yet been set + * + * @return string|null + */ + public function getUsername() + { + return $this->_username; + } + + /** + * Sets the username option value + * + * @param mixed $username + * @return Zend_Auth_Adapter_Digest Provides a fluent interface + */ + public function setUsername($username) + { + $this->_username = (string) $username; + return $this; + } + + /** + * Returns the password option value or null if it has not yet been set + * + * @return string|null + */ + public function getPassword() + { + return $this->_password; + } + + /** + * Sets the password option value + * + * @param mixed $password + * @return Zend_Auth_Adapter_Digest Provides a fluent interface + */ + public function setPassword($password) + { + $this->_password = (string) $password; + return $this; + } + + /** + * Defined by Zend_Auth_Adapter_Interface + * + * @throws Zend_Auth_Adapter_Exception + * @return Zend_Auth_Result + */ + public function authenticate() + { + $optionsRequired = array('filename', 'realm', 'username', 'password'); + foreach ($optionsRequired as $optionRequired) { + if (null === $this->{"_$optionRequired"}) { + /** + * @see Zend_Auth_Adapter_Exception + */ + require_once 'Zend/Auth/Adapter/Exception.php'; + throw new Zend_Auth_Adapter_Exception("Option '$optionRequired' must be set before authentication"); + } + } + + if (false === ($fileHandle = @fopen($this->_filename, 'r'))) { + /** + * @see Zend_Auth_Adapter_Exception + */ + require_once 'Zend/Auth/Adapter/Exception.php'; + throw new Zend_Auth_Adapter_Exception("Cannot open '$this->_filename' for reading"); + } + + $id = "$this->_username:$this->_realm"; + $idLength = strlen($id); + + $result = array( + 'code' => Zend_Auth_Result::FAILURE, + 'identity' => array( + 'realm' => $this->_realm, + 'username' => $this->_username, + ), + 'messages' => array() + ); + + while ($line = trim(fgets($fileHandle))) { + if (substr($line, 0, $idLength) === $id) { + if (substr($line, -32) === md5("$this->_username:$this->_realm:$this->_password")) { + $result['code'] = Zend_Auth_Result::SUCCESS; + } else { + $result['code'] = Zend_Auth_Result::FAILURE_CREDENTIAL_INVALID; + $result['messages'][] = 'Password incorrect'; + } + return new Zend_Auth_Result($result['code'], $result['identity'], $result['messages']); + } + } + + $result['code'] = Zend_Auth_Result::FAILURE_IDENTITY_NOT_FOUND; + $result['messages'][] = "Username '$this->_username' and realm '$this->_realm' combination not found"; + return new Zend_Auth_Result($result['code'], $result['identity'], $result['messages']); + } +} diff --git a/library/Zend/Auth/Adapter/Exception.php b/library/Zend/Auth/Adapter/Exception.php new file mode 100644 index 000000000..aee0a014b --- /dev/null +++ b/library/Zend/Auth/Adapter/Exception.php @@ -0,0 +1,38 @@ + 'basic'|'digest'|'basic digest' + * 'realm' => + * 'digest_domains' => Space-delimited list of URIs + * 'nonce_timeout' => + * 'use_opaque' => Whether to send the opaque value in the header + * 'alogrithm' => See $_supportedAlgos. Default: MD5 + * 'proxy_auth' => Whether to do authentication as a Proxy + * @throws Zend_Auth_Adapter_Exception + * @return void + */ + public function __construct(array $config) + { + if (!extension_loaded('hash')) { + /** + * @see Zend_Auth_Adapter_Exception + */ + require_once 'Zend/Auth/Adapter/Exception.php'; + throw new Zend_Auth_Adapter_Exception(__CLASS__ . ' requires the \'hash\' extension'); + } + + $this->_request = null; + $this->_response = null; + $this->_ieNoOpaque = false; + + + if (empty($config['accept_schemes'])) { + /** + * @see Zend_Auth_Adapter_Exception + */ + require_once 'Zend/Auth/Adapter/Exception.php'; + throw new Zend_Auth_Adapter_Exception('Config key \'accept_schemes\' is required'); + } + + $schemes = explode(' ', $config['accept_schemes']); + $this->_acceptSchemes = array_intersect($schemes, $this->_supportedSchemes); + if (empty($this->_acceptSchemes)) { + /** + * @see Zend_Auth_Adapter_Exception + */ + require_once 'Zend/Auth/Adapter/Exception.php'; + throw new Zend_Auth_Adapter_Exception('No supported schemes given in \'accept_schemes\'. Valid values: ' + . implode(', ', $this->_supportedSchemes)); + } + + // Double-quotes are used to delimit the realm string in the HTTP header, + // and colons are field delimiters in the password file. + if (empty($config['realm']) || + !ctype_print($config['realm']) || + strpos($config['realm'], ':') !== false || + strpos($config['realm'], '"') !== false) { + /** + * @see Zend_Auth_Adapter_Exception + */ + require_once 'Zend/Auth/Adapter/Exception.php'; + throw new Zend_Auth_Adapter_Exception('Config key \'realm\' is required, and must contain only printable ' + . 'characters, excluding quotation marks and colons'); + } else { + $this->_realm = $config['realm']; + } + + if (in_array('digest', $this->_acceptSchemes)) { + if (empty($config['digest_domains']) || + !ctype_print($config['digest_domains']) || + strpos($config['digest_domains'], '"') !== false) { + /** + * @see Zend_Auth_Adapter_Exception + */ + require_once 'Zend/Auth/Adapter/Exception.php'; + throw new Zend_Auth_Adapter_Exception('Config key \'digest_domains\' is required, and must contain ' + . 'only printable characters, excluding quotation marks'); + } else { + $this->_domains = $config['digest_domains']; + } + + if (empty($config['nonce_timeout']) || + !is_numeric($config['nonce_timeout'])) { + /** + * @see Zend_Auth_Adapter_Exception + */ + require_once 'Zend/Auth/Adapter/Exception.php'; + throw new Zend_Auth_Adapter_Exception('Config key \'nonce_timeout\' is required, and must be an ' + . 'integer'); + } else { + $this->_nonceTimeout = (int) $config['nonce_timeout']; + } + + // We use the opaque value unless explicitly told not to + if (isset($config['use_opaque']) && false == (bool) $config['use_opaque']) { + $this->_useOpaque = false; + } else { + $this->_useOpaque = true; + } + + if (isset($config['algorithm']) && in_array($config['algorithm'], $this->_supportedAlgos)) { + $this->_algo = $config['algorithm']; + } else { + $this->_algo = 'MD5'; + } + } + + // Don't be a proxy unless explicitly told to do so + if (isset($config['proxy_auth']) && true == (bool) $config['proxy_auth']) { + $this->_imaProxy = true; // I'm a Proxy + } else { + $this->_imaProxy = false; + } + } + + /** + * Setter for the _basicResolver property + * + * @param Zend_Auth_Adapter_Http_Resolver_Interface $resolver + * @return Zend_Auth_Adapter_Http Provides a fluent interface + */ + public function setBasicResolver(Zend_Auth_Adapter_Http_Resolver_Interface $resolver) + { + $this->_basicResolver = $resolver; + + return $this; + } + + /** + * Getter for the _basicResolver property + * + * @return Zend_Auth_Adapter_Http_Resolver_Interface + */ + public function getBasicResolver() + { + return $this->_basicResolver; + } + + /** + * Setter for the _digestResolver property + * + * @param Zend_Auth_Adapter_Http_Resolver_Interface $resolver + * @return Zend_Auth_Adapter_Http Provides a fluent interface + */ + public function setDigestResolver(Zend_Auth_Adapter_Http_Resolver_Interface $resolver) + { + $this->_digestResolver = $resolver; + + return $this; + } + + /** + * Getter for the _digestResolver property + * + * @return Zend_Auth_Adapter_Http_Resolver_Interface + */ + public function getDigestResolver() + { + return $this->_digestResolver; + } + + /** + * Setter for the Request object + * + * @param Zend_Controller_Request_Http $request + * @return Zend_Auth_Adapter_Http Provides a fluent interface + */ + public function setRequest(Zend_Controller_Request_Http $request) + { + $this->_request = $request; + + return $this; + } + + /** + * Getter for the Request object + * + * @return Zend_Controller_Request_Http + */ + public function getRequest() + { + return $this->_request; + } + + /** + * Setter for the Response object + * + * @param Zend_Controller_Response_Http $response + * @return Zend_Auth_Adapter_Http Provides a fluent interface + */ + public function setResponse(Zend_Controller_Response_Http $response) + { + $this->_response = $response; + + return $this; + } + + /** + * Getter for the Response object + * + * @return Zend_Controller_Response_Http + */ + public function getResponse() + { + return $this->_response; + } + + /** + * Authenticate + * + * @throws Zend_Auth_Adapter_Exception + * @return Zend_Auth_Result + */ + public function authenticate() + { + if (empty($this->_request) || + empty($this->_response)) { + /** + * @see Zend_Auth_Adapter_Exception + */ + require_once 'Zend/Auth/Adapter/Exception.php'; + throw new Zend_Auth_Adapter_Exception('Request and Response objects must be set before calling ' + . 'authenticate()'); + } + + if ($this->_imaProxy) { + $getHeader = 'Proxy-Authorization'; + } else { + $getHeader = 'Authorization'; + } + + $authHeader = $this->_request->getHeader($getHeader); + if (!$authHeader) { + return $this->_challengeClient(); + } + + list($clientScheme) = explode(' ', $authHeader); + $clientScheme = strtolower($clientScheme); + + // The server can issue multiple challenges, but the client should + // answer with only the selected auth scheme. + if (!in_array($clientScheme, $this->_supportedSchemes)) { + $this->_response->setHttpResponseCode(400); + return new Zend_Auth_Result( + Zend_Auth_Result::FAILURE_UNCATEGORIZED, + array(), + array('Client requested an incorrect or unsupported authentication scheme') + ); + } + + // client sent a scheme that is not the one required + if (!in_array($clientScheme, $this->_acceptSchemes)) { + // challenge again the client + return $this->_challengeClient(); + } + + switch ($clientScheme) { + case 'basic': + $result = $this->_basicAuth($authHeader); + break; + case 'digest': + $result = $this->_digestAuth($authHeader); + break; + default: + /** + * @see Zend_Auth_Adapter_Exception + */ + require_once 'Zend/Auth/Adapter/Exception.php'; + throw new Zend_Auth_Adapter_Exception('Unsupported authentication scheme'); + } + + return $result; + } + + /** + * Challenge Client + * + * Sets a 401 or 407 Unauthorized response code, and creates the + * appropriate Authenticate header(s) to prompt for credentials. + * + * @return Zend_Auth_Result Always returns a non-identity Auth result + */ + protected function _challengeClient() + { + if ($this->_imaProxy) { + $statusCode = 407; + $headerName = 'Proxy-Authenticate'; + } else { + $statusCode = 401; + $headerName = 'WWW-Authenticate'; + } + + $this->_response->setHttpResponseCode($statusCode); + + // Send a challenge in each acceptable authentication scheme + if (in_array('basic', $this->_acceptSchemes)) { + $this->_response->setHeader($headerName, $this->_basicHeader()); + } + if (in_array('digest', $this->_acceptSchemes)) { + $this->_response->setHeader($headerName, $this->_digestHeader()); + } + return new Zend_Auth_Result( + Zend_Auth_Result::FAILURE_CREDENTIAL_INVALID, + array(), + array('Invalid or absent credentials; challenging client') + ); + } + + /** + * Basic Header + * + * Generates a Proxy- or WWW-Authenticate header value in the Basic + * authentication scheme. + * + * @return string Authenticate header value + */ + protected function _basicHeader() + { + return 'Basic realm="' . $this->_realm . '"'; + } + + /** + * Digest Header + * + * Generates a Proxy- or WWW-Authenticate header value in the Digest + * authentication scheme. + * + * @return string Authenticate header value + */ + protected function _digestHeader() + { + $wwwauth = 'Digest realm="' . $this->_realm . '", ' + . 'domain="' . $this->_domains . '", ' + . 'nonce="' . $this->_calcNonce() . '", ' + . ($this->_useOpaque ? 'opaque="' . $this->_calcOpaque() . '", ' : '') + . 'algorithm="' . $this->_algo . '", ' + . 'qop="' . implode(',', $this->_supportedQops) . '"'; + + return $wwwauth; + } + + /** + * Basic Authentication + * + * @param string $header Client's Authorization header + * @throws Zend_Auth_Adapter_Exception + * @return Zend_Auth_Result + */ + protected function _basicAuth($header) + { + if (empty($header)) { + /** + * @see Zend_Auth_Adapter_Exception + */ + require_once 'Zend/Auth/Adapter/Exception.php'; + throw new Zend_Auth_Adapter_Exception('The value of the client Authorization header is required'); + } + if (empty($this->_basicResolver)) { + /** + * @see Zend_Auth_Adapter_Exception + */ + require_once 'Zend/Auth/Adapter/Exception.php'; + throw new Zend_Auth_Adapter_Exception('A basicResolver object must be set before doing Basic ' + . 'authentication'); + } + + // Decode the Authorization header + $auth = substr($header, strlen('Basic ')); + $auth = base64_decode($auth); + if (!$auth) { + /** + * @see Zend_Auth_Adapter_Exception + */ + require_once 'Zend/Auth/Adapter/Exception.php'; + throw new Zend_Auth_Adapter_Exception('Unable to base64_decode Authorization header value'); + } + + // See ZF-1253. Validate the credentials the same way the digest + // implementation does. If invalid credentials are detected, + // re-challenge the client. + if (!ctype_print($auth)) { + return $this->_challengeClient(); + } + // Fix for ZF-1515: Now re-challenges on empty username or password + $creds = array_filter(explode(':', $auth)); + if (count($creds) != 2) { + return $this->_challengeClient(); + } + + $password = $this->_basicResolver->resolve($creds[0], $this->_realm); + if ($password && $password == $creds[1]) { + $identity = array('username'=>$creds[0], 'realm'=>$this->_realm); + return new Zend_Auth_Result(Zend_Auth_Result::SUCCESS, $identity); + } else { + return $this->_challengeClient(); + } + } + + /** + * Digest Authentication + * + * @param string $header Client's Authorization header + * @throws Zend_Auth_Adapter_Exception + * @return Zend_Auth_Result Valid auth result only on successful auth + */ + protected function _digestAuth($header) + { + if (empty($header)) { + /** + * @see Zend_Auth_Adapter_Exception + */ + require_once 'Zend/Auth/Adapter/Exception.php'; + throw new Zend_Auth_Adapter_Exception('The value of the client Authorization header is required'); + } + if (empty($this->_digestResolver)) { + /** + * @see Zend_Auth_Adapter_Exception + */ + require_once 'Zend/Auth/Adapter/Exception.php'; + throw new Zend_Auth_Adapter_Exception('A digestResolver object must be set before doing Digest authentication'); + } + + $data = $this->_parseDigestAuth($header); + if ($data === false) { + $this->_response->setHttpResponseCode(400); + return new Zend_Auth_Result( + Zend_Auth_Result::FAILURE_UNCATEGORIZED, + array(), + array('Invalid Authorization header format') + ); + } + + // See ZF-1052. This code was a bit too unforgiving of invalid + // usernames. Now, if the username is bad, we re-challenge the client. + if ('::invalid::' == $data['username']) { + return $this->_challengeClient(); + } + + // Verify that the client sent back the same nonce + if ($this->_calcNonce() != $data['nonce']) { + return $this->_challengeClient(); + } + // The opaque value is also required to match, but of course IE doesn't + // play ball. + if (!$this->_ieNoOpaque && $this->_calcOpaque() != $data['opaque']) { + return $this->_challengeClient(); + } + + // Look up the user's password hash. If not found, deny access. + // This makes no assumptions about how the password hash was + // constructed beyond that it must have been built in such a way as + // to be recreatable with the current settings of this object. + $ha1 = $this->_digestResolver->resolve($data['username'], $data['realm']); + if ($ha1 === false) { + return $this->_challengeClient(); + } + + // If MD5-sess is used, a1 value is made of the user's password + // hash with the server and client nonce appended, separated by + // colons. + if ($this->_algo == 'MD5-sess') { + $ha1 = hash('md5', $ha1 . ':' . $data['nonce'] . ':' . $data['cnonce']); + } + + // Calculate h(a2). The value of this hash depends on the qop + // option selected by the client and the supported hash functions + switch ($data['qop']) { + case 'auth': + $a2 = $this->_request->getMethod() . ':' . $data['uri']; + break; + case 'auth-int': + // Should be REQUEST_METHOD . ':' . uri . ':' . hash(entity-body), + // but this isn't supported yet, so fall through to default case + default: + /** + * @see Zend_Auth_Adapter_Exception + */ + require_once 'Zend/Auth/Adapter/Exception.php'; + throw new Zend_Auth_Adapter_Exception('Client requested an unsupported qop option'); + } + // Using hash() should make parameterizing the hash algorithm + // easier + $ha2 = hash('md5', $a2); + + + // Calculate the server's version of the request-digest. This must + // match $data['response']. See RFC 2617, section 3.2.2.1 + $message = $data['nonce'] . ':' . $data['nc'] . ':' . $data['cnonce'] . ':' . $data['qop'] . ':' . $ha2; + $digest = hash('md5', $ha1 . ':' . $message); + + // If our digest matches the client's let them in, otherwise return + // a 401 code and exit to prevent access to the protected resource. + if ($digest == $data['response']) { + $identity = array('username'=>$data['username'], 'realm'=>$data['realm']); + return new Zend_Auth_Result(Zend_Auth_Result::SUCCESS, $identity); + } else { + return $this->_challengeClient(); + } + } + + /** + * Calculate Nonce + * + * @return string The nonce value + */ + protected function _calcNonce() + { + // Once subtle consequence of this timeout calculation is that it + // actually divides all of time into _nonceTimeout-sized sections, such + // that the value of timeout is the point in time of the next + // approaching "boundary" of a section. This allows the server to + // consistently generate the same timeout (and hence the same nonce + // value) across requests, but only as long as one of those + // "boundaries" is not crossed between requests. If that happens, the + // nonce will change on its own, and effectively log the user out. This + // would be surprising if the user just logged in. + $timeout = ceil(time() / $this->_nonceTimeout) * $this->_nonceTimeout; + + $nonce = hash('md5', $timeout . ':' . $this->_request->getServer('HTTP_USER_AGENT') . ':' . __CLASS__); + return $nonce; + } + + /** + * Calculate Opaque + * + * The opaque string can be anything; the client must return it exactly as + * it was sent. It may be useful to store data in this string in some + * applications. Ideally, a new value for this would be generated each time + * a WWW-Authenticate header is sent (in order to reduce predictability), + * but we would have to be able to create the same exact value across at + * least two separate requests from the same client. + * + * @return string The opaque value + */ + protected function _calcOpaque() + { + return hash('md5', 'Opaque Data:' . __CLASS__); + } + + /** + * Parse Digest Authorization header + * + * @param string $header Client's Authorization: HTTP header + * @return array|false Data elements from header, or false if any part of + * the header is invalid + */ + protected function _parseDigestAuth($header) + { + $temp = null; + $data = array(); + + // See ZF-1052. Detect invalid usernames instead of just returning a + // 400 code. + $ret = preg_match('/username="([^"]+)"/', $header, $temp); + if (!$ret || empty($temp[1]) + || !ctype_print($temp[1]) + || strpos($temp[1], ':') !== false) { + $data['username'] = '::invalid::'; + } else { + $data['username'] = $temp[1]; + } + $temp = null; + + $ret = preg_match('/realm="([^"]+)"/', $header, $temp); + if (!$ret || empty($temp[1])) { + return false; + } + if (!ctype_print($temp[1]) || strpos($temp[1], ':') !== false) { + return false; + } else { + $data['realm'] = $temp[1]; + } + $temp = null; + + $ret = preg_match('/nonce="([^"]+)"/', $header, $temp); + if (!$ret || empty($temp[1])) { + return false; + } + if (!ctype_xdigit($temp[1])) { + return false; + } else { + $data['nonce'] = $temp[1]; + } + $temp = null; + + $ret = preg_match('/uri="([^"]+)"/', $header, $temp); + if (!$ret || empty($temp[1])) { + return false; + } + // Section 3.2.2.5 in RFC 2617 says the authenticating server must + // verify that the URI field in the Authorization header is for the + // same resource requested in the Request Line. + $rUri = @parse_url($this->_request->getRequestUri()); + $cUri = @parse_url($temp[1]); + if (false === $rUri || false === $cUri) { + return false; + } else { + // Make sure the path portion of both URIs is the same + if ($rUri['path'] != $cUri['path']) { + return false; + } + // Section 3.2.2.5 seems to suggest that the value of the URI + // Authorization field should be made into an absolute URI if the + // Request URI is absolute, but it's vague, and that's a bunch of + // code I don't want to write right now. + $data['uri'] = $temp[1]; + } + $temp = null; + + $ret = preg_match('/response="([^"]+)"/', $header, $temp); + if (!$ret || empty($temp[1])) { + return false; + } + if (32 != strlen($temp[1]) || !ctype_xdigit($temp[1])) { + return false; + } else { + $data['response'] = $temp[1]; + } + $temp = null; + + // The spec says this should default to MD5 if omitted. OK, so how does + // that square with the algo we send out in the WWW-Authenticate header, + // if it can easily be overridden by the client? + $ret = preg_match('/algorithm="?(' . $this->_algo . ')"?/', $header, $temp); + if ($ret && !empty($temp[1]) + && in_array($temp[1], $this->_supportedAlgos)) { + $data['algorithm'] = $temp[1]; + } else { + $data['algorithm'] = 'MD5'; // = $this->_algo; ? + } + $temp = null; + + // Not optional in this implementation + $ret = preg_match('/cnonce="([^"]+)"/', $header, $temp); + if (!$ret || empty($temp[1])) { + return false; + } + if (!ctype_print($temp[1])) { + return false; + } else { + $data['cnonce'] = $temp[1]; + } + $temp = null; + + // If the server sent an opaque value, the client must send it back + if ($this->_useOpaque) { + $ret = preg_match('/opaque="([^"]+)"/', $header, $temp); + if (!$ret || empty($temp[1])) { + + // Big surprise: IE isn't RFC 2617-compliant. + if (false !== strpos($this->_request->getHeader('User-Agent'), 'MSIE')) { + $temp[1] = ''; + $this->_ieNoOpaque = true; + } else { + return false; + } + } + // This implementation only sends MD5 hex strings in the opaque value + if (!$this->_ieNoOpaque && + (32 != strlen($temp[1]) || !ctype_xdigit($temp[1]))) { + return false; + } else { + $data['opaque'] = $temp[1]; + } + $temp = null; + } + + // Not optional in this implementation, but must be one of the supported + // qop types + $ret = preg_match('/qop="?(' . implode('|', $this->_supportedQops) . ')"?/', $header, $temp); + if (!$ret || empty($temp[1])) { + return false; + } + if (!in_array($temp[1], $this->_supportedQops)) { + return false; + } else { + $data['qop'] = $temp[1]; + } + $temp = null; + + // Not optional in this implementation. The spec says this value + // shouldn't be a quoted string, but apparently some implementations + // quote it anyway. See ZF-1544. + $ret = preg_match('/nc="?([0-9A-Fa-f]{8})"?/', $header, $temp); + if (!$ret || empty($temp[1])) { + return false; + } + if (8 != strlen($temp[1]) || !ctype_xdigit($temp[1])) { + return false; + } else { + $data['nc'] = $temp[1]; + } + $temp = null; + + return $data; + } +} diff --git a/library/Zend/Auth/Adapter/Http/Resolver/Exception.php b/library/Zend/Auth/Adapter/Http/Resolver/Exception.php new file mode 100644 index 000000000..3e927f51f --- /dev/null +++ b/library/Zend/Auth/Adapter/Http/Resolver/Exception.php @@ -0,0 +1,40 @@ +setFile($path); + } + } + + /** + * Set the path to the credentials file + * + * @param string $path + * @throws Zend_Auth_Adapter_Http_Resolver_Exception + * @return Zend_Auth_Adapter_Http_Resolver_File Provides a fluent interface + */ + public function setFile($path) + { + if (empty($path) || !is_readable($path)) { + /** + * @see Zend_Auth_Adapter_Http_Resolver_Exception + */ + require_once 'Zend/Auth/Adapter/Http/Resolver/Exception.php'; + throw new Zend_Auth_Adapter_Http_Resolver_Exception('Path not readable: ' . $path); + } + $this->_file = $path; + + return $this; + } + + /** + * Returns the path to the credentials file + * + * @return string + */ + public function getFile() + { + return $this->_file; + } + + /** + * Resolve credentials + * + * Only the first matching username/realm combination in the file is + * returned. If the file contains credentials for Digest authentication, + * the returned string is the password hash, or h(a1) from RFC 2617. The + * returned string is the plain-text password for Basic authentication. + * + * The expected format of the file is: + * username:realm:sharedSecret + * + * That is, each line consists of the user's username, the applicable + * authentication realm, and the password or hash, each delimited by + * colons. + * + * @param string $username Username + * @param string $realm Authentication Realm + * @throws Zend_Auth_Adapter_Http_Resolver_Exception + * @return string|false User's shared secret, if the user is found in the + * realm, false otherwise. + */ + public function resolve($username, $realm) + { + if (empty($username)) { + /** + * @see Zend_Auth_Adapter_Http_Resolver_Exception + */ + require_once 'Zend/Auth/Adapter/Http/Resolver/Exception.php'; + throw new Zend_Auth_Adapter_Http_Resolver_Exception('Username is required'); + } else if (!ctype_print($username) || strpos($username, ':') !== false) { + /** + * @see Zend_Auth_Adapter_Http_Resolver_Exception + */ + require_once 'Zend/Auth/Adapter/Http/Resolver/Exception.php'; + throw new Zend_Auth_Adapter_Http_Resolver_Exception('Username must consist only of printable characters, ' + . 'excluding the colon'); + } + if (empty($realm)) { + /** + * @see Zend_Auth_Adapter_Http_Resolver_Exception + */ + require_once 'Zend/Auth/Adapter/Http/Resolver/Exception.php'; + throw new Zend_Auth_Adapter_Http_Resolver_Exception('Realm is required'); + } else if (!ctype_print($realm) || strpos($realm, ':') !== false) { + /** + * @see Zend_Auth_Adapter_Http_Resolver_Exception + */ + require_once 'Zend/Auth/Adapter/Http/Resolver/Exception.php'; + throw new Zend_Auth_Adapter_Http_Resolver_Exception('Realm must consist only of printable characters, ' + . 'excluding the colon.'); + } + + // Open file, read through looking for matching credentials + $fp = @fopen($this->_file, 'r'); + if (!$fp) { + /** + * @see Zend_Auth_Adapter_Http_Resolver_Exception + */ + require_once 'Zend/Auth/Adapter/Http/Resolver/Exception.php'; + throw new Zend_Auth_Adapter_Http_Resolver_Exception('Unable to open password file: ' . $this->_file); + } + + // No real validation is done on the contents of the password file. The + // assumption is that we trust the administrators to keep it secure. + while (($line = fgetcsv($fp, 512, ':')) !== false) { + if ($line[0] == $username && $line[1] == $realm) { + $password = $line[2]; + fclose($fp); + return $password; + } + } + + fclose($fp); + return false; + } +} diff --git a/library/Zend/Auth/Adapter/Http/Resolver/Interface.php b/library/Zend/Auth/Adapter/Http/Resolver/Interface.php new file mode 100644 index 000000000..78c034314 --- /dev/null +++ b/library/Zend/Auth/Adapter/Http/Resolver/Interface.php @@ -0,0 +1,47 @@ +_xmlToken = $strXmlDocument; + $this->_infoCard = new Zend_InfoCard(); + } + + /** + * Sets the InfoCard component Adapter to use + * + * @param Zend_InfoCard_Adapter_Interface $a + * @return Zend_Auth_Adapter_InfoCard Provides a fluent interface + */ + public function setAdapter(Zend_InfoCard_Adapter_Interface $a) + { + $this->_infoCard->setAdapter($a); + return $this; + } + + /** + * Retrieves the InfoCard component adapter being used + * + * @return Zend_InfoCard_Adapter_Interface + */ + public function getAdapter() + { + return $this->_infoCard->getAdapter(); + } + + /** + * Retrieves the InfoCard public key cipher object being used + * + * @return Zend_InfoCard_Cipher_PKI_Interface + */ + public function getPKCipherObject() + { + return $this->_infoCard->getPKCipherObject(); + } + + /** + * Sets the InfoCard public key cipher object to use + * + * @param Zend_InfoCard_Cipher_PKI_Interface $cipherObj + * @return Zend_Auth_Adapter_InfoCard Provides a fluent interface + */ + public function setPKICipherObject(Zend_InfoCard_Cipher_PKI_Interface $cipherObj) + { + $this->_infoCard->setPKICipherObject($cipherObj); + return $this; + } + + /** + * Retrieves the Symmetric cipher object being used + * + * @return Zend_InfoCard_Cipher_Symmetric_Interface + */ + public function getSymCipherObject() + { + return $this->_infoCard->getSymCipherObject(); + } + + /** + * Sets the InfoCard symmetric cipher object to use + * + * @param Zend_InfoCard_Cipher_Symmetric_Interface $cipherObj + * @return Zend_Auth_Adapter_InfoCard Provides a fluent interface + */ + public function setSymCipherObject(Zend_InfoCard_Cipher_Symmetric_Interface $cipherObj) + { + $this->_infoCard->setSymCipherObject($cipherObj); + return $this; + } + + /** + * Remove a Certificate Pair by Key ID from the search list + * + * @param string $key_id The Certificate Key ID returned from adding the certificate pair + * @throws Zend_InfoCard_Exception + * @return Zend_Auth_Adapter_InfoCard Provides a fluent interface + */ + public function removeCertificatePair($key_id) + { + $this->_infoCard->removeCertificatePair($key_id); + return $this; + } + + /** + * Add a Certificate Pair to the list of certificates searched by the component + * + * @param string $private_key_file The path to the private key file for the pair + * @param string $public_key_file The path to the certificate / public key for the pair + * @param string $type (optional) The URI for the type of key pair this is (default RSA with OAEP padding) + * @param string $password (optional) The password for the private key file if necessary + * @throws Zend_InfoCard_Exception + * @return string A key ID representing this key pair in the component + */ + public function addCertificatePair($private_key_file, $public_key_file, $type = Zend_InfoCard_Cipher::ENC_RSA_OAEP_MGF1P, $password = null) + { + return $this->_infoCard->addCertificatePair($private_key_file, $public_key_file, $type, $password); + } + + /** + * Return a Certificate Pair from a key ID + * + * @param string $key_id The Key ID of the certificate pair in the component + * @throws Zend_InfoCard_Exception + * @return array An array containing the path to the private/public key files, + * the type URI and the password if provided + */ + public function getCertificatePair($key_id) + { + return $this->_infoCard->getCertificatePair($key_id); + } + + /** + * Set the XML Token to be processed + * + * @param string $strXmlToken The XML token to process + * @return Zend_Auth_Adapter_InfoCard Provides a fluent interface + */ + public function setXmlToken($strXmlToken) + { + $this->_xmlToken = $strXmlToken; + return $this; + } + + /** + * Get the XML Token being processed + * + * @return string The XML token to be processed + */ + public function getXmlToken() + { + return $this->_xmlToken; + } + + /** + * Authenticates the XML token + * + * @return Zend_Auth_Result The result of the authentication + */ + public function authenticate() + { + try { + $claims = $this->_infoCard->process($this->getXmlToken()); + } catch(Exception $e) { + return new Zend_Auth_Result(Zend_Auth_Result::FAILURE , null, array('Exception Thrown', + $e->getMessage(), + $e->getTraceAsString(), + serialize($e))); + } + + if(!$claims->isValid()) { + switch($claims->getCode()) { + case Zend_infoCard_Claims::RESULT_PROCESSING_FAILURE: + return new Zend_Auth_Result( + Zend_Auth_Result::FAILURE, + $claims, + array( + 'Processing Failure', + $claims->getErrorMsg() + ) + ); + break; + case Zend_InfoCard_Claims::RESULT_VALIDATION_FAILURE: + return new Zend_Auth_Result( + Zend_Auth_Result::FAILURE_CREDENTIAL_INVALID, + $claims, + array( + 'Validation Failure', + $claims->getErrorMsg() + ) + ); + break; + default: + return new Zend_Auth_Result( + Zend_Auth_Result::FAILURE, + $claims, + array( + 'Unknown Failure', + $claims->getErrorMsg() + ) + ); + break; + } + } + + return new Zend_Auth_Result( + Zend_Auth_Result::SUCCESS, + $claims + ); + } +} diff --git a/library/Zend/Auth/Adapter/Interface.php b/library/Zend/Auth/Adapter/Interface.php new file mode 100644 index 000000000..d2c3207e3 --- /dev/null +++ b/library/Zend/Auth/Adapter/Interface.php @@ -0,0 +1,46 @@ +setOptions($options); + if ($username !== null) { + $this->setUsername($username); + } + if ($password !== null) { + $this->setPassword($password); + } + } + + /** + * Returns the array of arrays of Zend_Ldap options of this adapter. + * + * @return array|null + */ + public function getOptions() + { + return $this->_options; + } + + /** + * Sets the array of arrays of Zend_Ldap options to be used by + * this adapter. + * + * @param array $options The array of arrays of Zend_Ldap options + * @return Zend_Auth_Adapter_Ldap Provides a fluent interface + */ + public function setOptions($options) + { + $this->_options = is_array($options) ? $options : array(); + return $this; + } + + /** + * Returns the username of the account being authenticated, or + * NULL if none is set. + * + * @return string|null + */ + public function getUsername() + { + return $this->_username; + } + + /** + * Sets the username for binding + * + * @param string $username The username for binding + * @return Zend_Auth_Adapter_Ldap Provides a fluent interface + */ + public function setUsername($username) + { + $this->_username = (string) $username; + return $this; + } + + /** + * Returns the password of the account being authenticated, or + * NULL if none is set. + * + * @return string|null + */ + public function getPassword() + { + return $this->_password; + } + + /** + * Sets the passwort for the account + * + * @param string $password The password of the account being authenticated + * @return Zend_Auth_Adapter_Ldap Provides a fluent interface + */ + public function setPassword($password) + { + $this->_password = (string) $password; + return $this; + } + + /** + * setIdentity() - set the identity (username) to be used + * + * Proxies to {@see setUsername()} + * + * Closes ZF-6813 + * + * @param string $identity + * @return Zend_Auth_Adapter_Ldap Provides a fluent interface + */ + public function setIdentity($identity) + { + return $this->setUsername($identity); + } + + /** + * setCredential() - set the credential (password) value to be used + * + * Proxies to {@see setPassword()} + * + * Closes ZF-6813 + * + * @param string $credential + * @return Zend_Auth_Adapter_Ldap Provides a fluent interface + */ + public function setCredential($credential) + { + return $this->setPassword($credential); + } + + /** + * Returns the LDAP Object + * + * @return Zend_Ldap The Zend_Ldap object used to authenticate the credentials + */ + public function getLdap() + { + if ($this->_ldap === null) { + /** + * @see Zend_Ldap + */ + require_once 'Zend/Ldap.php'; + $this->_ldap = new Zend_Ldap(); + } + + return $this->_ldap; + } + + /** + * Set an Ldap connection + * + * @param Zend_Ldap $ldap An existing Ldap object + * @return Zend_Auth_Adapter_Ldap Provides a fluent interface + */ + public function setLdap(Zend_Ldap $ldap) + { + $this->_ldap = $ldap; + + $this->setOptions(array($ldap->getOptions())); + + return $this; + } + + /** + * Returns a domain name for the current LDAP options. This is used + * for skipping redundant operations (e.g. authentications). + * + * @return string + */ + protected function _getAuthorityName() + { + $options = $this->getLdap()->getOptions(); + $name = $options['accountDomainName']; + if (!$name) + $name = $options['accountDomainNameShort']; + return $name ? $name : ''; + } + + /** + * Authenticate the user + * + * @throws Zend_Auth_Adapter_Exception + * @return Zend_Auth_Result + */ + public function authenticate() + { + /** + * @see Zend_Ldap_Exception + */ + require_once 'Zend/Ldap/Exception.php'; + + $messages = array(); + $messages[0] = ''; // reserved + $messages[1] = ''; // reserved + + $username = $this->_username; + $password = $this->_password; + + if (!$username) { + $code = Zend_Auth_Result::FAILURE_IDENTITY_NOT_FOUND; + $messages[0] = 'A username is required'; + return new Zend_Auth_Result($code, '', $messages); + } + if (!$password) { + /* A password is required because some servers will + * treat an empty password as an anonymous bind. + */ + $code = Zend_Auth_Result::FAILURE_CREDENTIAL_INVALID; + $messages[0] = 'A password is required'; + return new Zend_Auth_Result($code, '', $messages); + } + + $ldap = $this->getLdap(); + + $code = Zend_Auth_Result::FAILURE; + $messages[0] = "Authority not found: $username"; + $failedAuthorities = array(); + + /* Iterate through each server and try to authenticate the supplied + * credentials against it. + */ + foreach ($this->_options as $name => $options) { + + if (!is_array($options)) { + /** + * @see Zend_Auth_Adapter_Exception + */ + require_once 'Zend/Auth/Adapter/Exception.php'; + throw new Zend_Auth_Adapter_Exception('Adapter options array not an array'); + } + $adapterOptions = $this->_prepareOptions($ldap, $options); + $dname = ''; + + try { + if ($messages[1]) + $messages[] = $messages[1]; + $messages[1] = ''; + $messages[] = $this->_optionsToString($options); + + $dname = $this->_getAuthorityName(); + if (isset($failedAuthorities[$dname])) { + /* If multiple sets of server options for the same domain + * are supplied, we want to skip redundant authentications + * where the identity or credentials where found to be + * invalid with another server for the same domain. The + * $failedAuthorities array tracks this condition (and also + * serves to supply the original error message). + * This fixes issue ZF-4093. + */ + $messages[1] = $failedAuthorities[$dname]; + $messages[] = "Skipping previously failed authority: $dname"; + continue; + } + + $canonicalName = $ldap->getCanonicalAccountName($username); + $ldap->bind($canonicalName, $password); + /* + * Fixes problem when authenticated user is not allowed to retrieve + * group-membership information or own account. + * This requires that the user specified with "username" and optionally + * "password" in the Zend_Ldap options is able to retrieve the required + * information. + */ + $requireRebind = false; + if (isset($options['username'])) { + $ldap->bind(); + $requireRebind = true; + } + $dn = $ldap->getCanonicalAccountName($canonicalName, Zend_Ldap::ACCTNAME_FORM_DN); + + $groupResult = $this->_checkGroupMembership($ldap, $canonicalName, $dn, $adapterOptions); + if ($groupResult === true) { + $this->_authenticatedDn = $dn; + $messages[0] = ''; + $messages[1] = ''; + $messages[] = "$canonicalName authentication successful"; + if ($requireRebind === true) { + // rebinding with authenticated user + $ldap->bind($dn, $password); + } + return new Zend_Auth_Result(Zend_Auth_Result::SUCCESS, $canonicalName, $messages); + } else { + $messages[0] = 'Account is not a member of the specified group'; + $messages[1] = $groupResult; + $failedAuthorities[$dname] = $groupResult; + } + } catch (Zend_Ldap_Exception $zle) { + + /* LDAP based authentication is notoriously difficult to diagnose. Therefore + * we bend over backwards to capture and record every possible bit of + * information when something goes wrong. + */ + + $err = $zle->getCode(); + + if ($err == Zend_Ldap_Exception::LDAP_X_DOMAIN_MISMATCH) { + /* This error indicates that the domain supplied in the + * username did not match the domains in the server options + * and therefore we should just skip to the next set of + * server options. + */ + continue; + } else if ($err == Zend_Ldap_Exception::LDAP_NO_SUCH_OBJECT) { + $code = Zend_Auth_Result::FAILURE_IDENTITY_NOT_FOUND; + $messages[0] = "Account not found: $username"; + $failedAuthorities[$dname] = $zle->getMessage(); + } else if ($err == Zend_Ldap_Exception::LDAP_INVALID_CREDENTIALS) { + $code = Zend_Auth_Result::FAILURE_CREDENTIAL_INVALID; + $messages[0] = 'Invalid credentials'; + $failedAuthorities[$dname] = $zle->getMessage(); + } else { + $line = $zle->getLine(); + $messages[] = $zle->getFile() . "($line): " . $zle->getMessage(); + $messages[] = str_replace($password, '*****', $zle->getTraceAsString()); + $messages[0] = 'An unexpected failure occurred'; + } + $messages[1] = $zle->getMessage(); + } + } + + $msg = isset($messages[1]) ? $messages[1] : $messages[0]; + $messages[] = "$username authentication failed: $msg"; + + return new Zend_Auth_Result($code, $username, $messages); + } + + /** + * Sets the LDAP specific options on the Zend_Ldap instance + * + * @param Zend_Ldap $ldap + * @param array $options + * @return array of auth-adapter specific options + */ + protected function _prepareOptions(Zend_Ldap $ldap, array $options) + { + $adapterOptions = array( + 'group' => null, + 'groupDn' => $ldap->getBaseDn(), + 'groupScope' => Zend_Ldap::SEARCH_SCOPE_SUB, + 'groupAttr' => 'cn', + 'groupFilter' => 'objectClass=groupOfUniqueNames', + 'memberAttr' => 'uniqueMember', + 'memberIsDn' => true + ); + foreach ($adapterOptions as $key => $value) { + if (array_key_exists($key, $options)) { + $value = $options[$key]; + unset($options[$key]); + switch ($key) { + case 'groupScope': + $value = (int)$value; + if (in_array($value, array(Zend_Ldap::SEARCH_SCOPE_BASE, + Zend_Ldap::SEARCH_SCOPE_ONE, Zend_Ldap::SEARCH_SCOPE_SUB), true)) { + $adapterOptions[$key] = $value; + } + break; + case 'memberIsDn': + $adapterOptions[$key] = ($value === true || + $value === '1' || strcasecmp($value, 'true') == 0); + break; + default: + $adapterOptions[$key] = trim($value); + break; + } + } + } + $ldap->setOptions($options); + return $adapterOptions; + } + + /** + * Checks the group membership of the bound user + * + * @param Zend_Ldap $ldap + * @param string $canonicalName + * @param string $dn + * @param array $adapterOptions + * @return string|true + */ + protected function _checkGroupMembership(Zend_Ldap $ldap, $canonicalName, $dn, array $adapterOptions) + { + if ($adapterOptions['group'] === null) { + return true; + } + + if ($adapterOptions['memberIsDn'] === false) { + $user = $canonicalName; + } else { + $user = $dn; + } + + /** + * @see Zend_Ldap_Filter + */ + require_once 'Zend/Ldap/Filter.php'; + $groupName = Zend_Ldap_Filter::equals($adapterOptions['groupAttr'], $adapterOptions['group']); + $membership = Zend_Ldap_Filter::equals($adapterOptions['memberAttr'], $user); + $group = Zend_Ldap_Filter::andFilter($groupName, $membership); + $groupFilter = $adapterOptions['groupFilter']; + if (!empty($groupFilter)) { + $group = $group->addAnd($groupFilter); + } + + $result = $ldap->count($group, $adapterOptions['groupDn'], $adapterOptions['groupScope']); + + if ($result === 1) { + return true; + } else { + return 'Failed to verify group membership with ' . $group->toString(); + } + } + + /** + * getAccountObject() - Returns the result entry as a stdClass object + * + * This resembles the feature {@see Zend_Auth_Adapter_DbTable::getResultRowObject()}. + * Closes ZF-6813 + * + * @param array $returnAttribs + * @param array $omitAttribs + * @return stdClass|boolean + */ + public function getAccountObject(array $returnAttribs = array(), array $omitAttribs = array()) + { + if (!$this->_authenticatedDn) { + return false; + } + + $returnObject = new stdClass(); + + $omitAttribs = array_map('strtolower', $omitAttribs); + + $entry = $this->getLdap()->getEntry($this->_authenticatedDn, $returnAttribs, true); + foreach ($entry as $attr => $value) { + if (in_array($attr, $omitAttribs)) { + // skip attributes marked to be omitted + continue; + } + if (is_array($value)) { + $returnObject->$attr = (count($value) > 1) ? $value : $value[0]; + } else { + $returnObject->$attr = $value; + } + } + return $returnObject; + } + + /** + * Converts options to string + * + * @param array $options + * @return string + */ + private function _optionsToString(array $options) + { + $str = ''; + foreach ($options as $key => $val) { + if ($key === 'password') + $val = '*****'; + if ($str) + $str .= ','; + $str .= $key . '=' . $val; + } + return $str; + } +} diff --git a/library/Zend/Auth/Adapter/OpenId.php b/library/Zend/Auth/Adapter/OpenId.php new file mode 100644 index 000000000..1d55605ab --- /dev/null +++ b/library/Zend/Auth/Adapter/OpenId.php @@ -0,0 +1,284 @@ +_id = $id; + $this->_storage = $storage; + $this->_returnTo = $returnTo; + $this->_root = $root; + $this->_extensions = $extensions; + $this->_response = $response; + } + + /** + * Sets the value to be used as the identity + * + * @param string $id the identity value + * @return Zend_Auth_Adapter_OpenId Provides a fluent interface + */ + public function setIdentity($id) + { + $this->_id = $id; + return $this; + } + + /** + * Sets the storage implementation which will be use by OpenId + * + * @param Zend_OpenId_Consumer_Storage $storage + * @return Zend_Auth_Adapter_OpenId Provides a fluent interface + */ + public function setStorage(Zend_OpenId_Consumer_Storage $storage) + { + $this->_storage = $storage; + return $this; + } + + /** + * Sets the HTTP URL to redirect response from server to + * + * @param string $returnTo + * @return Zend_Auth_Adapter_OpenId Provides a fluent interface + */ + public function setReturnTo($returnTo) + { + $this->_returnTo = $returnTo; + return $this; + } + + /** + * Sets HTTP URL to identify consumer on server + * + * @param string $root + * @return Zend_Auth_Adapter_OpenId Provides a fluent interface + */ + public function setRoot($root) + { + $this->_root = $root; + return $this; + } + + /** + * Sets OpenID extension(s) + * + * @param mixed $extensions + * @return Zend_Auth_Adapter_OpenId Provides a fluent interface + */ + public function setExtensions($extensions) + { + $this->_extensions = $extensions; + return $this; + } + + /** + * Sets an optional response object to perform HTTP or HTML form redirection + * + * @param string $root + * @return Zend_Auth_Adapter_OpenId Provides a fluent interface + */ + public function setResponse($response) + { + $this->_response = $response; + return $this; + } + + /** + * Enables or disables interaction with user during authentication on + * OpenID provider. + * + * @param bool $check_immediate + * @return Zend_Auth_Adapter_OpenId Provides a fluent interface + */ + public function setCheckImmediate($check_immediate) + { + $this->_check_immediate = $check_immediate; + return $this; + } + + /** + * Sets HTTP client object to make HTTP requests + * + * @param Zend_Http_Client $client HTTP client object to be used + */ + public function setHttpClient($client) { + $this->_httpClient = $client; + } + + /** + * Authenticates the given OpenId identity. + * Defined by Zend_Auth_Adapter_Interface. + * + * @throws Zend_Auth_Adapter_Exception If answering the authentication query is impossible + * @return Zend_Auth_Result + */ + public function authenticate() { + $id = $this->_id; + if (!empty($id)) { + $consumer = new Zend_OpenId_Consumer($this->_storage); + $consumer->setHttpClient($this->_httpClient); + /* login() is never returns on success */ + if (!$this->_check_immediate) { + if (!$consumer->login($id, + $this->_returnTo, + $this->_root, + $this->_extensions, + $this->_response)) { + return new Zend_Auth_Result( + Zend_Auth_Result::FAILURE, + $id, + array("Authentication failed", $consumer->getError())); + } + } else { + if (!$consumer->check($id, + $this->_returnTo, + $this->_root, + $this->_extensions, + $this->_response)) { + return new Zend_Auth_Result( + Zend_Auth_Result::FAILURE, + $id, + array("Authentication failed", $consumer->getError())); + } + } + } else { + $params = (isset($_SERVER['REQUEST_METHOD']) && + $_SERVER['REQUEST_METHOD']=='POST') ? $_POST: $_GET; + $consumer = new Zend_OpenId_Consumer($this->_storage); + $consumer->setHttpClient($this->_httpClient); + if ($consumer->verify( + $params, + $id, + $this->_extensions)) { + return new Zend_Auth_Result( + Zend_Auth_Result::SUCCESS, + $id, + array("Authentication successful")); + } else { + return new Zend_Auth_Result( + Zend_Auth_Result::FAILURE, + $id, + array("Authentication failed", $consumer->getError())); + } + } + } + +} diff --git a/library/Zend/Auth/Exception.php b/library/Zend/Auth/Exception.php new file mode 100644 index 000000000..933ce1c0d --- /dev/null +++ b/library/Zend/Auth/Exception.php @@ -0,0 +1,36 @@ + self::SUCCESS ) { + $code = 1; + } + + $this->_code = $code; + $this->_identity = $identity; + $this->_messages = $messages; + } + + /** + * Returns whether the result represents a successful authentication attempt + * + * @return boolean + */ + public function isValid() + { + return ($this->_code > 0) ? true : false; + } + + /** + * getCode() - Get the result code for this authentication attempt + * + * @return int + */ + public function getCode() + { + return $this->_code; + } + + /** + * Returns the identity used in the authentication attempt + * + * @return mixed + */ + public function getIdentity() + { + return $this->_identity; + } + + /** + * Returns an array of string reasons why the authentication attempt was unsuccessful + * + * If authentication was successful, this method returns an empty array. + * + * @return array + */ + public function getMessages() + { + return $this->_messages; + } +} diff --git a/library/Zend/Auth/Storage/Exception.php b/library/Zend/Auth/Storage/Exception.php new file mode 100644 index 000000000..d45afe7e9 --- /dev/null +++ b/library/Zend/Auth/Storage/Exception.php @@ -0,0 +1,38 @@ +_data); + } + + /** + * Returns the contents of storage + * Behavior is undefined when storage is empty. + * + * @throws Zend_Auth_Storage_Exception If reading contents from storage is impossible + * @return mixed + */ + public function read() + { + return $this->_data; + } + + /** + * Writes $contents to storage + * + * @param mixed $contents + * @throws Zend_Auth_Storage_Exception If writing $contents to storage is impossible + * @return void + */ + public function write($contents) + { + $this->_data = $contents; + } + + /** + * Clears contents from storage + * + * @throws Zend_Auth_Storage_Exception If clearing contents from storage is impossible + * @return void + */ + public function clear() + { + $this->_data = null; + } +} diff --git a/library/Zend/Auth/Storage/Session.php b/library/Zend/Auth/Storage/Session.php new file mode 100644 index 000000000..60579dbe9 --- /dev/null +++ b/library/Zend/Auth/Storage/Session.php @@ -0,0 +1,150 @@ +_namespace = $namespace; + $this->_member = $member; + $this->_session = new Zend_Session_Namespace($this->_namespace); + } + + /** + * Returns the session namespace + * + * @return string + */ + public function getNamespace() + { + return $this->_namespace; + } + + /** + * Returns the name of the session object member + * + * @return string + */ + public function getMember() + { + return $this->_member; + } + + /** + * Defined by Zend_Auth_Storage_Interface + * + * @return boolean + */ + public function isEmpty() + { + return !isset($this->_session->{$this->_member}); + } + + /** + * Defined by Zend_Auth_Storage_Interface + * + * @return mixed + */ + public function read() + { + return $this->_session->{$this->_member}; + } + + /** + * Defined by Zend_Auth_Storage_Interface + * + * @param mixed $contents + * @return void + */ + public function write($contents) + { + $this->_session->{$this->_member} = $contents; + } + + /** + * Defined by Zend_Auth_Storage_Interface + * + * @return void + */ + public function clear() + { + unset($this->_session->{$this->_member}); + } +} diff --git a/library/Zend/Barcode.php b/library/Zend/Barcode.php new file mode 100644 index 000000000..a5c0c636c --- /dev/null +++ b/library/Zend/Barcode.php @@ -0,0 +1,352 @@ +rendererParams)) { + $rendererConfig = $barcode->rendererParams->toArray(); + } + if (isset($barcode->renderer)) { + $renderer = (string) $barcode->renderer; + } + if (isset($barcode->barcodeParams)) { + $barcodeConfig = $barcode->barcodeParams->toArray(); + } + if (isset($barcode->barcode)) { + $barcode = (string) $barcode->barcode; + } else { + $barcode = null; + } + } + + try { + $barcode = self::makeBarcode($barcode, $barcodeConfig); + $renderer = self::makeRenderer($renderer, $rendererConfig); + } catch (Zend_Exception $e) { + $renderable = ($e instanceof Zend_Barcode_Exception) ? $e->isRenderable() : false; + if ($automaticRenderError && $renderable) { + $barcode = self::makeBarcode('error', array( + 'text' => $e->getMessage() + )); + $renderer = self::makeRenderer($renderer, array()); + } else { + throw $e; + } + } + + $renderer->setAutomaticRenderError($automaticRenderError); + return $renderer->setBarcode($barcode); + } + + /** + * Barcode Constructor + * + * @param mixed $barcode String name of barcode class, or Zend_Config object. + * @param mixed $barcodeConfig OPTIONAL; an array or Zend_Config object with barcode parameters. + * @return Zend_Barcode_Object + */ + public static function makeBarcode($barcode, $barcodeConfig = array()) + { + if ($barcode instanceof Zend_Barcode_Object_ObjectAbstract) { + return $barcode; + } + + /* + * Convert Zend_Config argument to plain string + * barcode name and separate config object. + */ + if ($barcode instanceof Zend_Config) { + if (isset($barcode->barcodeParams) && $barcode->barcodeParams instanceof Zend_Config) { + $barcodeConfig = $barcode->barcodeParams->toArray(); + } + if (isset($barcode->barcode)) { + $barcode = (string) $barcode->barcode; + } else { + $barcode = null; + } + } + if ($barcodeConfig instanceof Zend_Config) { + $barcodeConfig = $barcodeConfig->toArray(); + } + + /* + * Verify that barcode parameters are in an array. + */ + if (!is_array($barcodeConfig)) { + /** + * @see Zend_Barcode_Exception + */ + require_once 'Zend/Barcode/Exception.php'; + throw new Zend_Barcode_Exception( + 'Barcode parameters must be in an array or a Zend_Config object' + ); + } + + /* + * Verify that an barcode name has been specified. + */ + if (!is_string($barcode) || empty($barcode)) { + /** + * @see Zend_Barcode_Exception + */ + require_once 'Zend/Barcode/Exception.php'; + throw new Zend_Barcode_Exception( + 'Barcode name must be specified in a string' + ); + } + /* + * Form full barcode class name + */ + $barcodeNamespace = 'Zend_Barcode_Object'; + if (isset($barcodeConfig['barcodeNamespace'])) { + $barcodeNamespace = $barcodeConfig['barcodeNamespace']; + } + + $barcodeName = strtolower($barcodeNamespace . '_' . $barcode); + $barcodeName = str_replace(' ', '_', ucwords( + str_replace( '_', ' ', $barcodeName) + )); + + /* + * Load the barcode class. This throws an exception + * if the specified class cannot be loaded. + */ + if (!class_exists($barcodeName)) { + require_once 'Zend/Loader.php'; + Zend_Loader::loadClass($barcodeName); + } + + /* + * Create an instance of the barcode class. + * Pass the config to the barcode class constructor. + */ + $bcAdapter = new $barcodeName($barcodeConfig); + + /* + * Verify that the object created is a descendent of the abstract barcode type. + */ + if (!$bcAdapter instanceof Zend_Barcode_Object_ObjectAbstract) { + /** + * @see Zend_Barcode_Exception + */ + require_once 'Zend/Barcode/Exception.php'; + throw new Zend_Barcode_Exception( + "Barcode class '$barcodeName' does not extend Zend_Barcode_Object_ObjectAbstract" + ); + } + return $bcAdapter; + } + + /** + * Renderer Constructor + * + * @param mixed $renderer String name of renderer class, or Zend_Config object. + * @param mixed $rendererConfig OPTIONAL; an array or Zend_Config object with renderer parameters. + * @return Zend_Barcode_Renderer + */ + public static function makeRenderer($renderer = 'image', $rendererConfig = array()) + { + if ($renderer instanceof Zend_Barcode_Renderer_RendererAbstract) { + return $renderer; + } + + /* + * Convert Zend_Config argument to plain string + * barcode name and separate config object. + */ + if ($renderer instanceof Zend_Config) { + if (isset($renderer->rendererParams)) { + $rendererConfig = $renderer->rendererParams->toArray(); + } + if (isset($renderer->renderer)) { + $renderer = (string) $renderer->renderer; + } + } + if ($rendererConfig instanceof Zend_Config) { + $rendererConfig = $rendererConfig->toArray(); + } + + /* + * Verify that barcode parameters are in an array. + */ + if (!is_array($rendererConfig)) { + /** + * @see Zend_Barcode_Exception + */ + require_once 'Zend/Barcode/Exception.php'; + $e = new Zend_Barcode_Exception( + 'Barcode parameters must be in an array or a Zend_Config object' + ); + $e->setIsRenderable(false); + throw $e; + } + + /* + * Verify that an barcode name has been specified. + */ + if (!is_string($renderer) || empty($renderer)) { + /** + * @see Zend_Barcode_Exception + */ + require_once 'Zend/Barcode/Exception.php'; + $e = new Zend_Barcode_Exception( + 'Renderer name must be specified in a string' + ); + $e->setIsRenderable(false); + throw $e; + } + + /* + * Form full barcode class name + */ + $rendererNamespace = 'Zend_Barcode_Renderer'; + if (isset($rendererConfig['rendererNamespace'])) { + $rendererNamespace = $rendererConfig['rendererNamespace']; + } + + $rendererName = strtolower($rendererNamespace . '_' . $renderer); + $rendererName = str_replace(' ', '_', ucwords( + str_replace( '_', ' ', $rendererName) + )); + + /* + * Load the barcode class. This throws an exception + * if the specified class cannot be loaded. + */ + if (!class_exists($rendererName)) { + require_once 'Zend/Loader.php'; + Zend_Loader::loadClass($rendererName); + } + + /* + * Create an instance of the barcode class. + * Pass the config to the barcode class constructor. + */ + $rdrAdapter = new $rendererName($rendererConfig); + + /* + * Verify that the object created is a descendent of the abstract barcode type. + */ + if (!$rdrAdapter instanceof Zend_Barcode_Renderer_RendererAbstract) { + /** + * @see Zend_Barcode_Exception + */ + require_once 'Zend/Barcode/Exception.php'; + $e = new Zend_Barcode_Exception( + "Renderer class '$rendererName' does not extend Zend_Barcode_Renderer_RendererAbstract" + ); + $e->setIsRenderable(false); + throw $e; + } + return $rdrAdapter; + } + + /** + * Proxy to renderer render() method + * + * @param string | Zend_Barcode_Object | array | Zend_Config $barcode + * @param string | Zend_Barcode_Renderer $renderer + * @param array | Zend_Config $barcodeConfig + * @param array | Zend_Config $rendererConfig + */ + public static function render( + $barcode, + $renderer, + $barcodeConfig = array(), + $rendererConfig = array() + ) { + self::factory($barcode, $renderer, $barcodeConfig, $rendererConfig)->render(); + } + + /** + * Proxy to renderer draw() method + * + * @param string | Zend_Barcode_Object | array | Zend_Config $barcode + * @param string | Zend_Barcode_Renderer $renderer + * @param array | Zend_Config $barcodeConfig + * @param array | Zend_Config $rendererConfig + * @return mixed + */ + public static function draw( + $barcode, + $renderer, + $barcodeConfig = array(), + $rendererConfig = array() + ) { + return self::factory($barcode, $renderer, $barcodeConfig, $rendererConfig)->draw(); + } + + /** + * Proxy for setBarcodeFont of Zend_Barcode_Object + * @param string $font + * @eturn void + */ + public static function setBarcodeFont($font) + { + require_once 'Zend/Barcode/Object/ObjectAbstract.php'; + Zend_Barcode_Object_ObjectAbstract::setBarcodeFont($font); + } +} diff --git a/library/Zend/Barcode/Exception.php b/library/Zend/Barcode/Exception.php new file mode 100644 index 000000000..afc0e89db --- /dev/null +++ b/library/Zend/Barcode/Exception.php @@ -0,0 +1,63 @@ +_isRenderable = (bool) $flag; + return $this; + } + + /** + * Retrieve renderable flag + * + * @return bool + */ + public function isRenderable() + { + return $this->_isRenderable; + } +} diff --git a/library/Zend/Barcode/Object/Code25.php b/library/Zend/Barcode/Object/Code25.php new file mode 100644 index 000000000..538e4723c --- /dev/null +++ b/library/Zend/Barcode/Object/Code25.php @@ -0,0 +1,143 @@ + '00110', + '1' => '10001', + '2' => '01001', + '3' => '11000', + '4' => '00101', + '5' => '10100', + '6' => '01100', + '7' => '00011', + '8' => '10010', + '9' => '01010', + ); + + /** + * Width of the barcode (in pixels) + * @return integer + */ + protected function _calculateBarcodeWidth() + { + $quietZone = $this->getQuietZone(); + $startCharacter = (2 * $this->_barThickWidth + 4 * $this->_barThinWidth) * $this->_factor; + $characterLength = (3 * $this->_barThinWidth + 2 * $this->_barThickWidth + 5 * $this->_barThinWidth) + * $this->_factor; + $encodedData = strlen($this->getText()) * $characterLength; + $stopCharacter = (2 * $this->_barThickWidth + 4 * $this->_barThinWidth) * $this->_factor; + return $quietZone + $startCharacter + $encodedData + $stopCharacter + $quietZone; + } + + /** + * Partial check of interleaved 2 of 5 barcode + * @return void + */ + protected function _checkParams() + { + $this->_checkRatio(); + } + + /** + * Prepare array to draw barcode + * @return array + */ + protected function _prepareBarcode() + { + $barcodeTable = array(); + + // Start character (30301) + $barcodeTable[] = array(1 , $this->_barThickWidth , 0 , 1); + $barcodeTable[] = array(0 , $this->_barThinWidth , 0 , 1); + $barcodeTable[] = array(1 , $this->_barThickWidth , 0 , 1); + $barcodeTable[] = array(0 , $this->_barThinWidth , 0 , 1); + $barcodeTable[] = array(1 , $this->_barThinWidth , 0 , 1); + $barcodeTable[] = array(0 , 1); + + $text = str_split($this->getText()); + foreach ($text as $char) { + $barcodeChar = str_split($this->_codingMap[$char]); + foreach ($barcodeChar as $c) { + /* visible, width, top, length */ + $width = $c ? $this->_barThickWidth : $this->_barThinWidth; + $barcodeTable[] = array(1 , $width , 0 , 1); + $barcodeTable[] = array(0 , 1); + } + } + + // Stop character (30103) + $barcodeTable[] = array(1 , $this->_barThickWidth , 0 , 1); + $barcodeTable[] = array(0 , $this->_barThinWidth , 0 , 1); + $barcodeTable[] = array(1 , $this->_barThinWidth , 0 , 1); + $barcodeTable[] = array(0 , $this->_barThinWidth , 0 , 1); + $barcodeTable[] = array(1 , $this->_barThickWidth , 0 , 1); + return $barcodeTable; + } + + /** + * Get barcode checksum + * + * @param string $text + * @return int + */ + public function getChecksum($text) + { + $this->_checkText($text); + $factor = 3; + $checksum = 0; + + for ($i = strlen($text); $i > 0; $i --) { + $checksum += intval($text{$i - 1}) * $factor; + $factor = 4 - $factor; + } + + $checksum = (10 - ($checksum % 10)) % 10; + + return $checksum; + } +} diff --git a/library/Zend/Barcode/Object/Code25interleaved.php b/library/Zend/Barcode/Object/Code25interleaved.php new file mode 100644 index 000000000..e7e1a0197 --- /dev/null +++ b/library/Zend/Barcode/Object/Code25interleaved.php @@ -0,0 +1,179 @@ +_barcodeLength = 'even'; + } + + /** + * Activate/deactivate drawing of bearer bars + * @param boolean $value + * @return Zend_Barcode_Object_Int25 + */ + public function setWithBearerBars($value) + { + $this->_withBearerBars = (bool) $value; + return $this; + } + + /** + * Retrieve if bearer bars are enabled + * @return boolean + */ + public function getWithBearerBars() + { + return $this->_withBearerBars; + } + + /** + * Width of the barcode (in pixels) + * @return integer + */ + protected function _calculateBarcodeWidth() + { + $quietZone = $this->getQuietZone(); + $startCharacter = (4 * $this->_barThinWidth) * $this->_factor; + $characterLength = (3 * $this->_barThinWidth + 2 * $this->_barThickWidth) * $this->_factor; + $encodedData = strlen($this->getText()) * $characterLength; + $stopCharacter = ($this->_barThickWidth + 2 * $this->_barThinWidth) * $this->_factor; + return $quietZone + $startCharacter + $encodedData + $stopCharacter + $quietZone; + } + + /** + * Prepare array to draw barcode + * @return array + */ + protected function _prepareBarcode() + { + if ($this->_withBearerBars) { + $this->_withBorder = false; + } + + // Start character (0000) + $barcodeTable[] = array(1, $this->_barThinWidth, 0, 1); + $barcodeTable[] = array(0, $this->_barThinWidth, 0, 1); + $barcodeTable[] = array(1, $this->_barThinWidth, 0, 1); + $barcodeTable[] = array(0, $this->_barThinWidth, 0, 1); + + // Encoded $text + $text = $this->getText(); + for ($i = 0; $i < strlen($text); $i += 2) { // Draw 2 chars at a time + $char1 = substr($text, $i, 1); + $char2 = substr($text, $i + 1, 1); + + // Interleave + for ($ibar = 0; $ibar < 5; $ibar ++) { + // Draws char1 bar (fore color) + $barWidth = (substr($this->_codingMap[$char1], $ibar, 1)) + ? $this->_barThickWidth + : $this->_barThinWidth; + + $barcodeTable[] = array(1, $barWidth, 0, 1); + + // Left space corresponding to char2 (background color) + $barWidth = (substr($this->_codingMap[$char2], $ibar, 1)) + ? $this->_barThickWidth + : $this->_barThinWidth; + $barcodeTable[] = array(0, $barWidth, 0 , 1); + } + } + + // Stop character (100) + $barcodeTable[] = array(1 , $this->_barThickWidth, 0, 1); + $barcodeTable[] = array(0 , $this->_barThinWidth, 0, 1); + $barcodeTable[] = array(1 , $this->_barThinWidth, 0, 1); + return $barcodeTable; + } + + /** + * Drawing of bearer bars (if enabled) + * + * @return void + */ + protected function _postDrawBarcode() + { + if (!$this->_withBearerBars) { + return; + } + + $width = $this->_barThickWidth * $this->_factor; + $point1 = $this->_rotate(-1, -1); + $point2 = $this->_rotate($this->_calculateWidth() - 1, -1); + $point3 = $this->_rotate($this->_calculateWidth() - 1, $width - 1); + $point4 = $this->_rotate(-1, $width - 1); + $this->_addPolygon(array( + $point1, + $point2, + $point3, + $point4, + )); + $point1 = $this->_rotate( + 0, + 0 + $this->_barHeight * $this->_factor - 1 + ); + $point2 = $this->_rotate( + $this->_calculateWidth() - 1, + 0 + $this->_barHeight * $this->_factor - 1 + ); + $point3 = $this->_rotate( + $this->_calculateWidth() - 1, + 0 + $this->_barHeight * $this->_factor - $width + ); + $point4 = $this->_rotate( + 0, + 0 + $this->_barHeight * $this->_factor - $width + ); + $this->_addPolygon(array( + $point1, + $point2, + $point3, + $point4, + )); + } +} diff --git a/library/Zend/Barcode/Object/Code39.php b/library/Zend/Barcode/Object/Code39.php new file mode 100644 index 000000000..864fe3684 --- /dev/null +++ b/library/Zend/Barcode/Object/Code39.php @@ -0,0 +1,177 @@ + '000110100', + '1' => '100100001', + '2' => '001100001', + '3' => '101100000', + '4' => '000110001', + '5' => '100110000', + '6' => '001110000', + '7' => '000100101', + '8' => '100100100', + '9' => '001100100', + 'A' => '100001001', + 'B' => '001001001', + 'C' => '101001000', + 'D' => '000011001', + 'E' => '100011000', + 'F' => '001011000', + 'G' => '000001101', + 'H' => '100001100', + 'I' => '001001100', + 'J' => '000011100', + 'K' => '100000011', + 'L' => '001000011', + 'M' => '101000010', + 'N' => '000010011', + 'O' => '100010010', + 'P' => '001010010', + 'Q' => '000000111', + 'R' => '100000110', + 'S' => '001000110', + 'T' => '000010110', + 'U' => '110000001', + 'V' => '011000001', + 'W' => '111000000', + 'X' => '010010001', + 'Y' => '110010000', + 'Z' => '011010000', + '-' => '010000101', + '.' => '110000100', + ' ' => '011000100', + '$' => '010101000', + '/' => '010100010', + '+' => '010001010', + '%' => '000101010', + '*' => '010010100', + ); + + /** + * Partial check of Code39 barcode + * @return void + */ + protected function _checkParams() + { + $this->_checkRatio(); + } + + /** + * Width of the barcode (in pixels) + * @return int + */ + protected function _calculateBarcodeWidth() + { + $quietZone = $this->getQuietZone(); + $characterLength = (6 * $this->_barThinWidth + 3 * $this->_barThickWidth + 1) * $this->_factor; + $encodedData = strlen($this->getText()) * $characterLength - $this->_factor; + return $quietZone + $encodedData + $quietZone; + } + + /** + * Retrieve text to display + * @return string + */ + public function getText() + { + return '*' . parent::getText() . '*'; + } + + /** + * Retrieve text to display + * @return string + */ + public function getTextToDisplay() + { + $text = parent::getTextToDisplay(); + if (substr($text, 0, 1) != '*' && substr($text, -1) != '*') { + return '*' . $text . '*'; + } else { + return $text; + } + } + + /** + * Prepare array to draw barcode + * @return array + */ + protected function _prepareBarcode() + { + $text = str_split($this->getText()); + $barcodeTable = array(); + foreach ($text as $char) { + $barcodeChar = str_split($this->_codingMap[$char]); + $visible = true; + foreach ($barcodeChar as $c) { + /* visible, width, top, length */ + $width = $c ? $this->_barThickWidth : $this->_barThinWidth; + $barcodeTable[] = array((int) $visible, $width, 0, 1); + $visible = ! $visible; + } + $barcodeTable[] = array(0 , 1); + } + return $barcodeTable; + } + + /** + * Get barcode checksum + * + * @param string $text + * @return int + */ + public function getChecksum($text) + { + $this->_checkText($text); + $text = str_split($text); + $charset = array_flip(array_keys($this->_codingMap)); + $checksum = 0; + foreach ($text as $character) { + $checksum += $charset[$character]; + } + return array_search(($checksum % 43), $charset); + } +} diff --git a/library/Zend/Barcode/Object/Ean13.php b/library/Zend/Barcode/Object/Ean13.php new file mode 100644 index 000000000..2005708d4 --- /dev/null +++ b/library/Zend/Barcode/Object/Ean13.php @@ -0,0 +1,224 @@ + array( + 0 => "0001101", 1 => "0011001", 2 => "0010011", 3 => "0111101", 4 => "0100011", + 5 => "0110001", 6 => "0101111", 7 => "0111011", 8 => "0110111", 9 => "0001011" + ), + 'B' => array( + 0 => "0100111", 1 => "0110011", 2 => "0011011", 3 => "0100001", 4 => "0011101", + 5 => "0111001", 6 => "0000101", 7 => "0010001", 8 => "0001001", 9 => "0010111" + ), + 'C' => array( + 0 => "1110010", 1 => "1100110", 2 => "1101100", 3 => "1000010", 4 => "1011100", + 5 => "1001110", 6 => "1010000", 7 => "1000100", 8 => "1001000", 9 => "1110100" + )); + + protected $_parities = array( + 0 => array('A','A','A','A','A','A'), + 1 => array('A','A','B','A','B','B'), + 2 => array('A','A','B','B','A','B'), + 3 => array('A','A','B','B','B','A'), + 4 => array('A','B','A','A','B','B'), + 5 => array('A','B','B','A','A','B'), + 6 => array('A','B','B','B','A','A'), + 7 => array('A','B','A','B','A','B'), + 8 => array('A','B','A','B','B','A'), + 9 => array('A','B','B','A','B','A') + ); + + /** + * Default options for Postnet barcode + * @return void + */ + protected function _getDefaultOptions() + { + $this->_barcodeLength = 13; + $this->_mandatoryChecksum = true; + } + + /** + * Width of the barcode (in pixels) + * @return integer + */ + protected function _calculateBarcodeWidth() + { + $quietZone = $this->getQuietZone(); + $startCharacter = (3 * $this->_barThinWidth) * $this->_factor; + $middleCharacter = (5 * $this->_barThinWidth) * $this->_factor; + $stopCharacter = (3 * $this->_barThinWidth) * $this->_factor; + $encodedData = (7 * $this->_barThinWidth) * $this->_factor * 12; + return $quietZone + $startCharacter + $middleCharacter + $encodedData + $stopCharacter + $quietZone; + } + + /** + * Partial check of interleaved EAN/UPC barcode + * @return void + */ + protected function _checkParams() + {} + + /** + * Prepare array to draw barcode + * @return array + */ + protected function _prepareBarcode() + { + $barcodeTable = array(); + $height = ($this->_drawText) ? 1.1 : 1; + + // Start character (101) + $barcodeTable[] = array(1 , $this->_barThinWidth , 0 , $height); + $barcodeTable[] = array(0 , $this->_barThinWidth , 0 , $height); + $barcodeTable[] = array(1 , $this->_barThinWidth , 0 , $height); + + $textTable = str_split($this->getText()); + $parity = $this->_parities[$textTable[0]]; + + // First part + for ($i = 1; $i < 7; $i++) { + $bars = str_split($this->_codingMap[$parity[$i - 1]][$textTable[$i]]); + foreach ($bars as $b) { + $barcodeTable[] = array($b , $this->_barThinWidth , 0 , 1); + } + } + + // Middle character (01010) + $barcodeTable[] = array(0 , $this->_barThinWidth , 0 , $height); + $barcodeTable[] = array(1 , $this->_barThinWidth , 0 , $height); + $barcodeTable[] = array(0 , $this->_barThinWidth , 0 , $height); + $barcodeTable[] = array(1 , $this->_barThinWidth , 0 , $height); + $barcodeTable[] = array(0 , $this->_barThinWidth , 0 , $height); + + // Second part + for ($i = 7; $i < 13; $i++) { + $bars = str_split($this->_codingMap['C'][$textTable[$i]]); + foreach ($bars as $b) { + $barcodeTable[] = array($b , $this->_barThinWidth , 0 , 1); + } + } + + // Stop character (101) + $barcodeTable[] = array(1 , $this->_barThinWidth , 0 , $height); + $barcodeTable[] = array(0 , $this->_barThinWidth , 0 , $height); + $barcodeTable[] = array(1 , $this->_barThinWidth , 0 , $height); + return $barcodeTable; + } + + /** + * Get barcode checksum + * + * @param string $text + * @return int + */ + public function getChecksum($text) + { + $this->_checkText($text); + $factor = 3; + $checksum = 0; + + for ($i = strlen($text); $i > 0; $i --) { + $checksum += intval($text{$i - 1}) * $factor; + $factor = 4 - $factor; + } + + $checksum = (10 - ($checksum % 10)) % 10; + + return $checksum; + } + + /** + * Partial function to draw text + * @return void + */ + protected function _drawText() + { + if (get_class($this) == 'Zend_Barcode_Object_Ean13') { + $this->_drawEan13Text(); + } else { + parent::_drawText(); + } + } + + protected function _drawEan13Text() + { + if ($this->_drawText) { + $text = $this->getTextToDisplay(); + $characterWidth = (7 * $this->_barThinWidth) * $this->_factor; + $leftPosition = $this->getQuietZone() - $characterWidth; + for ($i = 0; $i < $this->_barcodeLength; $i ++) { + $this->_addText( + $text{$i}, + $this->_fontSize * $this->_factor, + $this->_rotate( + $leftPosition, + (int) $this->_withBorder * 2 + + $this->_factor * ($this->_barHeight + $this->_fontSize) + 1 + ), + $this->_font, + $this->_foreColor, + 'left', + - $this->_orientation + ); + switch ($i) { + case 0: + $factor = 3; + break; + case 6: + $factor = 4; + break; + default: + $factor = 0; + } + $leftPosition = $leftPosition + $characterWidth + ($factor * $this->_barThinWidth * $this->_factor); + } + } + } +} diff --git a/library/Zend/Barcode/Object/Ean2.php b/library/Zend/Barcode/Object/Ean2.php new file mode 100644 index 000000000..f214129ff --- /dev/null +++ b/library/Zend/Barcode/Object/Ean2.php @@ -0,0 +1,65 @@ + array('A','A'), + 1 => array('A','B'), + 2 => array('B','A'), + 3 => array('B','B') + ); + + /** + * Default options for Ean2 barcode + * @return void + */ + protected function _getDefaultOptions() + { + $this->_barcodeLength = 2; + } + + protected function _getParity($i) + { + $modulo = $this->getText() % 4; + return $this->_parities[$modulo][$i]; + } +} diff --git a/library/Zend/Barcode/Object/Ean5.php b/library/Zend/Barcode/Object/Ean5.php new file mode 100644 index 000000000..f983b31b2 --- /dev/null +++ b/library/Zend/Barcode/Object/Ean5.php @@ -0,0 +1,147 @@ + array('B','B','A','A','A'), + 1 => array('B','A','B','A','A'), + 2 => array('B','A','A','B','A'), + 3 => array('B','A','A','A','B'), + 4 => array('A','B','B','A','A'), + 5 => array('A','A','B','B','A'), + 6 => array('A','A','A','B','B'), + 7 => array('A','B','A','B','A'), + 8 => array('A','B','A','A','B'), + 9 => array('A','A','B','A','B') + ); + + /** + * Default options for Ean5 barcode + * @return void + */ + protected function _getDefaultOptions() + { + $this->_barcodeLength = 5; + } + + /** + * Width of the barcode (in pixels) + * @return integer + */ + protected function _calculateBarcodeWidth() + { + $quietZone = $this->getQuietZone(); + $startCharacter = (5 * $this->_barThinWidth) * $this->_factor; + $middleCharacter = (2 * $this->_barThinWidth) * $this->_factor; + $encodedData = (7 * $this->_barThinWidth) * $this->_factor; + return $quietZone + $startCharacter + ($this->_barcodeLength - 1) * $middleCharacter + $this->_barcodeLength * $encodedData + $quietZone; + } + + /** + * Prepare array to draw barcode + * @return array + */ + protected function _prepareBarcode() + { + $barcodeTable = array(); + + // Start character (01011) + $barcodeTable[] = array(0 , $this->_barThinWidth , 0 , 1); + $barcodeTable[] = array(1 , $this->_barThinWidth , 0 , 1); + $barcodeTable[] = array(0 , $this->_barThinWidth , 0 , 1); + $barcodeTable[] = array(1 , $this->_barThinWidth , 0 , 1); + $barcodeTable[] = array(1 , $this->_barThinWidth , 0 , 1); + + $firstCharacter = true; + $textTable = str_split($this->getText()); + + // Characters + for ($i = 0; $i < $this->_barcodeLength; $i++) { + if ($firstCharacter) { + $firstCharacter = false; + } else { + // Intermediate character (01) + $barcodeTable[] = array(0 , $this->_barThinWidth , 0 , 1); + $barcodeTable[] = array(1 , $this->_barThinWidth , 0 , 1); + } + $bars = str_split($this->_codingMap[$this->_getParity($i)][$textTable[$i]]); + foreach ($bars as $b) { + $barcodeTable[] = array($b , $this->_barThinWidth , 0 , 1); + } + } + + return $barcodeTable; + } + + /** + * Get barcode checksum + * + * @param string $text + * @return int + */ + public function getChecksum($text) + { + $this->_checkText($text); + $checksum = 0; + + for ($i = 0 ; $i < $this->_barcodeLength; $i ++) { + $checksum += intval($text{$i}) * ($i % 2 ? 9 : 3); + } + + return ($checksum % 10); + } + + protected function _getParity($i) + { + $checksum = $this->getChecksum($this->getText()); + return $this->_parities[$checksum][$i]; + } + + /** + * Retrieve text to encode + * @return string + */ + public function getText() + { + return $this->_addLeadingZeros($this->_text); + } +} diff --git a/library/Zend/Barcode/Object/Ean8.php b/library/Zend/Barcode/Object/Ean8.php new file mode 100644 index 000000000..1c10a193f --- /dev/null +++ b/library/Zend/Barcode/Object/Ean8.php @@ -0,0 +1,174 @@ +_barcodeLength = 8; + $this->_mandatoryChecksum = true; + } + + /** + * Width of the barcode (in pixels) + * @return integer + */ + protected function _calculateBarcodeWidth() + { + $quietZone = $this->getQuietZone(); + $startCharacter = (3 * $this->_barThinWidth) * $this->_factor; + $stopCharacter = (3 * $this->_barThinWidth) * $this->_factor; + $encodedData = (7 * $this->_barThinWidth) * $this->_factor * 8; + return $quietZone + $startCharacter + $encodedData + $stopCharacter + $quietZone; + } + + /** + * Prepare array to draw barcode + * @return array + */ + protected function _prepareBarcode() + { + $barcodeTable = array(); + $height = ($this->_drawText) ? 1.1 : 1; + + // Start character (101) + $barcodeTable[] = array(1 , $this->_barThinWidth , 0 , $height); + $barcodeTable[] = array(0 , $this->_barThinWidth , 0 , $height); + $barcodeTable[] = array(1 , $this->_barThinWidth , 0 , $height); + + $textTable = str_split($this->getText()); + + // First part + for ($i = 0; $i < 4; $i++) { + $bars = str_split($this->_codingMap['A'][$textTable[$i]]); + foreach ($bars as $b) { + $barcodeTable[] = array($b , $this->_barThinWidth , 0 , 1); + } + } + + // Middle character (01010) + $barcodeTable[] = array(0 , $this->_barThinWidth , 0 , $height); + $barcodeTable[] = array(1 , $this->_barThinWidth , 0 , $height); + $barcodeTable[] = array(0 , $this->_barThinWidth , 0 , $height); + $barcodeTable[] = array(1 , $this->_barThinWidth , 0 , $height); + $barcodeTable[] = array(0 , $this->_barThinWidth , 0 , $height); + + // Second part + for ($i = 4; $i < 8; $i++) { + $bars = str_split($this->_codingMap['C'][$textTable[$i]]); + foreach ($bars as $b) { + $barcodeTable[] = array($b , $this->_barThinWidth , 0 , 1); + } + } + + // Stop character (101) + $barcodeTable[] = array(1 , $this->_barThinWidth , 0 , $height); + $barcodeTable[] = array(0 , $this->_barThinWidth , 0 , $height); + $barcodeTable[] = array(1 , $this->_barThinWidth , 0 , $height); + return $barcodeTable; + } + + /** + * Partial function to draw text + * @return void + */ + protected function _drawText() + { + if ($this->_drawText) { + $text = $this->getTextToDisplay(); + $characterWidth = (7 * $this->_barThinWidth) * $this->_factor; + $leftPosition = $this->getQuietZone() + (3 * $this->_barThinWidth) * $this->_factor; + for ($i = 0; $i < $this->_barcodeLength; $i ++) { + $this->_addText( + $text{$i}, + $this->_fontSize * $this->_factor, + $this->_rotate( + $leftPosition, + (int) $this->_withBorder * 2 + + $this->_factor * ($this->_barHeight + $this->_fontSize) + 1 + ), + $this->_font, + $this->_foreColor, + 'left', + - $this->_orientation + ); + switch ($i) { + case 3: + $factor = 4; + break; + default: + $factor = 0; + } + $leftPosition = $leftPosition + $characterWidth + ($factor * $this->_barThinWidth * $this->_factor); + } + } + } + + /** + * Particular validation for Ean8 barcode objects + * (to suppress checksum character substitution) + * @param string $value + * @param array $options + */ + protected function _validateText($value, $options = array()) + { + $validator = new Zend_Validate_Barcode(array( + 'adapter' => 'ean8', + 'checksum' => false, + )); + + $value = $this->_addLeadingZeros($value, true); + + if (!$validator->isValid($value)) { + $message = implode("\n", $validator->getMessages()); + + /** + * @see Zend_Barcode_Object_Exception + */ + require_once 'Zend/Barcode/Object/Exception.php'; + throw new Zend_Barcode_Object_Exception($message); + } + } +} diff --git a/library/Zend/Barcode/Object/Error.php b/library/Zend/Barcode/Object/Error.php new file mode 100644 index 000000000..7a990460d --- /dev/null +++ b/library/Zend/Barcode/Object/Error.php @@ -0,0 +1,100 @@ +_instructions = array(); + $this->_addText('ERROR:', 10, array(5 , 18), $this->_font, 0, 'left'); + $this->_addText($this->_text, 10, array(5 , 32), $this->_font, 0, 'left'); + return $this->_instructions; + } + + /** + * For compatibility reason + * @return void + */ + protected function _prepareBarcode() + { + } + + /** + * For compatibility reason + * @return void + */ + protected function _checkParams() + { + } + + /** + * For compatibility reason + * @return void + */ + protected function _calculateBarcodeWidth() + { + } +} diff --git a/library/Zend/Barcode/Object/Exception.php b/library/Zend/Barcode/Object/Exception.php new file mode 100644 index 000000000..ad4a22670 --- /dev/null +++ b/library/Zend/Barcode/Object/Exception.php @@ -0,0 +1,35 @@ +_barcodeLength = 12; + $this->_mandatoryChecksum = true; + } + + /** + * Retrieve text to display + * @return string + */ + public function getTextToDisplay() + { + return preg_replace('/([0-9]{2})([0-9]{3})([0-9]{3})([0-9]{3})([0-9])/', + '$1.$2 $3.$4 $5', + $this->getText()); + } + + /** + * Check allowed characters + * @param string $value + * @return string + * @throw Zend_Barcode_Object_Exception + */ + public function validateText($value) + { + $this->_validateText($value, array('validator' => $this->getType())); + } + + /** + * Get barcode checksum + * + * @param string $text + * @return int + */ + public function getChecksum($text) + { + $this->_checkText($text); + $checksum = 0; + + for ($i = strlen($text); $i > 0; $i --) { + $checksum += intval($text{$i - 1}) * (($i % 2) ? 4 : 9); + } + + $checksum = (10 - ($checksum % 10)) % 10; + + return $checksum; + } +} diff --git a/library/Zend/Barcode/Object/Itf14.php b/library/Zend/Barcode/Object/Itf14.php new file mode 100644 index 000000000..909c4043a --- /dev/null +++ b/library/Zend/Barcode/Object/Itf14.php @@ -0,0 +1,49 @@ +_barcodeLength = 14; + $this->_mandatoryChecksum = true; + } +} diff --git a/library/Zend/Barcode/Object/Leitcode.php b/library/Zend/Barcode/Object/Leitcode.php new file mode 100644 index 000000000..ba5973a4a --- /dev/null +++ b/library/Zend/Barcode/Object/Leitcode.php @@ -0,0 +1,64 @@ +_barcodeLength = 14; + $this->_mandatoryChecksum = true; + } + + /** + * Retrieve text to display + * @return string + */ + public function getTextToDisplay() + { + return preg_replace('/([0-9]{5})([0-9]{3})([0-9]{3})([0-9]{2})([0-9])/', + '$1.$2.$3.$4 $5', + $this->getText()); + } +} diff --git a/library/Zend/Barcode/Object/ObjectAbstract.php b/library/Zend/Barcode/Object/ObjectAbstract.php new file mode 100644 index 000000000..b05cd768c --- /dev/null +++ b/library/Zend/Barcode/Object/ObjectAbstract.php @@ -0,0 +1,1281 @@ +_getDefaultOptions(); + if (self::$_staticFont !== null) { + $this->_font = self::$_staticFont; + } + if ($options instanceof Zend_Config) { + $options = $options->toArray(); + } + if (is_array($options)) { + $this->setOptions($options); + } + $this->_type = strtolower(substr(get_class($this), strlen($this->_barcodeNamespace) + 1)); + if ($this->_mandatoryChecksum) { + $this->_withChecksum = true; + $this->_withChecksumInText = true; + } + } + + /** + * Set default options for particular object + * @return void + */ + protected function _getDefaultOptions() + { + } + + /** + * Set barcode state from options array + * @param array $options + * @return Zend_Barcode_Object + */ + public function setOptions($options) + { + foreach ($options as $key => $value) { + $method = 'set' . $key; + if (method_exists($this, $method)) { + $this->$method($value); + } + } + return $this; + } + + /** + * Set barcode state from config object + * @param Zend_Config $config + * @return Zend_Barcode_Object + */ + public function setConfig(Zend_Config $config) + { + return $this->setOptions($config->toArray()); + } + + /** + * Set barcode namespace for autoloading + * + * @param string $namespace + * @return Zend_Barcode_Object + */ + public function setBarcodeNamespace($namespace) + { + $this->_barcodeNamespace = $namespace; + return $this; + } + + /** + * Retrieve barcode namespace + * + * @return string + */ + public function getBarcodeNamespace() + { + return $this->_barcodeNamespace; + } + + /** + * Retrieve type of barcode + * @return string + */ + public function getType() + { + return $this->_type; + } + + /** + * Set height of the barcode bar + * @param integer $value + * @return Zend_Barcode_Object + * @throw Zend_Barcode_Object_Exception + */ + public function setBarHeight($value) + { + if (intval($value) <= 0) { + require_once 'Zend/Barcode/Object/Exception.php'; + throw new Zend_Barcode_Object_Exception( + 'Bar height must be greater than 0' + ); + } + $this->_barHeight = intval($value); + return $this; + } + + /** + * Get height of the barcode bar + * @return integer + */ + public function getBarHeight() + { + return $this->_barHeight; + } + + /** + * Set thickness of thin bar + * @param integer $value + * @return Zend_Barcode_Object + * @throw Zend_Barcode_Object_Exception + */ + public function setBarThinWidth($value) + { + if (intval($value) <= 0) { + require_once 'Zend/Barcode/Object/Exception.php'; + throw new Zend_Barcode_Object_Exception( + 'Bar width must be greater than 0' + ); + } + $this->_barThinWidth = intval($value); + return $this; + } + + /** + * Get thickness of thin bar + * @return integer + */ + public function getBarThinWidth() + { + return $this->_barThinWidth; + } + + /** + * Set thickness of thick bar + * @param integer $value + * @return Zend_Barcode_Object + * @throw Zend_Barcode_Object_Exception + */ + public function setBarThickWidth($value) + { + if (intval($value) <= 0) { + require_once 'Zend/Barcode/Object/Exception.php'; + throw new Zend_Barcode_Object_Exception( + 'Bar width must be greater than 0' + ); + } + $this->_barThickWidth = intval($value); + return $this; + } + + /** + * Get thickness of thick bar + * @return integer + */ + public function getBarThickWidth() + { + return $this->_barThickWidth; + } + + /** + * Set factor applying to + * thinBarWidth - thickBarWidth - barHeight - fontSize + * @param integer $value + * @return Zend_Barcode_Object + * @throw Zend_Barcode_Object_Exception + */ + public function setFactor($value) + { + if (floatval($value) <= 0) { + require_once 'Zend/Barcode/Object/Exception.php'; + throw new Zend_Barcode_Object_Exception( + 'Factor must be greater than 0' + ); + } + $this->_factor = floatval($value); + return $this; + } + + /** + * Get factor applying to + * thinBarWidth - thickBarWidth - barHeight - fontSize + * @return integer + */ + public function getFactor() + { + return $this->_factor; + } + + /** + * Set color of the barcode and text + * @param string $value + * @return Zend_Barcode_Object + * @throw Zend_Barcode_Object_Exception + */ + public function setForeColor($value) + { + if (preg_match('`\#[0-9A-F]{6}`', $value)) { + $this->_foreColor = hexdec($value); + } elseif (is_numeric($value) && $value >= 0 && $value <= 16777125) { + $this->_foreColor = intval($value); + } else { + require_once 'Zend/Barcode/Object/Exception.php'; + throw new Zend_Barcode_Object_Exception( + 'Text color must be set as #[0-9A-F]{6}' + ); + } + return $this; + } + + /** + * Retrieve color of the barcode and text + * @return unknown + */ + public function getForeColor() + { + return $this->_foreColor; + } + + /** + * Set the color of the background + * @param integer $value + * @return Zend_Barcode_Object + * @throw Zend_Barcode_Object_Exception + */ + public function setBackgroundColor($value) + { + if (preg_match('`\#[0-9A-F]{6}`', $value)) { + $this->_backgroundColor = hexdec($value); + } elseif (is_numeric($value) && $value >= 0 && $value <= 16777125) { + $this->_backgroundColor = intval($value); + } else { + require_once 'Zend/Barcode/Object/Exception.php'; + throw new Zend_Barcode_Object_Exception( + 'Background color must be set as #[0-9A-F]{6}' + ); + } + return $this; + } + + /** + * Retrieve background color of the image + * @return integer + */ + public function getBackgroundColor() + { + return $this->_backgroundColor; + } + + /** + * Activate/deactivate drawing of the bar + * @param boolean $value + * @return Zend_Barcode_Object + */ + public function setWithBorder($value) + { + $this->_withBorder = (bool) $value; + return $this; + } + + /** + * Retrieve if border are draw or not + * @return boolean + */ + public function getWithBorder() + { + return $this->_withBorder; + } + + /** + * Allow fast inversion of font/bars color and background color + * @return Zend_Barcode_Object + */ + public function setReverseColor() + { + $tmp = $this->_foreColor; + $this->_foreColor = $this->_backgroundColor; + $this->_backgroundColor = $tmp; + return $this; + } + + /** + * Set orientation of barcode and text + * @param float $value + * @return Zend_Barcode_Object + * @throw Zend_Barcode_Object_Exception + */ + public function setOrientation($value) + { + $this->_orientation = floatval($value) - floor(floatval($value) / 360) * 360; + return $this; + } + + /** + * Retrieve orientation of barcode and text + * @return float + */ + public function getOrientation() + { + return $this->_orientation; + } + + /** + * Set text to encode + * @param string $value + * @return Zend_Barcode_Object + */ + public function setText($value) + { + $this->_text = trim($value); + return $this; + } + + /** + * Retrieve text to encode + * @return string + */ + public function getText() + { + $text = $this->_text; + if ($this->_withChecksum) { + $text .= $this->getChecksum($this->_text); + } + return $this->_addLeadingZeros($text); + } + + /** + * Automatically add leading zeros if barcode length is fixed + * @param string $text + * @param boolean $withoutChecksum + */ + protected function _addLeadingZeros($text, $withoutChecksum = false) + { + if ($this->_barcodeLength && $this->_addLeadingZeros) { + $omitChecksum = (int) ($this->_withChecksum && $withoutChecksum); + if (is_int($this->_barcodeLength)) { + $length = $this->_barcodeLength - $omitChecksum; + if (strlen($text) < $length) { + $text = str_repeat('0', $length - strlen($text)) . $text; + } + } else { + if ($this->_barcodeLength == 'even') { + $text = ((strlen($text) - $omitChecksum) % 2 ? '0' . $text : $text); + } + } + } + return $text; + } + + /** + * Retrieve text to encode + * @return string + */ + public function getRawText() + { + return $this->_text; + } + + /** + * Retrieve text to display + * @return string + */ + public function getTextToDisplay() + { + if ($this->_withChecksumInText) { + return $this->getText(); + } else { + return $this->_addLeadingZeros($this->_text, true); + } + } + + /** + * Activate/deactivate drawing of text to encode + * @param boolean $value + * @return Zend_Barcode_Object + */ + public function setDrawText($value) + { + $this->_drawText = (bool) $value; + return $this; + } + + /** + * Retrieve if drawing of text to encode is enabled + * @return boolean + */ + public function getDrawText() + { + return $this->_drawText; + } + + /** + * Activate/deactivate the adjustment of the position + * of the characters to the position of the bars + * @param boolean $value + * @return Zend_Barcode_Object + * @throw Zend_Barcode_Object_Exception + */ + public function setStretchText($value) + { + $this->_stretchText = (bool) $value; + return $this; + } + + /** + * Retrieve if the adjustment of the position of the characters + * to the position of the bars is enabled + * @return boolean + */ + public function getStretchText() + { + return $this->_stretchText; + } + + /** + * Activate/deactivate the automatic generation + * of the checksum character + * added to the barcode text + * @param boolean $value + * @return Zend_Barcode_Object + */ + public function setWithChecksum($value) + { + if (!$this->_mandatoryChecksum) { + $this->_withChecksum = (bool) $value; + } + return $this; + } + + /** + * Retrieve if the checksum character is automatically + * added to the barcode text + * @return boolean + */ + public function getWithChecksum() + { + return $this->_withChecksum; + } + + /** + * Activate/deactivate the automatic generation + * of the checksum character + * added to the barcode text + * @param boolean $value + * @return Zend_Barcode_Object + * @throw Zend_Barcode_Object_Exception + */ + public function setWithChecksumInText($value) + { + if (!$this->_mandatoryChecksum) { + $this->_withChecksumInText = (bool) $value; + } + return $this; + } + + /** + * Retrieve if the checksum character is automatically + * added to the barcode text + * @return boolean + */ + public function getWithChecksumInText() + { + return $this->_withChecksumInText; + } + + /** + * Set the font for all instances of barcode + * @param string $font + * @return void + */ + public static function setBarcodeFont($font) + { + if (is_string($font) || (is_int($font) && $font >= 1 && $font <= 5)) { + self::$_staticFont = $font; + } + } + + /** + * Set the font: + * - if integer between 1 and 5, use gd built-in fonts + * - if string, $value is assumed to be the path to a TTF font + * @param integer|string $value + * @return Zend_Barcode_Object + * @throw Zend_Barcode_Object_Exception + */ + public function setFont($value) + { + if (is_int($value) && $value >= 1 && $value <= 5) { + if (!extension_loaded('gd')) { + require_once 'Zend/Barcode/Object/Exception.php'; + throw new Zend_Barcode_Object_Exception( + 'GD extension is required to use numeric font' + ); + } + + // Case of numeric font with GD + $this->_font = $value; + + // In this case font size is given by: + $this->_fontSize = imagefontheight($value); + } elseif (is_string($value)) { + $this->_font = $value; + } else { + require_once 'Zend/Barcode/Object/Exception.php'; + throw new Zend_Barcode_Object_Exception(sprintf( + 'Invalid font "%s" provided to setFont()', + $value + )); + } + return $this; + } + + /** + * Retrieve the font + * @return integer|string + */ + public function getFont() + { + return $this->_font; + } + + /** + * Set the size of the font in case of TTF + * @param float $value + * @return Zend_Barcode_Object + * @throw Zend_Barcode_Object_Exception + */ + public function setFontSize($value) + { + if (is_numeric($this->_font)) { + // Case of numeric font with GD + return $this; + } + + if (!is_numeric($value)) { + require_once 'Zend/Barcode/Object/Exception.php'; + throw new Zend_Barcode_Object_Exception( + 'Font size must be a numeric value' + ); + } + + $this->_fontSize = $value; + return $this; + } + + /** + * Retrieve the size of the font in case of TTF + * @return float + */ + public function getFontSize() + { + return $this->_fontSize; + } + + /** + * Quiet zone before first bar + * and after the last bar + * @return integer + */ + public function getQuietZone() + { + return 10 * $this->_barThinWidth * $this->_factor; + } + + /** + * Add an instruction in the array of instructions + * @param array $instruction + */ + protected function _addInstruction(array $instruction) + { + $this->_instructions[] = $instruction; + } + + /** + * Retrieve the set of drawing instructions + * @return array + */ + public function getInstructions() + { + return $this->_instructions; + } + + /** + * Add a polygon drawing instruction in the set of instructions + * @param array $points + * @param integer $color + * @param boolean $filled + */ + protected function _addPolygon(array $points, $color = null, $filled = true) + { + if ($color === null) { + $color = $this->_foreColor; + } + $this->_addInstruction(array( + 'type' => 'polygon', + 'points' => $points, + 'color' => $color, + 'filled' => $filled, + )); + } + + /** + * Add a text drawing instruction in the set of instructions + * @param string $text + * @param float $size + * @param array $position + * @param string $font + * @param integer $color + * @param string $alignment + * @param float $orientation + */ + protected function _addText( + $text, + $size, + $position, + $font, + $color, + $alignment = 'center', + $orientation = 0 + ) { + if ($color === null) { + $color = $this->_foreColor; + } + $this->_addInstruction(array( + 'type' => 'text', + 'text' => $text, + 'size' => $size, + 'position' => $position, + 'font' => $font, + 'color' => $color, + 'alignment' => $alignment, + 'orientation' => $orientation, + )); + } + + /** + * Checking of parameters after all settings + * @return void + */ + public function checkParams() + { + $this->_checkText(); + $this->_checkFontAndOrientation(); + $this->_checkParams(); + return true; + } + + /** + * Check if a text is really provided to barcode + * @return void + * @throw Zend_Barcode_Object_Exception + */ + protected function _checkText($value = null) + { + if ($value === null) { + $value = $this->_text; + } + if (!strlen($value)) { + require_once 'Zend/Barcode/Object/Exception.php'; + throw new Zend_Barcode_Object_Exception( + 'A text must be provide to Barcode before drawing' + ); + } + $this->validateText($value); + } + + /** + * Check the ratio between the thick and the thin bar + * @param integer $min + * @param integer $max + * @return void + * @throw Zend_Barcode_Object_Exception + */ + protected function _checkRatio($min = 2, $max = 3) + { + $ratio = $this->_barThickWidth / $this->_barThinWidth; + if (!($ratio >= $min && $ratio <= $max)) { + require_once 'Zend/Barcode/Object/Exception.php'; + throw new Zend_Barcode_Object_Exception(sprintf( + 'Ratio thick/thin bar must be between %0.1f and %0.1f (actual %0.3f)', + $min, + $max, + $ratio + )); + } + } + + /** + * Drawing with an angle is just allow TTF font + * @return void + * @throw Zend_Barcode_Object_Exception + */ + protected function _checkFontAndOrientation() + { + if (is_numeric($this->_font) && $this->_orientation != 0) { + require_once 'Zend/Barcode/Object/Exception.php'; + throw new Zend_Barcode_Object_Exception( + 'Only drawing with TTF font allow orientation of the barcode.' + ); + } + } + + /** + * Width of the result image + * (before any rotation) + * @return integer + */ + protected function _calculateWidth() + { + return (int) $this->_withBorder + + $this->_calculateBarcodeWidth() + + (int) $this->_withBorder; + } + + /** + * Calculate the width of the barcode + * @return integer + */ + abstract protected function _calculateBarcodeWidth(); + + /** + * Height of the result object + * @return integer + */ + protected function _calculateHeight() + { + return (int) $this->_withBorder * 2 + + $this->_calculateBarcodeHeight() + + (int) $this->_withBorder * 2; + } + + /** + * Height of the barcode + * @return integer + */ + protected function _calculateBarcodeHeight() + { + $textHeight = 0; + $extraHeight = 0; + if ($this->_drawText) { + $textHeight += $this->_fontSize; + $extraHeight = 2; + } + return ($this->_barHeight + $textHeight) * $this->_factor + $extraHeight; + } + + /** + * Get height of the result object + * @return integer + */ + public function getHeight($recalculate = false) + { + if ($this->_height === null || $recalculate) { + $this->_height = + abs($this->_calculateHeight() * cos($this->_orientation / 180 * pi())) + + abs($this->_calculateWidth() * sin($this->_orientation / 180 * pi())); + } + return $this->_height; + } + + /** + * Get width of the result object + * @return integer + */ + public function getWidth($recalculate = false) + { + if ($this->_width === null || $recalculate) { + $this->_width = + abs($this->_calculateWidth() * cos($this->_orientation / 180 * pi())) + + abs($this->_calculateHeight() * sin($this->_orientation / 180 * pi())); + } + return $this->_width; + } + + /** + * Calculate the offset from the left of the object + * if an orientation is activated + * @param boolean $recalculate + * @return float + */ + public function getOffsetLeft($recalculate = false) + { + if ($this->_offsetLeft === null || $recalculate) { + $this->_offsetLeft = - min(array( + 0 * cos( + $this->_orientation / 180 * pi()) - 0 * sin( + $this->_orientation / 180 * pi()), + 0 * cos( + $this->_orientation / 180 * pi()) - $this->_calculateBarcodeHeight() * sin( + $this->_orientation / 180 * pi()), + $this->_calculateBarcodeWidth() * cos( + $this->_orientation / 180 * pi()) - $this->_calculateBarcodeHeight() * sin( + $this->_orientation / 180 * pi()), + $this->_calculateBarcodeWidth() * cos( + $this->_orientation / 180 * pi()) - 0 * sin( + $this->_orientation / 180 * pi()), + )); + } + return $this->_offsetLeft; + } + + /** + * Calculate the offset from the top of the object + * if an orientation is activated + * @param boolean $recalculate + * @return float + */ + public function getOffsetTop($recalculate = false) + { + if ($this->_offsetTop === null || $recalculate) { + $this->_offsetTop = - min(array( + 0 * cos( + $this->_orientation / 180 * pi()) + 0 * sin( + $this->_orientation / 180 * pi()), + $this->_calculateBarcodeHeight() * cos( + $this->_orientation / 180 * pi()) + 0 * sin( + $this->_orientation / 180 * pi()), + $this->_calculateBarcodeHeight() * cos( + $this->_orientation / 180 * pi()) + $this->_calculateBarcodeWidth() * sin( + $this->_orientation / 180 * pi()), + 0 * cos( + $this->_orientation / 180 * pi()) + $this->_calculateBarcodeWidth() * sin( + $this->_orientation / 180 * pi()), + )); + } + return $this->_offsetTop; + } + + /** + * Apply rotation on a point in X/Y dimensions + * @param float $x1 x-position before rotation + * @param float $y1 y-position before rotation + * @return array Array of two elements corresponding to the new XY point + */ + protected function _rotate($x1, $y1) + { + $x2 = $x1 * cos($this->_orientation / 180 * pi()) + - $y1 * sin($this->_orientation / 180 * pi()) + + $this->getOffsetLeft(); + $y2 = $y1 * cos($this->_orientation / 180 * pi()) + + $x1 * sin($this->_orientation / 180 * pi()) + + $this->getOffsetTop(); + return array(intval($x2) , intval($y2)); + } + + /** + * Complete drawing of the barcode + * @return array Table of instructions + */ + public function draw() + { + $this->checkParams(); + $this->_drawBarcode(); + $this->_drawBorder(); + $this->_drawText(); + return $this->getInstructions(); + } + + /** + * Draw the barcode + * @return void + */ + protected function _drawBarcode() + { + $barcodeTable = $this->_prepareBarcode(); + + $this->_preDrawBarcode(); + + $xpos = (int) $this->_withBorder; + $ypos = (int) $this->_withBorder; + + $point1 = $this->_rotate(0, 0); + $point2 = $this->_rotate(0, $this->_calculateHeight() - 1); + $point3 = $this->_rotate( + $this->_calculateWidth() - 1, + $this->_calculateHeight() - 1 + ); + $point4 = $this->_rotate($this->_calculateWidth() - 1, 0); + + $this->_addPolygon(array( + $point1, + $point2, + $point3, + $point4 + ), $this->_backgroundColor); + + $xpos += $this->getQuietZone(); + $barLength = $this->_barHeight * $this->_factor; + + foreach ($barcodeTable as $bar) { + $width = $bar[1] * $this->_factor; + if ($bar[0]) { + $point1 = $this->_rotate($xpos, $ypos + $bar[2] * $barLength); + $point2 = $this->_rotate($xpos, $ypos + $bar[3] * $barLength); + $point3 = $this->_rotate( + $xpos + $width - 1, + $ypos + $bar[3] * $barLength + ); + $point4 = $this->_rotate( + $xpos + $width - 1, + $ypos + $bar[2] * $barLength + ); + $this->_addPolygon(array( + $point1, + $point2, + $point3, + $point4, + )); + } + $xpos += $width; + } + + $this->_postDrawBarcode(); + } + + /** + * Partial function to draw border + * @return void + */ + protected function _drawBorder() + { + if ($this->_withBorder) { + $point1 = $this->_rotate(0, 0); + $point2 = $this->_rotate($this->_calculateWidth() - 1, 0); + $point3 = $this->_rotate( + $this->_calculateWidth() - 1, + $this->_calculateHeight() - 1 + ); + $point4 = $this->_rotate(0, $this->_calculateHeight() - 1); + $this->_addPolygon(array( + $point1, + $point2, + $point3, + $point4, + $point1, + ), $this->_foreColor, false); + } + } + + /** + * Partial function to draw text + * @return void + */ + protected function _drawText() + { + if ($this->_drawText) { + $text = $this->getTextToDisplay(); + if ($this->_stretchText) { + $textLength = strlen($text); + $space = ($this->_calculateWidth() - 2 * $this->getQuietZone()) / $textLength; + for ($i = 0; $i < $textLength; $i ++) { + $leftPosition = $this->getQuietZone() + $space * ($i + 0.5); + $this->_addText( + $text{$i}, + $this->_fontSize * $this->_factor, + $this->_rotate( + $leftPosition, + (int) $this->_withBorder * 2 + + $this->_factor * ($this->_barHeight + $this->_fontSize) + 1 + ), + $this->_font, + $this->_foreColor, + 'center', + - $this->_orientation + ); + } + } else { + $this->_addText( + $text, + $this->_fontSize * $this->_factor, + $this->_rotate( + $this->_calculateWidth() / 2, + (int) $this->_withBorder * 2 + + $this->_factor * ($this->_barHeight + $this->_fontSize) + 1 + ), + $this->_font, + $this->_foreColor, + 'center', + - $this->_orientation + ); + } + } + } + + /** + * Check for invalid characters + * @param string $value Text to be ckecked + * @return void + */ + public function validateText($value) + { + $this->_validateText($value); + } + + /** + * Standard validation for most of barcode objects + * @param string $value + * @param array $options + */ + protected function _validateText($value, $options = array()) + { + $validatorName = (isset($options['validator'])) ? $options['validator'] : $this->getType(); + + $validator = new Zend_Validate_Barcode(array( + 'adapter' => $validatorName, + 'checksum' => false, + )); + + $checksumCharacter = ''; + $withChecksum = false; + if ($this->_mandatoryChecksum) { + $checksumCharacter = $this->_substituteChecksumCharacter; + $withChecksum = true; + } + + $value = $this->_addLeadingZeros($value, $withChecksum) . $checksumCharacter; + + if (!$validator->isValid($value)) { + $message = implode("\n", $validator->getMessages()); + + /** + * @see Zend_Barcode_Object_Exception + */ + require_once 'Zend/Barcode/Object/Exception.php'; + throw new Zend_Barcode_Object_Exception($message); + } + } + + /** + * Each child must prepare the barcode and return + * a table like array( + * 0 => array( + * 0 => int (visible(black) or not(white)) + * 1 => int (width of the bar) + * 2 => float (0->1 position from the top of the beginning of the bar in %) + * 3 => float (0->1 position from the top of the end of the bar in %) + * ), + * 1 => ... + * ) + * + * @return array + */ + abstract protected function _prepareBarcode(); + + /** + * Checking of parameters after all settings + * + * @return void + */ + abstract protected function _checkParams(); + + /** + * Allow each child to draw something else + * + * @return void + */ + protected function _preDrawBarcode() + { + } + + /** + * Allow each child to draw something else + * (ex: bearer bars in interleaved 2 of 5 code) + * + * @return void + */ + protected function _postDrawBarcode() + { + } +} diff --git a/library/Zend/Barcode/Object/Planet.php b/library/Zend/Barcode/Object/Planet.php new file mode 100644 index 000000000..eb346a280 --- /dev/null +++ b/library/Zend/Barcode/Object/Planet.php @@ -0,0 +1,62 @@ + "00111", + 1 => "11100", + 2 => "11010", + 3 => "11001", + 4 => "10110", + 5 => "10101", + 6 => "10011", + 7 => "01110", + 8 => "01101", + 9 => "01011" + ); +} \ No newline at end of file diff --git a/library/Zend/Barcode/Object/Postnet.php b/library/Zend/Barcode/Object/Postnet.php new file mode 100644 index 000000000..4e7eced7e --- /dev/null +++ b/library/Zend/Barcode/Object/Postnet.php @@ -0,0 +1,136 @@ + "11000", + 1 => "00011", + 2 => "00101", + 3 => "00110", + 4 => "01001", + 5 => "01010", + 6 => "01100", + 7 => "10001", + 8 => "10010", + 9 => "10100" + ); + + /** + * Default options for Postnet barcode + * @return void + */ + protected function _getDefaultOptions() + { + $this->_barThinWidth = 2; + $this->_barHeight = 20; + $this->_drawText = false; + $this->_stretchText = true; + $this->_mandatoryChecksum = true; + } + + /** + * Width of the barcode (in pixels) + * @return integer + */ + protected function _calculateBarcodeWidth() + { + $quietZone = $this->getQuietZone(); + $startCharacter = (2 * $this->_barThinWidth) * $this->_factor; + $stopCharacter = (1 * $this->_barThinWidth) * $this->_factor; + $encodedData = (10 * $this->_barThinWidth) * $this->_factor * strlen($this->getText()); + return $quietZone + $startCharacter + $encodedData + $stopCharacter + $quietZone; + } + + /** + * Partial check of interleaved Postnet barcode + * @return void + */ + protected function _checkParams() + {} + + /** + * Prepare array to draw barcode + * @return array + */ + protected function _prepareBarcode() + { + $barcodeTable = array(); + + // Start character (1) + $barcodeTable[] = array(1 , $this->_barThinWidth , 0 , 1); + $barcodeTable[] = array(0 , $this->_barThinWidth , 0 , 1); + + // Text to encode + $textTable = str_split($this->getText()); + foreach ($textTable as $char) { + $bars = str_split($this->_codingMap[$char]); + foreach ($bars as $b) { + $barcodeTable[] = array(1 , $this->_barThinWidth , 0.5 - $b * 0.5 , 1); + $barcodeTable[] = array(0 , $this->_barThinWidth , 0 , 1); + } + } + + // Stop character (1) + $barcodeTable[] = array(1 , $this->_barThinWidth , 0 , 1); + return $barcodeTable; + } + + /** + * Get barcode checksum + * + * @param string $text + * @return int + */ + public function getChecksum($text) + { + $this->_checkText($text); + $sum = array_sum(str_split($text)); + $checksum = (10 - ($sum % 10)) % 10; + return $checksum; + } +} diff --git a/library/Zend/Barcode/Object/Royalmail.php b/library/Zend/Barcode/Object/Royalmail.php new file mode 100644 index 000000000..87052fdb0 --- /dev/null +++ b/library/Zend/Barcode/Object/Royalmail.php @@ -0,0 +1,163 @@ + '3300', '1' => '3210', '2' => '3201', '3' => '2310', '4' => '2301', '5' => '2211', + '6' => '3120', '7' => '3030', '8' => '3021', '9' => '2130', 'A' => '2121', 'B' => '2031', + 'C' => '3102', 'D' => '3012', 'E' => '3003', 'F' => '2112', 'G' => '2103', 'H' => '2013', + 'I' => '1320', 'J' => '1230', 'K' => '1221', 'L' => '0330', 'M' => '0321', 'N' => '0231', + 'O' => '1302', 'P' => '1212', 'Q' => '1203', 'R' => '0312', 'S' => '0303', 'T' => '0213', + 'U' => '1122', 'V' => '1032', 'W' => '1023', 'X' => '0132', 'Y' => '0123', 'Z' => '0033' + ); + + protected $_rows = array( + '0' => 1, '1' => 1, '2' => 1, '3' => 1, '4' => 1, '5' => 1, + '6' => 2, '7' => 2, '8' => 2, '9' => 2, 'A' => 2, 'B' => 2, + 'C' => 3, 'D' => 3, 'E' => 3, 'F' => 3, 'G' => 3, 'H' => 3, + 'I' => 4, 'J' => 4, 'K' => 4, 'L' => 4, 'M' => 4, 'N' => 4, + 'O' => 5, 'P' => 5, 'Q' => 5, 'R' => 5, 'S' => 5, 'T' => 5, + 'U' => 0, 'V' => 0, 'W' => 0, 'X' => 0, 'Y' => 0, 'Z' => 0, + ); + + protected $_columns = array( + '0' => 1, '1' => 2, '2' => 3, '3' => 4, '4' => 5, '5' => 0, + '6' => 1, '7' => 2, '8' => 3, '9' => 4, 'A' => 5, 'B' => 0, + 'C' => 1, 'D' => 2, 'E' => 3, 'F' => 4, 'G' => 5, 'H' => 0, + 'I' => 1, 'J' => 2, 'K' => 3, 'L' => 4, 'M' => 5, 'N' => 0, + 'O' => 1, 'P' => 2, 'Q' => 3, 'R' => 4, 'S' => 5, 'T' => 0, + 'U' => 1, 'V' => 2, 'W' => 3, 'X' => 4, 'Y' => 5, 'Z' => 0, + ); + + /** + * Default options for Postnet barcode + * @return void + */ + protected function _getDefaultOptions() + { + $this->_barThinWidth = 2; + $this->_barHeight = 20; + $this->_drawText = false; + $this->_stretchText = true; + $this->_mandatoryChecksum = true; + } + + /** + * Width of the barcode (in pixels) + * @return integer + */ + protected function _calculateBarcodeWidth() + { + $quietZone = $this->getQuietZone(); + $startCharacter = (2 * $this->_barThinWidth) * $this->_factor; + $stopCharacter = (1 * $this->_barThinWidth) * $this->_factor; + $encodedData = (8 * $this->_barThinWidth) * $this->_factor * strlen($this->getText()); + return $quietZone + $startCharacter + $encodedData + $stopCharacter + $quietZone; + } + + /** + * Partial check of interleaved Postnet barcode + * @return void + */ + protected function _checkParams() + {} + + /** + * Prepare array to draw barcode + * @return array + */ + protected function _prepareBarcode() + { + $barcodeTable = array(); + + // Start character (1) + $barcodeTable[] = array(1 , $this->_barThinWidth , 0 , 5/8); + $barcodeTable[] = array(0 , $this->_barThinWidth , 0 , 1); + + // Text to encode + $textTable = str_split($this->getText()); + foreach ($textTable as $char) { + $bars = str_split($this->_codingMap[$char]); + foreach ($bars as $b) { + $barcodeTable[] = array(1 , $this->_barThinWidth , ($b > 1 ? 3/8 : 0) , ($b % 2 ? 5/8 : 1)); + $barcodeTable[] = array(0 , $this->_barThinWidth , 0 , 1); + } + } + + // Stop character (1) + $barcodeTable[] = array(1 , $this->_barThinWidth , 0 , 1); + return $barcodeTable; + } + + /** + * Get barcode checksum + * + * @param string $text + * @return int + */ + public function getChecksum($text) + { + $this->_checkText($text); + $values = str_split($text); + $rowvalue = 0; + $colvalue = 0; + foreach($values as $row) { + $rowvalue += $this->_rows[$row]; + $colvalue += $this->_columns[$row]; + } + + $rowvalue %= 6; + $colvalue %= 6; + + $rowchkvalue = array_keys($this->_rows, $rowvalue); + $colchkvalue = array_keys($this->_columns, $colvalue); + return current(array_intersect($rowchkvalue, $colchkvalue)); + } +} diff --git a/library/Zend/Barcode/Object/Upca.php b/library/Zend/Barcode/Object/Upca.php new file mode 100644 index 000000000..f34225987 --- /dev/null +++ b/library/Zend/Barcode/Object/Upca.php @@ -0,0 +1,171 @@ +_barcodeLength = 12; + $this->_mandatoryChecksum = true; + } + + /** + * Width of the barcode (in pixels) + * @return integer + */ + protected function _calculateBarcodeWidth() + { + $quietZone = $this->getQuietZone(); + $startCharacter = (3 * $this->_barThinWidth) * $this->_factor; + $middleCharacter = (5 * $this->_barThinWidth) * $this->_factor; + $stopCharacter = (3 * $this->_barThinWidth) * $this->_factor; + $encodedData = (7 * $this->_barThinWidth) * $this->_factor * 12; + return $quietZone + $startCharacter + $middleCharacter + $encodedData + $stopCharacter + $quietZone; + } + + /** + * Prepare array to draw barcode + * @return array + */ + protected function _prepareBarcode() + { + $barcodeTable = array(); + $height = ($this->_drawText) ? 1.1 : 1; + + // Start character (101) + $barcodeTable[] = array(1 , $this->_barThinWidth , 0 , $height); + $barcodeTable[] = array(0 , $this->_barThinWidth , 0 , $height); + $barcodeTable[] = array(1 , $this->_barThinWidth , 0 , $height); + + $textTable = str_split($this->getText()); + + // First character + $bars = str_split($this->_codingMap['A'][$textTable[0]]); + foreach ($bars as $b) { + $barcodeTable[] = array($b , $this->_barThinWidth , 0 , $height); + } + + // First part + for ($i = 1; $i < 6; $i++) { + $bars = str_split($this->_codingMap['A'][$textTable[$i]]); + foreach ($bars as $b) { + $barcodeTable[] = array($b , $this->_barThinWidth , 0 , 1); + } + } + + // Middle character (01010) + $barcodeTable[] = array(0 , $this->_barThinWidth , 0 , $height); + $barcodeTable[] = array(1 , $this->_barThinWidth , 0 , $height); + $barcodeTable[] = array(0 , $this->_barThinWidth , 0 , $height); + $barcodeTable[] = array(1 , $this->_barThinWidth , 0 , $height); + $barcodeTable[] = array(0 , $this->_barThinWidth , 0 , $height); + + // Second part + for ($i = 6; $i < 11; $i++) { + $bars = str_split($this->_codingMap['C'][$textTable[$i]]); + foreach ($bars as $b) { + $barcodeTable[] = array($b , $this->_barThinWidth , 0 , 1); + } + } + + // Last character + $bars = str_split($this->_codingMap['C'][$textTable[11]]); + foreach ($bars as $b) { + $barcodeTable[] = array($b , $this->_barThinWidth , 0 , $height); + } + + // Stop character (101) + $barcodeTable[] = array(1 , $this->_barThinWidth , 0 , $height); + $barcodeTable[] = array(0 , $this->_barThinWidth , 0 , $height); + $barcodeTable[] = array(1 , $this->_barThinWidth , 0 , $height); + return $barcodeTable; + } + + /** + * Partial function to draw text + * @return void + */ + protected function _drawText() + { + if ($this->_drawText) { + $text = $this->getTextToDisplay(); + $characterWidth = (7 * $this->_barThinWidth) * $this->_factor; + $leftPosition = $this->getQuietZone() - $characterWidth; + for ($i = 0; $i < $this->_barcodeLength; $i ++) { + $fontSize = $this->_fontSize; + if ($i == 0 || $i == 11) { + $fontSize *= 0.8; + } + $this->_addText( + $text{$i}, + $fontSize * $this->_factor, + $this->_rotate( + $leftPosition, + (int) $this->_withBorder * 2 + + $this->_factor * ($this->_barHeight + $fontSize) + 1 + ), + $this->_font, + $this->_foreColor, + 'left', + - $this->_orientation + ); + switch ($i) { + case 0: + $factor = 10; + break; + case 5: + $factor = 4; + break; + case 10: + $factor = 11; + break; + default: + $factor = 0; + } + $leftPosition = $leftPosition + $characterWidth + ($factor * $this->_barThinWidth * $this->_factor); + } + } + } +} diff --git a/library/Zend/Barcode/Object/Upce.php b/library/Zend/Barcode/Object/Upce.php new file mode 100644 index 000000000..82098603d --- /dev/null +++ b/library/Zend/Barcode/Object/Upce.php @@ -0,0 +1,227 @@ + array( + 0 => array('B','B','B','A','A','A'), + 1 => array('B','B','A','B','A','A'), + 2 => array('B','B','A','A','B','A'), + 3 => array('B','B','A','A','A','B'), + 4 => array('B','A','B','B','A','A'), + 5 => array('B','A','A','B','B','A'), + 6 => array('B','A','A','A','B','B'), + 7 => array('B','A','B','A','B','A'), + 8 => array('B','A','B','A','A','B'), + 9 => array('B','A','A','B','A','B')), + 1 => array( + 0 => array('A','A','A','B','B','B'), + 1 => array('A','A','B','A','B','B'), + 2 => array('A','A','B','B','A','B'), + 3 => array('A','A','B','B','B','A'), + 4 => array('A','B','A','A','B','B'), + 5 => array('A','B','B','A','A','B'), + 6 => array('A','B','B','B','A','A'), + 7 => array('A','B','A','B','A','B'), + 8 => array('A','B','A','B','B','A'), + 9 => array('A','B','B','A','B','A')) + ); + + /** + * Default options for Postnet barcode + * @return void + */ + protected function _getDefaultOptions() + { + $this->_barcodeLength = 8; + $this->_mandatoryChecksum = true; + } + + /** + * Retrieve text to encode + * @return string + */ + public function getText() + { + $text = parent::getText(); + if ($text{0} != 1) { + $text{0} = 0; + } + return $text; + } + + /** + * Width of the barcode (in pixels) + * @return integer + */ + protected function _calculateBarcodeWidth() + { + $quietZone = $this->getQuietZone(); + $startCharacter = (3 * $this->_barThinWidth) * $this->_factor; + $stopCharacter = (6 * $this->_barThinWidth) * $this->_factor; + $encodedData = (7 * $this->_barThinWidth) * $this->_factor * 6; + return $quietZone + $startCharacter + $encodedData + $stopCharacter + $quietZone; + } + + /** + * Prepare array to draw barcode + * @return array + */ + protected function _prepareBarcode() + { + $barcodeTable = array(); + $height = ($this->_drawText) ? 1.1 : 1; + + // Start character (101) + $barcodeTable[] = array(1 , $this->_barThinWidth , 0 , $height); + $barcodeTable[] = array(0 , $this->_barThinWidth , 0 , $height); + $barcodeTable[] = array(1 , $this->_barThinWidth , 0 , $height); + + $textTable = str_split($this->getText()); + $system = 0; + if ($textTable[0] == 1) { + $system = 1; + } + $checksum = $textTable[7]; + $parity = $this->_parities[$system][$checksum]; + + for ($i = 1; $i < 7; $i++) { + $bars = str_split($this->_codingMap[$parity[$i - 1]][$textTable[$i]]); + foreach ($bars as $b) { + $barcodeTable[] = array($b , $this->_barThinWidth , 0 , 1); + } + } + + // Stop character (10101) + $barcodeTable[] = array(0 , $this->_barThinWidth , 0 , $height); + $barcodeTable[] = array(1 , $this->_barThinWidth , 0 , $height); + $barcodeTable[] = array(0 , $this->_barThinWidth , 0 , $height); + $barcodeTable[] = array(1 , $this->_barThinWidth , 0 , $height); + $barcodeTable[] = array(0 , $this->_barThinWidth , 0 , $height); + $barcodeTable[] = array(1 , $this->_barThinWidth , 0 , $height); + return $barcodeTable; + } + + /** + * Partial function to draw text + * @return void + */ + protected function _drawText() + { + if ($this->_drawText) { + $text = $this->getTextToDisplay(); + $characterWidth = (7 * $this->_barThinWidth) * $this->_factor; + $leftPosition = $this->getQuietZone() - $characterWidth; + for ($i = 0; $i < $this->_barcodeLength; $i ++) { + $fontSize = $this->_fontSize; + if ($i == 0 || $i == 7) { + $fontSize *= 0.8; + } + $this->_addText( + $text{$i}, + $fontSize * $this->_factor, + $this->_rotate( + $leftPosition, + (int) $this->_withBorder * 2 + + $this->_factor * ($this->_barHeight + $fontSize) + 1 + ), + $this->_font, + $this->_foreColor, + 'left', + - $this->_orientation + ); + switch ($i) { + case 0: + $factor = 3; + break; + case 6: + $factor = 5; + break; + default: + $factor = 0; + } + $leftPosition = $leftPosition + $characterWidth + ($factor * $this->_barThinWidth * $this->_factor); + } + } + } + + /** + * Particular validation for Upce barcode objects + * (to suppress checksum character substitution) + * @param string $value + * @param array $options + */ + protected function _validateText($value, $options = array()) + { + $validator = new Zend_Validate_Barcode(array( + 'adapter' => 'upce', + 'checksum' => false, + )); + + $value = $this->_addLeadingZeros($value, true); + + if (!$validator->isValid($value)) { + $message = implode("\n", $validator->getMessages()); + + /** + * @see Zend_Barcode_Object_Exception + */ + require_once 'Zend/Barcode/Object/Exception.php'; + throw new Zend_Barcode_Object_Exception($message); + } + } + + /** + * Get barcode checksum + * + * @param string $text + * @return int + */ + public function getChecksum($text) + { + $text = $this->_addLeadingZeros($text, true); + if ($text{0} != 1) { + $text{0} = 0; + } + return parent::getChecksum($text); + } +} diff --git a/library/Zend/Barcode/Renderer/Exception.php b/library/Zend/Barcode/Renderer/Exception.php new file mode 100644 index 000000000..b230884ed --- /dev/null +++ b/library/Zend/Barcode/Renderer/Exception.php @@ -0,0 +1,35 @@ +_userHeight = intval($value); + return $this; + } + + /** + * Get barcode height + * + * @return int + */ + public function getHeight() + { + return $this->_userHeight; + } + + /** + * Set barcode width + * + * @param mixed $value + * @return void + */ + public function setWidth($value) + { + if (!is_numeric($value) || intval($value) < 0) { + require_once 'Zend/Barcode/Renderer/Exception.php'; + throw new Zend_Barcode_Renderer_Exception( + 'Image width must be greater than or equals 0' + ); + } + $this->_userWidth = intval($value); + return $this; + } + + /** + * Get barcode width + * + * @return int + */ + public function getWidth() + { + return $this->_userWidth; + } + + /** + * Set an image resource to draw the barcode inside + * + * @param resource $value + * @return Zend_Barcode_Renderer + * @throw Zend_Barcode_Renderer_Exception + */ + public function setResource($image) + { + if (gettype($image) != 'resource' || get_resource_type($image) != 'gd') { + require_once 'Zend/Barcode/Renderer/Exception.php'; + throw new Zend_Barcode_Renderer_Exception( + 'Invalid image resource provided to setResource()' + ); + } + $this->_resource = $image; + return $this; + } + + /** + * Set the image type to produce (png, jpeg, gif) + * + * @param string $value + * @return Zend_Barcode_RendererAbstract + * @throw Zend_Barcode_Renderer_Exception + */ + public function setImageType($value) + { + if ($value == 'jpg') { + $value = 'jpeg'; + } + + if (!in_array($value, $this->_allowedImageType)) { + require_once 'Zend/Barcode/Renderer/Exception.php'; + throw new Zend_Barcode_Renderer_Exception(sprintf( + 'Invalid type "%s" provided to setImageType()', + $value + )); + } + + $this->_imageType = $value; + return $this; + } + + /** + * Retrieve the image type to produce + * + * @return string + */ + public function getImageType() + { + return $this->_imageType; + } + + /** + * Initialize the image resource + * + * @return void + */ + protected function _initRenderer() + { + if (!extension_loaded('gd')) { + require_once 'Zend/Barcode/Exception.php'; + $e = new Zend_Barcode_Exception( + 'Gd extension must be loaded to render barcode as image' + ); + $e->setIsRenderable(false); + throw $e; + } + + $barcodeWidth = $this->_barcode->getWidth(true); + $barcodeHeight = $this->_barcode->getHeight(true); + + if ($this->_resource !== null) { + $foreColor = $this->_barcode->getForeColor(); + $backgroundColor = $this->_barcode->getBackgroundColor(); + $this->_imageBackgroundColor = imagecolorallocate( + $this->_resource, + ($backgroundColor & 0xFF0000) >> 16, + ($backgroundColor & 0x00FF00) >> 8, + $backgroundColor & 0x0000FF + ); + $this->_imageForeColor = imagecolorallocate( + $this->_resource, + ($foreColor & 0xFF0000) >> 16, + ($foreColor & 0x00FF00) >> 8, + $foreColor & 0x0000FF + ); + } else { + $width = $barcodeWidth; + $height = $barcodeHeight; + if ($this->_userWidth && $this->_barcode->getType() != 'error') { + $width = $this->_userWidth; + } + if ($this->_userHeight && $this->_barcode->getType() != 'error') { + $height = $this->_userHeight; + } + + $foreColor = $this->_barcode->getForeColor(); + $backgroundColor = $this->_barcode->getBackgroundColor(); + $this->_resource = imagecreatetruecolor($width, $height); + + $this->_imageBackgroundColor = imagecolorallocate( + $this->_resource, + ($backgroundColor & 0xFF0000) >> 16, + ($backgroundColor & 0x00FF00) >> 8, + $backgroundColor & 0x0000FF + ); + $this->_imageForeColor = imagecolorallocate( + $this->_resource, + ($foreColor & 0xFF0000) >> 16, + ($foreColor & 0x00FF00) >> 8, + $foreColor & 0x0000FF + ); + $white = imagecolorallocate($this->_resource, 255, 255, 255); + imagefilledrectangle($this->_resource, 0, 0, $width - 1, $height - 1, $white); + } + $this->_adjustPosition(imagesy($this->_resource), imagesx($this->_resource)); + imagefilledrectangle( + $this->_resource, + $this->_leftOffset, + $this->_topOffset, + $this->_leftOffset + $barcodeWidth - 1, + $this->_topOffset + $barcodeHeight - 1, + $this->_imageBackgroundColor + ); + } + + /** + * Check barcode parameters + * + * @return void + */ + protected function _checkParams() + { + $this->_checkDimensions(); + } + + /** + * Check barcode dimensions + * + * @return void + */ + protected function _checkDimensions() + { + if ($this->_resource !== null) { + if (imagesy($this->_resource) < $this->_barcode->getHeight(true)) { + require_once 'Zend/Barcode/Renderer/Exception.php'; + throw new Zend_Barcode_Renderer_Exception( + 'Barcode is define outside the image (height)' + ); + } + } else { + if ($this->_userHeight) { + $height = $this->_barcode->getHeight(true); + if ($this->_userHeight < $height) { + require_once 'Zend/Barcode/Renderer/Exception.php'; + throw new Zend_Barcode_Renderer_Exception(sprintf( + "Barcode is define outside the image (calculated: '%d', provided: '%d')", + $height, + $this->_userHeight + )); + } + } + } + if ($this->_resource !== null) { + if (imagesx($this->_resource) < $this->_barcode->getWidth(true)) { + require_once 'Zend/Barcode/Renderer/Exception.php'; + throw new Zend_Barcode_Renderer_Exception( + 'Barcode is define outside the image (width)' + ); + } + } else { + if ($this->_userWidth) { + $width = $this->_barcode->getWidth(true); + if ($this->_userWidth < $width) { + require_once 'Zend/Barcode/Renderer/Exception.php'; + throw new Zend_Barcode_Renderer_Exception(sprintf( + "Barcode is define outside the image (calculated: '%d', provided: '%d')", + $width, + $this->_userWidth + )); + } + } + } + } + + /** + * Draw and render the barcode with correct headers + * + * @return mixed + */ + public function render() + { + $this->draw(); + header("Content-Type: image/" . $this->_imageType); + $functionName = 'image' . $this->_imageType; + call_user_func($functionName, $this->_resource); + @imagedestroy($this->_resource); + } + + /** + * Draw a polygon in the image resource + * + * @param array $points + * @param integer $color + * @param boolean $filled + */ + protected function _drawPolygon($points, $color, $filled = true) + { + $newPoints = array( + $points[0][0] + $this->_leftOffset, + $points[0][1] + $this->_topOffset, + $points[1][0] + $this->_leftOffset, + $points[1][1] + $this->_topOffset, + $points[2][0] + $this->_leftOffset, + $points[2][1] + $this->_topOffset, + $points[3][0] + $this->_leftOffset, + $points[3][1] + $this->_topOffset, + ); + + $allocatedColor = imagecolorallocate( + $this->_resource, + ($color & 0xFF0000) >> 16, + ($color & 0x00FF00) >> 8, + $color & 0x0000FF + ); + + if ($filled) { + imagefilledpolygon($this->_resource, $newPoints, 4, $allocatedColor); + } else { + imagepolygon($this->_resource, $newPoints, 4, $allocatedColor); + } + } + + /** + * Draw a polygon in the image resource + * + * @param string $text + * @param float $size + * @param array $position + * @param string $font + * @param integer $color + * @param string $alignment + * @param float $orientation + */ + protected function _drawText($text, $size, $position, $font, $color, $alignment = 'center', $orientation = 0) + { + $allocatedColor = imagecolorallocate( + $this->_resource, + ($color & 0xFF0000) >> 16, + ($color & 0x00FF00) >> 8, + $color & 0x0000FF + ); + + if ($font == null) { + $font = 3; + } + $position[0] += $this->_leftOffset; + $position[1] += $this->_topOffset; + + if (is_numeric($font)) { + if ($orientation) { + /** + * imagestring() doesn't allow orientation, if orientation + * needed: a TTF font is required. + * Throwing an exception here, allow to use automaticRenderError + * to informe user of the problem instead of simply not drawing + * the text + */ + require_once 'Zend/Barcode/Renderer/Exception.php'; + throw new Zend_Barcode_Renderer_Exception( + 'No orientation possible with GD internal font' + ); + } + $fontWidth = imagefontwidth($font); + $positionY = $position[1] - imagefontheight($font) + 1; + switch ($alignment) { + case 'left': + $positionX = $position[0]; + break; + case 'center': + $positionX = $position[0] - ceil(($fontWidth * strlen($text)) / 2); + break; + case 'right': + $positionX = $position[0] - ($fontWidth * strlen($text)); + break; + } + imagestring($this->_resource, $font, $positionX, $positionY, $text, $color); + } else { + + if (!function_exists('imagettfbbox')) { + require_once 'Zend/Barcode/Renderer/Exception.php'; + throw new Zend_Barcode_Renderer_Exception( + 'A font was provided, but this instance of PHP does not have TTF (FreeType) support' + ); + } + + $box = imagettfbbox($size, 0, $font, $text); + switch ($alignment) { + case 'left': + $width = 0; + break; + case 'center': + $width = ($box[2] - $box[0]) / 2; + break; + case 'right': + $width = ($box[2] - $box[0]); + break; + } + imagettftext( + $this->_resource, + $size, + $orientation, + $position[0] - ($width * cos(pi() * $orientation / 180)), + $position[1] + ($width * sin(pi() * $orientation / 180)), + $allocatedColor, + $font, + $text + ); + } + } +} diff --git a/library/Zend/Barcode/Renderer/Pdf.php b/library/Zend/Barcode/Renderer/Pdf.php new file mode 100644 index 000000000..402d09fc4 --- /dev/null +++ b/library/Zend/Barcode/Renderer/Pdf.php @@ -0,0 +1,242 @@ +_resource = $pdf; + $this->_page = intval($page); + + if (!count($this->_resource->pages)) { + $this->_page = 0; + $this->_resource->pages[] = new Zend_Pdf_Page( + Zend_Pdf_Page::SIZE_A4 + ); + } + return $this; + } + + /** + * Check renderer parameters + * + * @return void + */ + protected function _checkParams() + { + } + + /** + * Draw the barcode in the PDF, send headers and the PDF + * @return mixed + */ + public function render() + { + $this->draw(); + header("Content-Type: application/pdf"); + echo $this->_resource->render(); + } + + /** + * Initialize the PDF resource + * @return void + */ + protected function _initRenderer() + { + if ($this->_resource === null) { + $this->_resource = new Zend_Pdf(); + $this->_resource->pages[] = new Zend_Pdf_Page( + Zend_Pdf_Page::SIZE_A4 + ); + } + + $pdfPage = $this->_resource->pages[$this->_page]; + $this->_adjustPosition($pdfPage->getHeight(), $pdfPage->getWidth()); + } + + /** + * Draw a polygon in the rendering resource + * @param array $points + * @param integer $color + * @param boolean $filled + */ + protected function _drawPolygon($points, $color, $filled = true) + { + $page = $this->_resource->pages[$this->_page]; + foreach ($points as $point) { + $x[] = $point[0] * $this->_moduleSize + $this->_leftOffset; + $y[] = $page->getHeight() - $point[1] * $this->_moduleSize - $this->_topOffset; + } + if (count($y) == 4) { + if ($x[0] != $x[3] && $y[0] == $y[3]) { + $y[0] -= ($this->_moduleSize / 2); + $y[3] -= ($this->_moduleSize / 2); + } + if ($x[1] != $x[2] && $y[1] == $y[2]) { + $y[1] += ($this->_moduleSize / 2); + $y[2] += ($this->_moduleSize / 2); + } + } + + $color = new Zend_Pdf_Color_RGB( + ($color & 0xFF0000) >> 16, + ($color & 0x00FF00) >> 8, + $color & 0x0000FF + ); + + $page->setLineColor($color); + $page->setFillColor($color); + $page->setLineWidth($this->_moduleSize); + + $fillType = ($filled) + ? Zend_Pdf_Page::SHAPE_DRAW_FILL_AND_STROKE + : Zend_Pdf_Page::SHAPE_DRAW_STROKE; + + $page->drawPolygon($x, $y, $fillType); + } + + /** + * Draw a polygon in the rendering resource + * @param string $text + * @param float $size + * @param array $position + * @param string $font + * @param integer $color + * @param string $alignment + * @param float $orientation + */ + protected function _drawText( + $text, + $size, + $position, + $font, + $color, + $alignment = 'center', + $orientation = 0 + ) { + $page = $this->_resource->pages[$this->_page]; + $color = new Zend_Pdf_Color_RGB( + ($color & 0xFF0000) >> 16, + ($color & 0x00FF00) >> 8, + $color & 0x0000FF + ); + + $page->setLineColor($color); + $page->setFillColor($color); + $page->setFont(Zend_Pdf_Font::fontWithPath($font), $size * $this->_moduleSize * 1.2); + + $width = $this->widthForStringUsingFontSize( + $text, + Zend_Pdf_Font::fontWithPath($font), + $size * $this->_moduleSize + ); + + $angle = pi() * $orientation / 180; + $left = $position[0] * $this->_moduleSize + $this->_leftOffset; + $top = $page->getHeight() - $position[1] * $this->_moduleSize - $this->_topOffset; + + switch ($alignment) { + case 'center': + $left -= ($width / 2) * cos($angle); + $top -= ($width / 2) * sin($angle); + break; + case 'right': + $left -= $width; + break; + } + $page->rotate($left, $top, $angle); + $page->drawText($text, $left, $top); + $page->rotate($left, $top, - $angle); + } + + /** + * Calculate the width of a string: + * in case of using alignment parameter in drawText + * @param string $text + * @param Zend_Pdf_Font $font + * @param float $fontSize + * @return float + */ + public function widthForStringUsingFontSize($text, $font, $fontSize) + { + $drawingString = iconv('UTF-8', 'UTF-16BE//IGNORE', $text); + $characters = array(); + for ($i = 0; $i < strlen($drawingString); $i ++) { + $characters[] = (ord($drawingString[$i ++]) << 8) | ord($drawingString[$i]); + } + $glyphs = $font->glyphNumbersForCharacters($characters); + $widths = $font->widthsForGlyphs($glyphs); + $stringWidth = (array_sum($widths) / $font->getUnitsPerEm()) * $fontSize; + return $stringWidth; + } +} diff --git a/library/Zend/Barcode/Renderer/RendererAbstract.php b/library/Zend/Barcode/Renderer/RendererAbstract.php new file mode 100644 index 000000000..4abcaeea0 --- /dev/null +++ b/library/Zend/Barcode/Renderer/RendererAbstract.php @@ -0,0 +1,540 @@ +toArray(); + } + if (is_array($options)) { + $this->setOptions($options); + } + $this->_type = strtolower(substr( + get_class($this), + strlen($this->_rendererNamespace) + 1 + )); + } + + /** + * Set renderer state from options array + * @param array $options + * @return Zend_Renderer_Object + */ + public function setOptions($options) + { + foreach ($options as $key => $value) { + $method = 'set' . $key; + if (method_exists($this, $method)) { + $this->$method($value); + } + } + return $this; + } + + /** + * Set renderer state from config object + * @param Zend_Config $config + * @return Zend_Renderer_Object + */ + public function setConfig(Zend_Config $config) + { + return $this->setOptions($config->toArray()); + } + + /** + * Set renderer namespace for autoloading + * + * @param string $namespace + * @return Zend_Renderer_Object + */ + public function setRendererNamespace($namespace) + { + $this->_rendererNamespace = $namespace; + return $this; + } + + /** + * Retrieve renderer namespace + * + * @return string + */ + public function getRendererNamespace() + { + return $this->_rendererNamespace; + } + + /** + * Retrieve renderer type + * @return string + */ + public function getType() + { + return $this->_type; + } + + /** + * Manually adjust top position + * @param integer $value + * @return Zend_Barcode_Renderer + * @throw Zend_Barcode_Renderer_Exception + */ + public function setTopOffset($value) + { + if (!is_numeric($value) || intval($value) < 0) { + require_once 'Zend/Barcode/Renderer/Exception.php'; + throw new Zend_Barcode_Renderer_Exception( + 'Vertical position must be greater than or equals 0' + ); + } + $this->_topOffset = intval($value); + return $this; + } + + /** + * Retrieve vertical adjustment + * @return integer + */ + public function getTopOffset() + { + return $this->_topOffset; + } + + /** + * Manually adjust left position + * @param integer $value + * @return Zend_Barcode_Renderer + * @throw Zend_Barcode_Renderer_Exception + */ + public function setLeftOffset($value) + { + if (!is_numeric($value) || intval($value) < 0) { + require_once 'Zend/Barcode/Renderer/Exception.php'; + throw new Zend_Barcode_Renderer_Exception( + 'Horizontal position must be greater than or equals 0' + ); + } + $this->_leftOffset = intval($value); + return $this; + } + + /** + * Retrieve vertical adjustment + * @return integer + */ + public function getLeftOffset() + { + return $this->_leftOffset; + } + + /** + * Activate/Deactivate the automatic rendering of exception + * @param boolean $value + */ + public function setAutomaticRenderError($value) + { + $this->_automaticRenderError = (bool) $value; + return $this; + } + + /** + * Horizontal position of the barcode in the rendering resource + * @param string $value + * @return Zend_Barcode_Renderer + * @throw Zend_Barcode_Renderer_Exception + */ + public function setHorizontalPosition($value) + { + if (!in_array($value, array('left' , 'center' , 'right'))) { + require_once 'Zend/Barcode/Renderer/Exception.php'; + throw new Zend_Barcode_Renderer_Exception( + "Invalid barcode position provided must be 'left', 'center' or 'right'" + ); + } + $this->_horizontalPosition = $value; + return $this; + } + + /** + * Horizontal position of the barcode in the rendering resource + * @return string + */ + public function getHorizontalPosition() + { + return $this->_horizontalPosition; + } + + /** + * Vertical position of the barcode in the rendering resource + * @param string $value + * @return Zend_Barcode_Renderer + * @throw Zend_Barcode_Renderer_Exception + */ + public function setVerticalPosition($value) + { + if (!in_array($value, array('top' , 'middle' , 'bottom'))) { + require_once 'Zend/Barcode/Renderer/Exception.php'; + throw new Zend_Barcode_Renderer_Exception( + "Invalid barcode position provided must be 'top', 'middle' or 'bottom'" + ); + } + $this->_verticalPosition = $value; + return $this; + } + + /** + * Vertical position of the barcode in the rendering resource + * @return string + */ + public function getVerticalPosition() + { + return $this->_verticalPosition; + } + + /** + * Set the size of a module + * @param float $value + * @return Zend_Barcode_Renderer + * @throw Zend_Barcode_Renderer_Exception + */ + public function setModuleSize($value) + { + if (!is_numeric($value) || floatval($value) <= 0) { + require_once 'Zend/Barcode/Renderer/Exception.php'; + throw new Zend_Barcode_Renderer_Exception( + 'Float size must be greater than 0' + ); + } + $this->_moduleSize = floatval($value); + return $this; + } + + + /** + * Set the size of a module + * @return float + */ + public function getModuleSize() + { + return $this->_moduleSize; + } + + /** + * Retrieve the automatic rendering of exception + * @return boolean + */ + public function getAutomaticRenderError() + { + return $this->_automaticRenderError; + } + + /** + * Set the barcode object + * @param Zend_Barcode_Object $barcode + * @return Zend_Barcode_Renderer + */ + public function setBarcode($barcode) + { + if (!$barcode instanceof Zend_Barcode_Object_ObjectAbstract) { + require_once 'Zend/Barcode/Renderer/Exception.php'; + throw new Zend_Barcode_Renderer_Exception( + 'Invalid barcode object provided to setBarcode()' + ); + } + $this->_barcode = $barcode; + return $this; + } + + /** + * Retrieve the barcode object + * @return Zend_Barcode_Object + */ + public function getBarcode() + { + return $this->_barcode; + } + + /** + * Checking of parameters after all settings + * @return boolean + */ + public function checkParams() + { + $this->_checkBarcodeObject(); + $this->_checkParams(); + return true; + } + + /** + * Check if a barcode object is correctly provided + * @return void + * @throw Zend_Barcode_Renderer_Exception + */ + protected function _checkBarcodeObject() + { + if ($this->_barcode === null) { + /** + * @see Zend_Barcode_Renderer_Exception + */ + require_once 'Zend/Barcode/Renderer/Exception.php'; + throw new Zend_Barcode_Renderer_Exception( + 'No barcode object provided' + ); + } + } + + /** + * Calculate the left and top offset of the barcode in the + * rendering support + * + * @param float $supportHeight + * @param float $supportWidth + * @return void + */ + protected function _adjustPosition($supportHeight, $supportWidth) + { + $barcodeHeight = $this->_barcode->getHeight(true) * $this->_moduleSize; + if ($barcodeHeight != $supportHeight && $this->_topOffset == 0) { + switch ($this->_verticalPosition) { + case 'middle': + $this->_topOffset = floor( + ($supportHeight - $barcodeHeight) / 2); + break; + case 'bottom': + $this->_topOffset = $supportHeight - $barcodeHeight; + break; + case 'top': + default: + $this->_topOffset = 0; + break; + } + } + $barcodeWidth = $this->_barcode->getWidth(true) * $this->_moduleSize; + if ($barcodeWidth != $supportWidth && $this->_leftOffset == 0) { + switch ($this->_horizontalPosition) { + case 'center': + $this->_leftOffset = floor( + ($supportWidth - $barcodeWidth) / 2); + break; + case 'right': + $this->_leftOffset = $supportWidth - $barcodeWidth; + break; + case 'left': + default: + $this->_leftOffset = 0; + break; + } + } + } + + /** + * Draw the barcode in the rendering resource + * @return mixed + */ + public function draw() + { + try { + $this->checkParams(); + $this->_initRenderer(); + $this->_drawInstructionList(); + } catch (Zend_Exception $e) { + $renderable = false; + if ($e instanceof Zend_Barcode_Exception) { + $renderable = $e->isRenderable(); + } + if ($this->_automaticRenderError && $renderable) { + $barcode = Zend_Barcode::makeBarcode( + 'error', + array('text' => $e->getMessage()) + ); + $this->setBarcode($barcode); + $this->_resource = null; + $this->_initRenderer(); + $this->_drawInstructionList(); + } else { + if ($e instanceof Zend_Barcode_Exception) { + $e->setIsRenderable(false); + } + throw $e; + } + } + return $this->_resource; + } + + /** + * Sub process to draw the barcode instructions + * Needed by the automatic error rendering + */ + private function _drawInstructionList() + { + $instructionList = $this->_barcode->draw(); + foreach ($instructionList as $instruction) { + switch ($instruction['type']) { + case 'polygon': + $this->_drawPolygon( + $instruction['points'], + $instruction['color'], + $instruction['filled'] + ); + break; + case 'text': //$text, $size, $position, $font, $color, $alignment = 'center', $orientation = 0) + $this->_drawText( + $instruction['text'], + $instruction['size'], + $instruction['position'], + $instruction['font'], + $instruction['color'], + $instruction['alignment'], + $instruction['orientation'] + ); + break; + default: + /** + * @see Zend_Barcode_Renderer_Exception + */ + require_once 'Zend/Barcode/Renderer/Exception.php'; + throw new Zend_Barcode_Renderer_Exception( + 'Unkown drawing command' + ); + } + } + } + + /** + * Checking of parameters after all settings + * @return void + */ + abstract protected function _checkParams(); + + /** + * Render the resource by sending headers and drawed resource + * @return mixed + */ + abstract public function render(); + + /** + * Initialize the rendering resource + * @return void + */ + abstract protected function _initRenderer(); + + /** + * Draw a polygon in the rendering resource + * @param array $points + * @param integer $color + * @param boolean $filled + */ + abstract protected function _drawPolygon($points, $color, $filled = true); + + /** + * Draw a polygon in the rendering resource + * @param string $text + * @param float $size + * @param array $position + * @param string $font + * @param integer $color + * @param string $alignment + * @param float $orientation + */ + abstract protected function _drawText( + $text, + $size, + $position, + $font, + $color, + $alignment = 'center', + $orientation = 0 + ); +} diff --git a/library/Zend/Cache.php b/library/Zend/Cache.php new file mode 100644 index 000000000..379251151 --- /dev/null +++ b/library/Zend/Cache.php @@ -0,0 +1,245 @@ +setBackend($backendObject); + return $frontendObject; + } + + /** + * Frontend Constructor + * + * @param string $backend + * @param array $backendOptions + * @param boolean $customBackendNaming + * @param boolean $autoload + * @return Zend_Cache_Backend + */ + public static function _makeBackend($backend, $backendOptions, $customBackendNaming = false, $autoload = false) + { + if (!$customBackendNaming) { + $backend = self::_normalizeName($backend); + } + if (in_array($backend, Zend_Cache::$standardBackends)) { + // we use a standard backend + $backendClass = 'Zend_Cache_Backend_' . $backend; + // security controls are explicit + require_once str_replace('_', DIRECTORY_SEPARATOR, $backendClass) . '.php'; + } else { + // we use a custom backend + if (!preg_match('~^[\w]+$~D', $backend)) { + Zend_Cache::throwException("Invalid backend name [$backend]"); + } + if (!$customBackendNaming) { + // we use this boolean to avoid an API break + $backendClass = 'Zend_Cache_Backend_' . $backend; + } else { + $backendClass = $backend; + } + if (!$autoload) { + $file = str_replace('_', DIRECTORY_SEPARATOR, $backendClass) . '.php'; + if (!(self::_isReadable($file))) { + self::throwException("file $file not found in include_path"); + } + require_once $file; + } + } + return new $backendClass($backendOptions); + } + + /** + * Backend Constructor + * + * @param string $frontend + * @param array $frontendOptions + * @param boolean $customFrontendNaming + * @param boolean $autoload + * @return Zend_Cache_Core|Zend_Cache_Frontend + */ + public static function _makeFrontend($frontend, $frontendOptions = array(), $customFrontendNaming = false, $autoload = false) + { + if (!$customFrontendNaming) { + $frontend = self::_normalizeName($frontend); + } + if (in_array($frontend, self::$standardFrontends)) { + // we use a standard frontend + // For perfs reasons, with frontend == 'Core', we can interact with the Core itself + $frontendClass = 'Zend_Cache_' . ($frontend != 'Core' ? 'Frontend_' : '') . $frontend; + // security controls are explicit + require_once str_replace('_', DIRECTORY_SEPARATOR, $frontendClass) . '.php'; + } else { + // we use a custom frontend + if (!preg_match('~^[\w]+$~D', $frontend)) { + Zend_Cache::throwException("Invalid frontend name [$frontend]"); + } + if (!$customFrontendNaming) { + // we use this boolean to avoid an API break + $frontendClass = 'Zend_Cache_Frontend_' . $frontend; + } else { + $frontendClass = $frontend; + } + if (!$autoload) { + $file = str_replace('_', DIRECTORY_SEPARATOR, $frontendClass) . '.php'; + if (!(self::_isReadable($file))) { + self::throwException("file $file not found in include_path"); + } + require_once $file; + } + } + return new $frontendClass($frontendOptions); + } + + /** + * Throw an exception + * + * Note : for perf reasons, the "load" of Zend/Cache/Exception is dynamic + * @param string $msg Message for the exception + * @throws Zend_Cache_Exception + */ + public static function throwException($msg, Exception $e = null) + { + // For perfs reasons, we use this dynamic inclusion + require_once 'Zend/Cache/Exception.php'; + throw new Zend_Cache_Exception($msg, 0, $e); + } + + /** + * Normalize frontend and backend names to allow multiple words TitleCased + * + * @param string $name Name to normalize + * @return string + */ + protected static function _normalizeName($name) + { + $name = ucfirst(strtolower($name)); + $name = str_replace(array('-', '_', '.'), ' ', $name); + $name = ucwords($name); + $name = str_replace(' ', '', $name); + return $name; + } + + /** + * Returns TRUE if the $filename is readable, or FALSE otherwise. + * This function uses the PHP include_path, where PHP's is_readable() + * does not. + * + * Note : this method comes from Zend_Loader (see #ZF-2891 for details) + * + * @param string $filename + * @return boolean + */ + private static function _isReadable($filename) + { + if (!$fh = @fopen($filename, 'r', true)) { + return false; + } + @fclose($fh); + return true; + } + +} diff --git a/library/Zend/Cache/Backend.php b/library/Zend/Cache/Backend.php new file mode 100644 index 000000000..8049a1a84 --- /dev/null +++ b/library/Zend/Cache/Backend.php @@ -0,0 +1,266 @@ + (int) lifetime : + * - Cache lifetime (in seconds) + * - If null, the cache is valid forever + * + * =====> (int) logging : + * - if set to true, a logging is activated throw Zend_Log + * + * @var array directives + */ + protected $_directives = array( + 'lifetime' => 3600, + 'logging' => false, + 'logger' => null + ); + + /** + * Available options + * + * @var array available options + */ + protected $_options = array(); + + /** + * Constructor + * + * @param array $options Associative array of options + * @throws Zend_Cache_Exception + * @return void + */ + public function __construct(array $options = array()) + { + while (list($name, $value) = each($options)) { + $this->setOption($name, $value); + } + } + + /** + * Set the frontend directives + * + * @param array $directives Assoc of directives + * @throws Zend_Cache_Exception + * @return void + */ + public function setDirectives($directives) + { + if (!is_array($directives)) Zend_Cache::throwException('Directives parameter must be an array'); + while (list($name, $value) = each($directives)) { + if (!is_string($name)) { + Zend_Cache::throwException("Incorrect option name : $name"); + } + $name = strtolower($name); + if (array_key_exists($name, $this->_directives)) { + $this->_directives[$name] = $value; + } + + } + + $this->_loggerSanity(); + } + + /** + * Set an option + * + * @param string $name + * @param mixed $value + * @throws Zend_Cache_Exception + * @return void + */ + public function setOption($name, $value) + { + if (!is_string($name)) { + Zend_Cache::throwException("Incorrect option name : $name"); + } + $name = strtolower($name); + if (array_key_exists($name, $this->_options)) { + $this->_options[$name] = $value; + } + } + + /** + * Get the life time + * + * if $specificLifetime is not false, the given specific life time is used + * else, the global lifetime is used + * + * @param int $specificLifetime + * @return int Cache life time + */ + public function getLifetime($specificLifetime) + { + if ($specificLifetime === false) { + return $this->_directives['lifetime']; + } + return $specificLifetime; + } + + /** + * Return true if the automatic cleaning is available for the backend + * + * DEPRECATED : use getCapabilities() instead + * + * @deprecated + * @return boolean + */ + public function isAutomaticCleaningAvailable() + { + return true; + } + + /** + * Determine system TMP directory and detect if we have read access + * + * inspired from Zend_File_Transfer_Adapter_Abstract + * + * @return string + * @throws Zend_Cache_Exception if unable to determine directory + */ + public function getTmpDir() + { + $tmpdir = array(); + foreach (array($_ENV, $_SERVER) as $tab) { + foreach (array('TMPDIR', 'TEMP', 'TMP', 'windir', 'SystemRoot') as $key) { + if (isset($tab[$key])) { + if (($key == 'windir') or ($key == 'SystemRoot')) { + $dir = realpath($tab[$key] . '\\temp'); + } else { + $dir = realpath($tab[$key]); + } + if ($this->_isGoodTmpDir($dir)) { + return $dir; + } + } + } + } + $upload = ini_get('upload_tmp_dir'); + if ($upload) { + $dir = realpath($upload); + if ($this->_isGoodTmpDir($dir)) { + return $dir; + } + } + if (function_exists('sys_get_temp_dir')) { + $dir = sys_get_temp_dir(); + if ($this->_isGoodTmpDir($dir)) { + return $dir; + } + } + // Attemp to detect by creating a temporary file + $tempFile = tempnam(md5(uniqid(rand(), TRUE)), ''); + if ($tempFile) { + $dir = realpath(dirname($tempFile)); + unlink($tempFile); + if ($this->_isGoodTmpDir($dir)) { + return $dir; + } + } + if ($this->_isGoodTmpDir('/tmp')) { + return '/tmp'; + } + if ($this->_isGoodTmpDir('\\temp')) { + return '\\temp'; + } + Zend_Cache::throwException('Could not determine temp directory, please specify a cache_dir manually'); + } + + /** + * Verify if the given temporary directory is readable and writable + * + * @param $dir temporary directory + * @return boolean true if the directory is ok + */ + protected function _isGoodTmpDir($dir) + { + if (is_readable($dir)) { + if (is_writable($dir)) { + return true; + } + } + return false; + } + + /** + * Make sure if we enable logging that the Zend_Log class + * is available. + * Create a default log object if none is set. + * + * @throws Zend_Cache_Exception + * @return void + */ + protected function _loggerSanity() + { + if (!isset($this->_directives['logging']) || !$this->_directives['logging']) { + return; + } + + if (isset($this->_directives['logger'])) { + if ($this->_directives['logger'] instanceof Zend_Log) { + return; + } + Zend_Cache::throwException('Logger object is not an instance of Zend_Log class.'); + } + + // Create a default logger to the standard output stream + require_once 'Zend/Log.php'; + require_once 'Zend/Log/Writer/Stream.php'; + $logger = new Zend_Log(new Zend_Log_Writer_Stream('php://output')); + $this->_directives['logger'] = $logger; + } + + /** + * Log a message at the WARN (4) priority. + * + * @param string $message + * @throws Zend_Cache_Exception + * @return void + */ + protected function _log($message, $priority = 4) + { + if (!$this->_directives['logging']) { + return; + } + + if (!isset($this->_directives['logger'])) { + Zend_Cache::throwException('Logging is enabled but logger is not set.'); + } + $logger = $this->_directives['logger']; + if (!$logger instanceof Zend_Log) { + Zend_Cache::throwException('Logger object is not an instance of Zend_Log class.'); + } + $logger->log($message, $priority); + } +} diff --git a/library/Zend/Cache/Backend/Apc.php b/library/Zend/Cache/Backend/Apc.php new file mode 100644 index 000000000..0a13a5772 --- /dev/null +++ b/library/Zend/Cache/Backend/Apc.php @@ -0,0 +1,355 @@ + infinite lifetime) + * @return boolean true if no problem + */ + public function save($data, $id, $tags = array(), $specificLifetime = false) + { + $lifetime = $this->getLifetime($specificLifetime); + $result = apc_store($id, array($data, time(), $lifetime), $lifetime); + if (count($tags) > 0) { + $this->_log(self::TAGS_UNSUPPORTED_BY_SAVE_OF_APC_BACKEND); + } + return $result; + } + + /** + * Remove a cache record + * + * @param string $id cache id + * @return boolean true if no problem + */ + public function remove($id) + { + return apc_delete($id); + } + + /** + * Clean some cache records + * + * Available modes are : + * 'all' (default) => remove all cache entries ($tags is not used) + * 'old' => unsupported + * 'matchingTag' => unsupported + * 'notMatchingTag' => unsupported + * 'matchingAnyTag' => unsupported + * + * @param string $mode clean mode + * @param array $tags array of tags + * @throws Zend_Cache_Exception + * @return boolean true if no problem + */ + public function clean($mode = Zend_Cache::CLEANING_MODE_ALL, $tags = array()) + { + switch ($mode) { + case Zend_Cache::CLEANING_MODE_ALL: + return apc_clear_cache('user'); + break; + case Zend_Cache::CLEANING_MODE_OLD: + $this->_log("Zend_Cache_Backend_Apc::clean() : CLEANING_MODE_OLD is unsupported by the Apc backend"); + break; + case Zend_Cache::CLEANING_MODE_MATCHING_TAG: + case Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG: + case Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG: + $this->_log(self::TAGS_UNSUPPORTED_BY_CLEAN_OF_APC_BACKEND); + break; + default: + Zend_Cache::throwException('Invalid mode for clean() method'); + break; + } + } + + /** + * Return true if the automatic cleaning is available for the backend + * + * DEPRECATED : use getCapabilities() instead + * + * @deprecated + * @return boolean + */ + public function isAutomaticCleaningAvailable() + { + return false; + } + + /** + * Return the filling percentage of the backend storage + * + * @throws Zend_Cache_Exception + * @return int integer between 0 and 100 + */ + public function getFillingPercentage() + { + $mem = apc_sma_info(true); + $memSize = $mem['num_seg'] * $mem['seg_size']; + $memAvailable= $mem['avail_mem']; + $memUsed = $memSize - $memAvailable; + if ($memSize == 0) { + Zend_Cache::throwException('can\'t get apc memory size'); + } + if ($memUsed > $memSize) { + return 100; + } + return ((int) (100. * ($memUsed / $memSize))); + } + + /** + * Return an array of stored tags + * + * @return array array of stored tags (string) + */ + public function getTags() + { + $this->_log(self::TAGS_UNSUPPORTED_BY_SAVE_OF_APC_BACKEND); + return array(); + } + + /** + * Return an array of stored cache ids which match given tags + * + * In case of multiple tags, a logical AND is made between tags + * + * @param array $tags array of tags + * @return array array of matching cache ids (string) + */ + public function getIdsMatchingTags($tags = array()) + { + $this->_log(self::TAGS_UNSUPPORTED_BY_SAVE_OF_APC_BACKEND); + return array(); + } + + /** + * Return an array of stored cache ids which don't match given tags + * + * In case of multiple tags, a logical OR is made between tags + * + * @param array $tags array of tags + * @return array array of not matching cache ids (string) + */ + public function getIdsNotMatchingTags($tags = array()) + { + $this->_log(self::TAGS_UNSUPPORTED_BY_SAVE_OF_APC_BACKEND); + return array(); + } + + /** + * Return an array of stored cache ids which match any given tags + * + * In case of multiple tags, a logical AND is made between tags + * + * @param array $tags array of tags + * @return array array of any matching cache ids (string) + */ + public function getIdsMatchingAnyTags($tags = array()) + { + $this->_log(self::TAGS_UNSUPPORTED_BY_SAVE_OF_APC_BACKEND); + return array(); + } + + /** + * Return an array of stored cache ids + * + * @return array array of stored cache ids (string) + */ + public function getIds() + { + $res = array(); + $array = apc_cache_info('user', false); + $records = $array['cache_list']; + foreach ($records as $record) { + $res[] = $record['info']; + } + return $res; + } + + /** + * Return an array of metadatas for the given cache id + * + * The array must include these keys : + * - expire : the expire timestamp + * - tags : a string array of tags + * - mtime : timestamp of last modification time + * + * @param string $id cache id + * @return array array of metadatas (false if the cache id is not found) + */ + public function getMetadatas($id) + { + $tmp = apc_fetch($id); + if (is_array($tmp)) { + $data = $tmp[0]; + $mtime = $tmp[1]; + if (!isset($tmp[2])) { + // because this record is only with 1.7 release + // if old cache records are still there... + return false; + } + $lifetime = $tmp[2]; + return array( + 'expire' => $mtime + $lifetime, + 'tags' => array(), + 'mtime' => $mtime + ); + } + return false; + } + + /** + * Give (if possible) an extra lifetime to the given cache id + * + * @param string $id cache id + * @param int $extraLifetime + * @return boolean true if ok + */ + public function touch($id, $extraLifetime) + { + $tmp = apc_fetch($id); + if (is_array($tmp)) { + $data = $tmp[0]; + $mtime = $tmp[1]; + if (!isset($tmp[2])) { + // because this record is only with 1.7 release + // if old cache records are still there... + return false; + } + $lifetime = $tmp[2]; + $newLifetime = $lifetime - (time() - $mtime) + $extraLifetime; + if ($newLifetime <=0) { + return false; + } + apc_store($id, array($data, time(), $newLifetime), $newLifetime); + return true; + } + return false; + } + + /** + * Return an associative array of capabilities (booleans) of the backend + * + * The array must include these keys : + * - automatic_cleaning (is automating cleaning necessary) + * - tags (are tags supported) + * - expired_read (is it possible to read expired cache records + * (for doNotTestCacheValidity option for example)) + * - priority does the backend deal with priority when saving + * - infinite_lifetime (is infinite lifetime can work with this backend) + * - get_list (is it possible to get the list of cache ids and the complete list of tags) + * + * @return array associative of with capabilities + */ + public function getCapabilities() + { + return array( + 'automatic_cleaning' => false, + 'tags' => false, + 'expired_read' => false, + 'priority' => false, + 'infinite_lifetime' => false, + 'get_list' => true + ); + } + +} diff --git a/library/Zend/Cache/Backend/BlackHole.php b/library/Zend/Cache/Backend/BlackHole.php new file mode 100644 index 000000000..9ae72f397 --- /dev/null +++ b/library/Zend/Cache/Backend/BlackHole.php @@ -0,0 +1,250 @@ + infinite lifetime) + * @return boolean true if no problem + */ + public function save($data, $id, $tags = array(), $specificLifetime = false) + { + return true; + } + + /** + * Remove a cache record + * + * @param string $id cache id + * @return boolean true if no problem + */ + public function remove($id) + { + return true; + } + + /** + * Clean some cache records + * + * Available modes are : + * 'all' (default) => remove all cache entries ($tags is not used) + * 'old' => remove too old cache entries ($tags is not used) + * 'matchingTag' => remove cache entries matching all given tags + * ($tags can be an array of strings or a single string) + * 'notMatchingTag' => remove cache entries not matching one of the given tags + * ($tags can be an array of strings or a single string) + * 'matchingAnyTag' => remove cache entries matching any given tags + * ($tags can be an array of strings or a single string) + * + * @param string $mode clean mode + * @param tags array $tags array of tags + * @return boolean true if no problem + */ + public function clean($mode = Zend_Cache::CLEANING_MODE_ALL, $tags = array()) + { + return true; + } + + /** + * Return an array of stored cache ids + * + * @return array array of stored cache ids (string) + */ + public function getIds() + { + return array(); + } + + /** + * Return an array of stored tags + * + * @return array array of stored tags (string) + */ + public function getTags() + { + return array(); + } + + /** + * Return an array of stored cache ids which match given tags + * + * In case of multiple tags, a logical AND is made between tags + * + * @param array $tags array of tags + * @return array array of matching cache ids (string) + */ + public function getIdsMatchingTags($tags = array()) + { + return array(); + } + + /** + * Return an array of stored cache ids which don't match given tags + * + * In case of multiple tags, a logical OR is made between tags + * + * @param array $tags array of tags + * @return array array of not matching cache ids (string) + */ + public function getIdsNotMatchingTags($tags = array()) + { + return array(); + } + + /** + * Return an array of stored cache ids which match any given tags + * + * In case of multiple tags, a logical AND is made between tags + * + * @param array $tags array of tags + * @return array array of any matching cache ids (string) + */ + public function getIdsMatchingAnyTags($tags = array()) + { + return array(); + } + + /** + * Return the filling percentage of the backend storage + * + * @return int integer between 0 and 100 + * @throws Zend_Cache_Exception + */ + public function getFillingPercentage() + { + return 0; + } + + /** + * Return an array of metadatas for the given cache id + * + * The array must include these keys : + * - expire : the expire timestamp + * - tags : a string array of tags + * - mtime : timestamp of last modification time + * + * @param string $id cache id + * @return array array of metadatas (false if the cache id is not found) + */ + public function getMetadatas($id) + { + return false; + } + + /** + * Give (if possible) an extra lifetime to the given cache id + * + * @param string $id cache id + * @param int $extraLifetime + * @return boolean true if ok + */ + public function touch($id, $extraLifetime) + { + return false; + } + + /** + * Return an associative array of capabilities (booleans) of the backend + * + * The array must include these keys : + * - automatic_cleaning (is automating cleaning necessary) + * - tags (are tags supported) + * - expired_read (is it possible to read expired cache records + * (for doNotTestCacheValidity option for example)) + * - priority does the backend deal with priority when saving + * - infinite_lifetime (is infinite lifetime can work with this backend) + * - get_list (is it possible to get the list of cache ids and the complete list of tags) + * + * @return array associative of with capabilities + */ + public function getCapabilities() + { + return array( + 'automatic_cleaning' => true, + 'tags' => true, + 'expired_read' => true, + 'priority' => true, + 'infinite_lifetime' => true, + 'get_list' => true, + ); + } + + /** + * PUBLIC METHOD FOR UNIT TESTING ONLY ! + * + * Force a cache record to expire + * + * @param string $id cache id + */ + public function ___expire($id) + { + } +} diff --git a/library/Zend/Cache/Backend/ExtendedInterface.php b/library/Zend/Cache/Backend/ExtendedInterface.php new file mode 100644 index 000000000..ec735a476 --- /dev/null +++ b/library/Zend/Cache/Backend/ExtendedInterface.php @@ -0,0 +1,126 @@ + (string) cache_dir : + * - Directory where to put the cache files + * + * =====> (boolean) file_locking : + * - Enable / disable file_locking + * - Can avoid cache corruption under bad circumstances but it doesn't work on multithread + * webservers and on NFS filesystems for example + * + * =====> (boolean) read_control : + * - Enable / disable read control + * - If enabled, a control key is embeded in cache file and this key is compared with the one + * calculated after the reading. + * + * =====> (string) read_control_type : + * - Type of read control (only if read control is enabled). Available values are : + * 'md5' for a md5 hash control (best but slowest) + * 'crc32' for a crc32 hash control (lightly less safe but faster, better choice) + * 'adler32' for an adler32 hash control (excellent choice too, faster than crc32) + * 'strlen' for a length only test (fastest) + * + * =====> (int) hashed_directory_level : + * - Hashed directory level + * - Set the hashed directory structure level. 0 means "no hashed directory + * structure", 1 means "one level of directory", 2 means "two levels"... + * This option can speed up the cache only when you have many thousands of + * cache file. Only specific benchs can help you to choose the perfect value + * for you. Maybe, 1 or 2 is a good start. + * + * =====> (int) hashed_directory_umask : + * - Umask for hashed directory structure + * + * =====> (string) file_name_prefix : + * - prefix for cache files + * - be really carefull with this option because a too generic value in a system cache dir + * (like /tmp) can cause disasters when cleaning the cache + * + * =====> (int) cache_file_umask : + * - Umask for cache files + * + * =====> (int) metatadatas_array_max_size : + * - max size for the metadatas array (don't change this value unless you + * know what you are doing) + * + * @var array available options + */ + protected $_options = array( + 'cache_dir' => null, + 'file_locking' => true, + 'read_control' => true, + 'read_control_type' => 'crc32', + 'hashed_directory_level' => 0, + 'hashed_directory_umask' => 0700, + 'file_name_prefix' => 'zend_cache', + 'cache_file_umask' => 0600, + 'metadatas_array_max_size' => 100 + ); + + /** + * Array of metadatas (each item is an associative array) + * + * @var array + */ + protected $_metadatasArray = array(); + + + /** + * Constructor + * + * @param array $options associative array of options + * @throws Zend_Cache_Exception + * @return void + */ + public function __construct(array $options = array()) + { + parent::__construct($options); + if ($this->_options['cache_dir'] !== null) { // particular case for this option + $this->setCacheDir($this->_options['cache_dir']); + } else { + $this->setCacheDir(self::getTmpDir() . DIRECTORY_SEPARATOR, false); + } + if (isset($this->_options['file_name_prefix'])) { // particular case for this option + if (!preg_match('~^[a-zA-Z0-9_]+$~D', $this->_options['file_name_prefix'])) { + Zend_Cache::throwException('Invalid file_name_prefix : must use only [a-zA-Z0-9_]'); + } + } + if ($this->_options['metadatas_array_max_size'] < 10) { + Zend_Cache::throwException('Invalid metadatas_array_max_size, must be > 10'); + } + if (isset($options['hashed_directory_umask']) && is_string($options['hashed_directory_umask'])) { + // See #ZF-4422 + $this->_options['hashed_directory_umask'] = octdec($this->_options['hashed_directory_umask']); + } + if (isset($options['cache_file_umask']) && is_string($options['cache_file_umask'])) { + // See #ZF-4422 + $this->_options['cache_file_umask'] = octdec($this->_options['cache_file_umask']); + } + } + + /** + * Set the cache_dir (particular case of setOption() method) + * + * @param string $value + * @param boolean $trailingSeparator If true, add a trailing separator is necessary + * @throws Zend_Cache_Exception + * @return void + */ + public function setCacheDir($value, $trailingSeparator = true) + { + if (!is_dir($value)) { + Zend_Cache::throwException('cache_dir must be a directory'); + } + if (!is_writable($value)) { + Zend_Cache::throwException('cache_dir is not writable'); + } + if ($trailingSeparator) { + // add a trailing DIRECTORY_SEPARATOR if necessary + $value = rtrim(realpath($value), '\\/') . DIRECTORY_SEPARATOR; + } + $this->_options['cache_dir'] = $value; + } + + /** + * Test if a cache is available for the given id and (if yes) return it (false else) + * + * @param string $id cache id + * @param boolean $doNotTestCacheValidity if set to true, the cache validity won't be tested + * @return string|false cached datas + */ + public function load($id, $doNotTestCacheValidity = false) + { + if (!($this->_test($id, $doNotTestCacheValidity))) { + // The cache is not hit ! + return false; + } + $metadatas = $this->_getMetadatas($id); + $file = $this->_file($id); + $data = $this->_fileGetContents($file); + if ($this->_options['read_control']) { + $hashData = $this->_hash($data, $this->_options['read_control_type']); + $hashControl = $metadatas['hash']; + if ($hashData != $hashControl) { + // Problem detected by the read control ! + $this->_log('Zend_Cache_Backend_File::load() / read_control : stored hash and computed hash do not match'); + $this->remove($id); + return false; + } + } + return $data; + } + + /** + * Test if a cache is available or not (for the given id) + * + * @param string $id cache id + * @return mixed false (a cache is not available) or "last modified" timestamp (int) of the available cache record + */ + public function test($id) + { + clearstatcache(); + return $this->_test($id, false); + } + + /** + * Save some string datas into a cache record + * + * Note : $data is always "string" (serialization is done by the + * core not by the backend) + * + * @param string $data Datas to cache + * @param string $id Cache id + * @param array $tags Array of strings, the cache record will be tagged by each string entry + * @param int $specificLifetime If != false, set a specific lifetime for this cache record (null => infinite lifetime) + * @return boolean true if no problem + */ + public function save($data, $id, $tags = array(), $specificLifetime = false) + { + clearstatcache(); + $file = $this->_file($id); + $path = $this->_path($id); + if ($this->_options['hashed_directory_level'] > 0) { + if (!is_writable($path)) { + // maybe, we just have to build the directory structure + $this->_recursiveMkdirAndChmod($id); + } + if (!is_writable($path)) { + return false; + } + } + if ($this->_options['read_control']) { + $hash = $this->_hash($data, $this->_options['read_control_type']); + } else { + $hash = ''; + } + $metadatas = array( + 'hash' => $hash, + 'mtime' => time(), + 'expire' => $this->_expireTime($this->getLifetime($specificLifetime)), + 'tags' => $tags + ); + $res = $this->_setMetadatas($id, $metadatas); + if (!$res) { + $this->_log('Zend_Cache_Backend_File::save() / error on saving metadata'); + return false; + } + $res = $this->_filePutContents($file, $data); + return $res; + } + + /** + * Remove a cache record + * + * @param string $id cache id + * @return boolean true if no problem + */ + public function remove($id) + { + $file = $this->_file($id); + $boolRemove = $this->_remove($file); + $boolMetadata = $this->_delMetadatas($id); + return $boolMetadata && $boolRemove; + } + + /** + * Clean some cache records + * + * Available modes are : + * 'all' (default) => remove all cache entries ($tags is not used) + * 'old' => remove too old cache entries ($tags is not used) + * 'matchingTag' => remove cache entries matching all given tags + * ($tags can be an array of strings or a single string) + * 'notMatchingTag' => remove cache entries not matching one of the given tags + * ($tags can be an array of strings or a single string) + * 'matchingAnyTag' => remove cache entries matching any given tags + * ($tags can be an array of strings or a single string) + * + * @param string $mode clean mode + * @param tags array $tags array of tags + * @return boolean true if no problem + */ + public function clean($mode = Zend_Cache::CLEANING_MODE_ALL, $tags = array()) + { + // We use this protected method to hide the recursive stuff + clearstatcache(); + return $this->_clean($this->_options['cache_dir'], $mode, $tags); + } + + /** + * Return an array of stored cache ids + * + * @return array array of stored cache ids (string) + */ + public function getIds() + { + return $this->_get($this->_options['cache_dir'], 'ids', array()); + } + + /** + * Return an array of stored tags + * + * @return array array of stored tags (string) + */ + public function getTags() + { + return $this->_get($this->_options['cache_dir'], 'tags', array()); + } + + /** + * Return an array of stored cache ids which match given tags + * + * In case of multiple tags, a logical AND is made between tags + * + * @param array $tags array of tags + * @return array array of matching cache ids (string) + */ + public function getIdsMatchingTags($tags = array()) + { + return $this->_get($this->_options['cache_dir'], 'matching', $tags); + } + + /** + * Return an array of stored cache ids which don't match given tags + * + * In case of multiple tags, a logical OR is made between tags + * + * @param array $tags array of tags + * @return array array of not matching cache ids (string) + */ + public function getIdsNotMatchingTags($tags = array()) + { + return $this->_get($this->_options['cache_dir'], 'notMatching', $tags); + } + + /** + * Return an array of stored cache ids which match any given tags + * + * In case of multiple tags, a logical AND is made between tags + * + * @param array $tags array of tags + * @return array array of any matching cache ids (string) + */ + public function getIdsMatchingAnyTags($tags = array()) + { + return $this->_get($this->_options['cache_dir'], 'matchingAny', $tags); + } + + /** + * Return the filling percentage of the backend storage + * + * @throws Zend_Cache_Exception + * @return int integer between 0 and 100 + */ + public function getFillingPercentage() + { + $free = disk_free_space($this->_options['cache_dir']); + $total = disk_total_space($this->_options['cache_dir']); + if ($total == 0) { + Zend_Cache::throwException('can\'t get disk_total_space'); + } else { + if ($free >= $total) { + return 100; + } + return ((int) (100. * ($total - $free) / $total)); + } + } + + /** + * Return an array of metadatas for the given cache id + * + * The array must include these keys : + * - expire : the expire timestamp + * - tags : a string array of tags + * - mtime : timestamp of last modification time + * + * @param string $id cache id + * @return array array of metadatas (false if the cache id is not found) + */ + public function getMetadatas($id) + { + $metadatas = $this->_getMetadatas($id); + if (!$metadatas) { + return false; + } + if (time() > $metadatas['expire']) { + return false; + } + return array( + 'expire' => $metadatas['expire'], + 'tags' => $metadatas['tags'], + 'mtime' => $metadatas['mtime'] + ); + } + + /** + * Give (if possible) an extra lifetime to the given cache id + * + * @param string $id cache id + * @param int $extraLifetime + * @return boolean true if ok + */ + public function touch($id, $extraLifetime) + { + $metadatas = $this->_getMetadatas($id); + if (!$metadatas) { + return false; + } + if (time() > $metadatas['expire']) { + return false; + } + $newMetadatas = array( + 'hash' => $metadatas['hash'], + 'mtime' => time(), + 'expire' => $metadatas['expire'] + $extraLifetime, + 'tags' => $metadatas['tags'] + ); + $res = $this->_setMetadatas($id, $newMetadatas); + if (!$res) { + return false; + } + return true; + } + + /** + * Return an associative array of capabilities (booleans) of the backend + * + * The array must include these keys : + * - automatic_cleaning (is automating cleaning necessary) + * - tags (are tags supported) + * - expired_read (is it possible to read expired cache records + * (for doNotTestCacheValidity option for example)) + * - priority does the backend deal with priority when saving + * - infinite_lifetime (is infinite lifetime can work with this backend) + * - get_list (is it possible to get the list of cache ids and the complete list of tags) + * + * @return array associative of with capabilities + */ + public function getCapabilities() + { + return array( + 'automatic_cleaning' => true, + 'tags' => true, + 'expired_read' => true, + 'priority' => false, + 'infinite_lifetime' => true, + 'get_list' => true + ); + } + + /** + * PUBLIC METHOD FOR UNIT TESTING ONLY ! + * + * Force a cache record to expire + * + * @param string $id cache id + */ + public function ___expire($id) + { + $metadatas = $this->_getMetadatas($id); + if ($metadatas) { + $metadatas['expire'] = 1; + $this->_setMetadatas($id, $metadatas); + } + } + + /** + * Get a metadatas record + * + * @param string $id Cache id + * @return array|false Associative array of metadatas + */ + protected function _getMetadatas($id) + { + if (isset($this->_metadatasArray[$id])) { + return $this->_metadatasArray[$id]; + } else { + $metadatas = $this->_loadMetadatas($id); + if (!$metadatas) { + return false; + } + $this->_setMetadatas($id, $metadatas, false); + return $metadatas; + } + } + + /** + * Set a metadatas record + * + * @param string $id Cache id + * @param array $metadatas Associative array of metadatas + * @param boolean $save optional pass false to disable saving to file + * @return boolean True if no problem + */ + protected function _setMetadatas($id, $metadatas, $save = true) + { + if (count($this->_metadatasArray) >= $this->_options['metadatas_array_max_size']) { + $n = (int) ($this->_options['metadatas_array_max_size'] / 10); + $this->_metadatasArray = array_slice($this->_metadatasArray, $n); + } + if ($save) { + $result = $this->_saveMetadatas($id, $metadatas); + if (!$result) { + return false; + } + } + $this->_metadatasArray[$id] = $metadatas; + return true; + } + + /** + * Drop a metadata record + * + * @param string $id Cache id + * @return boolean True if no problem + */ + protected function _delMetadatas($id) + { + if (isset($this->_metadatasArray[$id])) { + unset($this->_metadatasArray[$id]); + } + $file = $this->_metadatasFile($id); + return $this->_remove($file); + } + + /** + * Clear the metadatas array + * + * @return void + */ + protected function _cleanMetadatas() + { + $this->_metadatasArray = array(); + } + + /** + * Load metadatas from disk + * + * @param string $id Cache id + * @return array|false Metadatas associative array + */ + protected function _loadMetadatas($id) + { + $file = $this->_metadatasFile($id); + $result = $this->_fileGetContents($file); + if (!$result) { + return false; + } + $tmp = @unserialize($result); + return $tmp; + } + + /** + * Save metadatas to disk + * + * @param string $id Cache id + * @param array $metadatas Associative array + * @return boolean True if no problem + */ + protected function _saveMetadatas($id, $metadatas) + { + $file = $this->_metadatasFile($id); + $result = $this->_filePutContents($file, serialize($metadatas)); + if (!$result) { + return false; + } + return true; + } + + /** + * Make and return a file name (with path) for metadatas + * + * @param string $id Cache id + * @return string Metadatas file name (with path) + */ + protected function _metadatasFile($id) + { + $path = $this->_path($id); + $fileName = $this->_idToFileName('internal-metadatas---' . $id); + return $path . $fileName; + } + + /** + * Check if the given filename is a metadatas one + * + * @param string $fileName File name + * @return boolean True if it's a metadatas one + */ + protected function _isMetadatasFile($fileName) + { + $id = $this->_fileNameToId($fileName); + if (substr($id, 0, 21) == 'internal-metadatas---') { + return true; + } else { + return false; + } + } + + /** + * Remove a file + * + * If we can't remove the file (because of locks or any problem), we will touch + * the file to invalidate it + * + * @param string $file Complete file path + * @return boolean True if ok + */ + protected function _remove($file) + { + if (!is_file($file)) { + return false; + } + if (!@unlink($file)) { + # we can't remove the file (because of locks or any problem) + $this->_log("Zend_Cache_Backend_File::_remove() : we can't remove $file"); + return false; + } + return true; + } + + /** + * Clean some cache records (protected method used for recursive stuff) + * + * Available modes are : + * Zend_Cache::CLEANING_MODE_ALL (default) => remove all cache entries ($tags is not used) + * Zend_Cache::CLEANING_MODE_OLD => remove too old cache entries ($tags is not used) + * Zend_Cache::CLEANING_MODE_MATCHING_TAG => remove cache entries matching all given tags + * ($tags can be an array of strings or a single string) + * Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG => remove cache entries not {matching one of the given tags} + * ($tags can be an array of strings or a single string) + * Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG => remove cache entries matching any given tags + * ($tags can be an array of strings or a single string) + * + * @param string $dir Directory to clean + * @param string $mode Clean mode + * @param array $tags Array of tags + * @throws Zend_Cache_Exception + * @return boolean True if no problem + */ + protected function _clean($dir, $mode = Zend_Cache::CLEANING_MODE_ALL, $tags = array()) + { + if (!is_dir($dir)) { + return false; + } + $result = true; + $prefix = $this->_options['file_name_prefix']; + $glob = @glob($dir . $prefix . '--*'); + if ($glob === false) { + // On some systems it is impossible to distinguish between empty match and an error. + return true; + } + foreach ($glob as $file) { + if (is_file($file)) { + $fileName = basename($file); + if ($this->_isMetadatasFile($fileName)) { + // in CLEANING_MODE_ALL, we drop anything, even remainings old metadatas files + if ($mode != Zend_Cache::CLEANING_MODE_ALL) { + continue; + } + } + $id = $this->_fileNameToId($fileName); + $metadatas = $this->_getMetadatas($id); + if ($metadatas === FALSE) { + $metadatas = array('expire' => 1, 'tags' => array()); + } + switch ($mode) { + case Zend_Cache::CLEANING_MODE_ALL: + $res = $this->remove($id); + if (!$res) { + // in this case only, we accept a problem with the metadatas file drop + $res = $this->_remove($file); + } + $result = $result && $res; + break; + case Zend_Cache::CLEANING_MODE_OLD: + if (time() > $metadatas['expire']) { + $result = $this->remove($id) && $result; + } + break; + case Zend_Cache::CLEANING_MODE_MATCHING_TAG: + $matching = true; + foreach ($tags as $tag) { + if (!in_array($tag, $metadatas['tags'])) { + $matching = false; + break; + } + } + if ($matching) { + $result = $this->remove($id) && $result; + } + break; + case Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG: + $matching = false; + foreach ($tags as $tag) { + if (in_array($tag, $metadatas['tags'])) { + $matching = true; + break; + } + } + if (!$matching) { + $result = $this->remove($id) && $result; + } + break; + case Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG: + $matching = false; + foreach ($tags as $tag) { + if (in_array($tag, $metadatas['tags'])) { + $matching = true; + break; + } + } + if ($matching) { + $result = $this->remove($id) && $result; + } + break; + default: + Zend_Cache::throwException('Invalid mode for clean() method'); + break; + } + } + if ((is_dir($file)) and ($this->_options['hashed_directory_level']>0)) { + // Recursive call + $result = $this->_clean($file . DIRECTORY_SEPARATOR, $mode, $tags) && $result; + if ($mode=='all') { + // if mode=='all', we try to drop the structure too + @rmdir($file); + } + } + } + return $result; + } + + protected function _get($dir, $mode, $tags = array()) + { + if (!is_dir($dir)) { + return false; + } + $result = array(); + $prefix = $this->_options['file_name_prefix']; + $glob = @glob($dir . $prefix . '--*'); + if ($glob === false) { + // On some systems it is impossible to distinguish between empty match and an error. + return array(); + } + foreach ($glob as $file) { + if (is_file($file)) { + $fileName = basename($file); + $id = $this->_fileNameToId($fileName); + $metadatas = $this->_getMetadatas($id); + if ($metadatas === FALSE) { + continue; + } + if (time() > $metadatas['expire']) { + continue; + } + switch ($mode) { + case 'ids': + $result[] = $id; + break; + case 'tags': + $result = array_unique(array_merge($result, $metadatas['tags'])); + break; + case 'matching': + $matching = true; + foreach ($tags as $tag) { + if (!in_array($tag, $metadatas['tags'])) { + $matching = false; + break; + } + } + if ($matching) { + $result[] = $id; + } + break; + case 'notMatching': + $matching = false; + foreach ($tags as $tag) { + if (in_array($tag, $metadatas['tags'])) { + $matching = true; + break; + } + } + if (!$matching) { + $result[] = $id; + } + break; + case 'matchingAny': + $matching = false; + foreach ($tags as $tag) { + if (in_array($tag, $metadatas['tags'])) { + $matching = true; + break; + } + } + if ($matching) { + $result[] = $id; + } + break; + default: + Zend_Cache::throwException('Invalid mode for _get() method'); + break; + } + } + if ((is_dir($file)) and ($this->_options['hashed_directory_level']>0)) { + // Recursive call + $recursiveRs = $this->_get($file . DIRECTORY_SEPARATOR, $mode, $tags); + if ($recursiveRs === false) { + $this->_log('Zend_Cache_Backend_File::_get() / recursive call : can\'t list entries of "'.$file.'"'); + } else { + $result = array_unique(array_merge($result, $recursiveRs)); + } + } + } + return array_unique($result); + } + + /** + * Compute & return the expire time + * + * @return int expire time (unix timestamp) + */ + protected function _expireTime($lifetime) + { + if ($lifetime === null) { + return 9999999999; + } + return time() + $lifetime; + } + + /** + * Make a control key with the string containing datas + * + * @param string $data Data + * @param string $controlType Type of control 'md5', 'crc32' or 'strlen' + * @throws Zend_Cache_Exception + * @return string Control key + */ + protected function _hash($data, $controlType) + { + switch ($controlType) { + case 'md5': + return md5($data); + case 'crc32': + return crc32($data); + case 'strlen': + return strlen($data); + case 'adler32': + return hash('adler32', $data); + default: + Zend_Cache::throwException("Incorrect hash function : $controlType"); + } + } + + /** + * Transform a cache id into a file name and return it + * + * @param string $id Cache id + * @return string File name + */ + protected function _idToFileName($id) + { + $prefix = $this->_options['file_name_prefix']; + $result = $prefix . '---' . $id; + return $result; + } + + /** + * Make and return a file name (with path) + * + * @param string $id Cache id + * @return string File name (with path) + */ + protected function _file($id) + { + $path = $this->_path($id); + $fileName = $this->_idToFileName($id); + return $path . $fileName; + } + + /** + * Return the complete directory path of a filename (including hashedDirectoryStructure) + * + * @param string $id Cache id + * @param boolean $parts if true, returns array of directory parts instead of single string + * @return string Complete directory path + */ + protected function _path($id, $parts = false) + { + $partsArray = array(); + $root = $this->_options['cache_dir']; + $prefix = $this->_options['file_name_prefix']; + if ($this->_options['hashed_directory_level']>0) { + $hash = hash('adler32', $id); + for ($i=0 ; $i < $this->_options['hashed_directory_level'] ; $i++) { + $root = $root . $prefix . '--' . substr($hash, 0, $i + 1) . DIRECTORY_SEPARATOR; + $partsArray[] = $root; + } + } + if ($parts) { + return $partsArray; + } else { + return $root; + } + } + + /** + * Make the directory strucuture for the given id + * + * @param string $id cache id + * @return boolean true + */ + protected function _recursiveMkdirAndChmod($id) + { + if ($this->_options['hashed_directory_level'] <=0) { + return true; + } + $partsArray = $this->_path($id, true); + foreach ($partsArray as $part) { + if (!is_dir($part)) { + @mkdir($part, $this->_options['hashed_directory_umask']); + @chmod($part, $this->_options['hashed_directory_umask']); // see #ZF-320 (this line is required in some configurations) + } + } + return true; + } + + /** + * Test if the given cache id is available (and still valid as a cache record) + * + * @param string $id Cache id + * @param boolean $doNotTestCacheValidity If set to true, the cache validity won't be tested + * @return boolean|mixed false (a cache is not available) or "last modified" timestamp (int) of the available cache record + */ + protected function _test($id, $doNotTestCacheValidity) + { + $metadatas = $this->_getMetadatas($id); + if (!$metadatas) { + return false; + } + if ($doNotTestCacheValidity || (time() <= $metadatas['expire'])) { + return $metadatas['mtime']; + } + return false; + } + + /** + * Return the file content of the given file + * + * @param string $file File complete path + * @return string File content (or false if problem) + */ + protected function _fileGetContents($file) + { + $result = false; + if (!is_file($file)) { + return false; + } + $f = @fopen($file, 'rb'); + if ($f) { + if ($this->_options['file_locking']) @flock($f, LOCK_SH); + $result = stream_get_contents($f); + if ($this->_options['file_locking']) @flock($f, LOCK_UN); + @fclose($f); + } + return $result; + } + + /** + * Put the given string into the given file + * + * @param string $file File complete path + * @param string $string String to put in file + * @return boolean true if no problem + */ + protected function _filePutContents($file, $string) + { + $result = false; + $f = @fopen($file, 'ab+'); + if ($f) { + if ($this->_options['file_locking']) @flock($f, LOCK_EX); + fseek($f, 0); + ftruncate($f, 0); + $tmp = @fwrite($f, $string); + if (!($tmp === FALSE)) { + $result = true; + } + @fclose($f); + } + @chmod($file, $this->_options['cache_file_umask']); + return $result; + } + + /** + * Transform a file name into cache id and return it + * + * @param string $fileName File name + * @return string Cache id + */ + protected function _fileNameToId($fileName) + { + $prefix = $this->_options['file_name_prefix']; + return preg_replace('~^' . $prefix . '---(.*)$~', '$1', $fileName); + } + +} diff --git a/library/Zend/Cache/Backend/Interface.php b/library/Zend/Cache/Backend/Interface.php new file mode 100644 index 000000000..3e8c7d121 --- /dev/null +++ b/library/Zend/Cache/Backend/Interface.php @@ -0,0 +1,99 @@ + infinite lifetime) + * @return boolean true if no problem + */ + public function save($data, $id, $tags = array(), $specificLifetime = false); + + /** + * Remove a cache record + * + * @param string $id Cache id + * @return boolean True if no problem + */ + public function remove($id); + + /** + * Clean some cache records + * + * Available modes are : + * Zend_Cache::CLEANING_MODE_ALL (default) => remove all cache entries ($tags is not used) + * Zend_Cache::CLEANING_MODE_OLD => remove too old cache entries ($tags is not used) + * Zend_Cache::CLEANING_MODE_MATCHING_TAG => remove cache entries matching all given tags + * ($tags can be an array of strings or a single string) + * Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG => remove cache entries not {matching one of the given tags} + * ($tags can be an array of strings or a single string) + * Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG => remove cache entries matching any given tags + * ($tags can be an array of strings or a single string) + * + * @param string $mode Clean mode + * @param array $tags Array of tags + * @return boolean true if no problem + */ + public function clean($mode = Zend_Cache::CLEANING_MODE_ALL, $tags = array()); + +} diff --git a/library/Zend/Cache/Backend/Memcached.php b/library/Zend/Cache/Backend/Memcached.php new file mode 100644 index 000000000..5c488a6e0 --- /dev/null +++ b/library/Zend/Cache/Backend/Memcached.php @@ -0,0 +1,504 @@ + (array) servers : + * an array of memcached server ; each memcached server is described by an associative array : + * 'host' => (string) : the name of the memcached server + * 'port' => (int) : the port of the memcached server + * 'persistent' => (bool) : use or not persistent connections to this memcached server + * 'weight' => (int) : number of buckets to create for this server which in turn control its + * probability of it being selected. The probability is relative to the total + * weight of all servers. + * 'timeout' => (int) : value in seconds which will be used for connecting to the daemon. Think twice + * before changing the default value of 1 second - you can lose all the + * advantages of caching if your connection is too slow. + * 'retry_interval' => (int) : controls how often a failed server will be retried, the default value + * is 15 seconds. Setting this parameter to -1 disables automatic retry. + * 'status' => (bool) : controls if the server should be flagged as online. + * 'failure_callback' => (callback) : Allows the user to specify a callback function to run upon + * encountering an error. The callback is run before failover + * is attempted. The function takes two parameters, the hostname + * and port of the failed server. + * + * =====> (boolean) compression : + * true if you want to use on-the-fly compression + * + * =====> (boolean) compatibility : + * true if you use old memcache server or extension + * + * @var array available options + */ + protected $_options = array( + 'servers' => array(array( + 'host' => self::DEFAULT_HOST, + 'port' => self::DEFAULT_PORT, + 'persistent' => self::DEFAULT_PERSISTENT, + 'weight' => self::DEFAULT_WEIGHT, + 'timeout' => self::DEFAULT_TIMEOUT, + 'retry_interval' => self::DEFAULT_RETRY_INTERVAL, + 'status' => self::DEFAULT_STATUS, + 'failure_callback' => self::DEFAULT_FAILURE_CALLBACK + )), + 'compression' => false, + 'compatibility' => false, + ); + + /** + * Memcache object + * + * @var mixed memcache object + */ + protected $_memcache = null; + + /** + * Constructor + * + * @param array $options associative array of options + * @throws Zend_Cache_Exception + * @return void + */ + public function __construct(array $options = array()) + { + if (!extension_loaded('memcache')) { + Zend_Cache::throwException('The memcache extension must be loaded for using this backend !'); + } + parent::__construct($options); + if (isset($this->_options['servers'])) { + $value= $this->_options['servers']; + if (isset($value['host'])) { + // in this case, $value seems to be a simple associative array (one server only) + $value = array(0 => $value); // let's transform it into a classical array of associative arrays + } + $this->setOption('servers', $value); + } + $this->_memcache = new Memcache; + foreach ($this->_options['servers'] as $server) { + if (!array_key_exists('port', $server)) { + $server['port'] = self::DEFAULT_PORT; + } + if (!array_key_exists('persistent', $server)) { + $server['persistent'] = self::DEFAULT_PERSISTENT; + } + if (!array_key_exists('weight', $server)) { + $server['weight'] = self::DEFAULT_WEIGHT; + } + if (!array_key_exists('timeout', $server)) { + $server['timeout'] = self::DEFAULT_TIMEOUT; + } + if (!array_key_exists('retry_interval', $server)) { + $server['retry_interval'] = self::DEFAULT_RETRY_INTERVAL; + } + if (!array_key_exists('status', $server)) { + $server['status'] = self::DEFAULT_STATUS; + } + if (!array_key_exists('failure_callback', $server)) { + $server['failure_callback'] = self::DEFAULT_FAILURE_CALLBACK; + } + if ($this->_options['compatibility']) { + // No status for compatibility mode (#ZF-5887) + $this->_memcache->addServer($server['host'], $server['port'], $server['persistent'], + $server['weight'], $server['timeout'], + $server['retry_interval']); + } else { + $this->_memcache->addServer($server['host'], $server['port'], $server['persistent'], + $server['weight'], $server['timeout'], + $server['retry_interval'], + $server['status'], $server['failure_callback']); + } + } + } + + /** + * Test if a cache is available for the given id and (if yes) return it (false else) + * + * @param string $id Cache id + * @param boolean $doNotTestCacheValidity If set to true, the cache validity won't be tested + * @return string|false cached datas + */ + public function load($id, $doNotTestCacheValidity = false) + { + $tmp = $this->_memcache->get($id); + if (is_array($tmp) && isset($tmp[0])) { + return $tmp[0]; + } + return false; + } + + /** + * Test if a cache is available or not (for the given id) + * + * @param string $id Cache id + * @return mixed|false (a cache is not available) or "last modified" timestamp (int) of the available cache record + */ + public function test($id) + { + $tmp = $this->_memcache->get($id); + if (is_array($tmp)) { + return $tmp[1]; + } + return false; + } + + /** + * Save some string datas into a cache record + * + * Note : $data is always "string" (serialization is done by the + * core not by the backend) + * + * @param string $data Datas to cache + * @param string $id Cache id + * @param array $tags Array of strings, the cache record will be tagged by each string entry + * @param int $specificLifetime If != false, set a specific lifetime for this cache record (null => infinite lifetime) + * @return boolean True if no problem + */ + public function save($data, $id, $tags = array(), $specificLifetime = false) + { + $lifetime = $this->getLifetime($specificLifetime); + if ($this->_options['compression']) { + $flag = MEMCACHE_COMPRESSED; + } else { + $flag = 0; + } + + // ZF-8856: using set because add needs a second request if item already exists + $result = @$this->_memcache->set($id, array($data, time(), $lifetime), $flag, $lifetime); + + if (count($tags) > 0) { + $this->_log("Zend_Cache_Backend_Memcached::save() : tags are unsupported by the Memcached backend"); + } + + return $result; + } + + /** + * Remove a cache record + * + * @param string $id Cache id + * @return boolean True if no problem + */ + public function remove($id) + { + return $this->_memcache->delete($id, 0); + } + + /** + * Clean some cache records + * + * Available modes are : + * 'all' (default) => remove all cache entries ($tags is not used) + * 'old' => unsupported + * 'matchingTag' => unsupported + * 'notMatchingTag' => unsupported + * 'matchingAnyTag' => unsupported + * + * @param string $mode Clean mode + * @param array $tags Array of tags + * @throws Zend_Cache_Exception + * @return boolean True if no problem + */ + public function clean($mode = Zend_Cache::CLEANING_MODE_ALL, $tags = array()) + { + switch ($mode) { + case Zend_Cache::CLEANING_MODE_ALL: + return $this->_memcache->flush(); + break; + case Zend_Cache::CLEANING_MODE_OLD: + $this->_log("Zend_Cache_Backend_Memcached::clean() : CLEANING_MODE_OLD is unsupported by the Memcached backend"); + break; + case Zend_Cache::CLEANING_MODE_MATCHING_TAG: + case Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG: + case Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG: + $this->_log(self::TAGS_UNSUPPORTED_BY_CLEAN_OF_MEMCACHED_BACKEND); + break; + default: + Zend_Cache::throwException('Invalid mode for clean() method'); + break; + } + } + + /** + * Return true if the automatic cleaning is available for the backend + * + * @return boolean + */ + public function isAutomaticCleaningAvailable() + { + return false; + } + + /** + * Set the frontend directives + * + * @param array $directives Assoc of directives + * @throws Zend_Cache_Exception + * @return void + */ + public function setDirectives($directives) + { + parent::setDirectives($directives); + $lifetime = $this->getLifetime(false); + if ($lifetime > 2592000) { + // #ZF-3490 : For the memcached backend, there is a lifetime limit of 30 days (2592000 seconds) + $this->_log('memcached backend has a limit of 30 days (2592000 seconds) for the lifetime'); + } + if ($lifetime === null) { + // #ZF-4614 : we tranform null to zero to get the maximal lifetime + parent::setDirectives(array('lifetime' => 0)); + } + } + + /** + * Return an array of stored cache ids + * + * @return array array of stored cache ids (string) + */ + public function getIds() + { + $this->_log("Zend_Cache_Backend_Memcached::save() : getting the list of cache ids is unsupported by the Memcache backend"); + return array(); + } + + /** + * Return an array of stored tags + * + * @return array array of stored tags (string) + */ + public function getTags() + { + $this->_log(self::TAGS_UNSUPPORTED_BY_SAVE_OF_MEMCACHED_BACKEND); + return array(); + } + + /** + * Return an array of stored cache ids which match given tags + * + * In case of multiple tags, a logical AND is made between tags + * + * @param array $tags array of tags + * @return array array of matching cache ids (string) + */ + public function getIdsMatchingTags($tags = array()) + { + $this->_log(self::TAGS_UNSUPPORTED_BY_SAVE_OF_MEMCACHED_BACKEND); + return array(); + } + + /** + * Return an array of stored cache ids which don't match given tags + * + * In case of multiple tags, a logical OR is made between tags + * + * @param array $tags array of tags + * @return array array of not matching cache ids (string) + */ + public function getIdsNotMatchingTags($tags = array()) + { + $this->_log(self::TAGS_UNSUPPORTED_BY_SAVE_OF_MEMCACHED_BACKEND); + return array(); + } + + /** + * Return an array of stored cache ids which match any given tags + * + * In case of multiple tags, a logical AND is made between tags + * + * @param array $tags array of tags + * @return array array of any matching cache ids (string) + */ + public function getIdsMatchingAnyTags($tags = array()) + { + $this->_log(self::TAGS_UNSUPPORTED_BY_SAVE_OF_MEMCACHED_BACKEND); + return array(); + } + + /** + * Return the filling percentage of the backend storage + * + * @throws Zend_Cache_Exception + * @return int integer between 0 and 100 + */ + public function getFillingPercentage() + { + $mems = $this->_memcache->getExtendedStats(); + + $memSize = null; + $memUsed = null; + foreach ($mems as $key => $mem) { + if ($mem === false) { + $this->_log('can\'t get stat from ' . $key); + continue; + } + + $eachSize = $mem['limit_maxbytes']; + $eachUsed = $mem['bytes']; + if ($eachUsed > $eachSize) { + $eachUsed = $eachSize; + } + + $memSize += $eachSize; + $memUsed += $eachUsed; + } + + if ($memSize === null || $memUsed === null) { + Zend_Cache::throwException('Can\'t get filling percentage'); + } + + return ((int) (100. * ($memUsed / $memSize))); + } + + /** + * Return an array of metadatas for the given cache id + * + * The array must include these keys : + * - expire : the expire timestamp + * - tags : a string array of tags + * - mtime : timestamp of last modification time + * + * @param string $id cache id + * @return array array of metadatas (false if the cache id is not found) + */ + public function getMetadatas($id) + { + $tmp = $this->_memcache->get($id); + if (is_array($tmp)) { + $data = $tmp[0]; + $mtime = $tmp[1]; + if (!isset($tmp[2])) { + // because this record is only with 1.7 release + // if old cache records are still there... + return false; + } + $lifetime = $tmp[2]; + return array( + 'expire' => $mtime + $lifetime, + 'tags' => array(), + 'mtime' => $mtime + ); + } + return false; + } + + /** + * Give (if possible) an extra lifetime to the given cache id + * + * @param string $id cache id + * @param int $extraLifetime + * @return boolean true if ok + */ + public function touch($id, $extraLifetime) + { + if ($this->_options['compression']) { + $flag = MEMCACHE_COMPRESSED; + } else { + $flag = 0; + } + $tmp = $this->_memcache->get($id); + if (is_array($tmp)) { + $data = $tmp[0]; + $mtime = $tmp[1]; + if (!isset($tmp[2])) { + // because this record is only with 1.7 release + // if old cache records are still there... + return false; + } + $lifetime = $tmp[2]; + $newLifetime = $lifetime - (time() - $mtime) + $extraLifetime; + if ($newLifetime <=0) { + return false; + } + // #ZF-5702 : we try replace() first becase set() seems to be slower + if (!($result = $this->_memcache->replace($id, array($data, time(), $newLifetime), $flag, $newLifetime))) { + $result = $this->_memcache->set($id, array($data, time(), $newLifetime), $flag, $newLifetime); + } + return $result; + } + return false; + } + + /** + * Return an associative array of capabilities (booleans) of the backend + * + * The array must include these keys : + * - automatic_cleaning (is automating cleaning necessary) + * - tags (are tags supported) + * - expired_read (is it possible to read expired cache records + * (for doNotTestCacheValidity option for example)) + * - priority does the backend deal with priority when saving + * - infinite_lifetime (is infinite lifetime can work with this backend) + * - get_list (is it possible to get the list of cache ids and the complete list of tags) + * + * @return array associative of with capabilities + */ + public function getCapabilities() + { + return array( + 'automatic_cleaning' => false, + 'tags' => false, + 'expired_read' => false, + 'priority' => false, + 'infinite_lifetime' => false, + 'get_list' => false + ); + } + +} diff --git a/library/Zend/Cache/Backend/Sqlite.php b/library/Zend/Cache/Backend/Sqlite.php new file mode 100644 index 000000000..5b81fc322 --- /dev/null +++ b/library/Zend/Cache/Backend/Sqlite.php @@ -0,0 +1,679 @@ + (string) cache_db_complete_path : + * - the complete path (filename included) of the SQLITE database + * + * ====> (int) automatic_vacuum_factor : + * - Disable / Tune the automatic vacuum process + * - The automatic vacuum process defragment the database file (and make it smaller) + * when a clean() or delete() is called + * 0 => no automatic vacuum + * 1 => systematic vacuum (when delete() or clean() methods are called) + * x (integer) > 1 => automatic vacuum randomly 1 times on x clean() or delete() + * + * @var array Available options + */ + protected $_options = array( + 'cache_db_complete_path' => null, + 'automatic_vacuum_factor' => 10 + ); + + /** + * DB ressource + * + * @var mixed $_db + */ + private $_db = null; + + /** + * Boolean to store if the structure has benn checked or not + * + * @var boolean $_structureChecked + */ + private $_structureChecked = false; + + /** + * Constructor + * + * @param array $options Associative array of options + * @throws Zend_cache_Exception + * @return void + */ + public function __construct(array $options = array()) + { + parent::__construct($options); + if ($this->_options['cache_db_complete_path'] === null) { + Zend_Cache::throwException('cache_db_complete_path option has to set'); + } + if (!extension_loaded('sqlite')) { + Zend_Cache::throwException("Cannot use SQLite storage because the 'sqlite' extension is not loaded in the current PHP environment"); + } + $this->_getConnection(); + } + + /** + * Destructor + * + * @return void + */ + public function __destruct() + { + @sqlite_close($this->_getConnection()); + } + + /** + * Test if a cache is available for the given id and (if yes) return it (false else) + * + * @param string $id Cache id + * @param boolean $doNotTestCacheValidity If set to true, the cache validity won't be tested + * @return string|false Cached datas + */ + public function load($id, $doNotTestCacheValidity = false) + { + $this->_checkAndBuildStructure(); + $sql = "SELECT content FROM cache WHERE id='$id'"; + if (!$doNotTestCacheValidity) { + $sql = $sql . " AND (expire=0 OR expire>" . time() . ')'; + } + $result = $this->_query($sql); + $row = @sqlite_fetch_array($result); + if ($row) { + return $row['content']; + } + return false; + } + + /** + * Test if a cache is available or not (for the given id) + * + * @param string $id Cache id + * @return mixed|false (a cache is not available) or "last modified" timestamp (int) of the available cache record + */ + public function test($id) + { + $this->_checkAndBuildStructure(); + $sql = "SELECT lastModified FROM cache WHERE id='$id' AND (expire=0 OR expire>" . time() . ')'; + $result = $this->_query($sql); + $row = @sqlite_fetch_array($result); + if ($row) { + return ((int) $row['lastModified']); + } + return false; + } + + /** + * Save some string datas into a cache record + * + * Note : $data is always "string" (serialization is done by the + * core not by the backend) + * + * @param string $data Datas to cache + * @param string $id Cache id + * @param array $tags Array of strings, the cache record will be tagged by each string entry + * @param int $specificLifetime If != false, set a specific lifetime for this cache record (null => infinite lifetime) + * @throws Zend_Cache_Exception + * @return boolean True if no problem + */ + public function save($data, $id, $tags = array(), $specificLifetime = false) + { + $this->_checkAndBuildStructure(); + $lifetime = $this->getLifetime($specificLifetime); + $data = @sqlite_escape_string($data); + $mktime = time(); + if ($lifetime === null) { + $expire = 0; + } else { + $expire = $mktime + $lifetime; + } + $this->_query("DELETE FROM cache WHERE id='$id'"); + $sql = "INSERT INTO cache (id, content, lastModified, expire) VALUES ('$id', '$data', $mktime, $expire)"; + $res = $this->_query($sql); + if (!$res) { + $this->_log("Zend_Cache_Backend_Sqlite::save() : impossible to store the cache id=$id"); + return false; + } + $res = true; + foreach ($tags as $tag) { + $res = $this->_registerTag($id, $tag) && $res; + } + return $res; + } + + /** + * Remove a cache record + * + * @param string $id Cache id + * @return boolean True if no problem + */ + public function remove($id) + { + $this->_checkAndBuildStructure(); + $res = $this->_query("SELECT COUNT(*) AS nbr FROM cache WHERE id='$id'"); + $result1 = @sqlite_fetch_single($res); + $result2 = $this->_query("DELETE FROM cache WHERE id='$id'"); + $result3 = $this->_query("DELETE FROM tag WHERE id='$id'"); + $this->_automaticVacuum(); + return ($result1 && $result2 && $result3); + } + + /** + * Clean some cache records + * + * Available modes are : + * Zend_Cache::CLEANING_MODE_ALL (default) => remove all cache entries ($tags is not used) + * Zend_Cache::CLEANING_MODE_OLD => remove too old cache entries ($tags is not used) + * Zend_Cache::CLEANING_MODE_MATCHING_TAG => remove cache entries matching all given tags + * ($tags can be an array of strings or a single string) + * Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG => remove cache entries not {matching one of the given tags} + * ($tags can be an array of strings or a single string) + * Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG => remove cache entries matching any given tags + * ($tags can be an array of strings or a single string) + * + * @param string $mode Clean mode + * @param array $tags Array of tags + * @return boolean True if no problem + */ + public function clean($mode = Zend_Cache::CLEANING_MODE_ALL, $tags = array()) + { + $this->_checkAndBuildStructure(); + $return = $this->_clean($mode, $tags); + $this->_automaticVacuum(); + return $return; + } + + /** + * Return an array of stored cache ids + * + * @return array array of stored cache ids (string) + */ + public function getIds() + { + $this->_checkAndBuildStructure(); + $res = $this->_query("SELECT id FROM cache WHERE (expire=0 OR expire>" . time() . ")"); + $result = array(); + while ($id = @sqlite_fetch_single($res)) { + $result[] = $id; + } + return $result; + } + + /** + * Return an array of stored tags + * + * @return array array of stored tags (string) + */ + public function getTags() + { + $this->_checkAndBuildStructure(); + $res = $this->_query("SELECT DISTINCT(name) AS name FROM tag"); + $result = array(); + while ($id = @sqlite_fetch_single($res)) { + $result[] = $id; + } + return $result; + } + + /** + * Return an array of stored cache ids which match given tags + * + * In case of multiple tags, a logical AND is made between tags + * + * @param array $tags array of tags + * @return array array of matching cache ids (string) + */ + public function getIdsMatchingTags($tags = array()) + { + $first = true; + $ids = array(); + foreach ($tags as $tag) { + $res = $this->_query("SELECT DISTINCT(id) AS id FROM tag WHERE name='$tag'"); + if (!$res) { + return array(); + } + $rows = @sqlite_fetch_all($res, SQLITE_ASSOC); + $ids2 = array(); + foreach ($rows as $row) { + $ids2[] = $row['id']; + } + if ($first) { + $ids = $ids2; + $first = false; + } else { + $ids = array_intersect($ids, $ids2); + } + } + $result = array(); + foreach ($ids as $id) { + $result[] = $id; + } + return $result; + } + + /** + * Return an array of stored cache ids which don't match given tags + * + * In case of multiple tags, a logical OR is made between tags + * + * @param array $tags array of tags + * @return array array of not matching cache ids (string) + */ + public function getIdsNotMatchingTags($tags = array()) + { + $res = $this->_query("SELECT id FROM cache"); + $rows = @sqlite_fetch_all($res, SQLITE_ASSOC); + $result = array(); + foreach ($rows as $row) { + $id = $row['id']; + $matching = false; + foreach ($tags as $tag) { + $res = $this->_query("SELECT COUNT(*) AS nbr FROM tag WHERE name='$tag' AND id='$id'"); + if (!$res) { + return array(); + } + $nbr = (int) @sqlite_fetch_single($res); + if ($nbr > 0) { + $matching = true; + } + } + if (!$matching) { + $result[] = $id; + } + } + return $result; + } + + /** + * Return an array of stored cache ids which match any given tags + * + * In case of multiple tags, a logical AND is made between tags + * + * @param array $tags array of tags + * @return array array of any matching cache ids (string) + */ + public function getIdsMatchingAnyTags($tags = array()) + { + $first = true; + $ids = array(); + foreach ($tags as $tag) { + $res = $this->_query("SELECT DISTINCT(id) AS id FROM tag WHERE name='$tag'"); + if (!$res) { + return array(); + } + $rows = @sqlite_fetch_all($res, SQLITE_ASSOC); + $ids2 = array(); + foreach ($rows as $row) { + $ids2[] = $row['id']; + } + if ($first) { + $ids = $ids2; + $first = false; + } else { + $ids = array_merge($ids, $ids2); + } + } + $result = array(); + foreach ($ids as $id) { + $result[] = $id; + } + return $result; + } + + /** + * Return the filling percentage of the backend storage + * + * @throws Zend_Cache_Exception + * @return int integer between 0 and 100 + */ + public function getFillingPercentage() + { + $dir = dirname($this->_options['cache_db_complete_path']); + $free = disk_free_space($dir); + $total = disk_total_space($dir); + if ($total == 0) { + Zend_Cache::throwException('can\'t get disk_total_space'); + } else { + if ($free >= $total) { + return 100; + } + return ((int) (100. * ($total - $free) / $total)); + } + } + + /** + * Return an array of metadatas for the given cache id + * + * The array must include these keys : + * - expire : the expire timestamp + * - tags : a string array of tags + * - mtime : timestamp of last modification time + * + * @param string $id cache id + * @return array array of metadatas (false if the cache id is not found) + */ + public function getMetadatas($id) + { + $tags = array(); + $res = $this->_query("SELECT name FROM tag WHERE id='$id'"); + if ($res) { + $rows = @sqlite_fetch_all($res, SQLITE_ASSOC); + foreach ($rows as $row) { + $tags[] = $row['name']; + } + } + $this->_query('CREATE TABLE cache (id TEXT PRIMARY KEY, content BLOB, lastModified INTEGER, expire INTEGER)'); + $res = $this->_query("SELECT lastModified,expire FROM cache WHERE id='$id'"); + if (!$res) { + return false; + } + $row = @sqlite_fetch_array($res, SQLITE_ASSOC); + return array( + 'tags' => $tags, + 'mtime' => $row['lastModified'], + 'expire' => $row['expire'] + ); + } + + /** + * Give (if possible) an extra lifetime to the given cache id + * + * @param string $id cache id + * @param int $extraLifetime + * @return boolean true if ok + */ + public function touch($id, $extraLifetime) + { + $sql = "SELECT expire FROM cache WHERE id='$id' AND (expire=0 OR expire>" . time() . ')'; + $res = $this->_query($sql); + if (!$res) { + return false; + } + $expire = @sqlite_fetch_single($res); + $newExpire = $expire + $extraLifetime; + $res = $this->_query("UPDATE cache SET lastModified=" . time() . ", expire=$newExpire WHERE id='$id'"); + if ($res) { + return true; + } else { + return false; + } + } + + /** + * Return an associative array of capabilities (booleans) of the backend + * + * The array must include these keys : + * - automatic_cleaning (is automating cleaning necessary) + * - tags (are tags supported) + * - expired_read (is it possible to read expired cache records + * (for doNotTestCacheValidity option for example)) + * - priority does the backend deal with priority when saving + * - infinite_lifetime (is infinite lifetime can work with this backend) + * - get_list (is it possible to get the list of cache ids and the complete list of tags) + * + * @return array associative of with capabilities + */ + public function getCapabilities() + { + return array( + 'automatic_cleaning' => true, + 'tags' => true, + 'expired_read' => true, + 'priority' => false, + 'infinite_lifetime' => true, + 'get_list' => true + ); + } + + /** + * PUBLIC METHOD FOR UNIT TESTING ONLY ! + * + * Force a cache record to expire + * + * @param string $id Cache id + */ + public function ___expire($id) + { + $time = time() - 1; + $this->_query("UPDATE cache SET lastModified=$time, expire=$time WHERE id='$id'"); + } + + /** + * Return the connection resource + * + * If we are not connected, the connection is made + * + * @throws Zend_Cache_Exception + * @return resource Connection resource + */ + private function _getConnection() + { + if (is_resource($this->_db)) { + return $this->_db; + } else { + $this->_db = @sqlite_open($this->_options['cache_db_complete_path']); + if (!(is_resource($this->_db))) { + Zend_Cache::throwException("Impossible to open " . $this->_options['cache_db_complete_path'] . " cache DB file"); + } + return $this->_db; + } + } + + /** + * Execute an SQL query silently + * + * @param string $query SQL query + * @return mixed|false query results + */ + private function _query($query) + { + $db = $this->_getConnection(); + if (is_resource($db)) { + $res = @sqlite_query($db, $query); + if ($res === false) { + return false; + } else { + return $res; + } + } + return false; + } + + /** + * Deal with the automatic vacuum process + * + * @return void + */ + private function _automaticVacuum() + { + if ($this->_options['automatic_vacuum_factor'] > 0) { + $rand = rand(1, $this->_options['automatic_vacuum_factor']); + if ($rand == 1) { + $this->_query('VACUUM'); + @sqlite_close($this->_getConnection()); + } + } + } + + /** + * Register a cache id with the given tag + * + * @param string $id Cache id + * @param string $tag Tag + * @return boolean True if no problem + */ + private function _registerTag($id, $tag) { + $res = $this->_query("DELETE FROM TAG WHERE name='$tag' AND id='$id'"); + $res = $this->_query("INSERT INTO tag (name, id) VALUES ('$tag', '$id')"); + if (!$res) { + $this->_log("Zend_Cache_Backend_Sqlite::_registerTag() : impossible to register tag=$tag on id=$id"); + return false; + } + return true; + } + + /** + * Build the database structure + * + * @return false + */ + private function _buildStructure() + { + $this->_query('DROP INDEX tag_id_index'); + $this->_query('DROP INDEX tag_name_index'); + $this->_query('DROP INDEX cache_id_expire_index'); + $this->_query('DROP TABLE version'); + $this->_query('DROP TABLE cache'); + $this->_query('DROP TABLE tag'); + $this->_query('CREATE TABLE version (num INTEGER PRIMARY KEY)'); + $this->_query('CREATE TABLE cache (id TEXT PRIMARY KEY, content BLOB, lastModified INTEGER, expire INTEGER)'); + $this->_query('CREATE TABLE tag (name TEXT, id TEXT)'); + $this->_query('CREATE INDEX tag_id_index ON tag(id)'); + $this->_query('CREATE INDEX tag_name_index ON tag(name)'); + $this->_query('CREATE INDEX cache_id_expire_index ON cache(id, expire)'); + $this->_query('INSERT INTO version (num) VALUES (1)'); + } + + /** + * Check if the database structure is ok (with the good version) + * + * @return boolean True if ok + */ + private function _checkStructureVersion() + { + $result = $this->_query("SELECT num FROM version"); + if (!$result) return false; + $row = @sqlite_fetch_array($result); + if (!$row) { + return false; + } + if (((int) $row['num']) != 1) { + // old cache structure + $this->_log('Zend_Cache_Backend_Sqlite::_checkStructureVersion() : old cache structure version detected => the cache is going to be dropped'); + return false; + } + return true; + } + + /** + * Clean some cache records + * + * Available modes are : + * Zend_Cache::CLEANING_MODE_ALL (default) => remove all cache entries ($tags is not used) + * Zend_Cache::CLEANING_MODE_OLD => remove too old cache entries ($tags is not used) + * Zend_Cache::CLEANING_MODE_MATCHING_TAG => remove cache entries matching all given tags + * ($tags can be an array of strings or a single string) + * Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG => remove cache entries not {matching one of the given tags} + * ($tags can be an array of strings or a single string) + * Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG => remove cache entries matching any given tags + * ($tags can be an array of strings or a single string) + * + * @param string $mode Clean mode + * @param array $tags Array of tags + * @return boolean True if no problem + */ + private function _clean($mode = Zend_Cache::CLEANING_MODE_ALL, $tags = array()) + { + switch ($mode) { + case Zend_Cache::CLEANING_MODE_ALL: + $res1 = $this->_query('DELETE FROM cache'); + $res2 = $this->_query('DELETE FROM tag'); + return $res1 && $res2; + break; + case Zend_Cache::CLEANING_MODE_OLD: + $mktime = time(); + $res1 = $this->_query("DELETE FROM tag WHERE id IN (SELECT id FROM cache WHERE expire>0 AND expire<=$mktime)"); + $res2 = $this->_query("DELETE FROM cache WHERE expire>0 AND expire<=$mktime"); + return $res1 && $res2; + break; + case Zend_Cache::CLEANING_MODE_MATCHING_TAG: + $ids = $this->getIdsMatchingTags($tags); + $result = true; + foreach ($ids as $id) { + $result = $this->remove($id) && $result; + } + return $result; + break; + case Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG: + $ids = $this->getIdsNotMatchingTags($tags); + $result = true; + foreach ($ids as $id) { + $result = $this->remove($id) && $result; + } + return $result; + break; + case Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG: + $ids = $this->getIdsMatchingAnyTags($tags); + $result = true; + foreach ($ids as $id) { + $result = $this->remove($id) && $result; + } + return $result; + break; + default: + break; + } + return false; + } + + /** + * Check if the database structure is ok (with the good version), if no : build it + * + * @throws Zend_Cache_Exception + * @return boolean True if ok + */ + private function _checkAndBuildStructure() + { + if (!($this->_structureChecked)) { + if (!$this->_checkStructureVersion()) { + $this->_buildStructure(); + if (!$this->_checkStructureVersion()) { + Zend_Cache::throwException("Impossible to build cache structure in " . $this->_options['cache_db_complete_path']); + } + } + $this->_structureChecked = true; + } + return true; + } + +} diff --git a/library/Zend/Cache/Backend/Static.php b/library/Zend/Cache/Backend/Static.php new file mode 100644 index 000000000..224e9d4e7 --- /dev/null +++ b/library/Zend/Cache/Backend/Static.php @@ -0,0 +1,558 @@ + null, + 'sub_dir' => 'html', + 'file_extension' => '.html', + 'index_filename' => 'index', + 'file_locking' => true, + 'cache_file_umask' => 0600, + 'cache_directory_umask' => 0700, + 'debug_header' => false, + 'tag_cache' => null, + 'disable_caching' => false + ); + + /** + * Cache for handling tags + * @var Zend_Cache_Core + */ + protected $_tagCache = null; + + /** + * Tagged items + * @var array + */ + protected $_tagged = null; + + /** + * Interceptor child method to handle the case where an Inner + * Cache object is being set since it's not supported by the + * standard backend interface + * + * @param string $name + * @param mixed $value + * @return Zend_Cache_Backend_Static + */ + public function setOption($name, $value) + { + if ($name == 'tag_cache') { + $this->setInnerCache($value); + } else { + parent::setOption($name, $value); + } + return $this; + } + + /** + * Retrieve any option via interception of the parent's statically held + * options including the local option for a tag cache. + * + * @param string $name + * @return mixed + */ + public function getOption($name) + { + if ($name == 'tag_cache') { + return $this->getInnerCache(); + } else { + if (in_array($name, $this->_options)) { + return $this->_options[$name]; + } + if ($name == 'lifetime') { + return parent::getLifetime(); + } + return null; + } + } + + /** + * Test if a cache is available for the given id and (if yes) return it (false else) + * + * Note : return value is always "string" (unserialization is done by the core not by the backend) + * + * @param string $id Cache id + * @param boolean $doNotTestCacheValidity If set to true, the cache validity won't be tested + * @return string|false cached datas + */ + public function load($id, $doNotTestCacheValidity = false) + { + if (empty($id)) { + $id = $this->_detectId(); + } else { + $id = $this->_decodeId($id); + } + if (!$this->_verifyPath($id)) { + Zend_Cache::throwException('Invalid cache id: does not match expected public_dir path'); + } + if ($doNotTestCacheValidity) { + $this->_log("Zend_Cache_Backend_Static::load() : \$doNotTestCacheValidity=true is unsupported by the Static backend"); + } + + $fileName = basename($id); + if (empty($fileName)) { + $fileName = $this->_options['index_filename']; + } + $pathName = $this->_options['public_dir'] . dirname($id); + $file = rtrim($pathName, '/') . '/' . $fileName . $this->_options['file_extension']; + if (file_exists($file)) { + $content = file_get_contents($file); + return $content; + } + + return false; + } + + /** + * Test if a cache is available or not (for the given id) + * + * @param string $id cache id + * @return bool + */ + public function test($id) + { + $id = $this->_decodeId($id); + if (!$this->_verifyPath($id)) { + Zend_Cache::throwException('Invalid cache id: does not match expected public_dir path'); + } + + $fileName = basename($id); + if (empty($fileName)) { + $fileName = $this->_options['index_filename']; + } + if (is_null($this->_tagged) && $tagged = $this->getInnerCache()->load(self::INNER_CACHE_NAME)) { + $this->_tagged = $tagged; + } elseif (!$this->_tagged) { + return false; + } + $pathName = $this->_options['public_dir'] . dirname($id); + + // Switch extension if needed + if (isset($this->_tagged[$id])) { + $extension = $this->_tagged[$id]['extension']; + } else { + $extension = $this->_options['file_extension']; + } + $file = $pathName . '/' . $fileName . $extension; + if (file_exists($file)) { + return true; + } + return false; + } + + /** + * Save some string datas into a cache record + * + * Note : $data is always "string" (serialization is done by the + * core not by the backend) + * + * @param string $data Datas to cache + * @param string $id Cache id + * @param array $tags Array of strings, the cache record will be tagged by each string entry + * @param int $specificLifetime If != false, set a specific lifetime for this cache record (null => infinite lifetime) + * @return boolean true if no problem + */ + public function save($data, $id, $tags = array(), $specificLifetime = false) + { + if ($this->_options['disable_caching']) { + return true; + } + $extension = null; + if ($this->_isSerialized($data)) { + $data = unserialize($data); + $extension = '.' . ltrim($data[1], '.'); + $data = $data[0]; + } + + clearstatcache(); + if (is_null($id) || strlen($id) == 0) { + $id = $this->_detectId(); + } else { + $id = $this->_decodeId($id); + } + + $fileName = basename($id); + if (empty($fileName)) { + $fileName = $this->_options['index_filename']; + } + + $pathName = realpath($this->_options['public_dir']) . dirname($id); + $this->_createDirectoriesFor($pathName); + + if (is_null($id) || strlen($id) == 0) { + $dataUnserialized = unserialize($data); + $data = $dataUnserialized['data']; + } + $ext = $this->_options['file_extension']; + if ($extension) $ext = $extension; + $file = rtrim($pathName, '/') . '/' . $fileName . $ext; + if ($this->_options['file_locking']) { + $result = file_put_contents($file, $data, LOCK_EX); + } else { + $result = file_put_contents($file, $data); + } + @chmod($file, $this->_octdec($this->_options['cache_file_umask'])); + + if (is_null($this->_tagged) && $tagged = $this->getInnerCache()->load(self::INNER_CACHE_NAME)) { + $this->_tagged = $tagged; + } elseif (is_null($this->_tagged)) { + $this->_tagged = array(); + } + if (!isset($this->_tagged[$id])) { + $this->_tagged[$id] = array(); + } + if (!isset($this->_tagged[$id]['tags'])) { + $this->_tagged[$id]['tags'] = array(); + } + $this->_tagged[$id]['tags'] = array_unique(array_merge($this->_tagged[$id]['tags'], $tags)); + $this->_tagged[$id]['extension'] = $ext; + $this->getInnerCache()->save($this->_tagged, self::INNER_CACHE_NAME); + return (bool) $result; + } + + /** + * Recursively create the directories needed to write the static file + */ + protected function _createDirectoriesFor($path) + { + $parts = explode('/', $path); + $directory = ''; + foreach ($parts as $part) { + $directory = rtrim($directory, '/') . '/' . $part; + if (!is_dir($directory)) { + mkdir($directory, $this->_octdec($this->_options['cache_directory_umask'])); + } + } + } + + /** + * Detect serialization of data (cannot predict since this is the only way + * to obey the interface yet pass in another parameter). + * + * In future, ZF 2.0, check if we can just avoid the interface restraints. + * + * This format is the only valid one possible for the class, so it's simple + * to just run a regular expression for the starting serialized format. + */ + protected function _isSerialized($data) + { + return preg_match("/a:2:\{i:0;s:\d+:\"/", $data); + } + + /** + * Remove a cache record + * + * @param string $id Cache id + * @return boolean True if no problem + */ + public function remove($id) + { + if (!$this->_verifyPath($id)) { + Zend_Cache::throwException('Invalid cache id: does not match expected public_dir path'); + } + $fileName = basename($id); + if (is_null($this->_tagged) && $tagged = $this->getInnerCache()->load(self::INNER_CACHE_NAME)) { + $this->_tagged = $tagged; + } elseif (!$this->_tagged) { + return false; + } + if (isset($this->_tagged[$id])) { + $extension = $this->_tagged[$id]['extension']; + } else { + $extension = $this->_options['file_extension']; + } + if (empty($fileName)) { + $fileName = $this->_options['index_filename']; + } + $pathName = $this->_options['public_dir'] . dirname($id); + $file = realpath($pathName) . '/' . $fileName . $extension; + if (!file_exists($file)) { + return false; + } + return unlink($file); + } + + /** + * Remove a cache record recursively for the given directory matching a + * REQUEST_URI based relative path (deletes the actual file matching this + * in addition to the matching directory) + * + * @param string $id Cache id + * @return boolean True if no problem + */ + public function removeRecursively($id) + { + if (!$this->_verifyPath($id)) { + Zend_Cache::throwException('Invalid cache id: does not match expected public_dir path'); + } + $fileName = basename($id); + if (empty($fileName)) { + $fileName = $this->_options['index_filename']; + } + $pathName = $this->_options['public_dir'] . dirname($id); + $file = $pathName . '/' . $fileName . $this->_options['file_extension']; + $directory = $pathName . '/' . $fileName; + if (file_exists($directory)) { + if (!is_writable($directory)) { + return false; + } + foreach (new DirectoryIterator($directory) as $file) { + if (true === $file->isFile()) { + if (false === unlink($file->getPathName())) { + return false; + } + } + } + rmdir(dirname($path)); + } + if (file_exists($file)) { + if (!is_writable($file)) { + return false; + } + return unlink($file); + } + return true; + } + + /** + * Clean some cache records + * + * Available modes are : + * Zend_Cache::CLEANING_MODE_ALL (default) => remove all cache entries ($tags is not used) + * Zend_Cache::CLEANING_MODE_OLD => remove too old cache entries ($tags is not used) + * Zend_Cache::CLEANING_MODE_MATCHING_TAG => remove cache entries matching all given tags + * ($tags can be an array of strings or a single string) + * Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG => remove cache entries not {matching one of the given tags} + * ($tags can be an array of strings or a single string) + * Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG => remove cache entries matching any given tags + * ($tags can be an array of strings or a single string) + * + * @param string $mode Clean mode + * @param array $tags Array of tags + * @return boolean true if no problem + */ + public function clean($mode = Zend_Cache::CLEANING_MODE_ALL, $tags = array()) + { + $result = false; + switch ($mode) { + case Zend_Cache::CLEANING_MODE_MATCHING_TAG: + case Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG: + if (empty($tags)) { + throw new Zend_Exception('Cannot use tag matching modes as no tags were defined'); + } + if (is_null($this->_tagged) && $tagged = $this->getInnerCache()->load(self::INNER_CACHE_NAME)) { + $this->_tagged = $tagged; + } elseif (!$this->_tagged) { + return true; + } + foreach ($tags as $tag) { + $urls = array_keys($this->_tagged); + foreach ($urls as $url) { + if (isset($this->_tagged[$url]['tags']) && in_array($tag, $this->_tagged[$url]['tags'])) { + $this->remove($url); + unset($this->_tagged[$url]); + } + } + } + $this->getInnerCache()->save($this->_tagged, self::INNER_CACHE_NAME); + $result = true; + break; + case Zend_Cache::CLEANING_MODE_ALL: + if (is_null($this->_tagged)) { + $tagged = $this->getInnerCache()->load(self::INNER_CACHE_NAME); + $this->_tagged = $tagged; + } + if (is_null($this->_tagged) || empty($this->_tagged)) { + return true; + } + $urls = array_keys($this->_tagged); + foreach ($urls as $url) { + $this->remove($url); + unset($this->_tagged[$url]); + } + $this->getInnerCache()->save($this->_tagged, self::INNER_CACHE_NAME); + $result = true; + break; + case Zend_Cache::CLEANING_MODE_OLD: + $this->_log("Zend_Cache_Backend_Static : Selected Cleaning Mode Currently Unsupported By This Backend"); + break; + case Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG: + if (empty($tags)) { + throw new Zend_Exception('Cannot use tag matching modes as no tags were defined'); + } + if (is_null($this->_tagged)) { + $tagged = $this->getInnerCache()->load(self::INNER_CACHE_NAME); + $this->_tagged = $tagged; + } + if (is_null($this->_tagged) || empty($this->_tagged)) { + return true; + } + $urls = array_keys($this->_tagged); + foreach ($urls as $url) { + $difference = array_diff($tags, $this->_tagged[$url]['tags']); + if (count($tags) == count($difference)) { + $this->remove($url); + unset($this->_tagged[$url]); + } + } + $this->getInnerCache()->save($this->_tagged, self::INNER_CACHE_NAME); + $result = true; + break; + default: + Zend_Cache::throwException('Invalid mode for clean() method'); + break; + } + return $result; + } + + /** + * Set an Inner Cache, used here primarily to store Tags associated + * with caches created by this backend. Note: If Tags are lost, the cache + * should be completely cleaned as the mapping of tags to caches will + * have been irrevocably lost. + * + * @param Zend_Cache_Core + * @return void + */ + public function setInnerCache(Zend_Cache_Core $cache) + { + $this->_tagCache = $cache; + $this->_options['tag_cache'] = $cache; + } + + /** + * Get the Inner Cache if set + * + * @return Zend_Cache_Core + */ + public function getInnerCache() + { + if (is_null($this->_tagCache)) { + Zend_Cache::throwException('An Inner Cache has not been set; use setInnerCache()'); + } + return $this->_tagCache; + } + + /** + * Verify path exists and is non-empty + * + * @param string $path + * @return bool + */ + protected function _verifyPath($path) + { + $path = realpath($path); + $base = realpath($this->_options['public_dir']); + return strncmp($path, $base, strlen($base)) !== 0; + } + + /** + * Determine the page to save from the request + * + * @return string + */ + protected function _detectId() + { + return $_SERVER['REQUEST_URI']; + } + + /** + * Validate a cache id or a tag (security, reliable filenames, reserved prefixes...) + * + * Throw an exception if a problem is found + * + * @param string $string Cache id or tag + * @throws Zend_Cache_Exception + * @return void + * @deprecated Not usable until perhaps ZF 2.0 + */ + protected static function _validateIdOrTag($string) + { + if (!is_string($string)) { + Zend_Cache::throwException('Invalid id or tag : must be a string'); + } + + // Internal only checked in Frontend - not here! + if (substr($string, 0, 9) == 'internal-') { + return; + } + + // Validation assumes no query string, fragments or scheme included - only the path + if (!preg_match( + '/^(?:\/(?:(?:%[[:xdigit:]]{2}|[A-Za-z0-9-_.!~*\'()\[\]:@&=+$,;])*)?)+$/', + $string + ) + ) { + Zend_Cache::throwException("Invalid id or tag '$string' : must be a valid URL path"); + } + } + + /** + * Detect an octal string and return its octal value for file permission ops + * otherwise return the non-string (assumed octal or decimal int already) + * + * @param $val The potential octal in need of conversion + * @return int + */ + protected function _octdec($val) + { + if (decoct(octdec($val)) == $val && is_string($val)) { + return octdec($val); + } + return $val; + } + + /** + * Decode a request URI from the provided ID + */ + protected function _decodeId($id) + { + return pack('H*', $id);; + } +} diff --git a/library/Zend/Cache/Backend/Test.php b/library/Zend/Cache/Backend/Test.php new file mode 100644 index 000000000..eaeec3b4b --- /dev/null +++ b/library/Zend/Cache/Backend/Test.php @@ -0,0 +1,410 @@ +_addLog('construct', array($options)); + } + + /** + * Set the frontend directives + * + * @param array $directives assoc of directives + * @return void + */ + public function setDirectives($directives) + { + $this->_addLog('setDirectives', array($directives)); + } + + /** + * Test if a cache is available for the given id and (if yes) return it (false else) + * + * For this test backend only, if $id == 'false', then the method will return false + * if $id == 'serialized', the method will return a serialized array + * ('foo' else) + * + * @param string $id Cache id + * @param boolean $doNotTestCacheValidity If set to true, the cache validity won't be tested + * @return string Cached datas (or false) + */ + public function load($id, $doNotTestCacheValidity = false) + { + $this->_addLog('get', array($id, $doNotTestCacheValidity)); + if ( $id == 'false' + || $id == 'd8523b3ee441006261eeffa5c3d3a0a7' + || $id == 'e83249ea22178277d5befc2c5e2e9ace' + || $id == '40f649b94977c0a6e76902e2a0b43587') + { + return false; + } + if ($id=='serialized') { + return serialize(array('foo')); + } + if ($id=='serialized2') { + return serialize(array('headers' => array(), 'data' => 'foo')); + } + if (($id=='71769f39054f75894288e397df04e445') or ($id=='615d222619fb20b527168340cebd0578')) { + return serialize(array('foo', 'bar')); + } + if (($id=='8a02d218a5165c467e7a5747cc6bd4b6') or ($id=='648aca1366211d17cbf48e65dc570bee')) { + return serialize(array('foo', 'bar')); + } + return 'foo'; + } + + /** + * Test if a cache is available or not (for the given id) + * + * For this test backend only, if $id == 'false', then the method will return false + * (123456 else) + * + * @param string $id Cache id + * @return mixed|false false (a cache is not available) or "last modified" timestamp (int) of the available cache record + */ + public function test($id) + { + $this->_addLog('test', array($id)); + if ($id=='false') { + return false; + } + if (($id=='3c439c922209e2cb0b54d6deffccd75a')) { + return false; + } + return 123456; + } + + /** + * Save some string datas into a cache record + * + * For this test backend only, if $id == 'false', then the method will return false + * (true else) + * + * @param string $data Datas to cache + * @param string $id Cache id + * @param array $tags Array of strings, the cache record will be tagged by each string entry + * @param int $specificLifetime If != false, set a specific lifetime for this cache record (null => infinite lifetime) + * @return boolean True if no problem + */ + public function save($data, $id, $tags = array(), $specificLifetime = false) + { + $this->_addLog('save', array($data, $id, $tags)); + if ($id=='false') { + return false; + } + return true; + } + + /** + * Remove a cache record + * + * For this test backend only, if $id == 'false', then the method will return false + * (true else) + * + * @param string $id Cache id + * @return boolean True if no problem + */ + public function remove($id) + { + $this->_addLog('remove', array($id)); + if ($id=='false') { + return false; + } + return true; + } + + /** + * Clean some cache records + * + * For this test backend only, if $mode == 'false', then the method will return false + * (true else) + * + * Available modes are : + * Zend_Cache::CLEANING_MODE_ALL (default) => remove all cache entries ($tags is not used) + * Zend_Cache::CLEANING_MODE_OLD => remove too old cache entries ($tags is not used) + * Zend_Cache::CLEANING_MODE_MATCHING_TAG => remove cache entries matching all given tags + * ($tags can be an array of strings or a single string) + * Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG => remove cache entries not {matching one of the given tags} + * ($tags can be an array of strings or a single string) + * + * @param string $mode Clean mode + * @param array $tags Array of tags + * @return boolean True if no problem + */ + public function clean($mode = Zend_Cache::CLEANING_MODE_ALL, $tags = array()) + { + $this->_addLog('clean', array($mode, $tags)); + if ($mode=='false') { + return false; + } + return true; + } + + /** + * Get the last log + * + * @return string The last log + */ + public function getLastLog() + { + return $this->_log[$this->_index - 1]; + } + + /** + * Get the log index + * + * @return int Log index + */ + public function getLogIndex() + { + return $this->_index; + } + + /** + * Get the complete log array + * + * @return array Complete log array + */ + public function getAllLogs() + { + return $this->_log; + } + + /** + * Return true if the automatic cleaning is available for the backend + * + * @return boolean + */ + public function isAutomaticCleaningAvailable() + { + return true; + } + + /** + * Return an array of stored cache ids + * + * @return array array of stored cache ids (string) + */ + public function getIds() + { + return array( + 'prefix_id1', 'prefix_id2' + ); + } + + /** + * Return an array of stored tags + * + * @return array array of stored tags (string) + */ + public function getTags() + { + return array( + 'tag1', 'tag2' + ); + } + + /** + * Return an array of stored cache ids which match given tags + * + * In case of multiple tags, a logical AND is made between tags + * + * @param array $tags array of tags + * @return array array of matching cache ids (string) + */ + public function getIdsMatchingTags($tags = array()) + { + if ($tags == array('tag1', 'tag2')) { + return array('prefix_id1', 'prefix_id2'); + } + + return array(); + } + + /** + * Return an array of stored cache ids which don't match given tags + * + * In case of multiple tags, a logical OR is made between tags + * + * @param array $tags array of tags + * @return array array of not matching cache ids (string) + */ + public function getIdsNotMatchingTags($tags = array()) + { + if ($tags == array('tag3', 'tag4')) { + return array('prefix_id3', 'prefix_id4'); + } + + return array(); + } + + /** + * Return an array of stored cache ids which match any given tags + * + * In case of multiple tags, a logical AND is made between tags + * + * @param array $tags array of tags + * @return array array of any matching cache ids (string) + */ + public function getIdsMatchingAnyTags($tags = array()) + { + if ($tags == array('tag5', 'tag6')) { + return array('prefix_id5', 'prefix_id6'); + } + + return array(); + } + + /** + * Return the filling percentage of the backend storage + * + * @return int integer between 0 and 100 + */ + public function getFillingPercentage() + { + return 50; + } + + /** + * Return an array of metadatas for the given cache id + * + * The array must include these keys : + * - expire : the expire timestamp + * - tags : a string array of tags + * - mtime : timestamp of last modification time + * + * @param string $id cache id + * @return array array of metadatas (false if the cache id is not found) + */ + public function getMetadatas($id) + { + return false; + } + + /** + * Give (if possible) an extra lifetime to the given cache id + * + * @param string $id cache id + * @param int $extraLifetime + * @return boolean true if ok + */ + public function touch($id, $extraLifetime) + { + return true; + } + + /** + * Return an associative array of capabilities (booleans) of the backend + * + * The array must include these keys : + * - automatic_cleaning (is automating cleaning necessary) + * - tags (are tags supported) + * - expired_read (is it possible to read expired cache records + * (for doNotTestCacheValidity option for example)) + * - priority does the backend deal with priority when saving + * - infinite_lifetime (is infinite lifetime can work with this backend) + * - get_list (is it possible to get the list of cache ids and the complete list of tags) + * + * @return array associative of with capabilities + */ + public function getCapabilities() + { + return array( + 'automatic_cleaning' => true, + 'tags' => true, + 'expired_read' => false, + 'priority' => true, + 'infinite_lifetime' => true, + 'get_list' => true + ); + } + + /** + * Add an event to the log array + * + * @param string $methodName MethodName + * @param array $args Arguments + * @return void + */ + private function _addLog($methodName, $args) + { + $this->_log[$this->_index] = array( + 'methodName' => $methodName, + 'args' => $args + ); + $this->_index = $this->_index + 1; + } + +} diff --git a/library/Zend/Cache/Backend/TwoLevels.php b/library/Zend/Cache/Backend/TwoLevels.php new file mode 100644 index 000000000..c02784494 --- /dev/null +++ b/library/Zend/Cache/Backend/TwoLevels.php @@ -0,0 +1,506 @@ + (string) slow_backend : + * - Slow backend name + * - Must implement the Zend_Cache_Backend_ExtendedInterface + * - Should provide a big storage + * + * =====> (string) fast_backend : + * - Flow backend name + * - Must implement the Zend_Cache_Backend_ExtendedInterface + * - Must be much faster than slow_backend + * + * =====> (array) slow_backend_options : + * - Slow backend options (see corresponding backend) + * + * =====> (array) fast_backend_options : + * - Fast backend options (see corresponding backend) + * + * =====> (int) stats_update_factor : + * - Disable / Tune the computation of the fast backend filling percentage + * - When saving a record into cache : + * 1 => systematic computation of the fast backend filling percentage + * x (integer) > 1 => computation of the fast backend filling percentage randomly 1 times on x cache write + * + * =====> (boolean) slow_backend_custom_naming : + * =====> (boolean) fast_backend_custom_naming : + * =====> (boolean) slow_backend_autoload : + * =====> (boolean) fast_backend_autoload : + * - See Zend_Cache::factory() method + * + * =====> (boolean) auto_refresh_fast_cache + * - If true, auto refresh the fast cache when a cache record is hit + * + * @var array available options + */ + protected $_options = array( + 'slow_backend' => 'File', + 'fast_backend' => 'Apc', + 'slow_backend_options' => array(), + 'fast_backend_options' => array(), + 'stats_update_factor' => 10, + 'slow_backend_custom_naming' => false, + 'fast_backend_custom_naming' => false, + 'slow_backend_autoload' => false, + 'fast_backend_autoload' => false, + 'auto_refresh_fast_cache' => true + ); + + /** + * Slow Backend + * + * @var Zend_Cache_Backend + */ + protected $_slowBackend; + + /** + * Fast Backend + * + * @var Zend_Cache_Backend + */ + protected $_fastBackend; + + /** + * Cache for the fast backend filling percentage + * + * @var int + */ + protected $_fastBackendFillingPercentage = null; + + /** + * Constructor + * + * @param array $options Associative array of options + * @throws Zend_Cache_Exception + * @return void + */ + public function __construct(array $options = array()) + { + parent::__construct($options); + if ($this->_options['slow_backend'] === null) { + Zend_Cache::throwException('slow_backend option has to set'); + } + if ($this->_options['fast_backend'] === null) { + Zend_Cache::throwException('fast_backend option has to set'); + } + $this->_slowBackend = Zend_Cache::_makeBackend($this->_options['slow_backend'], $this->_options['slow_backend_options'], $this->_options['slow_backend_custom_naming'], $this->_options['slow_backend_autoload']); + $this->_fastBackend = Zend_Cache::_makeBackend($this->_options['fast_backend'], $this->_options['fast_backend_options'], $this->_options['fast_backend_custom_naming'], $this->_options['fast_backend_autoload']); + if (!in_array('Zend_Cache_Backend_ExtendedInterface', class_implements($this->_slowBackend))) { + Zend_Cache::throwException('slow_backend must implement the Zend_Cache_Backend_ExtendedInterface interface'); + } + if (!in_array('Zend_Cache_Backend_ExtendedInterface', class_implements($this->_fastBackend))) { + Zend_Cache::throwException('fast_backend must implement the Zend_Cache_Backend_ExtendedInterface interface'); + } + $this->_slowBackend->setDirectives($this->_directives); + $this->_fastBackend->setDirectives($this->_directives); + } + + /** + * Test if a cache is available or not (for the given id) + * + * @param string $id cache id + * @return mixed|false (a cache is not available) or "last modified" timestamp (int) of the available cache record + */ + public function test($id) + { + $fastTest = $this->_fastBackend->test($id); + if ($fastTest) { + return $fastTest; + } else { + return $this->_slowBackend->test($id); + } + } + + /** + * Save some string datas into a cache record + * + * Note : $data is always "string" (serialization is done by the + * core not by the backend) + * + * @param string $data Datas to cache + * @param string $id Cache id + * @param array $tags Array of strings, the cache record will be tagged by each string entry + * @param int $specificLifetime If != false, set a specific lifetime for this cache record (null => infinite lifetime) + * @param int $priority integer between 0 (very low priority) and 10 (maximum priority) used by some particular backends + * @return boolean true if no problem + */ + public function save($data, $id, $tags = array(), $specificLifetime = false, $priority = 8) + { + $usage = $this->_getFastFillingPercentage('saving'); + $boolFast = true; + $lifetime = $this->getLifetime($specificLifetime); + $preparedData = $this->_prepareData($data, $lifetime, $priority); + if (($priority > 0) && (10 * $priority >= $usage)) { + $fastLifetime = $this->_getFastLifetime($lifetime, $priority); + $boolFast = $this->_fastBackend->save($preparedData, $id, array(), $fastLifetime); + } + $boolSlow = $this->_slowBackend->save($preparedData, $id, $tags, $lifetime); + return ($boolFast && $boolSlow); + } + + /** + * Test if a cache is available for the given id and (if yes) return it (false else) + * + * Note : return value is always "string" (unserialization is done by the core not by the backend) + * + * @param string $id Cache id + * @param boolean $doNotTestCacheValidity If set to true, the cache validity won't be tested + * @return string|false cached datas + */ + public function load($id, $doNotTestCacheValidity = false) + { + $res = $this->_fastBackend->load($id, $doNotTestCacheValidity); + if ($res === false) { + $res = $this->_slowBackend->load($id, $doNotTestCacheValidity); + if ($res === false) { + // there is no cache at all for this id + return false; + } + } + $array = unserialize($res); + // maybe, we have to refresh the fast cache ? + if ($this->_options['auto_refresh_fast_cache']) { + if ($array['priority'] == 10) { + // no need to refresh the fast cache with priority = 10 + return $array['data']; + } + $newFastLifetime = $this->_getFastLifetime($array['lifetime'], $array['priority'], time() - $array['expire']); + // we have the time to refresh the fast cache + $usage = $this->_getFastFillingPercentage('loading'); + if (($array['priority'] > 0) && (10 * $array['priority'] >= $usage)) { + // we can refresh the fast cache + $preparedData = $this->_prepareData($array['data'], $array['lifetime'], $array['priority']); + $this->_fastBackend->save($preparedData, $id, array(), $newFastLifetime); + } + } + return $array['data']; + } + + /** + * Remove a cache record + * + * @param string $id Cache id + * @return boolean True if no problem + */ + public function remove($id) + { + $boolFast = $this->_fastBackend->remove($id); + $boolSlow = $this->_slowBackend->remove($id); + return $boolFast && $boolSlow; + } + + /** + * Clean some cache records + * + * Available modes are : + * Zend_Cache::CLEANING_MODE_ALL (default) => remove all cache entries ($tags is not used) + * Zend_Cache::CLEANING_MODE_OLD => remove too old cache entries ($tags is not used) + * Zend_Cache::CLEANING_MODE_MATCHING_TAG => remove cache entries matching all given tags + * ($tags can be an array of strings or a single string) + * Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG => remove cache entries not {matching one of the given tags} + * ($tags can be an array of strings or a single string) + * Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG => remove cache entries matching any given tags + * ($tags can be an array of strings or a single string) + * + * @param string $mode Clean mode + * @param array $tags Array of tags + * @throws Zend_Cache_Exception + * @return boolean true if no problem + */ + public function clean($mode = Zend_Cache::CLEANING_MODE_ALL, $tags = array()) + { + switch($mode) { + case Zend_Cache::CLEANING_MODE_ALL: + $boolFast = $this->_fastBackend->clean(Zend_Cache::CLEANING_MODE_ALL); + $boolSlow = $this->_slowBackend->clean(Zend_Cache::CLEANING_MODE_ALL); + return $boolFast && $boolSlow; + break; + case Zend_Cache::CLEANING_MODE_OLD: + return $this->_slowBackend->clean(Zend_Cache::CLEANING_MODE_OLD); + case Zend_Cache::CLEANING_MODE_MATCHING_TAG: + $ids = $this->_slowBackend->getIdsMatchingTags($tags); + $res = true; + foreach ($ids as $id) { + $bool = $this->remove($id); + $res = $res && $bool; + } + return $res; + break; + case Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG: + $ids = $this->_slowBackend->getIdsNotMatchingTags($tags); + $res = true; + foreach ($ids as $id) { + $bool = $this->remove($id); + $res = $res && $bool; + } + return $res; + break; + case Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG: + $ids = $this->_slowBackend->getIdsMatchingAnyTags($tags); + $res = true; + foreach ($ids as $id) { + $bool = $this->remove($id); + $res = $res && $bool; + } + return $res; + break; + default: + Zend_Cache::throwException('Invalid mode for clean() method'); + break; + } + } + + /** + * Return an array of stored cache ids + * + * @return array array of stored cache ids (string) + */ + public function getIds() + { + return $this->_slowBackend->getIds(); + } + + /** + * Return an array of stored tags + * + * @return array array of stored tags (string) + */ + public function getTags() + { + return $this->_slowBackend->getTags(); + } + + /** + * Return an array of stored cache ids which match given tags + * + * In case of multiple tags, a logical AND is made between tags + * + * @param array $tags array of tags + * @return array array of matching cache ids (string) + */ + public function getIdsMatchingTags($tags = array()) + { + return $this->_slowBackend->getIdsMatchingTags($tags); + } + + /** + * Return an array of stored cache ids which don't match given tags + * + * In case of multiple tags, a logical OR is made between tags + * + * @param array $tags array of tags + * @return array array of not matching cache ids (string) + */ + public function getIdsNotMatchingTags($tags = array()) + { + return $this->_slowBackend->getIdsNotMatchingTags($tags); + } + + /** + * Return an array of stored cache ids which match any given tags + * + * In case of multiple tags, a logical AND is made between tags + * + * @param array $tags array of tags + * @return array array of any matching cache ids (string) + */ + public function getIdsMatchingAnyTags($tags = array()) + { + return $this->_slowBackend->getIdsMatchingAnyTags($tags); + } + + + /** + * Return the filling percentage of the backend storage + * + * @return int integer between 0 and 100 + */ + public function getFillingPercentage() + { + return $this->_slowBackend->getFillingPercentage(); + } + + /** + * Return an array of metadatas for the given cache id + * + * The array must include these keys : + * - expire : the expire timestamp + * - tags : a string array of tags + * - mtime : timestamp of last modification time + * + * @param string $id cache id + * @return array array of metadatas (false if the cache id is not found) + */ + public function getMetadatas($id) + { + return $this->_slowBackend->getMetadatas($id); + } + + /** + * Give (if possible) an extra lifetime to the given cache id + * + * @param string $id cache id + * @param int $extraLifetime + * @return boolean true if ok + */ + public function touch($id, $extraLifetime) + { + return $this->_slowBackend->touch($id, $extraLifetime); + } + + /** + * Return an associative array of capabilities (booleans) of the backend + * + * The array must include these keys : + * - automatic_cleaning (is automating cleaning necessary) + * - tags (are tags supported) + * - expired_read (is it possible to read expired cache records + * (for doNotTestCacheValidity option for example)) + * - priority does the backend deal with priority when saving + * - infinite_lifetime (is infinite lifetime can work with this backend) + * - get_list (is it possible to get the list of cache ids and the complete list of tags) + * + * @return array associative of with capabilities + */ + public function getCapabilities() + { + $slowBackendCapabilities = $this->_slowBackend->getCapabilities(); + return array( + 'automatic_cleaning' => $slowBackendCapabilities['automatic_cleaning'], + 'tags' => $slowBackendCapabilities['tags'], + 'expired_read' => $slowBackendCapabilities['expired_read'], + 'priority' => $slowBackendCapabilities['priority'], + 'infinite_lifetime' => $slowBackendCapabilities['infinite_lifetime'], + 'get_list' => $slowBackendCapabilities['get_list'] + ); + } + + /** + * Prepare a serialized array to store datas and metadatas informations + * + * @param string $data data to store + * @param int $lifetime original lifetime + * @param int $priority priority + * @return string serialize array to store into cache + */ + private function _prepareData($data, $lifetime, $priority) + { + $lt = $lifetime; + if ($lt === null) { + $lt = 9999999999; + } + return serialize(array( + 'data' => $data, + 'lifetime' => $lifetime, + 'expire' => time() + $lt, + 'priority' => $priority + )); + } + + /** + * Compute and return the lifetime for the fast backend + * + * @param int $lifetime original lifetime + * @param int $priority priority + * @param int $maxLifetime maximum lifetime + * @return int lifetime for the fast backend + */ + private function _getFastLifetime($lifetime, $priority, $maxLifetime = null) + { + if ($lifetime === null) { + // if lifetime is null, we have an infinite lifetime + // we need to use arbitrary lifetimes + $fastLifetime = (int) (2592000 / (11 - $priority)); + } else { + $fastLifetime = (int) ($lifetime / (11 - $priority)); + } + if (($maxLifetime !== null) && ($maxLifetime >= 0)) { + if ($fastLifetime > $maxLifetime) { + return $maxLifetime; + } + } + return $fastLifetime; + } + + /** + * PUBLIC METHOD FOR UNIT TESTING ONLY ! + * + * Force a cache record to expire + * + * @param string $id cache id + */ + public function ___expire($id) + { + $this->_fastBackend->remove($id); + $this->_slowBackend->___expire($id); + } + + private function _getFastFillingPercentage($mode) + { + + if ($mode == 'saving') { + // mode saving + if ($this->_fastBackendFillingPercentage === null) { + $this->_fastBackendFillingPercentage = $this->_fastBackend->getFillingPercentage(); + } else { + $rand = rand(1, $this->_options['stats_update_factor']); + if ($rand == 1) { + // we force a refresh + $this->_fastBackendFillingPercentage = $this->_fastBackend->getFillingPercentage(); + } + } + } else { + // mode loading + // we compute the percentage only if it's not available in cache + if ($this->_fastBackendFillingPercentage === null) { + $this->_fastBackendFillingPercentage = $this->_fastBackend->getFillingPercentage(); + } + } + return $this->_fastBackendFillingPercentage; + } + +} diff --git a/library/Zend/Cache/Backend/Xcache.php b/library/Zend/Cache/Backend/Xcache.php new file mode 100644 index 000000000..fbdf4d0f4 --- /dev/null +++ b/library/Zend/Cache/Backend/Xcache.php @@ -0,0 +1,216 @@ + (string) user : + * xcache.admin.user (necessary for the clean() method) + * + * =====> (string) password : + * xcache.admin.pass (clear, not MD5) (necessary for the clean() method) + * + * @var array available options + */ + protected $_options = array( + 'user' => null, + 'password' => null + ); + + /** + * Constructor + * + * @param array $options associative array of options + * @throws Zend_Cache_Exception + * @return void + */ + public function __construct(array $options = array()) + { + if (!extension_loaded('xcache')) { + Zend_Cache::throwException('The xcache extension must be loaded for using this backend !'); + } + parent::__construct($options); + } + + /** + * Test if a cache is available for the given id and (if yes) return it (false else) + * + * WARNING $doNotTestCacheValidity=true is unsupported by the Xcache backend + * + * @param string $id cache id + * @param boolean $doNotTestCacheValidity if set to true, the cache validity won't be tested + * @return string cached datas (or false) + */ + public function load($id, $doNotTestCacheValidity = false) + { + if ($doNotTestCacheValidity) { + $this->_log("Zend_Cache_Backend_Xcache::load() : \$doNotTestCacheValidity=true is unsupported by the Xcache backend"); + } + $tmp = xcache_get($id); + if (is_array($tmp)) { + return $tmp[0]; + } + return false; + } + + /** + * Test if a cache is available or not (for the given id) + * + * @param string $id cache id + * @return mixed false (a cache is not available) or "last modified" timestamp (int) of the available cache record + */ + public function test($id) + { + if (xcache_isset($id)) { + $tmp = xcache_get($id); + if (is_array($tmp)) { + return $tmp[1]; + } + } + return false; + } + + /** + * Save some string datas into a cache record + * + * Note : $data is always "string" (serialization is done by the + * core not by the backend) + * + * @param string $data datas to cache + * @param string $id cache id + * @param array $tags array of strings, the cache record will be tagged by each string entry + * @param int $specificLifetime if != false, set a specific lifetime for this cache record (null => infinite lifetime) + * @return boolean true if no problem + */ + public function save($data, $id, $tags = array(), $specificLifetime = false) + { + $lifetime = $this->getLifetime($specificLifetime); + $result = xcache_set($id, array($data, time()), $lifetime); + if (count($tags) > 0) { + $this->_log(self::TAGS_UNSUPPORTED_BY_SAVE_OF_XCACHE_BACKEND); + } + return $result; + } + + /** + * Remove a cache record + * + * @param string $id cache id + * @return boolean true if no problem + */ + public function remove($id) + { + return xcache_unset($id); + } + + /** + * Clean some cache records + * + * Available modes are : + * 'all' (default) => remove all cache entries ($tags is not used) + * 'old' => unsupported + * 'matchingTag' => unsupported + * 'notMatchingTag' => unsupported + * 'matchingAnyTag' => unsupported + * + * @param string $mode clean mode + * @param array $tags array of tags + * @throws Zend_Cache_Exception + * @return boolean true if no problem + */ + public function clean($mode = Zend_Cache::CLEANING_MODE_ALL, $tags = array()) + { + switch ($mode) { + case Zend_Cache::CLEANING_MODE_ALL: + // Necessary because xcache_clear_cache() need basic authentification + $backup = array(); + if (isset($_SERVER['PHP_AUTH_USER'])) { + $backup['PHP_AUTH_USER'] = $_SERVER['PHP_AUTH_USER']; + } + if (isset($_SERVER['PHP_AUTH_PW'])) { + $backup['PHP_AUTH_PW'] = $_SERVER['PHP_AUTH_PW']; + } + if ($this->_options['user']) { + $_SERVER['PHP_AUTH_USER'] = $this->_options['user']; + } + if ($this->_options['password']) { + $_SERVER['PHP_AUTH_PW'] = $this->_options['password']; + } + xcache_clear_cache(XC_TYPE_VAR, 0); + if (isset($backup['PHP_AUTH_USER'])) { + $_SERVER['PHP_AUTH_USER'] = $backup['PHP_AUTH_USER']; + $_SERVER['PHP_AUTH_PW'] = $backup['PHP_AUTH_PW']; + } + return true; + break; + case Zend_Cache::CLEANING_MODE_OLD: + $this->_log("Zend_Cache_Backend_Xcache::clean() : CLEANING_MODE_OLD is unsupported by the Xcache backend"); + break; + case Zend_Cache::CLEANING_MODE_MATCHING_TAG: + case Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG: + case Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG: + $this->_log(self::TAGS_UNSUPPORTED_BY_CLEAN_OF_XCACHE_BACKEND); + break; + default: + Zend_Cache::throwException('Invalid mode for clean() method'); + break; + } + } + + /** + * Return true if the automatic cleaning is available for the backend + * + * @return boolean + */ + public function isAutomaticCleaningAvailable() + { + return false; + } + +} diff --git a/library/Zend/Cache/Backend/ZendPlatform.php b/library/Zend/Cache/Backend/ZendPlatform.php new file mode 100644 index 000000000..011428384 --- /dev/null +++ b/library/Zend/Cache/Backend/ZendPlatform.php @@ -0,0 +1,317 @@ +_directives['lifetime']; + } + $res = output_cache_get($id, $lifetime); + if($res) { + return $res[0]; + } else { + return false; + } + } + + + /** + * Test if a cache is available or not (for the given id) + * + * @param string $id Cache id + * @return mixed|false false (a cache is not available) or "last modified" timestamp (int) of the available cache record + */ + public function test($id) + { + $result = output_cache_get($id, $this->_directives['lifetime']); + if ($result) { + return $result[1]; + } + return false; + } + + /** + * Save some string datas into a cache record + * + * Note : $data is always "string" (serialization is done by the + * core not by the backend) + * + * @param string $data Data to cache + * @param string $id Cache id + * @param array $tags Array of strings, the cache record will be tagged by each string entry + * @param int $specificLifetime If != false, set a specific lifetime for this cache record (null => infinite lifetime) + * @return boolean true if no problem + */ + public function save($data, $id, $tags = array(), $specificLifetime = false) + { + if (!($specificLifetime === false)) { + $this->_log("Zend_Cache_Backend_ZendPlatform::save() : non false specifc lifetime is unsuported for this backend"); + } + + $lifetime = $this->_directives['lifetime']; + $result1 = output_cache_put($id, array($data, time())); + $result2 = (count($tags) == 0); + + foreach ($tags as $tag) { + $tagid = self::TAGS_PREFIX.$tag; + $old_tags = output_cache_get($tagid, $lifetime); + if ($old_tags === false) { + $old_tags = array(); + } + $old_tags[$id] = $id; + output_cache_remove_key($tagid); + $result2 = output_cache_put($tagid, $old_tags); + } + + return $result1 && $result2; + } + + + /** + * Remove a cache record + * + * @param string $id Cache id + * @return boolean True if no problem + */ + public function remove($id) + { + return output_cache_remove_key($id); + } + + + /** + * Clean some cache records + * + * Available modes are : + * Zend_Cache::CLEANING_MODE_ALL (default) => remove all cache entries ($tags is not used) + * Zend_Cache::CLEANING_MODE_OLD => remove too old cache entries ($tags is not used) + * This mode is not supported in this backend + * Zend_Cache::CLEANING_MODE_MATCHING_TAG => remove cache entries matching all given tags + * ($tags can be an array of strings or a single string) + * Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG => unsupported + * Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG => remove cache entries matching any given tags + * ($tags can be an array of strings or a single string) + * + * @param string $mode Clean mode + * @param array $tags Array of tags + * @throws Zend_Cache_Exception + * @return boolean True if no problem + */ + public function clean($mode = Zend_Cache::CLEANING_MODE_ALL, $tags = array()) + { + switch ($mode) { + case Zend_Cache::CLEANING_MODE_ALL: + case Zend_Cache::CLEANING_MODE_OLD: + $cache_dir = ini_get('zend_accelerator.output_cache_dir'); + if (!$cache_dir) { + return false; + } + $cache_dir .= '/.php_cache_api/'; + return $this->_clean($cache_dir, $mode); + break; + case Zend_Cache::CLEANING_MODE_MATCHING_TAG: + $idlist = null; + foreach ($tags as $tag) { + $next_idlist = output_cache_get(self::TAGS_PREFIX.$tag, $this->_directives['lifetime']); + if ($idlist) { + $idlist = array_intersect_assoc($idlist, $next_idlist); + } else { + $idlist = $next_idlist; + } + if (count($idlist) == 0) { + // if ID list is already empty - we may skip checking other IDs + $idlist = null; + break; + } + } + if ($idlist) { + foreach ($idlist as $id) { + output_cache_remove_key($id); + } + } + return true; + break; + case Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG: + $this->_log("Zend_Cache_Backend_ZendPlatform::clean() : CLEANING_MODE_NOT_MATCHING_TAG is not supported by the Zend Platform backend"); + return false; + break; + case Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG: + $idlist = null; + foreach ($tags as $tag) { + $next_idlist = output_cache_get(self::TAGS_PREFIX.$tag, $this->_directives['lifetime']); + if ($idlist) { + $idlist = array_merge_recursive($idlist, $next_idlist); + } else { + $idlist = $next_idlist; + } + if (count($idlist) == 0) { + // if ID list is already empty - we may skip checking other IDs + $idlist = null; + break; + } + } + if ($idlist) { + foreach ($idlist as $id) { + output_cache_remove_key($id); + } + } + return true; + break; + default: + Zend_Cache::throwException('Invalid mode for clean() method'); + break; + } + } + + /** + * Clean a directory and recursivly go over it's subdirectories + * + * Remove all the cached files that need to be cleaned (according to mode and files mtime) + * + * @param string $dir Path of directory ot clean + * @param string $mode The same parameter as in Zend_Cache_Backend_ZendPlatform::clean() + * @return boolean True if ok + */ + private function _clean($dir, $mode) + { + $d = @dir($dir); + if (!$d) { + return false; + } + $result = true; + while (false !== ($file = $d->read())) { + if ($file == '.' || $file == '..') { + continue; + } + $file = $d->path . $file; + if (is_dir($file)) { + $result = ($this->_clean($file .'/', $mode)) && ($result); + } else { + if ($mode == Zend_Cache::CLEANING_MODE_ALL) { + $result = ($this->_remove($file)) && ($result); + } else if ($mode == Zend_Cache::CLEANING_MODE_OLD) { + // Files older than lifetime get deleted from cache + if ($this->_directives['lifetime'] !== null) { + if ((time() - @filemtime($file)) > $this->_directives['lifetime']) { + $result = ($this->_remove($file)) && ($result); + } + } + } + } + } + $d->close(); + return $result; + } + + /** + * Remove a file + * + * If we can't remove the file (because of locks or any problem), we will touch + * the file to invalidate it + * + * @param string $file Complete file path + * @return boolean True if ok + */ + private function _remove($file) + { + if (!@unlink($file)) { + # If we can't remove the file (because of locks or any problem), we will touch + # the file to invalidate it + $this->_log("Zend_Cache_Backend_ZendPlatform::_remove() : we can't remove $file => we are going to try to invalidate it"); + if ($this->_directives['lifetime'] === null) { + return false; + } + if (!file_exists($file)) { + return false; + } + return @touch($file, time() - 2*abs($this->_directives['lifetime'])); + } + return true; + } + +} diff --git a/library/Zend/Cache/Backend/ZendServer.php b/library/Zend/Cache/Backend/ZendServer.php new file mode 100644 index 000000000..ee9dc4c06 --- /dev/null +++ b/library/Zend/Cache/Backend/ZendServer.php @@ -0,0 +1,207 @@ + (string) namespace : + * Namespace to be used for chaching operations + * + * @var array available options + */ + protected $_options = array( + 'namespace' => 'zendframework' + ); + + /** + * Store data + * + * @param mixed $data Object to store + * @param string $id Cache id + * @param int $timeToLive Time to live in seconds + * @throws Zend_Cache_Exception + */ + abstract protected function _store($data, $id, $timeToLive); + + /** + * Fetch data + * + * @param string $id Cache id + * @throws Zend_Cache_Exception + */ + abstract protected function _fetch($id); + + /** + * Unset data + * + * @param string $id Cache id + */ + abstract protected function _unset($id); + + /** + * Clear cache + */ + abstract protected function _clear(); + + /** + * Test if a cache is available for the given id and (if yes) return it (false else) + * + * @param string $id cache id + * @param boolean $doNotTestCacheValidity if set to true, the cache validity won't be tested + * @return string cached datas (or false) + */ + public function load($id, $doNotTestCacheValidity = false) + { + $tmp = $this->_fetch($id); + if ($tmp !== null) { + return $tmp; + } + return false; + } + + /** + * Test if a cache is available or not (for the given id) + * + * @param string $id cache id + * @return mixed false (a cache is not available) or "last modified" timestamp (int) of the available cache record + * @throws Zend_Cache_Exception + */ + public function test($id) + { + $tmp = $this->_fetch('internal-metadatas---' . $id); + if ($tmp !== false) { + if (!is_array($tmp) || !isset($tmp['mtime'])) { + Zend_Cache::throwException('Cache metadata for \'' . $id . '\' id is corrupted' ); + } + return $tmp['mtime']; + } + return false; + } + + /** + * Compute & return the expire time + * + * @return int expire time (unix timestamp) + */ + private function _expireTime($lifetime) + { + if ($lifetime === null) { + return 9999999999; + } + return time() + $lifetime; + } + + /** + * Save some string datas into a cache record + * + * Note : $data is always "string" (serialization is done by the + * core not by the backend) + * + * @param string $data datas to cache + * @param string $id cache id + * @param array $tags array of strings, the cache record will be tagged by each string entry + * @param int $specificLifetime if != false, set a specific lifetime for this cache record (null => infinite lifetime) + * @return boolean true if no problem + */ + public function save($data, $id, $tags = array(), $specificLifetime = false) + { + $lifetime = $this->getLifetime($specificLifetime); + $metadatas = array( + 'mtime' => time(), + 'expire' => $this->_expireTime($lifetime), + ); + + if (count($tags) > 0) { + $this->_log('Zend_Cache_Backend_ZendServer::save() : tags are unsupported by the ZendServer backends'); + } + + return $this->_store($data, $id, $lifetime) && + $this->_store($metadatas, 'internal-metadatas---' . $id, $lifetime); + } + + /** + * Remove a cache record + * + * @param string $id cache id + * @return boolean true if no problem + */ + public function remove($id) + { + $result1 = $this->_unset($id); + $result2 = $this->_unset('internal-metadatas---' . $id); + + return $result1 && $result2; + } + + /** + * Clean some cache records + * + * Available modes are : + * 'all' (default) => remove all cache entries ($tags is not used) + * 'old' => unsupported + * 'matchingTag' => unsupported + * 'notMatchingTag' => unsupported + * 'matchingAnyTag' => unsupported + * + * @param string $mode clean mode + * @param array $tags array of tags + * @throws Zend_Cache_Exception + * @return boolean true if no problem + */ + public function clean($mode = Zend_Cache::CLEANING_MODE_ALL, $tags = array()) + { + switch ($mode) { + case Zend_Cache::CLEANING_MODE_ALL: + $this->_clear(); + return true; + break; + case Zend_Cache::CLEANING_MODE_OLD: + $this->_log("Zend_Cache_Backend_ZendServer::clean() : CLEANING_MODE_OLD is unsupported by the Zend Server backends."); + break; + case Zend_Cache::CLEANING_MODE_MATCHING_TAG: + case Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG: + case Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG: + $this->_clear(); + $this->_log('Zend_Cache_Backend_ZendServer::clean() : tags are unsupported by the Zend Server backends.'); + break; + default: + Zend_Cache::throwException('Invalid mode for clean() method'); + break; + } + } +} diff --git a/library/Zend/Cache/Backend/ZendServer/Disk.php b/library/Zend/Cache/Backend/ZendServer/Disk.php new file mode 100644 index 000000000..40ffb5158 --- /dev/null +++ b/library/Zend/Cache/Backend/ZendServer/Disk.php @@ -0,0 +1,100 @@ +_options['namespace'] . '::' . $id, + $data, + $timeToLive) === false) { + $this->_log('Store operation failed.'); + return false; + } + return true; + } + + /** + * Fetch data + * + * @param string $id Cache id + */ + protected function _fetch($id) + { + return zend_disk_cache_fetch($this->_options['namespace'] . '::' . $id); + } + + /** + * Unset data + * + * @param string $id Cache id + * @return boolean true if no problem + */ + protected function _unset($id) + { + return zend_disk_cache_delete($this->_options['namespace'] . '::' . $id); + } + + /** + * Clear cache + */ + protected function _clear() + { + zend_disk_cache_clear($this->_options['namespace']); + } +} diff --git a/library/Zend/Cache/Backend/ZendServer/ShMem.php b/library/Zend/Cache/Backend/ZendServer/ShMem.php new file mode 100644 index 000000000..46bc94622 --- /dev/null +++ b/library/Zend/Cache/Backend/ZendServer/ShMem.php @@ -0,0 +1,100 @@ +_options['namespace'] . '::' . $id, + $data, + $timeToLive) === false) { + $this->_log('Store operation failed.'); + return false; + } + return true; + } + + /** + * Fetch data + * + * @param string $id Cache id + */ + protected function _fetch($id) + { + return zend_shm_cache_fetch($this->_options['namespace'] . '::' . $id); + } + + /** + * Unset data + * + * @param string $id Cache id + * @return boolean true if no problem + */ + protected function _unset($id) + { + return zend_shm_cache_delete($this->_options['namespace'] . '::' . $id); + } + + /** + * Clear cache + */ + protected function _clear() + { + zend_shm_cache_clear($this->_options['namespace']); + } +} diff --git a/library/Zend/Cache/Core.php b/library/Zend/Cache/Core.php new file mode 100644 index 000000000..6598c109a --- /dev/null +++ b/library/Zend/Cache/Core.php @@ -0,0 +1,756 @@ + (boolean) write_control : + * - Enable / disable write control (the cache is read just after writing to detect corrupt entries) + * - Enable write control will lightly slow the cache writing but not the cache reading + * Write control can detect some corrupt cache files but maybe it's not a perfect control + * + * ====> (boolean) caching : + * - Enable / disable caching + * (can be very useful for the debug of cached scripts) + * + * =====> (string) cache_id_prefix : + * - prefix for cache ids (namespace) + * + * ====> (boolean) automatic_serialization : + * - Enable / disable automatic serialization + * - It can be used to save directly datas which aren't strings (but it's slower) + * + * ====> (int) automatic_cleaning_factor : + * - Disable / Tune the automatic cleaning process + * - The automatic cleaning process destroy too old (for the given life time) + * cache files when a new cache file is written : + * 0 => no automatic cache cleaning + * 1 => systematic cache cleaning + * x (integer) > 1 => automatic cleaning randomly 1 times on x cache write + * + * ====> (int) lifetime : + * - Cache lifetime (in seconds) + * - If null, the cache is valid forever. + * + * ====> (boolean) logging : + * - If set to true, logging is activated (but the system is slower) + * + * ====> (boolean) ignore_user_abort + * - If set to true, the core will set the ignore_user_abort PHP flag inside the + * save() method to avoid cache corruptions in some cases (default false) + * + * @var array $_options available options + */ + protected $_options = array( + 'write_control' => true, + 'caching' => true, + 'cache_id_prefix' => null, + 'automatic_serialization' => false, + 'automatic_cleaning_factor' => 10, + 'lifetime' => 3600, + 'logging' => false, + 'logger' => null, + 'ignore_user_abort' => false + ); + + /** + * Array of options which have to be transfered to backend + * + * @var array $_directivesList + */ + protected static $_directivesList = array('lifetime', 'logging', 'logger'); + + /** + * Not used for the core, just a sort a hint to get a common setOption() method (for the core and for frontends) + * + * @var array $_specificOptions + */ + protected $_specificOptions = array(); + + /** + * Last used cache id + * + * @var string $_lastId + */ + private $_lastId = null; + + /** + * True if the backend implements Zend_Cache_Backend_ExtendedInterface + * + * @var boolean $_extendedBackend + */ + protected $_extendedBackend = false; + + /** + * Array of capabilities of the backend (only if it implements Zend_Cache_Backend_ExtendedInterface) + * + * @var array + */ + protected $_backendCapabilities = array(); + + /** + * Constructor + * + * @param array|Zend_Config $options Associative array of options or Zend_Config instance + * @throws Zend_Cache_Exception + * @return void + */ + public function __construct($options = array()) + { + if ($options instanceof Zend_Config) { + $options = $options->toArray(); + } + if (!is_array($options)) { + Zend_Cache::throwException("Options passed were not an array" + . " or Zend_Config instance."); + } + while (list($name, $value) = each($options)) { + $this->setOption($name, $value); + } + $this->_loggerSanity(); + } + + /** + * Set options using an instance of type Zend_Config + * + * @param Zend_Config $config + * @return Zend_Cache_Core + */ + public function setConfig(Zend_Config $config) + { + $options = $config->toArray(); + while (list($name, $value) = each($options)) { + $this->setOption($name, $value); + } + return $this; + } + + /** + * Set the backend + * + * @param object $backendObject + * @throws Zend_Cache_Exception + * @return void + */ + public function setBackend(Zend_Cache_Backend $backendObject) + { + $this->_backend= $backendObject; + // some options (listed in $_directivesList) have to be given + // to the backend too (even if they are not "backend specific") + $directives = array(); + foreach (Zend_Cache_Core::$_directivesList as $directive) { + $directives[$directive] = $this->_options[$directive]; + } + $this->_backend->setDirectives($directives); + if (in_array('Zend_Cache_Backend_ExtendedInterface', class_implements($this->_backend))) { + $this->_extendedBackend = true; + $this->_backendCapabilities = $this->_backend->getCapabilities(); + } + + } + + /** + * Returns the backend + * + * @return object backend object + */ + public function getBackend() + { + return $this->_backend; + } + + /** + * Public frontend to set an option + * + * There is an additional validation (relatively to the protected _setOption method) + * + * @param string $name Name of the option + * @param mixed $value Value of the option + * @throws Zend_Cache_Exception + * @return void + */ + public function setOption($name, $value) + { + if (!is_string($name)) { + Zend_Cache::throwException("Incorrect option name : $name"); + } + $name = strtolower($name); + if (array_key_exists($name, $this->_options)) { + // This is a Core option + $this->_setOption($name, $value); + return; + } + if (array_key_exists($name, $this->_specificOptions)) { + // This a specic option of this frontend + $this->_specificOptions[$name] = $value; + return; + } + } + + /** + * Public frontend to get an option value + * + * @param string $name Name of the option + * @throws Zend_Cache_Exception + * @return mixed option value + */ + public function getOption($name) + { + if (is_string($name)) { + $name = strtolower($name); + if (array_key_exists($name, $this->_options)) { + // This is a Core option + return $this->_options[$name]; + } + if (array_key_exists($name, $this->_specificOptions)) { + // This a specic option of this frontend + return $this->_specificOptions[$name]; + } + } + Zend_Cache::throwException("Incorrect option name : $name"); + } + + /** + * Set an option + * + * @param string $name Name of the option + * @param mixed $value Value of the option + * @throws Zend_Cache_Exception + * @return void + */ + private function _setOption($name, $value) + { + if (!is_string($name) || !array_key_exists($name, $this->_options)) { + Zend_Cache::throwException("Incorrect option name : $name"); + } + if ($name == 'lifetime' && empty($value)) { + $value = null; + } + $this->_options[$name] = $value; + } + + /** + * Force a new lifetime + * + * The new value is set for the core/frontend but for the backend too (directive) + * + * @param int $newLifetime New lifetime (in seconds) + * @return void + */ + public function setLifetime($newLifetime) + { + $this->_options['lifetime'] = $newLifetime; + $this->_backend->setDirectives(array( + 'lifetime' => $newLifetime + )); + } + + /** + * Test if a cache is available for the given id and (if yes) return it (false else) + * + * @param string $id Cache id + * @param boolean $doNotTestCacheValidity If set to true, the cache validity won't be tested + * @param boolean $doNotUnserialize Do not serialize (even if automatic_serialization is true) => for internal use + * @return mixed|false Cached datas + */ + public function load($id, $doNotTestCacheValidity = false, $doNotUnserialize = false) + { + if (!$this->_options['caching']) { + return false; + } + $id = $this->_id($id); // cache id may need prefix + $this->_lastId = $id; + self::_validateIdOrTag($id); + $data = $this->_backend->load($id, $doNotTestCacheValidity); + if ($data===false) { + // no cache available + return false; + } + if ((!$doNotUnserialize) && $this->_options['automatic_serialization']) { + // we need to unserialize before sending the result + return unserialize($data); + } + return $data; + } + + /** + * Test if a cache is available for the given id + * + * @param string $id Cache id + * @return int|false Last modified time of cache entry if it is available, false otherwise + */ + public function test($id) + { + if (!$this->_options['caching']) { + return false; + } + $id = $this->_id($id); // cache id may need prefix + self::_validateIdOrTag($id); + $this->_lastId = $id; + return $this->_backend->test($id); + } + + /** + * Save some data in a cache + * + * @param mixed $data Data to put in cache (can be another type than string if automatic_serialization is on) + * @param string $id Cache id (if not set, the last cache id will be used) + * @param array $tags Cache tags + * @param int $specificLifetime If != false, set a specific lifetime for this cache record (null => infinite lifetime) + * @param int $priority integer between 0 (very low priority) and 10 (maximum priority) used by some particular backends + * @throws Zend_Cache_Exception + * @return boolean True if no problem + */ + public function save($data, $id = null, $tags = array(), $specificLifetime = false, $priority = 8) + { + if (!$this->_options['caching']) { + return true; + } + if ($id === null) { + $id = $this->_lastId; + } else { + $id = $this->_id($id); + } + self::_validateIdOrTag($id); + self::_validateTagsArray($tags); + if ($this->_options['automatic_serialization']) { + // we need to serialize datas before storing them + $data = serialize($data); + } else { + if (!is_string($data)) { + Zend_Cache::throwException("Datas must be string or set automatic_serialization = true"); + } + } + // automatic cleaning + if ($this->_options['automatic_cleaning_factor'] > 0) { + $rand = rand(1, $this->_options['automatic_cleaning_factor']); + if ($rand==1) { + if ($this->_extendedBackend) { + // New way + if ($this->_backendCapabilities['automatic_cleaning']) { + $this->clean(Zend_Cache::CLEANING_MODE_OLD); + } else { + $this->_log('Zend_Cache_Core::save() / automatic cleaning is not available/necessary with this backend'); + } + } else { + // Deprecated way (will be removed in next major version) + if (method_exists($this->_backend, 'isAutomaticCleaningAvailable') && ($this->_backend->isAutomaticCleaningAvailable())) { + $this->clean(Zend_Cache::CLEANING_MODE_OLD); + } else { + $this->_log('Zend_Cache_Core::save() / automatic cleaning is not available/necessary with this backend'); + } + } + } + } + if ($this->_options['ignore_user_abort']) { + $abort = ignore_user_abort(true); + } + if (($this->_extendedBackend) && ($this->_backendCapabilities['priority'])) { + $result = $this->_backend->save($data, $id, $tags, $specificLifetime, $priority); + } else { + $result = $this->_backend->save($data, $id, $tags, $specificLifetime); + } + if ($this->_options['ignore_user_abort']) { + ignore_user_abort($abort); + } + if (!$result) { + // maybe the cache is corrupted, so we remove it ! + if ($this->_options['logging']) { + $this->_log("Zend_Cache_Core::save() : impossible to save cache (id=$id)"); + } + $this->remove($id); + return false; + } + if ($this->_options['write_control']) { + $data2 = $this->_backend->load($id, true); + if ($data!=$data2) { + $this->_log('Zend_Cache_Core::save() / write_control : written and read data do not match'); + $this->_backend->remove($id); + return false; + } + } + return true; + } + + /** + * Remove a cache + * + * @param string $id Cache id to remove + * @return boolean True if ok + */ + public function remove($id) + { + if (!$this->_options['caching']) { + return true; + } + $id = $this->_id($id); // cache id may need prefix + self::_validateIdOrTag($id); + return $this->_backend->remove($id); + } + + /** + * Clean cache entries + * + * Available modes are : + * 'all' (default) => remove all cache entries ($tags is not used) + * 'old' => remove too old cache entries ($tags is not used) + * 'matchingTag' => remove cache entries matching all given tags + * ($tags can be an array of strings or a single string) + * 'notMatchingTag' => remove cache entries not matching one of the given tags + * ($tags can be an array of strings or a single string) + * 'matchingAnyTag' => remove cache entries matching any given tags + * ($tags can be an array of strings or a single string) + * + * @param string $mode + * @param array|string $tags + * @throws Zend_Cache_Exception + * @return boolean True if ok + */ + public function clean($mode = 'all', $tags = array()) + { + if (!$this->_options['caching']) { + return true; + } + if (!in_array($mode, array(Zend_Cache::CLEANING_MODE_ALL, + Zend_Cache::CLEANING_MODE_OLD, + Zend_Cache::CLEANING_MODE_MATCHING_TAG, + Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG, + Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG))) { + Zend_Cache::throwException('Invalid cleaning mode'); + } + self::_validateTagsArray($tags); + return $this->_backend->clean($mode, $tags); + } + + /** + * Return an array of stored cache ids which match given tags + * + * In case of multiple tags, a logical AND is made between tags + * + * @param array $tags array of tags + * @return array array of matching cache ids (string) + */ + public function getIdsMatchingTags($tags = array()) + { + if (!$this->_extendedBackend) { + Zend_Cache::throwException(self::BACKEND_NOT_IMPLEMENTS_EXTENDED_IF); + } + if (!($this->_backendCapabilities['tags'])) { + Zend_Cache::throwException(self::BACKEND_NOT_SUPPORT_TAG); + } + + $ids = $this->_backend->getIdsMatchingTags($tags); + + // we need to remove cache_id_prefix from ids (see #ZF-6178, #ZF-7600) + if (isset($this->_options['cache_id_prefix']) && $this->_options['cache_id_prefix'] !== '') { + $prefix = & $this->_options['cache_id_prefix']; + $prefixLen = strlen($prefix); + foreach ($ids as &$id) { + if (strpos($id, $prefix) === 0) { + $id = substr($id, $prefixLen); + } + } + } + + return $ids; + } + + /** + * Return an array of stored cache ids which don't match given tags + * + * In case of multiple tags, a logical OR is made between tags + * + * @param array $tags array of tags + * @return array array of not matching cache ids (string) + */ + public function getIdsNotMatchingTags($tags = array()) + { + if (!$this->_extendedBackend) { + Zend_Cache::throwException(self::BACKEND_NOT_IMPLEMENTS_EXTENDED_IF); + } + if (!($this->_backendCapabilities['tags'])) { + Zend_Cache::throwException(self::BACKEND_NOT_SUPPORT_TAG); + } + + $ids = $this->_backend->getIdsNotMatchingTags($tags); + + // we need to remove cache_id_prefix from ids (see #ZF-6178, #ZF-7600) + if (isset($this->_options['cache_id_prefix']) && $this->_options['cache_id_prefix'] !== '') { + $prefix = & $this->_options['cache_id_prefix']; + $prefixLen = strlen($prefix); + foreach ($ids as &$id) { + if (strpos($id, $prefix) === 0) { + $id = substr($id, $prefixLen); + } + } + } + + return $ids; + } + + /** + * Return an array of stored cache ids which match any given tags + * + * In case of multiple tags, a logical OR is made between tags + * + * @param array $tags array of tags + * @return array array of matching any cache ids (string) + */ + public function getIdsMatchingAnyTags($tags = array()) + { + if (!$this->_extendedBackend) { + Zend_Cache::throwException(self::BACKEND_NOT_IMPLEMENTS_EXTENDED_IF); + } + if (!($this->_backendCapabilities['tags'])) { + Zend_Cache::throwException(self::BACKEND_NOT_SUPPORT_TAG); + } + + $ids = $this->_backend->getIdsMatchingAnyTags($tags); + + // we need to remove cache_id_prefix from ids (see #ZF-6178, #ZF-7600) + if (isset($this->_options['cache_id_prefix']) && $this->_options['cache_id_prefix'] !== '') { + $prefix = & $this->_options['cache_id_prefix']; + $prefixLen = strlen($prefix); + foreach ($ids as &$id) { + if (strpos($id, $prefix) === 0) { + $id = substr($id, $prefixLen); + } + } + } + + return $ids; + } + + /** + * Return an array of stored cache ids + * + * @return array array of stored cache ids (string) + */ + public function getIds() + { + if (!$this->_extendedBackend) { + Zend_Cache::throwException(self::BACKEND_NOT_IMPLEMENTS_EXTENDED_IF); + } + + $ids = $this->_backend->getIds(); + + // we need to remove cache_id_prefix from ids (see #ZF-6178, #ZF-7600) + if (isset($this->_options['cache_id_prefix']) && $this->_options['cache_id_prefix'] !== '') { + $prefix = & $this->_options['cache_id_prefix']; + $prefixLen = strlen($prefix); + foreach ($ids as &$id) { + if (strpos($id, $prefix) === 0) { + $id = substr($id, $prefixLen); + } + } + } + + return $ids; + } + + /** + * Return an array of stored tags + * + * @return array array of stored tags (string) + */ + public function getTags() + { + if (!$this->_extendedBackend) { + Zend_Cache::throwException(self::BACKEND_NOT_IMPLEMENTS_EXTENDED_IF); + } + if (!($this->_backendCapabilities['tags'])) { + Zend_Cache::throwException(self::BACKEND_NOT_SUPPORT_TAG); + } + return $this->_backend->getTags(); + } + + /** + * Return the filling percentage of the backend storage + * + * @return int integer between 0 and 100 + */ + public function getFillingPercentage() + { + if (!$this->_extendedBackend) { + Zend_Cache::throwException(self::BACKEND_NOT_IMPLEMENTS_EXTENDED_IF); + } + return $this->_backend->getFillingPercentage(); + } + + /** + * Return an array of metadatas for the given cache id + * + * The array will include these keys : + * - expire : the expire timestamp + * - tags : a string array of tags + * - mtime : timestamp of last modification time + * + * @param string $id cache id + * @return array array of metadatas (false if the cache id is not found) + */ + public function getMetadatas($id) + { + if (!$this->_extendedBackend) { + Zend_Cache::throwException(self::BACKEND_NOT_IMPLEMENTS_EXTENDED_IF); + } + $id = $this->_id($id); // cache id may need prefix + return $this->_backend->getMetadatas($id); + } + + /** + * Give (if possible) an extra lifetime to the given cache id + * + * @param string $id cache id + * @param int $extraLifetime + * @return boolean true if ok + */ + public function touch($id, $extraLifetime) + { + if (!$this->_extendedBackend) { + Zend_Cache::throwException(self::BACKEND_NOT_IMPLEMENTS_EXTENDED_IF); + } + $id = $this->_id($id); // cache id may need prefix + return $this->_backend->touch($id, $extraLifetime); + } + + /** + * Validate a cache id or a tag (security, reliable filenames, reserved prefixes...) + * + * Throw an exception if a problem is found + * + * @param string $string Cache id or tag + * @throws Zend_Cache_Exception + * @return void + */ + protected static function _validateIdOrTag($string) + { + if (!is_string($string)) { + Zend_Cache::throwException('Invalid id or tag : must be a string'); + } + if (substr($string, 0, 9) == 'internal-') { + Zend_Cache::throwException('"internal-*" ids or tags are reserved'); + } + if (!preg_match('~^[a-zA-Z0-9_]+$~D', $string)) { + Zend_Cache::throwException("Invalid id or tag '$string' : must use only [a-zA-Z0-9_]"); + } + } + + /** + * Validate a tags array (security, reliable filenames, reserved prefixes...) + * + * Throw an exception if a problem is found + * + * @param array $tags Array of tags + * @throws Zend_Cache_Exception + * @return void + */ + protected static function _validateTagsArray($tags) + { + if (!is_array($tags)) { + Zend_Cache::throwException('Invalid tags array : must be an array'); + } + foreach($tags as $tag) { + self::_validateIdOrTag($tag); + } + reset($tags); + } + + /** + * Make sure if we enable logging that the Zend_Log class + * is available. + * Create a default log object if none is set. + * + * @throws Zend_Cache_Exception + * @return void + */ + protected function _loggerSanity() + { + if (!isset($this->_options['logging']) || !$this->_options['logging']) { + return; + } + + if (isset($this->_options['logger']) && $this->_options['logger'] instanceof Zend_Log) { + return; + } + + // Create a default logger to the standard output stream + require_once 'Zend/Log/Writer/Stream.php'; + $logger = new Zend_Log(new Zend_Log_Writer_Stream('php://output')); + $this->_options['logger'] = $logger; + } + + /** + * Log a message at the WARN (4) priority. + * + * @param string $message + * @throws Zend_Cache_Exception + * @return void + */ + protected function _log($message, $priority = 4) + { + if (!$this->_options['logging']) { + return; + } + if (!(isset($this->_options['logger']) || $this->_options['logger'] instanceof Zend_Log)) { + Zend_Cache::throwException('Logging is enabled but logger is not set'); + } + $logger = $this->_options['logger']; + $logger->log($message, $priority); + } + + /** + * Make and return a cache id + * + * Checks 'cache_id_prefix' and returns new id with prefix or simply the id if null + * + * @param string $id Cache id + * @return string Cache id (with or without prefix) + */ + protected function _id($id) + { + if (($id !== null) && isset($this->_options['cache_id_prefix'])) { + return $this->_options['cache_id_prefix'] . $id; // return with prefix + } + return $id; // no prefix, just return the $id passed + } + +} diff --git a/library/Zend/Cache/Exception.php b/library/Zend/Cache/Exception.php new file mode 100644 index 000000000..5ca005d18 --- /dev/null +++ b/library/Zend/Cache/Exception.php @@ -0,0 +1,32 @@ +_tags = $tags; + $this->_extension = $extension; + ob_start(array($this, '_flush')); + ob_implicit_flush(false); + $this->_idStack[] = $id; + return false; + } + + /** + * callback for output buffering + * (shouldn't really be called manually) + * + * @param string $data Buffered output + * @return string Data to send to browser + */ + public function _flush($data) + { + $id = array_pop($this->_idStack); + if (is_null($id)) { + Zend_Cache::throwException('use of _flush() without a start()'); + } + if ($this->_extension) { + $this->save(serialize(array($data, $this->_extension)), $id, $this->_tags); + } else { + $this->save($data, $id, $this->_tags); + } + return $data; + } +} diff --git a/library/Zend/Cache/Frontend/Class.php b/library/Zend/Cache/Frontend/Class.php new file mode 100644 index 000000000..e5e2d0eb3 --- /dev/null +++ b/library/Zend/Cache/Frontend/Class.php @@ -0,0 +1,244 @@ + (mixed) cached_entity : + * - if set to a class name, we will cache an abstract class and will use only static calls + * - if set to an object, we will cache this object methods + * + * ====> (boolean) cache_by_default : + * - if true, method calls will be cached by default + * + * ====> (array) cached_methods : + * - an array of method names which will be cached (even if cache_by_default = false) + * + * ====> (array) non_cached_methods : + * - an array of method names which won't be cached (even if cache_by_default = true) + * + * @var array available options + */ + protected $_specificOptions = array( + 'cached_entity' => null, + 'cache_by_default' => true, + 'cached_methods' => array(), + 'non_cached_methods' => array() + ); + + /** + * Tags array + * + * @var array + */ + private $_tags = array(); + + /** + * SpecificLifetime value + * + * false => no specific life time + * + * @var int + */ + private $_specificLifetime = false; + + /** + * The cached object or the name of the cached abstract class + * + * @var mixed + */ + private $_cachedEntity = null; + + /** + * The class name of the cached object or cached abstract class + * + * Used to differentiate between different classes with the same method calls. + * + * @var string + */ + private $_cachedEntityLabel = ''; + + /** + * Priority (used by some particular backends) + * + * @var int + */ + private $_priority = 8; + + /** + * Constructor + * + * @param array $options Associative array of options + * @throws Zend_Cache_Exception + * @return void + */ + public function __construct(array $options = array()) + { + while (list($name, $value) = each($options)) { + $this->setOption($name, $value); + } + if ($this->_specificOptions['cached_entity'] === null) { + Zend_Cache::throwException('cached_entity must be set !'); + } + $this->setCachedEntity($this->_specificOptions['cached_entity']); + $this->setOption('automatic_serialization', true); + } + + /** + * Set a specific life time + * + * @param int $specificLifetime + * @return void + */ + public function setSpecificLifetime($specificLifetime = false) + { + $this->_specificLifetime = $specificLifetime; + } + + /** + * Set the priority (used by some particular backends) + * + * @param int $priority integer between 0 (very low priority) and 10 (maximum priority) + */ + public function setPriority($priority) + { + $this->_priority = $priority; + } + + /** + * Public frontend to set an option + * + * Just a wrapper to get a specific behaviour for cached_entity + * + * @param string $name Name of the option + * @param mixed $value Value of the option + * @throws Zend_Cache_Exception + * @return void + */ + public function setOption($name, $value) + { + if ($name == 'cached_entity') { + $this->setCachedEntity($value); + } else { + parent::setOption($name, $value); + } + } + + /** + * Specific method to set the cachedEntity + * + * if set to a class name, we will cache an abstract class and will use only static calls + * if set to an object, we will cache this object methods + * + * @param mixed $cachedEntity + */ + public function setCachedEntity($cachedEntity) + { + if (!is_string($cachedEntity) && !is_object($cachedEntity)) { + Zend_Cache::throwException('cached_entity must be an object or a class name'); + } + $this->_cachedEntity = $cachedEntity; + $this->_specificOptions['cached_entity'] = $cachedEntity; + if (is_string($this->_cachedEntity)){ + $this->_cachedEntityLabel = $this->_cachedEntity; + } else { + $ro = new ReflectionObject($this->_cachedEntity); + $this->_cachedEntityLabel = $ro->getName(); + } + } + + /** + * Set the cache array + * + * @param array $tags + * @return void + */ + public function setTagsArray($tags = array()) + { + $this->_tags = $tags; + } + + /** + * Main method : call the specified method or get the result from cache + * + * @param string $name Method name + * @param array $parameters Method parameters + * @return mixed Result + */ + public function __call($name, $parameters) + { + $cacheBool1 = $this->_specificOptions['cache_by_default']; + $cacheBool2 = in_array($name, $this->_specificOptions['cached_methods']); + $cacheBool3 = in_array($name, $this->_specificOptions['non_cached_methods']); + $cache = (($cacheBool1 || $cacheBool2) && (!$cacheBool3)); + if (!$cache) { + // We do not have not cache + return call_user_func_array(array($this->_cachedEntity, $name), $parameters); + } + + $id = $this->_makeId($name, $parameters); + if ( ($rs = $this->load($id)) && isset($rs[0], $rs[1]) ) { + // A cache is available + $output = $rs[0]; + $return = $rs[1]; + } else { + // A cache is not available (or not valid for this frontend) + ob_start(); + ob_implicit_flush(false); + $return = call_user_func_array(array($this->_cachedEntity, $name), $parameters); + $output = ob_get_contents(); + ob_end_clean(); + $data = array($output, $return); + $this->save($data, $id, $this->_tags, $this->_specificLifetime, $this->_priority); + } + + echo $output; + return $return; + } + + /** + * Make a cache id from the method name and parameters + * + * @param string $name Method name + * @param array $parameters Method parameters + * @return string Cache id + */ + private function _makeId($name, $parameters) + { + return md5($this->_cachedEntityLabel . '__' . $name . '__' . serialize($parameters)); + } + +} diff --git a/library/Zend/Cache/Frontend/File.php b/library/Zend/Cache/Frontend/File.php new file mode 100644 index 000000000..07c7ddcab --- /dev/null +++ b/library/Zend/Cache/Frontend/File.php @@ -0,0 +1,209 @@ + (string) master_file : + * - a complete path of the master file + * - deprecated (see master_files) + * + * ====> (array) master_files : + * - an array of complete path of master files + * - this option has to be set ! + * + * ====> (string) master_files_mode : + * - Zend_Cache_Frontend_File::MODE_AND or Zend_Cache_Frontend_File::MODE_OR + * - if MODE_AND, then all master files have to be touched to get a cache invalidation + * - if MODE_OR (default), then a single touched master file is enough to get a cache invalidation + * + * ====> (boolean) ignore_missing_master_files + * - if set to true, missing master files are ignored silently + * - if set to false (default), an exception is thrown if there is a missing master file + * @var array available options + */ + protected $_specificOptions = array( + 'master_file' => null, + 'master_files' => null, + 'master_files_mode' => 'OR', + 'ignore_missing_master_files' => false + ); + + /** + * Master file mtimes + * + * Array of int + * + * @var array + */ + private $_masterFile_mtimes = null; + + /** + * Constructor + * + * @param array $options Associative array of options + * @throws Zend_Cache_Exception + * @return void + */ + public function __construct(array $options = array()) + { + while (list($name, $value) = each($options)) { + $this->setOption($name, $value); + } + if (!isset($this->_specificOptions['master_files'])) { + Zend_Cache::throwException('master_files option must be set'); + } + } + + /** + * Change the master_file option + * + * @param string $masterFile the complete path and name of the master file + */ + public function setMasterFiles($masterFiles) + { + clearstatcache(); + $this->_specificOptions['master_file'] = $masterFiles[0]; // to keep a compatibility + $this->_specificOptions['master_files'] = $masterFiles; + $this->_masterFile_mtimes = array(); + $i = 0; + foreach ($masterFiles as $masterFile) { + $this->_masterFile_mtimes[$i] = @filemtime($masterFile); + if ((!($this->_specificOptions['ignore_missing_master_files'])) && (!($this->_masterFile_mtimes[$i]))) { + Zend_Cache::throwException('Unable to read master_file : '.$masterFile); + } + $i++; + } + } + + /** + * Change the master_file option + * + * To keep the compatibility + * + * @deprecated + * @param string $masterFile the complete path and name of the master file + */ + public function setMasterFile($masterFile) + { + $this->setMasterFiles(array(0 => $masterFile)); + } + + /** + * Public frontend to set an option + * + * Just a wrapper to get a specific behaviour for master_file + * + * @param string $name Name of the option + * @param mixed $value Value of the option + * @throws Zend_Cache_Exception + * @return void + */ + public function setOption($name, $value) + { + if ($name == 'master_file') { + $this->setMasterFile($value); + } else if ($name == 'master_files') { + $this->setMasterFiles($value); + } else { + parent::setOption($name, $value); + } + } + + /** + * Test if a cache is available for the given id and (if yes) return it (false else) + * + * @param string $id Cache id + * @param boolean $doNotTestCacheValidity If set to true, the cache validity won't be tested + * @param boolean $doNotUnserialize Do not serialize (even if automatic_serialization is true) => for internal use + * @return mixed|false Cached datas + */ + public function load($id, $doNotTestCacheValidity = false, $doNotUnserialize = false) + { + if (!$doNotTestCacheValidity) { + if ($this->test($id)) { + return parent::load($id, true, $doNotUnserialize); + } + return false; + } + return parent::load($id, true, $doNotUnserialize); + } + + /** + * Test if a cache is available for the given id + * + * @param string $id Cache id + * @return int|false Last modified time of cache entry if it is available, false otherwise + */ + public function test($id) + { + $lastModified = parent::test($id); + if ($lastModified) { + if ($this->_specificOptions['master_files_mode'] == self::MODE_AND) { + // MODE_AND + foreach($this->_masterFile_mtimes as $masterFileMTime) { + if ($masterFileMTime) { + if ($lastModified > $masterFileMTime) { + return $lastModified; + } + } + } + } else { + // MODE_OR + $res = true; + foreach($this->_masterFile_mtimes as $masterFileMTime) { + if ($masterFileMTime) { + if ($lastModified <= $masterFileMTime) { + return false; + } + } + } + return $lastModified; + } + } + return false; + } + +} + diff --git a/library/Zend/Cache/Frontend/Function.php b/library/Zend/Cache/Frontend/Function.php new file mode 100644 index 000000000..d6ca522ff --- /dev/null +++ b/library/Zend/Cache/Frontend/Function.php @@ -0,0 +1,132 @@ + (boolean) cache_by_default : + * - if true, function calls will be cached by default + * + * ====> (array) cached_functions : + * - an array of function names which will be cached (even if cache_by_default = false) + * + * ====> (array) non_cached_functions : + * - an array of function names which won't be cached (even if cache_by_default = true) + * + * @var array options + */ + protected $_specificOptions = array( + 'cache_by_default' => true, + 'cached_functions' => array(), + 'non_cached_functions' => array() + ); + + /** + * Constructor + * + * @param array $options Associative array of options + * @return void + */ + public function __construct(array $options = array()) + { + while (list($name, $value) = each($options)) { + $this->setOption($name, $value); + } + $this->setOption('automatic_serialization', true); + } + + /** + * Main method : call the specified function or get the result from cache + * + * @param string $name Function name + * @param array $parameters Function parameters + * @param array $tags Cache tags + * @param int $specificLifetime If != false, set a specific lifetime for this cache record (null => infinite lifetime) + * @param int $priority integer between 0 (very low priority) and 10 (maximum priority) used by some particular backends + * @return mixed Result + */ + public function call($name, $parameters = array(), $tags = array(), $specificLifetime = false, $priority = 8) + { + $cacheBool1 = $this->_specificOptions['cache_by_default']; + $cacheBool2 = in_array($name, $this->_specificOptions['cached_functions']); + $cacheBool3 = in_array($name, $this->_specificOptions['non_cached_functions']); + $cache = (($cacheBool1 || $cacheBool2) && (!$cacheBool3)); + if (!$cache) { + // We do not have not cache + return call_user_func_array($name, $parameters); + } + + $id = $this->_makeId($name, $parameters); + if ( ($rs = $this->load($id)) && isset($rs[0], $rs[1])) { + // A cache is available + $output = $rs[0]; + $return = $rs[1]; + } else { + // A cache is not available (or not valid for this frontend) + ob_start(); + ob_implicit_flush(false); + $return = call_user_func_array($name, $parameters); + $output = ob_get_contents(); + ob_end_clean(); + $data = array($output, $return); + $this->save($data, $id, $tags, $specificLifetime, $priority); + } + + echo $output; + return $return; + } + + /** + * Make a cache id from the function name and parameters + * + * @param string $name Function name + * @param array $parameters Function parameters + * @throws Zend_Cache_Exception + * @return string Cache id + */ + private function _makeId($name, $parameters) + { + if (!is_string($name)) { + Zend_Cache::throwException('Incorrect function name'); + } + if (!is_array($parameters)) { + Zend_Cache::throwException('parameters argument must be an array'); + } + return md5($name . serialize($parameters)); + } + +} diff --git a/library/Zend/Cache/Frontend/Output.php b/library/Zend/Cache/Frontend/Output.php new file mode 100644 index 000000000..021150e6d --- /dev/null +++ b/library/Zend/Cache/Frontend/Output.php @@ -0,0 +1,106 @@ +_idStack = array(); + } + + /** + * Start the cache + * + * @param string $id Cache id + * @param boolean $doNotTestCacheValidity If set to true, the cache validity won't be tested + * @param boolean $echoData If set to true, datas are sent to the browser if the cache is hit (simpy returned else) + * @return mixed True if the cache is hit (false else) with $echoData=true (default) ; string else (datas) + */ + public function start($id, $doNotTestCacheValidity = false, $echoData = true) + { + $data = $this->load($id, $doNotTestCacheValidity); + if ($data !== false) { + if ( $echoData ) { + echo($data); + return true; + } else { + return $data; + } + } + ob_start(); + ob_implicit_flush(false); + $this->_idStack[] = $id; + return false; + } + + /** + * Stop the cache + * + * @param array $tags Tags array + * @param int $specificLifetime If != false, set a specific lifetime for this cache record (null => infinite lifetime) + * @param string $forcedDatas If not null, force written datas with this + * @param boolean $echoData If set to true, datas are sent to the browser + * @param int $priority integer between 0 (very low priority) and 10 (maximum priority) used by some particular backends + * @return void + */ + public function end($tags = array(), $specificLifetime = false, $forcedDatas = null, $echoData = true, $priority = 8) + { + if ($forcedDatas === null) { + $data = ob_get_contents(); + ob_end_clean(); + } else { + $data =& $forcedDatas; + } + $id = array_pop($this->_idStack); + if ($id === null) { + Zend_Cache::throwException('use of end() without a start()'); + } + $this->save($data, $id, $tags, $specificLifetime, $priority); + if ($echoData) { + echo($data); + } + } + +} diff --git a/library/Zend/Cache/Frontend/Page.php b/library/Zend/Cache/Frontend/Page.php new file mode 100644 index 000000000..c8edceb7f --- /dev/null +++ b/library/Zend/Cache/Frontend/Page.php @@ -0,0 +1,402 @@ + (boolean) http_conditional : + * - if true, http conditional mode is on + * WARNING : http_conditional OPTION IS NOT IMPLEMENTED FOR THE MOMENT (TODO) + * + * ====> (boolean) debug_header : + * - if true, a debug text is added before each cached pages + * + * ====> (boolean) content_type_memorization : + * - deprecated => use memorize_headers instead + * - if the Content-Type header is sent after the cache was started, the + * corresponding value can be memorized and replayed when the cache is hit + * (if false (default), the frontend doesn't take care of Content-Type header) + * + * ====> (array) memorize_headers : + * - an array of strings corresponding to some HTTP headers name. Listed headers + * will be stored with cache datas and "replayed" when the cache is hit + * + * ====> (array) default_options : + * - an associative array of default options : + * - (boolean) cache : cache is on by default if true + * - (boolean) cacheWithXXXVariables (XXXX = 'Get', 'Post', 'Session', 'Files' or 'Cookie') : + * if true, cache is still on even if there are some variables in this superglobal array + * if false, cache is off if there are some variables in this superglobal array + * - (boolean) makeIdWithXXXVariables (XXXX = 'Get', 'Post', 'Session', 'Files' or 'Cookie') : + * if true, we have to use the content of this superglobal array to make a cache id + * if false, the cache id won't be dependent of the content of this superglobal array + * - (int) specific_lifetime : cache specific lifetime + * (false => global lifetime is used, null => infinite lifetime, + * integer => this lifetime is used), this "lifetime" is probably only + * usefull when used with "regexps" array + * - (array) tags : array of tags (strings) + * - (int) priority : integer between 0 (very low priority) and 10 (maximum priority) used by + * some particular backends + * + * ====> (array) regexps : + * - an associative array to set options only for some REQUEST_URI + * - keys are (pcre) regexps + * - values are associative array with specific options to set if the regexp matchs on $_SERVER['REQUEST_URI'] + * (see default_options for the list of available options) + * - if several regexps match the $_SERVER['REQUEST_URI'], only the last one will be used + * + * @var array options + */ + protected $_specificOptions = array( + 'http_conditional' => false, + 'debug_header' => false, + 'content_type_memorization' => false, + 'memorize_headers' => array(), + 'default_options' => array( + 'cache_with_get_variables' => false, + 'cache_with_post_variables' => false, + 'cache_with_session_variables' => false, + 'cache_with_files_variables' => false, + 'cache_with_cookie_variables' => false, + 'make_id_with_get_variables' => true, + 'make_id_with_post_variables' => true, + 'make_id_with_session_variables' => true, + 'make_id_with_files_variables' => true, + 'make_id_with_cookie_variables' => true, + 'cache' => true, + 'specific_lifetime' => false, + 'tags' => array(), + 'priority' => null + ), + 'regexps' => array() + ); + + /** + * Internal array to store some options + * + * @var array associative array of options + */ + protected $_activeOptions = array(); + + /** + * If true, the page won't be cached + * + * @var boolean + */ + protected $_cancel = false; + + /** + * Constructor + * + * @param array $options Associative array of options + * @param boolean $doNotTestCacheValidity If set to true, the cache validity won't be tested + * @throws Zend_Cache_Exception + * @return void + */ + public function __construct(array $options = array()) + { + while (list($name, $value) = each($options)) { + $name = strtolower($name); + switch ($name) { + case 'regexps': + $this->_setRegexps($value); + break; + case 'default_options': + $this->_setDefaultOptions($value); + break; + case 'content_type_memorization': + $this->_setContentTypeMemorization($value); + break; + default: + $this->setOption($name, $value); + } + } + if (isset($this->_specificOptions['http_conditional'])) { + if ($this->_specificOptions['http_conditional']) { + Zend_Cache::throwException('http_conditional is not implemented for the moment !'); + } + } + $this->setOption('automatic_serialization', true); + } + + /** + * Specific setter for the 'default_options' option (with some additional tests) + * + * @param array $options Associative array + * @throws Zend_Cache_Exception + * @return void + */ + protected function _setDefaultOptions($options) + { + if (!is_array($options)) { + Zend_Cache::throwException('default_options must be an array !'); + } + foreach ($options as $key=>$value) { + if (!is_string($key)) { + Zend_Cache::throwException("invalid option [$key] !"); + } + $key = strtolower($key); + if (isset($this->_specificOptions['default_options'][$key])) { + $this->_specificOptions['default_options'][$key] = $value; + } + } + } + + /** + * Set the deprecated contentTypeMemorization option + * + * @param boolean $value value + * @return void + * @deprecated + */ + protected function _setContentTypeMemorization($value) + { + $found = null; + foreach ($this->_specificOptions['memorize_headers'] as $key => $value) { + if (strtolower($value) == 'content-type') { + $found = $key; + } + } + if ($value) { + if (!$found) { + $this->_specificOptions['memorize_headers'][] = 'Content-Type'; + } + } else { + if ($found) { + unset($this->_specificOptions['memorize_headers'][$found]); + } + } + } + + /** + * Specific setter for the 'regexps' option (with some additional tests) + * + * @param array $options Associative array + * @throws Zend_Cache_Exception + * @return void + */ + protected function _setRegexps($regexps) + { + if (!is_array($regexps)) { + Zend_Cache::throwException('regexps option must be an array !'); + } + foreach ($regexps as $regexp=>$conf) { + if (!is_array($conf)) { + Zend_Cache::throwException('regexps option must be an array of arrays !'); + } + $validKeys = array_keys($this->_specificOptions['default_options']); + foreach ($conf as $key=>$value) { + if (!is_string($key)) { + Zend_Cache::throwException("unknown option [$key] !"); + } + $key = strtolower($key); + if (!in_array($key, $validKeys)) { + unset($regexps[$regexp][$key]); + } + } + } + $this->setOption('regexps', $regexps); + } + + /** + * Start the cache + * + * @param string $id (optional) A cache id (if you set a value here, maybe you have to use Output frontend instead) + * @param boolean $doNotDie For unit testing only ! + * @return boolean True if the cache is hit (false else) + */ + public function start($id = false, $doNotDie = false) + { + $this->_cancel = false; + $lastMatchingRegexp = null; + foreach ($this->_specificOptions['regexps'] as $regexp => $conf) { + if (preg_match("`$regexp`", $_SERVER['REQUEST_URI'])) { + $lastMatchingRegexp = $regexp; + } + } + $this->_activeOptions = $this->_specificOptions['default_options']; + if ($lastMatchingRegexp !== null) { + $conf = $this->_specificOptions['regexps'][$lastMatchingRegexp]; + foreach ($conf as $key=>$value) { + $this->_activeOptions[$key] = $value; + } + } + if (!($this->_activeOptions['cache'])) { + return false; + } + if (!$id) { + $id = $this->_makeId(); + if (!$id) { + return false; + } + } + $array = $this->load($id); + if ($array !== false) { + $data = $array['data']; + $headers = $array['headers']; + if (!headers_sent()) { + foreach ($headers as $key=>$headerCouple) { + $name = $headerCouple[0]; + $value = $headerCouple[1]; + header("$name: $value"); + } + } + if ($this->_specificOptions['debug_header']) { + echo 'DEBUG HEADER : This is a cached page !'; + } + echo $data; + if ($doNotDie) { + return true; + } + die(); + } + ob_start(array($this, '_flush')); + ob_implicit_flush(false); + return false; + } + + /** + * Cancel the current caching process + */ + public function cancel() + { + $this->_cancel = true; + } + + /** + * callback for output buffering + * (shouldn't really be called manually) + * + * @param string $data Buffered output + * @return string Data to send to browser + */ + public function _flush($data) + { + if ($this->_cancel) { + return $data; + } + $contentType = null; + $storedHeaders = array(); + $headersList = headers_list(); + foreach($this->_specificOptions['memorize_headers'] as $key=>$headerName) { + foreach ($headersList as $headerSent) { + $tmp = explode(':', $headerSent); + $headerSentName = trim(array_shift($tmp)); + if (strtolower($headerName) == strtolower($headerSentName)) { + $headerSentValue = trim(implode(':', $tmp)); + $storedHeaders[] = array($headerSentName, $headerSentValue); + } + } + } + $array = array( + 'data' => $data, + 'headers' => $storedHeaders + ); + $this->save($array, null, $this->_activeOptions['tags'], $this->_activeOptions['specific_lifetime'], $this->_activeOptions['priority']); + return $data; + } + + /** + * Make an id depending on REQUEST_URI and superglobal arrays (depending on options) + * + * @return mixed|false a cache id (string), false if the cache should have not to be used + */ + protected function _makeId() + { + $tmp = $_SERVER['REQUEST_URI']; + $array = explode('?', $tmp, 2); + $tmp = $array[0]; + foreach (array('Get', 'Post', 'Session', 'Files', 'Cookie') as $arrayName) { + $tmp2 = $this->_makePartialId($arrayName, $this->_activeOptions['cache_with_' . strtolower($arrayName) . '_variables'], $this->_activeOptions['make_id_with_' . strtolower($arrayName) . '_variables']); + if ($tmp2===false) { + return false; + } + $tmp = $tmp . $tmp2; + } + return md5($tmp); + } + + /** + * Make a partial id depending on options + * + * @param string $arrayName Superglobal array name + * @param bool $bool1 If true, cache is still on even if there are some variables in the superglobal array + * @param bool $bool2 If true, we have to use the content of the superglobal array to make a partial id + * @return mixed|false Partial id (string) or false if the cache should have not to be used + */ + protected function _makePartialId($arrayName, $bool1, $bool2) + { + switch ($arrayName) { + case 'Get': + $var = $_GET; + break; + case 'Post': + $var = $_POST; + break; + case 'Session': + if (isset($_SESSION)) { + $var = $_SESSION; + } else { + $var = null; + } + break; + case 'Cookie': + if (isset($_COOKIE)) { + $var = $_COOKIE; + } else { + $var = null; + } + break; + case 'Files': + $var = $_FILES; + break; + default: + return false; + } + if ($bool1) { + if ($bool2) { + return serialize($var); + } + return ''; + } + if (count($var) > 0) { + return false; + } + return ''; + } + +} diff --git a/library/Zend/Cache/Manager.php b/library/Zend/Cache/Manager.php new file mode 100644 index 000000000..a409a2b81 --- /dev/null +++ b/library/Zend/Cache/Manager.php @@ -0,0 +1,283 @@ + array( + 'frontend' => array( + 'name' => null, + 'options' => array(), + ), + 'backend' => array( + 'name' => null, + 'options' => array(), + ), + ), + // Simple Common Default + 'default' => array( + 'frontend' => array( + 'name' => 'Core', + 'options' => array( + 'automatic_serialization' => true, + ), + ), + 'backend' => array( + 'name' => 'File', + 'options' => array( + 'cache_dir' => '../cache', + ), + ), + ), + // Static Page HTML Cache + 'page' => array( + 'frontend' => array( + 'name' => 'Capture', + 'options' => array( + 'ignore_user_abort' => true, + ), + ), + 'backend' => array( + 'name' => 'Static', + 'options' => array( + 'public_dir' => '../public', + ), + ), + ), + // Tag Cache + 'pagetag' => array( + 'frontend' => array( + 'name' => 'Core', + 'options' => array( + 'automatic_serialization' => true, + 'lifetime' => null + ), + ), + 'backend' => array( + 'name' => 'File', + 'options' => array( + 'cache_dir' => '../cache', + 'cache_file_umask' => 0644 + ), + ), + ), + ); + + /** + * Set a new cache for the Cache Manager to contain + * + * @param string $name + * @param Zend_Cache_Core $cache + * @return Zend_Cache_Manager + */ + public function setCache($name, Zend_Cache_Core $cache) + { + $this->_caches[$name] = $cache; + return $this; + } + + /** + * Check if the Cache Manager contains the named cache object, or a named + * configuration template to lazy load the cache object + * + * @param string $name + * @return bool + */ + public function hasCache($name) + { + if (isset($this->_caches[$name]) + || $this->hasCacheTemplate($name) + ) { + return true; + } + return false; + } + + /** + * Fetch the named cache object, or instantiate and return a cache object + * using a named configuration template + * + * @param string $name + * @return Zend_Cache_Core + */ + public function getCache($name) + { + if (isset($this->_caches[$name])) { + return $this->_caches[$name]; + } + if (isset($this->_optionTemplates[$name])) { + if ($name == self::PAGECACHE + && (!isset($this->_optionTemplates[$name]['backend']['options']['tag_cache']) + || !$this->_optionTemplates[$name]['backend']['options']['tag_cache'] instanceof Zend_Cache_Core) + ) { + $this->_optionTemplates[$name]['backend']['options']['tag_cache'] + = $this->getCache(self::PAGETAGCACHE ); + } + $this->_caches[$name] = Zend_Cache::factory( + $this->_optionTemplates[$name]['frontend']['name'], + $this->_optionTemplates[$name]['backend']['name'], + isset($this->_optionTemplates[$name]['frontend']['options']) ? $this->_optionTemplates[$name]['frontend']['options'] : array(), + isset($this->_optionTemplates[$name]['backend']['options']) ? $this->_optionTemplates[$name]['backend']['options'] : array() + ); + return $this->_caches[$name]; + } + } + + /** + * Set a named configuration template from which a cache object can later + * be lazy loaded + * + * @param string $name + * @param array $options + * @return Zend_Cache_Manager + */ + public function setCacheTemplate($name, $options) + { + if ($options instanceof Zend_Config) { + $options = $options->toArray(); + } elseif (!is_array($options)) { + require_once 'Zend/Cache/Exception.php'; + throw new Zend_Cache_Exception('Options passed must be in' + . ' an associative array or instance of Zend_Config'); + } + $this->_optionTemplates[$name] = $options; + return $this; + } + + /** + * Check if the named configuration template + * + * @param string $name + * @return bool + */ + public function hasCacheTemplate($name) + { + if (isset($this->_optionTemplates[$name])) { + return true; + } + return false; + } + + /** + * Get the named configuration template + * + * @param string $name + * @return array + */ + public function getCacheTemplate($name) + { + if (isset($this->_optionTemplates[$name])) { + return $this->_optionTemplates[$name]; + } + } + + /** + * Pass an array containing changes to be applied to a named + * configuration + * template + * + * @param string $name + * @param array $options + * @return Zend_Cache_Manager + * @throws Zend_Cache_Exception for invalid options format or if option templates do not have $name + */ + public function setTemplateOptions($name, $options) + { + if ($options instanceof Zend_Config) { + $options = $options->toArray(); + } elseif (!is_array($options)) { + require_once 'Zend/Cache/Exception.php'; + throw new Zend_Cache_Exception('Options passed must be in' + . ' an associative array or instance of Zend_Config'); + } + if (!isset($this->_optionTemplates[$name])) { + throw new Zend_Cache_Exception('A cache configuration template' + . 'does not exist with the name "' . $name . '"'); + } + $this->_optionTemplates[$name] + = $this->_mergeOptions($this->_optionTemplates[$name], $options); + return $this; + } + + /** + * Simple method to merge two configuration arrays + * + * @param array $current + * @param array $options + * @return array + */ + protected function _mergeOptions(array $current, array $options) + { + if (isset($options['frontend']['name'])) { + $current['frontend']['name'] = $options['frontend']['name']; + } + if (isset($options['backend']['name'])) { + $current['backend']['name'] = $options['backend']['name']; + } + if (isset($options['frontend']['options'])) { + foreach ($options['frontend']['options'] as $key=>$value) { + $current['frontend']['options'][$key] = $value; + } + } + if (isset($options['backend']['options'])) { + foreach ($options['backend']['options'] as $key=>$value) { + $current['backend']['options'][$key] = $value; + } + } + return $current; + } +} diff --git a/library/Zend/Captcha/Adapter.php b/library/Zend/Captcha/Adapter.php new file mode 100644 index 000000000..02c1391bc --- /dev/null +++ b/library/Zend/Captcha/Adapter.php @@ -0,0 +1,76 @@ +_name; + } + + /** + * Set name + * + * @param string $name + */ + public function setName($name) + { + $this->_name = $name; + return $this; + } + + /** + * Constructor + * + * @param array|Zend_Config $options + * @return void + */ + public function __construct($options = null) + { + // Set options + if (is_array($options)) { + $this->setOptions($options); + } else if ($options instanceof Zend_Config) { + $this->setConfig($options); + } + } + + /** + * Set single option for the object + * + * @param string $key + * @param string $value + * @return Zend_Form_Element + */ + public function setOption($key, $value) + { + if (in_array(strtolower($key), $this->_skipOptions)) { + return $this; + } + + $method = 'set' . ucfirst ($key); + if (method_exists ($this, $method)) { + // Setter exists; use it + $this->$method ($value); + $this->_options[$key] = $value; + } elseif (property_exists($this, $key)) { + // Assume it's metadata + $this->$key = $value; + $this->_options[$key] = $value; + } + return $this; + } + + /** + * Set object state from options array + * + * @param array $options + * @return Zend_Form_Element + */ + public function setOptions($options = null) + { + foreach ($options as $key => $value) { + $this->setOption($key, $value); + } + return $this; + } + + /** + * Retrieve options representing object state + * + * @return array + */ + public function getOptions() + { + return $this->_options; + } + + /** + * Set object state from config object + * + * @param Zend_Config $config + * @return Zend_Captcha_Base + */ + public function setConfig(Zend_Config $config) + { + return $this->setOptions($config->toArray()); + } + + /** + * Get optional decorator + * + * By default, return null, indicating no extra decorator needed. + * + * @return null + */ + public function getDecorator() + { + return null; + } +} diff --git a/library/Zend/Captcha/Dumb.php b/library/Zend/Captcha/Dumb.php new file mode 100644 index 000000000..795b27c9d --- /dev/null +++ b/library/Zend/Captcha/Dumb.php @@ -0,0 +1,52 @@ +' + . strrev($this->getWord()) + . ''; + } +} diff --git a/library/Zend/Captcha/Exception.php b/library/Zend/Captcha/Exception.php new file mode 100644 index 000000000..e247f42bb --- /dev/null +++ b/library/Zend/Captcha/Exception.php @@ -0,0 +1,37 @@ +_figlet = new Zend_Text_Figlet($options); + } + + /** + * Generate new captcha + * + * @return string + */ + public function generate() + { + $this->_useNumbers = false; + return parent::generate(); + } + + /** + * Display the captcha + * + * @param Zend_View_Interface $view + * @param mixed $element + * @return string + */ + public function render(Zend_View_Interface $view = null, $element = null) + { + return '
    '
    +             . $this->_figlet->render($this->getWord())
    +             . "
    \n"; + } +} diff --git a/library/Zend/Captcha/Image.php b/library/Zend/Captcha/Image.php new file mode 100644 index 000000000..cc79a4e25 --- /dev/null +++ b/library/Zend/Captcha/Image.php @@ -0,0 +1,600 @@ +_imgAlt; + } + /** + * @return string + */ + public function getStartImage () + { + return $this->_startImage; + } + /** + * @return int + */ + public function getDotNoiseLevel () + { + return $this->_dotNoiseLevel; + } + /** + * @return int + */ + public function getLineNoiseLevel () + { + return $this->_lineNoiseLevel; + } + /** + * Get captcha expiration + * + * @return int + */ + public function getExpiration() + { + return $this->_expiration; + } + + /** + * Get garbage collection frequency + * + * @return int + */ + public function getGcFreq() + { + return $this->_gcFreq; + } + /** + * Get font to use when generating captcha + * + * @return string + */ + public function getFont() + { + return $this->_font; + } + + /** + * Get font size + * + * @return int + */ + public function getFontSize() + { + return $this->_fsize; + } + + /** + * Get captcha image height + * + * @return int + */ + public function getHeight() + { + return $this->_height; + } + + /** + * Get captcha image directory + * + * @return string + */ + public function getImgDir() + { + return $this->_imgDir; + } + /** + * Get captcha image base URL + * + * @return string + */ + public function getImgUrl() + { + return $this->_imgUrl; + } + /** + * Get captcha image file suffix + * + * @return string + */ + public function getSuffix() + { + return $this->_suffix; + } + /** + * Get captcha image width + * + * @return int + */ + public function getWidth() + { + return $this->_width; + } + /** + * @param string $startImage + */ + public function setStartImage ($startImage) + { + $this->_startImage = $startImage; + return $this; + } + /** + * @param int $dotNoiseLevel + */ + public function setDotNoiseLevel ($dotNoiseLevel) + { + $this->_dotNoiseLevel = $dotNoiseLevel; + return $this; + } + /** + * @param int $lineNoiseLevel + */ + public function setLineNoiseLevel ($lineNoiseLevel) + { + $this->_lineNoiseLevel = $lineNoiseLevel; + return $this; + } + + /** + * Set captcha expiration + * + * @param int $expiration + * @return Zend_Captcha_Image + */ + public function setExpiration($expiration) + { + $this->_expiration = $expiration; + return $this; + } + + /** + * Set garbage collection frequency + * + * @param int $gcFreq + * @return Zend_Captcha_Image + */ + public function setGcFreq($gcFreq) + { + $this->_gcFreq = $gcFreq; + return $this; + } + + /** + * Set captcha font + * + * @param string $font + * @return Zend_Captcha_Image + */ + public function setFont($font) + { + $this->_font = $font; + return $this; + } + + /** + * Set captcha font size + * + * @param int $fsize + * @return Zend_Captcha_Image + */ + public function setFontSize($fsize) + { + $this->_fsize = $fsize; + return $this; + } + + /** + * Set captcha image height + * + * @param int $height + * @return Zend_Captcha_Image + */ + public function setHeight($height) + { + $this->_height = $height; + return $this; + } + + /** + * Set captcha image storage directory + * + * @param string $imgDir + * @return Zend_Captcha_Image + */ + public function setImgDir($imgDir) + { + $this->_imgDir = rtrim($imgDir, "/\\") . '/'; + return $this; + } + + /** + * Set captcha image base URL + * + * @param string $imgUrl + * @return Zend_Captcha_Image + */ + public function setImgUrl($imgUrl) + { + $this->_imgUrl = rtrim($imgUrl, "/\\") . '/'; + return $this; + } + /** + * @param string $imgAlt + */ + public function setImgAlt ($imgAlt) + { + $this->_imgAlt = $imgAlt; + return $this; + } + + /** + * Set captch image filename suffix + * + * @param string $suffix + * @return Zend_Captcha_Image + */ + public function setSuffix($suffix) + { + $this->_suffix = $suffix; + return $this; + } + + /** + * Set captcha image width + * + * @param int $width + * @return Zend_Captcha_Image + */ + public function setWidth($width) + { + $this->_width = $width; + return $this; + } + + /** + * Generate random frequency + * + * @return float + */ + protected function _randomFreq() + { + return mt_rand(700000, 1000000) / 15000000; + } + + /** + * Generate random phase + * + * @return float + */ + protected function _randomPhase() + { + // random phase from 0 to pi + return mt_rand(0, 3141592) / 1000000; + } + + /** + * Generate random character size + * + * @return int + */ + protected function _randomSize() + { + return mt_rand(300, 700) / 100; + } + + /** + * Generate captcha + * + * @return string captcha ID + */ + public function generate() + { + $id = parent::generate(); + $tries = 5; + // If there's already such file, try creating a new ID + while($tries-- && file_exists($this->getImgDir() . $id . $this->getSuffix())) { + $id = $this->_generateRandomId(); + $this->_setId($id); + } + $this->_generateImage($id, $this->getWord()); + + if (mt_rand(1, $this->getGcFreq()) == 1) { + $this->_gc(); + } + return $id; + } + + /** + * Generate image captcha + * + * Override this function if you want different image generator + * Wave transform from http://www.captcha.ru/captchas/multiwave/ + * + * @param string $id Captcha ID + * @param string $word Captcha word + */ + protected function _generateImage($id, $word) + { + if (!extension_loaded("gd")) { + require_once 'Zend/Captcha/Exception.php'; + throw new Zend_Captcha_Exception("Image CAPTCHA requires GD extension"); + } + + if (!function_exists("imagepng")) { + require_once 'Zend/Captcha/Exception.php'; + throw new Zend_Captcha_Exception("Image CAPTCHA requires PNG support"); + } + + if (!function_exists("imageftbbox")) { + require_once 'Zend/Captcha/Exception.php'; + throw new Zend_Captcha_Exception("Image CAPTCHA requires FT fonts support"); + } + + $font = $this->getFont(); + + if (empty($font)) { + require_once 'Zend/Captcha/Exception.php'; + throw new Zend_Captcha_Exception("Image CAPTCHA requires font"); + } + + $w = $this->getWidth(); + $h = $this->getHeight(); + $fsize = $this->getFontSize(); + + $img_file = $this->getImgDir() . $id . $this->getSuffix(); + if(empty($this->_startImage)) { + $img = imagecreatetruecolor($w, $h); + } else { + $img = imagecreatefrompng($this->_startImage); + if(!$img) { + require_once 'Zend/Captcha/Exception.php'; + throw new Zend_Captcha_Exception("Can not load start image"); + } + $w = imagesx($img); + $h = imagesy($img); + } + $text_color = imagecolorallocate($img, 0, 0, 0); + $bg_color = imagecolorallocate($img, 255, 255, 255); + imagefilledrectangle($img, 0, 0, $w-1, $h-1, $bg_color); + $textbox = imageftbbox($fsize, 0, $font, $word); + $x = ($w - ($textbox[2] - $textbox[0])) / 2; + $y = ($h - ($textbox[7] - $textbox[1])) / 2; + imagefttext($img, $fsize, 0, $x, $y, $text_color, $font, $word); + + // generate noise + for ($i=0; $i<$this->_dotNoiseLevel; $i++) { + imagefilledellipse($img, mt_rand(0,$w), mt_rand(0,$h), 2, 2, $text_color); + } + for($i=0; $i<$this->_lineNoiseLevel; $i++) { + imageline($img, mt_rand(0,$w), mt_rand(0,$h), mt_rand(0,$w), mt_rand(0,$h), $text_color); + } + + // transformed image + $img2 = imagecreatetruecolor($w, $h); + $bg_color = imagecolorallocate($img2, 255, 255, 255); + imagefilledrectangle($img2, 0, 0, $w-1, $h-1, $bg_color); + // apply wave transforms + $freq1 = $this->_randomFreq(); + $freq2 = $this->_randomFreq(); + $freq3 = $this->_randomFreq(); + $freq4 = $this->_randomFreq(); + + $ph1 = $this->_randomPhase(); + $ph2 = $this->_randomPhase(); + $ph3 = $this->_randomPhase(); + $ph4 = $this->_randomPhase(); + + $szx = $this->_randomSize(); + $szy = $this->_randomSize(); + + for ($x = 0; $x < $w; $x++) { + for ($y = 0; $y < $h; $y++) { + $sx = $x + (sin($x*$freq1 + $ph1) + sin($y*$freq3 + $ph3)) * $szx; + $sy = $y + (sin($x*$freq2 + $ph2) + sin($y*$freq4 + $ph4)) * $szy; + + if ($sx < 0 || $sy < 0 || $sx >= $w - 1 || $sy >= $h - 1) { + continue; + } else { + $color = (imagecolorat($img, $sx, $sy) >> 16) & 0xFF; + $color_x = (imagecolorat($img, $sx + 1, $sy) >> 16) & 0xFF; + $color_y = (imagecolorat($img, $sx, $sy + 1) >> 16) & 0xFF; + $color_xy = (imagecolorat($img, $sx + 1, $sy + 1) >> 16) & 0xFF; + } + if ($color == 255 && $color_x == 255 && $color_y == 255 && $color_xy == 255) { + // ignore background + continue; + } elseif ($color == 0 && $color_x == 0 && $color_y == 0 && $color_xy == 0) { + // transfer inside of the image as-is + $newcolor = 0; + } else { + // do antialiasing for border items + $frac_x = $sx-floor($sx); + $frac_y = $sy-floor($sy); + $frac_x1 = 1-$frac_x; + $frac_y1 = 1-$frac_y; + + $newcolor = $color * $frac_x1 * $frac_y1 + + $color_x * $frac_x * $frac_y1 + + $color_y * $frac_x1 * $frac_y + + $color_xy * $frac_x * $frac_y; + } + imagesetpixel($img2, $x, $y, imagecolorallocate($img2, $newcolor, $newcolor, $newcolor)); + } + } + + // generate noise + for ($i=0; $i<$this->_dotNoiseLevel; $i++) { + imagefilledellipse($img2, mt_rand(0,$w), mt_rand(0,$h), 2, 2, $text_color); + } + for ($i=0; $i<$this->_lineNoiseLevel; $i++) { + imageline($img2, mt_rand(0,$w), mt_rand(0,$h), mt_rand(0,$w), mt_rand(0,$h), $text_color); + } + + imagepng($img2, $img_file); + imagedestroy($img); + imagedestroy($img2); + } + + /** + * Remove old files from image directory + * + */ + protected function _gc() + { + $expire = time() - $this->getExpiration(); + $imgdir = $this->getImgDir(); + if(!$imgdir || strlen($imgdir) < 2) { + // safety guard + return; + } + foreach (new DirectoryIterator($imgdir) as $file) { + if (!$file->isDot() && !$file->isDir()) { + if ($file->getMTime() < $expire) { + unlink($file->getPathname()); + } + } + } + } + + /** + * Display the captcha + * + * @param Zend_View_Interface $view + * @param mixed $element + * @return string + */ + public function render(Zend_View_Interface $view = null, $element = null) + { + return ''.$this->getImgAlt().'
    '; + } +} diff --git a/library/Zend/Captcha/ReCaptcha.php b/library/Zend/Captcha/ReCaptcha.php new file mode 100644 index 000000000..820c4406d --- /dev/null +++ b/library/Zend/Captcha/ReCaptcha.php @@ -0,0 +1,266 @@ + 'Missing captcha fields', + self::ERR_CAPTCHA => 'Failed to validate captcha', + self::BAD_CAPTCHA => 'Captcha value is wrong: %value%', + ); + + /** + * Retrieve ReCaptcha Private key + * + * @return string + */ + public function getPrivkey() + { + return $this->getService()->getPrivateKey(); + } + + /** + * Retrieve ReCaptcha Public key + * + * @return string + */ + public function getPubkey() + { + return $this->getService()->getPublicKey(); + } + + /** + * Set ReCaptcha Private key + * + * @param string $privkey + * @return Zend_Captcha_ReCaptcha + */ + public function setPrivkey($privkey) + { + $this->getService()->setPrivateKey($privkey); + return $this; + } + + /** + * Set ReCaptcha public key + * + * @param string $pubkey + * @return Zend_Captcha_ReCaptcha + */ + public function setPubkey($pubkey) + { + $this->getService()->setPublicKey($pubkey); + return $this; + } + + /** + * Constructor + * + * @param array|Zend_Config $options + * @return void + */ + public function __construct($options = null) + { + $this->setService(new Zend_Service_ReCaptcha()); + $this->_serviceParams = $this->getService()->getParams(); + $this->_serviceOptions = $this->getService()->getOptions(); + + parent::__construct($options); + + if ($options instanceof Zend_Config) { + $options = $options->toArray(); + } + if (!empty($options)) { + $this->setOptions($options); + } + } + + /** + * Set service object + * + * @param Zend_Service_ReCaptcha $service + * @return Zend_Captcha_ReCaptcha + */ + public function setService(Zend_Service_ReCaptcha $service) + { + $this->_service = $service; + return $this; + } + + /** + * Retrieve ReCaptcha service object + * + * @return Zend_Service_ReCaptcha + */ + public function getService() + { + return $this->_service; + } + + /** + * Set option + * + * If option is a service parameter, proxies to the service. The same + * goes for any service options (distinct from service params) + * + * @param string $key + * @param mixed $value + * @return Zend_Captcha_ReCaptcha + */ + public function setOption($key, $value) + { + $service = $this->getService(); + if (isset($this->_serviceParams[$key])) { + $service->setParam($key, $value); + return $this; + } + if (isset($this->_serviceOptions[$key])) { + $service->setOption($key, $value); + return $this; + } + return parent::setOption($key, $value); + } + + /** + * Generate captcha + * + * @see Zend_Form_Captcha_Adapter::generate() + * @return string + */ + public function generate() + { + return ""; + } + + /** + * Validate captcha + * + * @see Zend_Validate_Interface::isValid() + * @param mixed $value + * @return boolean + */ + public function isValid($value, $context = null) + { + if (!is_array($value) && !is_array($context)) { + $this->_error(self::MISSING_VALUE); + return false; + } + + if (!is_array($value) && is_array($context)) { + $value = $context; + } + + if (empty($value[$this->_CHALLENGE]) || empty($value[$this->_RESPONSE])) { + $this->_error(self::MISSING_VALUE); + return false; + } + + $service = $this->getService(); + + $res = $service->verify($value[$this->_CHALLENGE], $value[$this->_RESPONSE]); + + if (!$res) { + $this->_error(self::ERR_CAPTCHA); + return false; + } + + if (!$res->isValid()) { + $this->_error(self::BAD_CAPTCHA, $res->getErrorCode()); + $service->setParam('error', $res->getErrorCode()); + return false; + } + + return true; + } + + /** + * Render captcha + * + * @param Zend_View_Interface $view + * @param mixed $element + * @return string + */ + public function render(Zend_View_Interface $view = null, $element = null) + { + return $this->getService()->getHTML(); + } +} diff --git a/library/Zend/Captcha/Word.php b/library/Zend/Captcha/Word.php new file mode 100644 index 000000000..b7216b50c --- /dev/null +++ b/library/Zend/Captcha/Word.php @@ -0,0 +1,396 @@ + 'Empty captcha value', + self::MISSING_ID => 'Captcha ID field is missing', + self::BAD_CAPTCHA => 'Captcha value is wrong', + ); + + /** + * Length of the word to generate + * + * @var integer + */ + protected $_wordlen = 8; + + /** + * Retrieve session class to utilize + * + * @return string + */ + public function getSessionClass() + { + return $this->_sessionClass; + } + + /** + * Set session class for persistence + * + * @param string $_sessionClass + * @return Zend_Captcha_Word + */ + public function setSessionClass($_sessionClass) + { + $this->_sessionClass = $_sessionClass; + return $this; + } + + /** + * Retrieve word length to use when genrating captcha + * + * @return integer + */ + public function getWordlen() + { + return $this->_wordlen; + } + + /** + * Set word length of captcha + * + * @param integer $wordlen + * @return Zend_Captcha_Word + */ + public function setWordlen($wordlen) + { + $this->_wordlen = $wordlen; + return $this; + } + + /** + * Retrieve captcha ID + * + * @return string + */ + public function getId () + { + if (null === $this->_id) { + $this->_setId($this->_generateRandomId()); + } + return $this->_id; + } + + /** + * Set captcha identifier + * + * @param string $id + * return Zend_Captcha_Word + */ + protected function _setId ($id) + { + $this->_id = $id; + return $this; + } + + /** + * Set timeout for session token + * + * @param int $ttl + * @return Zend_Captcha_Word + */ + public function setTimeout($ttl) + { + $this->_timeout = (int) $ttl; + return $this; + } + + /** + * Get session token timeout + * + * @return int + */ + public function getTimeout() + { + return $this->_timeout; + } + + /** + * Sets if session should be preserved on generate() + * + * @param $keepSession Should session be kept on generate()? + * @return Zend_Captcha_Word + */ + public function setKeepSession($keepSession) + { + $this->_keepSession = $keepSession; + return $this; + } + + /** + * Get session object + * + * @return Zend_Session_Namespace + */ + public function getSession() + { + if (!isset($this->_session) || (null === $this->_session)) { + $id = $this->getId(); + if (!class_exists($this->_sessionClass)) { + require_once 'Zend/Loader.php'; + Zend_Loader::loadClass($this->_sessionClass); + } + $this->_session = new $this->_sessionClass('Zend_Form_Captcha_' . $id); + $this->_session->setExpirationHops(1, null, true); + $this->_session->setExpirationSeconds($this->getTimeout()); + } + return $this->_session; + } + + /** + * Set session namespace object + * + * @param Zend_Session_Namespace $session + * @return Zend_Captcha_Word + */ + public function setSession(Zend_Session_Namespace $session) + { + $this->_session = $session; + if($session) { + $this->_keepSession = true; + } + return $this; + } + + /** + * Get captcha word + * + * @return string + */ + public function getWord() + { + if (empty($this->_word)) { + $session = $this->getSession(); + $this->_word = $session->word; + } + return $this->_word; + } + + /** + * Set captcha word + * + * @param string $word + * @return Zend_Captcha_Word + */ + protected function _setWord($word) + { + $session = $this->getSession(); + $session->word = $word; + $this->_word = $word; + return $this; + } + + /** + * Generate new random word + * + * @return string + */ + protected function _generateWord() + { + $word = ''; + $wordLen = $this->getWordLen(); + $vowels = $this->_useNumbers ? self::$VN : self::$V; + $consonants = $this->_useNumbers ? self::$CN : self::$C; + + for ($i=0; $i < $wordLen; $i = $i + 2) { + // generate word with mix of vowels and consonants + $consonant = $consonants[array_rand($consonants)]; + $vowel = $vowels[array_rand($vowels)]; + $word .= $consonant . $vowel; + } + + if (strlen($word) > $wordLen) { + $word = substr($word, 0, $wordLen); + } + + return $word; + } + + /** + * Generate new session ID and new word + * + * @return string session ID + */ + public function generate() + { + if(!$this->_keepSession) { + $this->_session = null; + } + $id = $this->_generateRandomId(); + $this->_setId($id); + $word = $this->_generateWord(); + $this->_setWord($word); + return $id; + } + + protected function _generateRandomId() + { + return md5(mt_rand(0, 1000) . microtime(true)); + } + + /** + * Validate the word + * + * @see Zend_Validate_Interface::isValid() + * @param mixed $value + * @return boolean + */ + public function isValid($value, $context = null) + { + if (!is_array($value) && !is_array($context)) { + $this->_error(self::MISSING_VALUE); + return false; + } + if (!is_array($value) && is_array($context)) { + $value = $context; + } + + $name = $this->getName(); + + if (isset($value[$name])) { + $value = $value[$name]; + } + + if (!isset($value['input'])) { + $this->_error(self::MISSING_VALUE); + return false; + } + $input = strtolower($value['input']); + $this->_setValue($input); + + if (!isset($value['id'])) { + $this->_error(self::MISSING_ID); + return false; + } + + $this->_id = $value['id']; + if ($input !== $this->getWord()) { + $this->_error(self::BAD_CAPTCHA); + return false; + } + + return true; + } + + /** + * Get captcha decorator + * + * @return string + */ + public function getDecorator() + { + return "Captcha_Word"; + } +} diff --git a/library/Zend/CodeGenerator/Abstract.php b/library/Zend/CodeGenerator/Abstract.php new file mode 100644 index 000000000..c74a397c6 --- /dev/null +++ b/library/Zend/CodeGenerator/Abstract.php @@ -0,0 +1,147 @@ +_init(); + if ($options != null) { + // use Zend_Config objects if provided + if ($options instanceof Zend_Config) { + $options = $options->toArray(); + } + // pass arrays to setOptions + if (is_array($options)) { + $this->setOptions($options); + } + } + $this->_prepare(); + } + + /** + * setConfig() + * + * @param Zend_Config $config + * @return Zend_CodeGenerator_Abstract + */ + public function setConfig(Zend_Config $config) + { + $this->setOptions($config->toArray()); + return $this; + } + + /** + * setOptions() + * + * @param array $options + * @return Zend_CodeGenerator_Abstract + */ + public function setOptions(Array $options) + { + foreach ($options as $optionName => $optionValue) { + $methodName = 'set' . $optionName; + if (method_exists($this, $methodName)) { + $this->{$methodName}($optionValue); + } + } + return $this; + } + + /** + * setSourceContent() + * + * @param string $sourceContent + */ + public function setSourceContent($sourceContent) + { + $this->_sourceContent = $sourceContent; + return; + } + + /** + * getSourceContent() + * + * @return string + */ + public function getSourceContent() + { + return $this->_sourceContent; + } + + /** + * _init() - this is called before the constuctor + * + */ + protected function _init() + { + + } + + /** + * _prepare() - this is called at construction completion + * + */ + protected function _prepare() + { + + } + + /** + * generate() - must be implemented by the child + * + */ + abstract public function generate(); + + /** + * __toString() - casting to a string will in turn call generate() + * + * @return string + */ + final public function __toString() + { + return $this->generate(); + } + +} diff --git a/library/Zend/CodeGenerator/Exception.php b/library/Zend/CodeGenerator/Exception.php new file mode 100644 index 000000000..7e9ad09d4 --- /dev/null +++ b/library/Zend/CodeGenerator/Exception.php @@ -0,0 +1,35 @@ +_isSourceDirty = ($isSourceDirty) ? true : false; + return $this; + } + + /** + * isSourceDirty() + * + * @return bool + */ + public function isSourceDirty() + { + return $this->_isSourceDirty; + } + + /** + * setIndentation() + * + * @param string|int $indentation + * @return Zend_CodeGenerator_Php_Abstract + */ + public function setIndentation($indentation) + { + $this->_indentation = $indentation; + return $this; + } + + /** + * getIndentation() + * + * @return string|int + */ + public function getIndentation() + { + return $this->_indentation; + } + +} diff --git a/library/Zend/CodeGenerator/Php/Body.php b/library/Zend/CodeGenerator/Php/Body.php new file mode 100644 index 000000000..8526ba4e3 --- /dev/null +++ b/library/Zend/CodeGenerator/Php/Body.php @@ -0,0 +1,73 @@ +_content = $content; + return $this; + } + + /** + * getContent() + * + * @return string + */ + public function getContent() + { + return (string) $this->_content; + } + + /** + * generate() + * + * @return string + */ + public function generate() + { + return $this->getContent(); + } +} diff --git a/library/Zend/CodeGenerator/Php/Class.php b/library/Zend/CodeGenerator/Php/Class.php new file mode 100644 index 000000000..307a4b0c6 --- /dev/null +++ b/library/Zend/CodeGenerator/Php/Class.php @@ -0,0 +1,513 @@ +setSourceContent($class->getSourceContent()); + $class->setSourceDirty(false); + + if ($reflectionClass->getDocComment() != '') { + $class->setDocblock(Zend_CodeGenerator_Php_Docblock::fromReflection($reflectionClass->getDocblock())); + } + + $class->setAbstract($reflectionClass->isAbstract()); + $class->setName($reflectionClass->getName()); + + if ($parentClass = $reflectionClass->getParentClass()) { + $class->setExtendedClass($parentClass->getName()); + $interfaces = array_diff($reflectionClass->getInterfaces(), $parentClass->getInterfaces()); + } else { + $interfaces = $reflectionClass->getInterfaces(); + } + + $interfaceNames = array(); + foreach($interfaces AS $interface) { + $interfaceNames[] = $interface->getName(); + } + + $class->setImplementedInterfaces($interfaceNames); + + $properties = array(); + foreach ($reflectionClass->getProperties() as $reflectionProperty) { + if ($reflectionProperty->getDeclaringClass()->getName() == $class->getName()) { + $properties[] = Zend_CodeGenerator_Php_Property::fromReflection($reflectionProperty); + } + } + $class->setProperties($properties); + + $methods = array(); + foreach ($reflectionClass->getMethods() as $reflectionMethod) { + if ($reflectionMethod->getDeclaringClass()->getName() == $class->getName()) { + $methods[] = Zend_CodeGenerator_Php_Method::fromReflection($reflectionMethod); + } + } + $class->setMethods($methods); + + return $class; + } + + /** + * setDocblock() Set the docblock + * + * @param Zend_CodeGenerator_Php_Docblock|array|string $docblock + * @return Zend_CodeGenerator_Php_File + */ + public function setDocblock($docblock) + { + if (is_string($docblock)) { + $docblock = array('shortDescription' => $docblock); + } + + if (is_array($docblock)) { + $docblock = new Zend_CodeGenerator_Php_Docblock($docblock); + } elseif (!$docblock instanceof Zend_CodeGenerator_Php_Docblock) { + require_once 'Zend/CodeGenerator/Php/Exception.php'; + throw new Zend_CodeGenerator_Php_Exception('setDocblock() is expecting either a string, array or an instance of Zend_CodeGenerator_Php_Docblock'); + } + + $this->_docblock = $docblock; + return $this; + } + + /** + * getDocblock() + * + * @return Zend_CodeGenerator_Php_Docblock + */ + public function getDocblock() + { + return $this->_docblock; + } + + /** + * setName() + * + * @param string $name + * @return Zend_CodeGenerator_Php_Class + */ + public function setName($name) + { + $this->_name = $name; + return $this; + } + + /** + * getName() + * + * @return string + */ + public function getName() + { + return $this->_name; + } + + /** + * setAbstract() + * + * @param bool $isAbstract + * @return Zend_CodeGenerator_Php_Class + */ + public function setAbstract($isAbstract) + { + $this->_isAbstract = ($isAbstract) ? true : false; + return $this; + } + + /** + * isAbstract() + * + * @return bool + */ + public function isAbstract() + { + return $this->_isAbstract; + } + + /** + * setExtendedClass() + * + * @param string $extendedClass + * @return Zend_CodeGenerator_Php_Class + */ + public function setExtendedClass($extendedClass) + { + $this->_extendedClass = $extendedClass; + return $this; + } + + /** + * getExtendedClass() + * + * @return string + */ + public function getExtendedClass() + { + return $this->_extendedClass; + } + + /** + * setImplementedInterfaces() + * + * @param array $implementedInterfaces + * @return Zend_CodeGenerator_Php_Class + */ + public function setImplementedInterfaces(Array $implementedInterfaces) + { + $this->_implementedInterfaces = $implementedInterfaces; + return $this; + } + + /** + * getImplementedInterfaces + * + * @return array + */ + public function getImplementedInterfaces() + { + return $this->_implementedInterfaces; + } + + /** + * setProperties() + * + * @param array $properties + * @return Zend_CodeGenerator_Php_Class + */ + public function setProperties(Array $properties) + { + foreach ($properties as $property) { + $this->setProperty($property); + } + + return $this; + } + + /** + * setProperty() + * + * @param array|Zend_CodeGenerator_Php_Property $property + * @return Zend_CodeGenerator_Php_Class + */ + public function setProperty($property) + { + if (is_array($property)) { + $property = new Zend_CodeGenerator_Php_Property($property); + $propertyName = $property->getName(); + } elseif ($property instanceof Zend_CodeGenerator_Php_Property) { + $propertyName = $property->getName(); + } else { + require_once 'Zend/CodeGenerator/Php/Exception.php'; + throw new Zend_CodeGenerator_Php_Exception('setProperty() expects either an array of property options or an instance of Zend_CodeGenerator_Php_Property'); + } + + if (isset($this->_properties[$propertyName])) { + require_once 'Zend/CodeGenerator/Php/Exception.php'; + throw new Zend_CodeGenerator_Php_Exception('A property by name ' . $propertyName . ' already exists in this class.'); + } + + $this->_properties[$propertyName] = $property; + return $this; + } + + /** + * getProperties() + * + * @return array + */ + public function getProperties() + { + return $this->_properties; + } + + /** + * getProperty() + * + * @param string $propertyName + * @return Zend_CodeGenerator_Php_Property + */ + public function getProperty($propertyName) + { + foreach ($this->_properties as $property) { + if ($property->getName() == $propertyName) { + return $property; + } + } + return false; + } + + /** + * hasProperty() + * + * @param string $propertyName + * @return bool + */ + public function hasProperty($propertyName) + { + return isset($this->_properties[$propertyName]); + } + + /** + * setMethods() + * + * @param array $methods + * @return Zend_CodeGenerator_Php_Class + */ + public function setMethods(Array $methods) + { + foreach ($methods as $method) { + $this->setMethod($method); + } + return $this; + } + + /** + * setMethod() + * + * @param array|Zend_CodeGenerator_Php_Method $method + * @return Zend_CodeGenerator_Php_Class + */ + public function setMethod($method) + { + if (is_array($method)) { + $method = new Zend_CodeGenerator_Php_Method($method); + $methodName = $method->getName(); + } elseif ($method instanceof Zend_CodeGenerator_Php_Method) { + $methodName = $method->getName(); + } else { + require_once 'Zend/CodeGenerator/Php/Exception.php'; + throw new Zend_CodeGenerator_Php_Exception('setMethod() expects either an array of method options or an instance of Zend_CodeGenerator_Php_Method'); + } + + if (isset($this->_methods[$methodName])) { + require_once 'Zend/CodeGenerator/Php/Exception.php'; + throw new Zend_CodeGenerator_Php_Exception('A method by name ' . $methodName . ' already exists in this class.'); + } + + $this->_methods[$methodName] = $method; + return $this; + } + + /** + * getMethods() + * + * @return array + */ + public function getMethods() + { + return $this->_methods; + } + + /** + * getMethod() + * + * @param string $methodName + * @return Zend_CodeGenerator_Php_Method + */ + public function getMethod($methodName) + { + foreach ($this->_methods as $method) { + if ($method->getName() == $methodName) { + return $method; + } + } + return false; + } + + /** + * hasMethod() + * + * @param string $methodName + * @return bool + */ + public function hasMethod($methodName) + { + return isset($this->_methods[$methodName]); + } + + /** + * isSourceDirty() + * + * @return bool + */ + public function isSourceDirty() + { + if (($docblock = $this->getDocblock()) && $docblock->isSourceDirty()) { + return true; + } + + foreach ($this->_properties as $property) { + if ($property->isSourceDirty()) { + return true; + } + } + + foreach ($this->_methods as $method) { + if ($method->isSourceDirty()) { + return true; + } + } + + return parent::isSourceDirty(); + } + + /** + * generate() + * + * @return string + */ + public function generate() + { + if (!$this->isSourceDirty()) { + return $this->getSourceContent(); + } + + $output = ''; + + if (null !== ($docblock = $this->getDocblock())) { + $docblock->setIndentation(''); + $output .= $docblock->generate(); + } + + if ($this->isAbstract()) { + $output .= 'abstract '; + } + + $output .= 'class ' . $this->getName(); + + if (null !== ($ec = $this->_extendedClass)) { + $output .= ' extends ' . $ec; + } + + $implemented = $this->getImplementedInterfaces(); + if (!empty($implemented)) { + $output .= ' implements ' . implode(', ', $implemented); + } + + $output .= self::LINE_FEED . '{' . self::LINE_FEED . self::LINE_FEED; + + $properties = $this->getProperties(); + if (!empty($properties)) { + foreach ($properties as $property) { + $output .= $property->generate() . self::LINE_FEED . self::LINE_FEED; + } + } + + $methods = $this->getMethods(); + if (!empty($methods)) { + foreach ($methods as $method) { + $output .= $method->generate() . self::LINE_FEED; + } + } + + $output .= self::LINE_FEED . '}' . self::LINE_FEED; + + return $output; + } + + /** + * _init() - is called at construction time + * + */ + protected function _init() + { + $this->_properties = new Zend_CodeGenerator_Php_Member_Container(Zend_CodeGenerator_Php_Member_Container::TYPE_PROPERTY); + $this->_methods = new Zend_CodeGenerator_Php_Member_Container(Zend_CodeGenerator_Php_Member_Container::TYPE_METHOD); + } + +} diff --git a/library/Zend/CodeGenerator/Php/Docblock.php b/library/Zend/CodeGenerator/Php/Docblock.php new file mode 100644 index 000000000..8dec8fa13 --- /dev/null +++ b/library/Zend/CodeGenerator/Php/Docblock.php @@ -0,0 +1,220 @@ +setSourceContent($reflectionDocblock->getContents()); + $docblock->setSourceDirty(false); + + $docblock->setShortDescription($reflectionDocblock->getShortDescription()); + $docblock->setLongDescription($reflectionDocblock->getLongDescription()); + + foreach ($reflectionDocblock->getTags() as $tag) { + $docblock->setTag(Zend_CodeGenerator_Php_Docblock_Tag::fromReflection($tag)); + } + + return $docblock; + } + + /** + * setShortDescription() + * + * @param string $shortDescription + * @return Zend_CodeGenerator_Php_Docblock + */ + public function setShortDescription($shortDescription) + { + $this->_shortDescription = $shortDescription; + return $this; + } + + /** + * getShortDescription() + * + * @return string + */ + public function getShortDescription() + { + return $this->_shortDescription; + } + + /** + * setLongDescription() + * + * @param string $longDescription + * @return Zend_CodeGenerator_Php_Docblock + */ + public function setLongDescription($longDescription) + { + $this->_longDescription = $longDescription; + return $this; + } + + /** + * getLongDescription() + * + * @return string + */ + public function getLongDescription() + { + return $this->_longDescription; + } + + /** + * setTags() + * + * @param array $tags + * @return Zend_CodeGenerator_Php_Docblock + */ + public function setTags(Array $tags) + { + foreach ($tags as $tag) { + $this->setTag($tag); + } + + return $this; + } + + /** + * setTag() + * + * @param array|Zend_CodeGenerator_Php_Docblock_Tag $tag + * @return Zend_CodeGenerator_Php_Docblock + */ + public function setTag($tag) + { + if (is_array($tag)) { + $tag = new Zend_CodeGenerator_Php_Docblock_Tag($tag); + } elseif (!$tag instanceof Zend_CodeGenerator_Php_Docblock_Tag) { + require_once 'Zend/CodeGenerator/Php/Exception.php'; + throw new Zend_CodeGenerator_Php_Exception( + 'setTag() expects either an array of method options or an ' + . 'instance of Zend_CodeGenerator_Php_Docblock_Tag' + ); + } + + $this->_tags[] = $tag; + return $this; + } + + /** + * getTags + * + * @return array Array of Zend_CodeGenerator_Php_Docblock_Tag + */ + public function getTags() + { + return $this->_tags; + } + + /** + * generate() + * + * @return string + */ + public function generate() + { + if (!$this->isSourceDirty()) { + return $this->_docCommentize($this->getSourceContent()); + } + + $output = ''; + if (null !== ($sd = $this->getShortDescription())) { + $output .= $sd . self::LINE_FEED . self::LINE_FEED; + } + if (null !== ($ld = $this->getLongDescription())) { + $output .= $ld . self::LINE_FEED . self::LINE_FEED; + } + + foreach ($this->getTags() as $tag) { + $output .= $tag->generate() . self::LINE_FEED; + } + + return $this->_docCommentize(trim($output)); + } + + /** + * _docCommentize() + * + * @param string $content + * @return string + */ + protected function _docCommentize($content) + { + $indent = $this->getIndentation(); + $output = $indent . '/**' . self::LINE_FEED; + $content = wordwrap($content, 80, self::LINE_FEED); + $lines = explode(self::LINE_FEED, $content); + foreach ($lines as $line) { + $output .= $indent . ' * ' . $line . self::LINE_FEED; + } + $output .= $indent . ' */' . self::LINE_FEED; + return $output; + } + +} diff --git a/library/Zend/CodeGenerator/Php/Docblock/Tag.php b/library/Zend/CodeGenerator/Php/Docblock/Tag.php new file mode 100644 index 000000000..eaf430e48 --- /dev/null +++ b/library/Zend/CodeGenerator/Php/Docblock/Tag.php @@ -0,0 +1,184 @@ +getName(); + + $codeGenDocblockTag = self::factory($tagName); + + // transport any properties via accessors and mutators from reflection to codegen object + $reflectionClass = new ReflectionClass($reflectionTag); + foreach ($reflectionClass->getMethods(ReflectionMethod::IS_PUBLIC) as $method) { + if (substr($method->getName(), 0, 3) == 'get') { + $propertyName = substr($method->getName(), 3); + if (method_exists($codeGenDocblockTag, 'set' . $propertyName)) { + $codeGenDocblockTag->{'set' . $propertyName}($reflectionTag->{'get' . $propertyName}()); + } + } + } + + return $codeGenDocblockTag; + } + + /** + * setPluginLoader() + * + * @param Zend_Loader_PluginLoader $pluginLoader + */ + public static function setPluginLoader(Zend_Loader_PluginLoader $pluginLoader) + { + self::$_pluginLoader = $pluginLoader; + return; + } + + /** + * getPluginLoader() + * + * @return Zend_Loader_PluginLoader + */ + public static function getPluginLoader() + { + if (self::$_pluginLoader == null) { + require_once 'Zend/Loader/PluginLoader.php'; + self::setPluginLoader(new Zend_Loader_PluginLoader(array( + 'Zend_CodeGenerator_Php_Docblock_Tag' => dirname(__FILE__) . '/Tag/')) + ); + } + + return self::$_pluginLoader; + } + + public static function factory($tagName) + { + $pluginLoader = self::getPluginLoader(); + + try { + $tagClass = $pluginLoader->load($tagName); + } catch (Zend_Loader_Exception $exception) { + $tagClass = 'Zend_CodeGenerator_Php_Docblock_Tag'; + } + + $tag = new $tagClass(array('name' => $tagName)); + return $tag; + } + + /** + * setName() + * + * @param string $name + * @return Zend_CodeGenerator_Php_Docblock_Tag + */ + public function setName($name) + { + $this->_name = ltrim($name, '@'); + return $this; + } + + /** + * getName() + * + * @return string + */ + public function getName() + { + return $this->_name; + } + + /** + * setDescription() + * + * @param string $description + * @return Zend_CodeGenerator_Php_Docblock_Tag + */ + public function setDescription($description) + { + $this->_description = $description; + return $this; + } + + /** + * getDescription() + * + * @return string + */ + public function getDescription() + { + return $this->_description; + } + + /** + * generate() + * + * @return string + */ + public function generate() + { + return '@' . $this->_name . ' ' . $this->_description; + } + +} \ No newline at end of file diff --git a/library/Zend/CodeGenerator/Php/Docblock/Tag/License.php b/library/Zend/CodeGenerator/Php/Docblock/Tag/License.php new file mode 100644 index 000000000..dc76cbd78 --- /dev/null +++ b/library/Zend/CodeGenerator/Php/Docblock/Tag/License.php @@ -0,0 +1,98 @@ +setName('license'); + $returnTag->setUrl($reflectionTagLicense->getUrl()); + $returnTag->setDescription($reflectionTagLicense->getDescription()); + + return $returnTag; + } + + /** + * setUrl() + * + * @param string $url + * @return Zend_CodeGenerator_Php_Docblock_Tag_License + */ + public function setUrl($url) + { + $this->_url = $url; + return $this; + } + + /** + * getUrl() + * + * @return string + */ + public function getUrl() + { + return $this->_url; + } + + + /** + * generate() + * + * @return string + */ + public function generate() + { + $output = '@license ' . $this->_url . ' ' . $this->_description . self::LINE_FEED; + return $output; + } + +} \ No newline at end of file diff --git a/library/Zend/CodeGenerator/Php/Docblock/Tag/Param.php b/library/Zend/CodeGenerator/Php/Docblock/Tag/Param.php new file mode 100644 index 000000000..4d14be5e9 --- /dev/null +++ b/library/Zend/CodeGenerator/Php/Docblock/Tag/Param.php @@ -0,0 +1,128 @@ +setName('param'); + $paramTag->setDatatype($reflectionTagParam->getType()); // @todo rename + $paramTag->setParamName($reflectionTagParam->getVariableName()); + $paramTag->setDescription($reflectionTagParam->getDescription()); + + return $paramTag; + } + + /** + * setDatatype() + * + * @param string $datatype + * @return Zend_CodeGenerator_Php_Docblock_Tag_Param + */ + public function setDatatype($datatype) + { + $this->_datatype = $datatype; + return $this; + } + + /** + * getDatatype + * + * @return string + */ + public function getDatatype() + { + return $this->_datatype; + } + + /** + * setParamName() + * + * @param string $paramName + * @return Zend_CodeGenerator_Php_Docblock_Tag_Param + */ + public function setParamName($paramName) + { + $this->_paramName = $paramName; + return $this; + } + + /** + * getParamName() + * + * @return string + */ + public function getParamName() + { + return $this->_paramName; + } + + /** + * generate() + * + * @return string + */ + public function generate() + { + $output = '@param ' + . (($this->_datatype != null) ? $this->_datatype : 'unknown') + . (($this->_paramName != null) ? ' $' . $this->_paramName : '') + . (($this->_description != null) ? ' ' . $this->_description : ''); + return $output; + } + +} diff --git a/library/Zend/CodeGenerator/Php/Docblock/Tag/Return.php b/library/Zend/CodeGenerator/Php/Docblock/Tag/Return.php new file mode 100644 index 000000000..b0acb60a8 --- /dev/null +++ b/library/Zend/CodeGenerator/Php/Docblock/Tag/Return.php @@ -0,0 +1,98 @@ +setName('return'); + $returnTag->setDatatype($reflectionTagReturn->getType()); // @todo rename + $returnTag->setDescription($reflectionTagReturn->getDescription()); + + return $returnTag; + } + + /** + * setDatatype() + * + * @param string $datatype + * @return Zend_CodeGenerator_Php_Docblock_Tag_Return + */ + public function setDatatype($datatype) + { + $this->_datatype = $datatype; + return $this; + } + + /** + * getDatatype() + * + * @return string + */ + public function getDatatype() + { + return $this->_datatype; + } + + + /** + * generate() + * + * @return string + */ + public function generate() + { + $output = '@return ' . $this->_datatype . ' ' . $this->_description; + return $output; + } + +} \ No newline at end of file diff --git a/library/Zend/CodeGenerator/Php/Exception.php b/library/Zend/CodeGenerator/Php/Exception.php new file mode 100644 index 000000000..70622d2d8 --- /dev/null +++ b/library/Zend/CodeGenerator/Php/Exception.php @@ -0,0 +1,37 @@ +getFilename(); + } + + if ($fileName == '') { + require_once 'Zend/CodeGenerator/Php/Exception.php'; + throw new Zend_CodeGenerator_Php_Exception('FileName does not exist.'); + } + + // cannot use realpath since the file might not exist, but we do need to have the index + // in the same DIRECTORY_SEPARATOR that realpath would use: + $fileName = str_replace(array('\\', '/'), DIRECTORY_SEPARATOR, $fileName); + + self::$_fileCodeGenerators[$fileName] = $fileCodeGenerator; + + } + + /** + * fromReflectedFilePath() - use this if you intend on generating code generation objects based on the same file. + * This will keep previous changes to the file in tact during the same PHP process + * + * @param string $filePath + * @param bool $usePreviousCodeGeneratorIfItExists + * @param bool $includeIfNotAlreadyIncluded + * @return Zend_CodeGenerator_Php_File + */ + public static function fromReflectedFileName($filePath, $usePreviousCodeGeneratorIfItExists = true, $includeIfNotAlreadyIncluded = true) + { + $realpath = realpath($filePath); + + if ($realpath === false) { + if ( ($realpath = Zend_Reflection_file::findRealpathInIncludePath($filePath)) === false) { + require_once 'Zend/CodeGenerator/Php/Exception.php'; + throw new Zend_CodeGenerator_Php_Exception('No file for ' . $realpath . ' was found.'); + } + } + + if ($usePreviousCodeGeneratorIfItExists && isset(self::$_fileCodeGenerators[$realpath])) { + return self::$_fileCodeGenerators[$realpath]; + } + + if ($includeIfNotAlreadyIncluded && !in_array($realpath, get_included_files())) { + include $realpath; + } + + $codeGenerator = self::fromReflection(($fileReflector = new Zend_Reflection_File($realpath))); + + if (!isset(self::$_fileCodeGenerators[$fileReflector->getFileName()])) { + self::$_fileCodeGenerators[$fileReflector->getFileName()] = $codeGenerator; + } + + return $codeGenerator; + } + + /** + * fromReflection() + * + * @param Zend_Reflection_File $reflectionFile + * @return Zend_CodeGenerator_Php_File + */ + public static function fromReflection(Zend_Reflection_File $reflectionFile) + { + $file = new self(); + + $file->setSourceContent($reflectionFile->getContents()); + $file->setSourceDirty(false); + + $body = $reflectionFile->getContents(); + + // @todo this whole area needs to be reworked with respect to how body lines are processed + foreach ($reflectionFile->getClasses() as $class) { + $file->setClass(Zend_CodeGenerator_Php_Class::fromReflection($class)); + $classStartLine = $class->getStartLine(true); + $classEndLine = $class->getEndLine(); + + $bodyLines = explode("\n", $body); + $bodyReturn = array(); + for ($lineNum = 1; $lineNum <= count($bodyLines); $lineNum++) { + if ($lineNum == $classStartLine) { + $bodyReturn[] = str_replace('?', $class->getName(), self::$_markerClass); //'/* Zend_CodeGenerator_Php_File-ClassMarker: {' . $class->getName() . '} */'; + $lineNum = $classEndLine; + } else { + $bodyReturn[] = $bodyLines[$lineNum - 1]; // adjust for index -> line conversion + } + } + $body = implode("\n", $bodyReturn); + unset($bodyLines, $bodyReturn, $classStartLine, $classEndLine); + } + + if (($reflectionFile->getDocComment() != '')) { + $docblock = $reflectionFile->getDocblock(); + $file->setDocblock(Zend_CodeGenerator_Php_Docblock::fromReflection($docblock)); + + $bodyLines = explode("\n", $body); + $bodyReturn = array(); + for ($lineNum = 1; $lineNum <= count($bodyLines); $lineNum++) { + if ($lineNum == $docblock->getStartLine()) { + $bodyReturn[] = str_replace('?', $class->getName(), self::$_markerDocblock); //'/* Zend_CodeGenerator_Php_File-ClassMarker: {' . $class->getName() . '} */'; + $lineNum = $docblock->getEndLine(); + } else { + $bodyReturn[] = $bodyLines[$lineNum - 1]; // adjust for index -> line conversion + } + } + $body = implode("\n", $bodyReturn); + unset($bodyLines, $bodyReturn, $classStartLine, $classEndLine); + } + + $file->setBody($body); + + return $file; + } + + /** + * setDocblock() Set the docblock + * + * @param Zend_CodeGenerator_Php_Docblock|array|string $docblock + * @return Zend_CodeGenerator_Php_File + */ + public function setDocblock($docblock) + { + if (is_string($docblock)) { + $docblock = array('shortDescription' => $docblock); + } + + if (is_array($docblock)) { + $docblock = new Zend_CodeGenerator_Php_Docblock($docblock); + } elseif (!$docblock instanceof Zend_CodeGenerator_Php_Docblock) { + require_once 'Zend/CodeGenerator/Php/Exception.php'; + throw new Zend_CodeGenerator_Php_Exception('setDocblock() is expecting either a string, array or an instance of Zend_CodeGenerator_Php_Docblock'); + } + + $this->_docblock = $docblock; + return $this; + } + + /** + * Get docblock + * + * @return Zend_CodeGenerator_Php_Docblock + */ + public function getDocblock() + { + return $this->_docblock; + } + + /** + * setRequiredFiles + * + * @param array $requiredFiles + * @return Zend_CodeGenerator_Php_File + */ + public function setRequiredFiles($requiredFiles) + { + $this->_requiredFiles = $requiredFiles; + return $this; + } + + /** + * getRequiredFiles() + * + * @return array + */ + public function getRequiredFiles() + { + return $this->_requiredFiles; + } + + /** + * setClasses() + * + * @param array $classes + * @return Zend_CodeGenerator_Php_File + */ + public function setClasses(Array $classes) + { + foreach ($classes as $class) { + $this->setClass($class); + } + return $this; + } + + /** + * getClass() + * + * @param string $name + * @return Zend_CodeGenerator_Php_Class + */ + public function getClass($name = null) + { + if ($name == null) { + reset($this->_classes); + return current($this->_classes); + } + + return $this->_classes[$name]; + } + + /** + * setClass() + * + * @param Zend_CodeGenerator_Php_Class|array $class + * @return Zend_CodeGenerator_Php_File + */ + public function setClass($class) + { + if (is_array($class)) { + $class = new Zend_CodeGenerator_Php_Class($class); + $className = $class->getName(); + } elseif ($class instanceof Zend_CodeGenerator_Php_Class) { + $className = $class->getName(); + } else { + require_once 'Zend/CodeGenerator/Php/Exception.php'; + throw new Zend_CodeGenerator_Php_Exception('Expecting either an array or an instance of Zend_CodeGenerator_Php_Class'); + } + + // @todo check for dup here + + $this->_classes[$className] = $class; + return $this; + } + + /** + * setFilename() + * + * @param string $filename + * @return Zend_CodeGenerator_Php_File + */ + public function setFilename($filename) + { + $this->_filename = $filename; + return $this; + } + + /** + * getFilename() + * + * @return string + */ + public function getFilename() + { + return $this->_filename; + } + + /** + * getClasses() + * + * @return array Array of Zend_CodeGenerator_Php_Class + */ + public function getClasses() + { + return $this->_classes; + } + + /** + * setBody() + * + * @param string $body + * @return Zend_CodeGenerator_Php_File + */ + public function setBody($body) + { + $this->_body = $body; + return $this; + } + + /** + * getBody() + * + * @return string + */ + public function getBody() + { + return $this->_body; + } + + /** + * isSourceDirty() + * + * @return bool + */ + public function isSourceDirty() + { + if (($docblock = $this->getDocblock()) && $docblock->isSourceDirty()) { + return true; + } + + foreach ($this->_classes as $class) { + if ($class->isSourceDirty()) { + return true; + } + } + + return parent::isSourceDirty(); + } + + /** + * generate() + * + * @return string + */ + public function generate() + { + if ($this->isSourceDirty() === false) { + return $this->_sourceContent; + } + + $output = ''; + + // start with the body (if there), or open tag + if (preg_match('#(?:\s*)<\?php#', $this->getBody()) == false) { + $output = 'getBody(); + if (preg_match('#/\* Zend_CodeGenerator_Php_File-(.*?)Marker:#', $body)) { + $output .= $body; + $body = ''; + } + + // Add file docblock, if any + if (null !== ($docblock = $this->getDocblock())) { + $docblock->setIndentation(''); + $regex = preg_quote(self::$_markerDocblock, '#'); + if (preg_match('#'.$regex.'#', $output)) { + $output = preg_replace('#'.$regex.'#', $docblock->generate(), $output, 1); + } else { + $output .= $docblock->generate() . self::LINE_FEED; + } + } + + // newline + $output .= self::LINE_FEED; + + // process required files + // @todo marker replacement for required files + $requiredFiles = $this->getRequiredFiles(); + if (!empty($requiredFiles)) { + foreach ($requiredFiles as $requiredFile) { + $output .= 'require_once \'' . $requiredFile . '\';' . self::LINE_FEED; + } + + $output .= self::LINE_FEED; + } + + // process classes + $classes = $this->getClasses(); + if (!empty($classes)) { + foreach ($classes as $class) { + $regex = str_replace('?', $class->getName(), self::$_markerClass); + $regex = preg_quote($regex, '#'); + if (preg_match('#'.$regex.'#', $output)) { + $output = preg_replace('#'.$regex.'#', $class->generate(), $output, 1); + } else { + $output .= $class->generate() . self::LINE_FEED; + } + } + + } + + if (!empty($body)) { + + // add an extra space betwee clsses and + if (!empty($classes)) { + $output .= self::LINE_FEED; + } + + $output .= $body; + } + + return $output; + } + + public function write() + { + if ($this->_filename == '' || !is_writable(dirname($this->_filename))) { + require_once 'Zend/CodeGenerator/Php/Exception.php'; + throw new Zend_CodeGenerator_Php_Exception('This code generator object is not writable.'); + } + file_put_contents($this->_filename, $this->generate()); + return $this; + } + +} diff --git a/library/Zend/CodeGenerator/Php/Member/Abstract.php b/library/Zend/CodeGenerator/Php/Member/Abstract.php new file mode 100644 index 000000000..2df8fd18f --- /dev/null +++ b/library/Zend/CodeGenerator/Php/Member/Abstract.php @@ -0,0 +1,222 @@ + $docblock); + } + + if (is_array($docblock)) { + $docblock = new Zend_CodeGenerator_Php_Docblock($docblock); + } elseif (!$docblock instanceof Zend_CodeGenerator_Php_Docblock) { + require_once 'Zend/CodeGenerator/Php/Exception.php'; + throw new Zend_CodeGenerator_Php_Exception('setDocblock() is expecting either a string, array or an instance of Zend_CodeGenerator_Php_Docblock'); + } + + $this->_docblock = $docblock; + return $this; + } + + /** + * getDocblock() + * + * @return Zend_CodeGenerator_Php_Docblock + */ + public function getDocblock() + { + return $this->_docblock; + } + + /** + * setAbstract() + * + * @param bool $isAbstract + * @return Zend_CodeGenerator_Php_Member_Abstract + */ + public function setAbstract($isAbstract) + { + $this->_isAbstract = ($isAbstract) ? true : false; + return $this; + } + + /** + * isAbstract() + * + * @return bool + */ + public function isAbstract() + { + return $this->_isAbstract; + } + + /** + * setFinal() + * + * @param bool $isFinal + * @return Zend_CodeGenerator_Php_Member_Abstract + */ + public function setFinal($isFinal) + { + $this->_isFinal = ($isFinal) ? true : false; + return $this; + } + + /** + * isFinal() + * + * @return bool + */ + public function isFinal() + { + return $this->_isFinal; + } + + /** + * setStatic() + * + * @param bool $isStatic + * @return Zend_CodeGenerator_Php_Member_Abstract + */ + public function setStatic($isStatic) + { + $this->_isStatic = ($isStatic) ? true : false; + return $this; + } + + /** + * isStatic() + * + * @return bool + */ + public function isStatic() + { + return $this->_isStatic; + } + + /** + * setVisitibility() + * + * @param const $visibility + * @return Zend_CodeGenerator_Php_Member_Abstract + */ + public function setVisibility($visibility) + { + $this->_visibility = $visibility; + return $this; + } + + /** + * getVisibility() + * + * @return const + */ + public function getVisibility() + { + return $this->_visibility; + } + + /** + * setName() + * + * @param string $name + * @return Zend_CodeGenerator_Php_Member_Abstract + */ + public function setName($name) + { + $this->_name = $name; + return $this; + } + + /** + * getName() + * + * @return string + */ + public function getName() + { + return $this->_name; + } +} diff --git a/library/Zend/CodeGenerator/Php/Member/Container.php b/library/Zend/CodeGenerator/Php/Member/Container.php new file mode 100644 index 000000000..e0e613291 --- /dev/null +++ b/library/Zend/CodeGenerator/Php/Member/Container.php @@ -0,0 +1,55 @@ +_type = $type; + parent::__construct(array(), self::ARRAY_AS_PROPS); + } + +} \ No newline at end of file diff --git a/library/Zend/CodeGenerator/Php/Method.php b/library/Zend/CodeGenerator/Php/Method.php new file mode 100644 index 000000000..f02b3ef21 --- /dev/null +++ b/library/Zend/CodeGenerator/Php/Method.php @@ -0,0 +1,234 @@ +setSourceContent($reflectionMethod->getContents(false)); + $method->setSourceDirty(false); + + if ($reflectionMethod->getDocComment() != '') { + $method->setDocblock(Zend_CodeGenerator_Php_Docblock::fromReflection($reflectionMethod->getDocblock())); + } + + $method->setFinal($reflectionMethod->isFinal()); + + if ($reflectionMethod->isPrivate()) { + $method->setVisibility(self::VISIBILITY_PRIVATE); + } elseif ($reflectionMethod->isProtected()) { + $method->setVisibility(self::VISIBILITY_PROTECTED); + } else { + $method->setVisibility(self::VISIBILITY_PUBLIC); + } + + $method->setStatic($reflectionMethod->isStatic()); + + $method->setName($reflectionMethod->getName()); + + foreach ($reflectionMethod->getParameters() as $reflectionParameter) { + $method->setParameter(Zend_CodeGenerator_Php_Parameter::fromReflection($reflectionParameter)); + } + + $method->setBody($reflectionMethod->getBody()); + + return $method; + } + + /** + * setFinal() + * + * @param bool $isFinal + */ + public function setFinal($isFinal) + { + $this->_isFinal = ($isFinal) ? true : false; + } + + /** + * setParameters() + * + * @param array $parameters + * @return Zend_CodeGenerator_Php_Method + */ + public function setParameters(Array $parameters) + { + foreach ($parameters as $parameter) { + $this->setParameter($parameter); + } + return $this; + } + + /** + * setParameter() + * + * @param Zend_CodeGenerator_Php_Parameter|array $parameter + * @return Zend_CodeGenerator_Php_Method + */ + public function setParameter($parameter) + { + if (is_array($parameter)) { + $parameter = new Zend_CodeGenerator_Php_Parameter($parameter); + $parameterName = $parameter->getName(); + } elseif ($parameter instanceof Zend_CodeGenerator_Php_Parameter) { + $parameterName = $parameter->getName(); + } else { + require_once 'Zend/CodeGenerator/Php/Exception.php'; + throw new Zend_CodeGenerator_Php_Exception('setParameter() expects either an array of method options or an instance of Zend_CodeGenerator_Php_Parameter'); + } + + $this->_parameters[$parameterName] = $parameter; + return $this; + } + + /** + * getParameters() + * + * @return array Array of Zend_CodeGenerator_Php_Parameter + */ + public function getParameters() + { + return $this->_parameters; + } + + /** + * setBody() + * + * @param string $body + * @return Zend_CodeGenerator_Php_Method + */ + public function setBody($body) + { + $this->_body = $body; + return $this; + } + + /** + * getBody() + * + * @return string + */ + public function getBody() + { + return $this->_body; + } + + /** + * generate() + * + * @return string + */ + public function generate() + { + $output = ''; + + $indent = $this->getIndentation(); + + if (($docblock = $this->getDocblock()) !== null) { + $docblock->setIndentation($indent); + $output .= $docblock->generate(); + } + + $output .= $indent; + + if ($this->isAbstract()) { + $output .= 'abstract '; + } else { + $output .= (($this->isFinal()) ? 'final ' : ''); + } + + $output .= $this->getVisibility() + . (($this->isStatic()) ? ' static' : '') + . ' function ' . $this->getName() . '('; + + $parameters = $this->getParameters(); + if (!empty($parameters)) { + foreach ($parameters as $parameter) { + $parameterOuput[] = $parameter->generate(); + } + + $output .= implode(', ', $parameterOuput); + } + + $output .= ')' . self::LINE_FEED . $indent . '{' . self::LINE_FEED; + + if ($this->_body) { + $output .= ' ' + . str_replace(self::LINE_FEED, self::LINE_FEED . $indent . $indent, trim($this->_body)) + . self::LINE_FEED; + } + + $output .= $indent . '}' . self::LINE_FEED; + + return $output; + } + +} diff --git a/library/Zend/CodeGenerator/Php/Parameter.php b/library/Zend/CodeGenerator/Php/Parameter.php new file mode 100644 index 000000000..321ab2495 --- /dev/null +++ b/library/Zend/CodeGenerator/Php/Parameter.php @@ -0,0 +1,250 @@ +setName($reflectionParameter->getName()); + + if($reflectionParameter->isArray()) { + $param->setType('array'); + } else { + $typeClass = $reflectionParameter->getClass(); + if($typeClass !== null) { + $param->setType($typeClass->getName()); + } + } + + $param->setPosition($reflectionParameter->getPosition()); + + if($reflectionParameter->isOptional()) { + $param->setDefaultValue($reflectionParameter->getDefaultValue()); + } + $param->setPassedByReference($reflectionParameter->isPassedByReference()); + + return $param; + } + + /** + * setType() + * + * @param string $type + * @return Zend_CodeGenerator_Php_Parameter + */ + public function setType($type) + { + $this->_type = $type; + return $this; + } + + /** + * getType() + * + * @return string + */ + public function getType() + { + return $this->_type; + } + + /** + * setName() + * + * @param string $name + * @return Zend_CodeGenerator_Php_Parameter + */ + public function setName($name) + { + $this->_name = $name; + return $this; + } + + /** + * getName() + * + * @return string + */ + public function getName() + { + return $this->_name; + } + + /** + * Set the default value of the parameter. + * + * Certain variables are difficult to expres + * + * @param null|bool|string|int|float|Zend_CodeGenerator_Php_Parameter_DefaultValue $defaultValue + * @return Zend_CodeGenerator_Php_Parameter + */ + public function setDefaultValue($defaultValue) + { + if($defaultValue === null) { + $this->_defaultValue = new Zend_CodeGenerator_Php_Parameter_DefaultValue("null"); + } else if(is_array($defaultValue)) { + $defaultValue = str_replace(array("\r", "\n"), "", var_export($defaultValue, true)); + $this->_defaultValue = new Zend_CodeGenerator_Php_Parameter_DefaultValue($defaultValue); + } else if(is_bool($defaultValue)) { + if($defaultValue == true) { + $this->_defaultValue = new Zend_CodeGenerator_Php_Parameter_DefaultValue("true"); + } else { + $this->_defaultValue = new Zend_CodeGenerator_Php_Parameter_DefaultValue("false"); + } + } else { + $this->_defaultValue = $defaultValue; + } + return $this; + } + + /** + * getDefaultValue() + * + * @return string + */ + public function getDefaultValue() + { + return $this->_defaultValue; + } + + /** + * setPosition() + * + * @param int $position + * @return Zend_CodeGenerator_Php_Parameter + */ + public function setPosition($position) + { + $this->_position = $position; + return $this; + } + + /** + * getPosition() + * + * @return int + */ + public function getPosition() + { + return $this->_position; + } + + /** + * @return bool + */ + public function getPassedByReference() + { + return $this->_passedByReference; + } + + /** + * @param bool $passedByReference + * @return Zend_CodeGenerator_Php_Parameter + */ + public function setPassedByReference($passedByReference) + { + $this->_passedByReference = $passedByReference; + return $this; + } + + /** + * generate() + * + * @return string + */ + public function generate() + { + $output = ''; + + if ($this->_type) { + $output .= $this->_type . ' '; + } + + if($this->_passedByReference === true) { + $output .= '&'; + } + + $output .= '$' . $this->_name; + + if ($this->_defaultValue !== null) { + $output .= ' = '; + if (is_string($this->_defaultValue)) { + $output .= '\'' . $this->_defaultValue . '\''; + } else if($this->_defaultValue instanceof Zend_CodeGenerator_Php_ParameterDefaultValue) { + $output .= (string)$this->_defaultValue; + } else { + $output .= $this->_defaultValue; + } + } + + return $output; + } + +} \ No newline at end of file diff --git a/library/Zend/CodeGenerator/Php/Parameter/DefaultValue.php b/library/Zend/CodeGenerator/Php/Parameter/DefaultValue.php new file mode 100644 index 000000000..f94ce5ae9 --- /dev/null +++ b/library/Zend/CodeGenerator/Php/Parameter/DefaultValue.php @@ -0,0 +1,60 @@ +_defaultValue = $defaultValue; + } + + public function __toString() + { + return $this->_defaultValue; + } +} \ No newline at end of file diff --git a/library/Zend/CodeGenerator/Php/Property.php b/library/Zend/CodeGenerator/Php/Property.php new file mode 100644 index 000000000..dcf462b8f --- /dev/null +++ b/library/Zend/CodeGenerator/Php/Property.php @@ -0,0 +1,179 @@ +setName($reflectionProperty->getName()); + + $allDefaultProperties = $reflectionProperty->getDeclaringClass()->getDefaultProperties(); + + $property->setDefaultValue($allDefaultProperties[$reflectionProperty->getName()]); + + if ($reflectionProperty->getDocComment() != '') { + $property->setDocblock(Zend_CodeGenerator_Php_Docblock::fromReflection($reflectionProperty->getDocComment())); + } + + if ($reflectionProperty->isStatic()) { + $property->setStatic(true); + } + + if ($reflectionProperty->isPrivate()) { + $property->setVisibility(self::VISIBILITY_PRIVATE); + } elseif ($reflectionProperty->isProtected()) { + $property->setVisibility(self::VISIBILITY_PROTECTED); + } else { + $property->setVisibility(self::VISIBILITY_PUBLIC); + } + + $property->setSourceDirty(false); + + return $property; + } + + /** + * setConst() + * + * @param bool $const + * @return Zend_CodeGenerator_Php_Property + */ + public function setConst($const) + { + $this->_isConst = $const; + return $this; + } + + /** + * isConst() + * + * @return bool + */ + public function isConst() + { + return ($this->_isConst) ? true : false; + } + + /** + * setDefaultValue() + * + * @param Zend_CodeGenerator_Php_Property_DefaultValue|string|array $defaultValue + * @return Zend_CodeGenerator_Php_Property + */ + public function setDefaultValue($defaultValue) + { + // if it looks like + if (is_array($defaultValue) + && array_key_exists('value', $defaultValue) + && array_key_exists('type', $defaultValue)) { + $defaultValue = new Zend_CodeGenerator_Php_Property_DefaultValue($defaultValue); + } + + if (!($defaultValue instanceof Zend_CodeGenerator_Php_Property_DefaultValue)) { + $defaultValue = new Zend_CodeGenerator_Php_Property_DefaultValue(array('value' => $defaultValue)); + } + + $this->_defaultValue = $defaultValue; + return $this; + } + + /** + * getDefaultValue() + * + * @return Zend_CodeGenerator_Php_Property_DefaultValue + */ + public function getDefaultValue() + { + return $this->_defaultValue; + } + + /** + * generate() + * + * @return string + */ + public function generate() + { + $name = $this->getName(); + $defaultValue = $this->getDefaultValue(); + + $output = ''; + + if (($docblock = $this->getDocblock()) !== null) { + $docblock->setIndentation(' '); + $output .= $docblock->generate(); + } + + if ($this->isConst()) { + if ($defaultValue != null && !$defaultValue->isValidConstantType()) { + require_once 'Zend/CodeGenerator/Php/Exception.php'; + throw new Zend_CodeGenerator_Php_Exception('The property ' . $this->_name . ' is said to be ' + . 'constant but does not have a valid constant value.'); + } + $output .= $this->_indentation . 'const ' . $name . ' = ' + . (($defaultValue !== null) ? $defaultValue->generate() : 'null;'); + } else { + $output .= $this->_indentation + . $this->getVisibility() + . (($this->isStatic()) ? ' static' : '') + . ' $' . $name . ' = ' + . (($defaultValue !== null) ? $defaultValue->generate() : 'null;'); + } + return $output; + } + +} diff --git a/library/Zend/CodeGenerator/Php/Property/DefaultValue.php b/library/Zend/CodeGenerator/Php/Property/DefaultValue.php new file mode 100644 index 000000000..210611666 --- /dev/null +++ b/library/Zend/CodeGenerator/Php/Property/DefaultValue.php @@ -0,0 +1,323 @@ +getConstants(); + unset($reflect); + } + } + + /** + * isValidConstantType() + * + * @return bool + */ + public function isValidConstantType() + { + if ($this->_type == self::TYPE_AUTO) { + $type = $this->_getAutoDeterminedType($this->_value); + } + + // valid types for constants + $scalarTypes = array( + self::TYPE_BOOLEAN, + self::TYPE_BOOL, + self::TYPE_NUMBER, + self::TYPE_INTEGER, + self::TYPE_INT, + self::TYPE_FLOAT, + self::TYPE_DOUBLE, + self::TYPE_STRING, + self::TYPE_CONSTANT, + self::TYPE_NULL + ); + + return in_array($type, $scalarTypes); + } + + /** + * setValue() + * + * @param mixed $value + * @return Zend_CodeGenerator_Php_Property_DefaultValue + */ + public function setValue($value) + { + $this->_value = $value; + return $this; + } + + /** + * getValue() + * + * @return mixed + */ + public function getValue() + { + return $this->_value; + } + + /** + * setType() + * + * @param string $type + * @return Zend_CodeGenerator_Php_Property_DefaultValue + */ + public function setType($type) + { + $this->_type = $type; + return $this; + } + + /** + * getType() + * + * @return string + */ + public function getType() + { + return $this->_type; + } + + /** + * setArrayDepth() + * + * @param int $arrayDepth + * @return Zend_CodeGenerator_Php_Property_DefaultValue + */ + public function setArrayDepth($arrayDepth) + { + $this->_arrayDepth = $arrayDepth; + return $this; + } + + /** + * getArrayDepth() + * + * @return int + */ + public function getArrayDepth() + { + return $this->_arrayDepth; + } + + /** + * _getValidatedType() + * + * @param string $type + * @return string + */ + protected function _getValidatedType($type) + { + if (($constName = array_search($type, self::$_constants)) !== false) { + return $type; + } + + return self::TYPE_AUTO; + } + + /** + * _getAutoDeterminedType() + * + * @param mixed $value + * @return string + */ + public function _getAutoDeterminedType($value) + { + switch (gettype($value)) { + case 'boolean': + return self::TYPE_BOOLEAN; + case 'integer': + return self::TYPE_INT; + case 'string': + return self::TYPE_STRING; + case 'double': + case 'float': + case 'integer': + return self::TYPE_NUMBER; + case 'array': + return self::TYPE_ARRAY; + case 'NULL': + return self::TYPE_NULL; + case 'object': + case 'resource': + case 'unknown type': + default: + return self::TYPE_OTHER; + } + } + + /** + * generate() + * + * @return string + */ + public function generate() + { + $type = $this->_type; + + if ($type != self::TYPE_AUTO) { + $type = $this->_getValidatedType($type); + } + + $value = $this->_value; + + if ($type == self::TYPE_AUTO) { + $type = $this->_getAutoDeterminedType($value); + + if ($type == self::TYPE_ARRAY) { + $rii = new RecursiveIteratorIterator( + $it = new RecursiveArrayIterator($value), + RecursiveIteratorIterator::SELF_FIRST + ); + foreach ($rii as $curKey => $curValue) { + if (!$curValue instanceof Zend_CodeGenerator_Php_Property_DefaultValue) { + $curValue = new self(array('value' => $curValue)); + $rii->getSubIterator()->offsetSet($curKey, $curValue); + } + $curValue->setArrayDepth($rii->getDepth()); + } + $value = $rii->getSubIterator()->getArrayCopy(); + } + + } + + $output = ''; + + switch ($type) { + case self::TYPE_BOOLEAN: + case self::TYPE_BOOL: + $output .= ( $value ? 'true' : 'false' ); + break; + case self::TYPE_STRING: + $output .= "'" . addcslashes($value, "'") . "'"; + break; + case self::TYPE_NULL: + $output .= 'null'; + break; + case self::TYPE_NUMBER: + case self::TYPE_INTEGER: + case self::TYPE_INT: + case self::TYPE_FLOAT: + case self::TYPE_DOUBLE: + case self::TYPE_CONSTANT: + $output .= $value; + break; + case self::TYPE_ARRAY: + $output .= 'array('; + $curArrayMultiblock = false; + if (count($value) > 1) { + $curArrayMultiblock = true; + $output .= PHP_EOL . str_repeat($this->_indentation, $this->_arrayDepth+1); + } + $outputParts = array(); + $noKeyIndex = 0; + foreach ($value as $n => $v) { + $v->setArrayDepth($this->_arrayDepth + 1); + $partV = $v->generate(); + $partV = substr($partV, 0, strlen($partV)-1); + if ($n === $noKeyIndex) { + $outputParts[] = $partV; + $noKeyIndex++; + } else { + $outputParts[] = (is_int($n) ? $n : "'" . addcslashes($n, "'") . "'") . ' => ' . $partV; + } + + } + $output .= implode(',' . PHP_EOL . str_repeat($this->_indentation, $this->_arrayDepth+1), $outputParts); + if ($curArrayMultiblock == true) { + $output .= PHP_EOL . str_repeat($this->_indentation, $this->_arrayDepth+1); + } + $output .= ')'; + break; + case self::TYPE_OTHER: + default: + require_once "Zend/CodeGenerator/Php/Exception.php"; + throw new Zend_CodeGenerator_Php_Exception( + "Type '".get_class($value)."' is unknown or cannot be used as property default value." + ); + } + + $output .= ';'; + + return $output; + } +} diff --git a/library/Zend/Config.php b/library/Zend/Config.php new file mode 100644 index 000000000..357f18839 --- /dev/null +++ b/library/Zend/Config.php @@ -0,0 +1,484 @@ +_allowModifications = (boolean) $allowModifications; + $this->_loadedSection = null; + $this->_index = 0; + $this->_data = array(); + foreach ($array as $key => $value) { + if (is_array($value)) { + $this->_data[$key] = new self($value, $this->_allowModifications); + } else { + $this->_data[$key] = $value; + } + } + $this->_count = count($this->_data); + } + + /** + * Retrieve a value and return $default if there is no element set. + * + * @param string $name + * @param mixed $default + * @return mixed + */ + public function get($name, $default = null) + { + $result = $default; + if (array_key_exists($name, $this->_data)) { + $result = $this->_data[$name]; + } + return $result; + } + + /** + * Magic function so that $obj->value will work. + * + * @param string $name + * @return mixed + */ + public function __get($name) + { + return $this->get($name); + } + + /** + * Only allow setting of a property if $allowModifications + * was set to true on construction. Otherwise, throw an exception. + * + * @param string $name + * @param mixed $value + * @throws Zend_Config_Exception + * @return void + */ + public function __set($name, $value) + { + if ($this->_allowModifications) { + if (is_array($value)) { + $this->_data[$name] = new self($value, true); + } else { + $this->_data[$name] = $value; + } + $this->_count = count($this->_data); + } else { + /** @see Zend_Config_Exception */ + require_once 'Zend/Config/Exception.php'; + throw new Zend_Config_Exception('Zend_Config is read only'); + } + } + + /** + * Deep clone of this instance to ensure that nested Zend_Configs + * are also cloned. + * + * @return void + */ + public function __clone() + { + $array = array(); + foreach ($this->_data as $key => $value) { + if ($value instanceof Zend_Config) { + $array[$key] = clone $value; + } else { + $array[$key] = $value; + } + } + $this->_data = $array; + } + + /** + * Return an associative array of the stored data. + * + * @return array + */ + public function toArray() + { + $array = array(); + $data = $this->_data; + foreach ($data as $key => $value) { + if ($value instanceof Zend_Config) { + $array[$key] = $value->toArray(); + } else { + $array[$key] = $value; + } + } + return $array; + } + + /** + * Support isset() overloading on PHP 5.1 + * + * @param string $name + * @return boolean + */ + public function __isset($name) + { + return isset($this->_data[$name]); + } + + /** + * Support unset() overloading on PHP 5.1 + * + * @param string $name + * @throws Zend_Config_Exception + * @return void + */ + public function __unset($name) + { + if ($this->_allowModifications) { + unset($this->_data[$name]); + $this->_count = count($this->_data); + $this->_skipNextIteration = true; + } else { + /** @see Zend_Config_Exception */ + require_once 'Zend/Config/Exception.php'; + throw new Zend_Config_Exception('Zend_Config is read only'); + } + + } + + /** + * Defined by Countable interface + * + * @return int + */ + public function count() + { + return $this->_count; + } + + /** + * Defined by Iterator interface + * + * @return mixed + */ + public function current() + { + $this->_skipNextIteration = false; + return current($this->_data); + } + + /** + * Defined by Iterator interface + * + * @return mixed + */ + public function key() + { + return key($this->_data); + } + + /** + * Defined by Iterator interface + * + */ + public function next() + { + if ($this->_skipNextIteration) { + $this->_skipNextIteration = false; + return; + } + next($this->_data); + $this->_index++; + } + + /** + * Defined by Iterator interface + * + */ + public function rewind() + { + $this->_skipNextIteration = false; + reset($this->_data); + $this->_index = 0; + } + + /** + * Defined by Iterator interface + * + * @return boolean + */ + public function valid() + { + return $this->_index < $this->_count; + } + + /** + * Returns the section name(s) loaded. + * + * @return mixed + */ + public function getSectionName() + { + if(is_array($this->_loadedSection) && count($this->_loadedSection) == 1) { + $this->_loadedSection = $this->_loadedSection[0]; + } + return $this->_loadedSection; + } + + /** + * Returns true if all sections were loaded + * + * @return boolean + */ + public function areAllSectionsLoaded() + { + return $this->_loadedSection === null; + } + + + /** + * Merge another Zend_Config with this one. The items + * in $merge will override the same named items in + * the current config. + * + * @param Zend_Config $merge + * @return Zend_Config + */ + public function merge(Zend_Config $merge) + { + foreach($merge as $key => $item) { + if(array_key_exists($key, $this->_data)) { + if($item instanceof Zend_Config && $this->$key instanceof Zend_Config) { + $this->$key = $this->$key->merge(new Zend_Config($item->toArray(), !$this->readOnly())); + } else { + $this->$key = $item; + } + } else { + if($item instanceof Zend_Config) { + $this->$key = new Zend_Config($item->toArray(), !$this->readOnly()); + } else { + $this->$key = $item; + } + } + } + + return $this; + } + + /** + * Prevent any more modifications being made to this instance. Useful + * after merge() has been used to merge multiple Zend_Config objects + * into one object which should then not be modified again. + * + */ + public function setReadOnly() + { + $this->_allowModifications = false; + foreach ($this->_data as $key => $value) { + if ($value instanceof Zend_Config) { + $value->setReadOnly(); + } + } + } + + /** + * Returns if this Zend_Config object is read only or not. + * + * @return boolean + */ + public function readOnly() + { + return !$this->_allowModifications; + } + + /** + * Get the current extends + * + * @return array + */ + public function getExtends() + { + return $this->_extends; + } + + /** + * Set an extend for Zend_Config_Writer + * + * @param string $extendingSection + * @param string $extendedSection + * @return void + */ + public function setExtend($extendingSection, $extendedSection = null) + { + if ($extendedSection === null && isset($this->_extends[$extendingSection])) { + unset($this->_extends[$extendingSection]); + } else if ($extendedSection !== null) { + $this->_extends[$extendingSection] = $extendedSection; + } + } + + /** + * Throws an exception if $extendingSection may not extend $extendedSection, + * and tracks the section extension if it is valid. + * + * @param string $extendingSection + * @param string $extendedSection + * @throws Zend_Config_Exception + * @return void + */ + protected function _assertValidExtend($extendingSection, $extendedSection) + { + // detect circular section inheritance + $extendedSectionCurrent = $extendedSection; + while (array_key_exists($extendedSectionCurrent, $this->_extends)) { + if ($this->_extends[$extendedSectionCurrent] == $extendingSection) { + /** @see Zend_Config_Exception */ + require_once 'Zend/Config/Exception.php'; + throw new Zend_Config_Exception('Illegal circular inheritance detected'); + } + $extendedSectionCurrent = $this->_extends[$extendedSectionCurrent]; + } + // remember that this section extends another section + $this->_extends[$extendingSection] = $extendedSection; + } + + /** + * Handle any errors from simplexml_load_file or parse_ini_file + * + * @param integer $errno + * @param string $errstr + * @param string $errfile + * @param integer $errline + */ + protected function _loadFileErrorHandler($errno, $errstr, $errfile, $errline) + { + if ($this->_loadFileErrorStr === null) { + $this->_loadFileErrorStr = $errstr; + } else { + $this->_loadFileErrorStr .= (PHP_EOL . $errstr); + } + } + + /** + * Merge two arrays recursively, overwriting keys of the same name + * in $firstArray with the value in $secondArray. + * + * @param mixed $firstArray First array + * @param mixed $secondArray Second array to merge into first array + * @return array + */ + protected function _arrayMergeRecursive($firstArray, $secondArray) + { + if (is_array($firstArray) && is_array($secondArray)) { + foreach ($secondArray as $key => $value) { + if (isset($firstArray[$key])) { + $firstArray[$key] = $this->_arrayMergeRecursive($firstArray[$key], $value); + } else { + if($key === 0) { + $firstArray= array(0=>$this->_arrayMergeRecursive($firstArray, $value)); + } else { + $firstArray[$key] = $value; + } + } + } + } else { + $firstArray = $secondArray; + } + + return $firstArray; + } +} \ No newline at end of file diff --git a/library/Zend/Config/Exception.php b/library/Zend/Config/Exception.php new file mode 100644 index 000000000..1532c1652 --- /dev/null +++ b/library/Zend/Config/Exception.php @@ -0,0 +1,33 @@ +hostname === "staging" + * $data->db->connection === "database" + * + * The $options parameter may be provided as either a boolean or an array. + * If provided as a boolean, this sets the $allowModifications option of + * Zend_Config. If provided as an array, there are two configuration + * directives that may be set. For example: + * + * $options = array( + * 'allowModifications' => false, + * 'nestSeparator' => '->' + * ); + * + * @param string $filename + * @param string|null $section + * @param boolean|array $options + * @throws Zend_Config_Exception + * @return void + */ + public function __construct($filename, $section = null, $options = false) + { + if (empty($filename)) { + /** + * @see Zend_Config_Exception + */ + require_once 'Zend/Config/Exception.php'; + throw new Zend_Config_Exception('Filename is not set'); + } + + $allowModifications = false; + if (is_bool($options)) { + $allowModifications = $options; + } elseif (is_array($options)) { + if (isset($options['allowModifications'])) { + $allowModifications = (bool) $options['allowModifications']; + } + if (isset($options['nestSeparator'])) { + $this->_nestSeparator = (string) $options['nestSeparator']; + } + if (isset($options['skipExtends'])) { + $this->_skipExtends = (bool) $options['skipExtends']; + } + } + + $iniArray = $this->_loadIniFile($filename); + + if (null === $section) { + // Load entire file + $dataArray = array(); + foreach ($iniArray as $sectionName => $sectionData) { + if(!is_array($sectionData)) { + $dataArray = $this->_arrayMergeRecursive($dataArray, $this->_processKey(array(), $sectionName, $sectionData)); + } else { + $dataArray[$sectionName] = $this->_processSection($iniArray, $sectionName); + } + } + parent::__construct($dataArray, $allowModifications); + } else { + // Load one or more sections + if (!is_array($section)) { + $section = array($section); + } + $dataArray = array(); + foreach ($section as $sectionName) { + if (!isset($iniArray[$sectionName])) { + /** + * @see Zend_Config_Exception + */ + require_once 'Zend/Config/Exception.php'; + throw new Zend_Config_Exception("Section '$sectionName' cannot be found in $filename"); + } + $dataArray = $this->_arrayMergeRecursive($this->_processSection($iniArray, $sectionName), $dataArray); + + } + parent::__construct($dataArray, $allowModifications); + } + + $this->_loadedSection = $section; + } + + /** + * Load the INI file from disk using parse_ini_file(). Use a private error + * handler to convert any loading errors into a Zend_Config_Exception + * + * @param string $filename + * @throws Zend_Config_Exception + * @return array + */ + protected function _parseIniFile($filename) + { + set_error_handler(array($this, '_loadFileErrorHandler')); + $iniArray = parse_ini_file($filename, true); // Warnings and errors are suppressed + restore_error_handler(); + + // Check if there was a error while loading file + if ($this->_loadFileErrorStr !== null) { + /** + * @see Zend_Config_Exception + */ + require_once 'Zend/Config/Exception.php'; + throw new Zend_Config_Exception($this->_loadFileErrorStr); + } + + return $iniArray; + } + + /** + * Load the ini file and preprocess the section separator (':' in the + * section name (that is used for section extension) so that the resultant + * array has the correct section names and the extension information is + * stored in a sub-key called ';extends'. We use ';extends' as this can + * never be a valid key name in an INI file that has been loaded using + * parse_ini_file(). + * + * @param string $filename + * @throws Zend_Config_Exception + * @return array + */ + protected function _loadIniFile($filename) + { + $loaded = $this->_parseIniFile($filename); + $iniArray = array(); + foreach ($loaded as $key => $data) + { + $pieces = explode($this->_sectionSeparator, $key); + $thisSection = trim($pieces[0]); + switch (count($pieces)) { + case 1: + $iniArray[$thisSection] = $data; + break; + + case 2: + $extendedSection = trim($pieces[1]); + $iniArray[$thisSection] = array_merge(array(';extends'=>$extendedSection), $data); + break; + + default: + /** + * @see Zend_Config_Exception + */ + require_once 'Zend/Config/Exception.php'; + throw new Zend_Config_Exception("Section '$thisSection' may not extend multiple sections in $filename"); + } + } + + return $iniArray; + } + + /** + * Process each element in the section and handle the ";extends" inheritance + * key. Passes control to _processKey() to handle the nest separator + * sub-property syntax that may be used within the key name. + * + * @param array $iniArray + * @param string $section + * @param array $config + * @throws Zend_Config_Exception + * @return array + */ + protected function _processSection($iniArray, $section, $config = array()) + { + $thisSection = $iniArray[$section]; + + foreach ($thisSection as $key => $value) { + if (strtolower($key) == ';extends') { + if (isset($iniArray[$value])) { + $this->_assertValidExtend($section, $value); + + if (!$this->_skipExtends) { + $config = $this->_processSection($iniArray, $value, $config); + } + } else { + /** + * @see Zend_Config_Exception + */ + require_once 'Zend/Config/Exception.php'; + throw new Zend_Config_Exception("Parent section '$section' cannot be found"); + } + } else { + $config = $this->_processKey($config, $key, $value); + } + } + return $config; + } + + /** + * Assign the key's value to the property list. Handles the + * nest separator for sub-properties. + * + * @param array $config + * @param string $key + * @param string $value + * @throws Zend_Config_Exception + * @return array + */ + protected function _processKey($config, $key, $value) + { + if (strpos($key, $this->_nestSeparator) !== false) { + $pieces = explode($this->_nestSeparator, $key, 2); + if (strlen($pieces[0]) && strlen($pieces[1])) { + if (!isset($config[$pieces[0]])) { + if ($pieces[0] === '0' && !empty($config)) { + // convert the current values in $config into an array + $config = array($pieces[0] => $config); + } else { + $config[$pieces[0]] = array(); + } + } elseif (!is_array($config[$pieces[0]])) { + /** + * @see Zend_Config_Exception + */ + require_once 'Zend/Config/Exception.php'; + throw new Zend_Config_Exception("Cannot create sub-key for '{$pieces[0]}' as key already exists"); + } + $config[$pieces[0]] = $this->_processKey($config[$pieces[0]], $pieces[1], $value); + } else { + /** + * @see Zend_Config_Exception + */ + require_once 'Zend/Config/Exception.php'; + throw new Zend_Config_Exception("Invalid key '$key'"); + } + } else { + $config[$key] = $value; + } + return $config; + } +} diff --git a/library/Zend/Config/Writer.php b/library/Zend/Config/Writer.php new file mode 100644 index 000000000..9bfedca74 --- /dev/null +++ b/library/Zend/Config/Writer.php @@ -0,0 +1,101 @@ +setOptions($options); + } + } + + /** + * Set options via a Zend_Config instance + * + * @param Zend_Config $config + * @return Zend_Config_Writer + */ + public function setConfig(Zend_Config $config) + { + $this->_config = $config; + + return $this; + } + + /** + * Set options via an array + * + * @param array $options + * @return Zend_Config_Writer + */ + public function setOptions(array $options) + { + foreach ($options as $key => $value) { + if (in_array(strtolower($key), $this->_skipOptions)) { + continue; + } + + $method = 'set' . ucfirst($key); + if (method_exists($this, $method)) { + $this->$method($value); + } + } + + return $this; + } + + /** + * Write a Zend_Config object to it's target + * + * @return void + */ + abstract public function write(); +} diff --git a/library/Zend/Config/Writer/Array.php b/library/Zend/Config/Writer/Array.php new file mode 100644 index 000000000..d6fb134c5 --- /dev/null +++ b/library/Zend/Config/Writer/Array.php @@ -0,0 +1,55 @@ +_config->toArray(); + $sectionName = $this->_config->getSectionName(); + + if (is_string($sectionName)) { + $data = array($sectionName => $data); + } + + $arrayString = "_filename = $filename; + + return $this; + } + + /** + * Set wether to exclusively lock the file or not + * + * @param boolean $exclusiveLock + * @return Zend_Config_Writer_Array + */ + public function setExclusiveLock($exclusiveLock) + { + $this->_exclusiveLock = $exclusiveLock; + + return $this; + } + + /** + * Write configuration to file. + * + * @param string $filename + * @param Zend_Config $config + * @param bool $exclusiveLock + * @return void + */ + public function write($filename = null, Zend_Config $config = null, $exclusiveLock = null) + { + if ($filename !== null) { + $this->setFilename($filename); + } + + if ($config !== null) { + $this->setConfig($config); + } + + if ($exclusiveLock !== null) { + $this->setExclusiveLock($exclusiveLock); + } + + if ($this->_filename === null) { + require_once 'Zend/Config/Exception.php'; + throw new Zend_Config_Exception('No filename was set'); + } + + if ($this->_config === null) { + require_once 'Zend/Config/Exception.php'; + throw new Zend_Config_Exception('No config was set'); + } + + $configString = $this->render(); + + $flags = 0; + + if ($this->_exclusiveLock) { + $flags |= LOCK_EX; + } + + $result = @file_put_contents($this->_filename, $configString, $flags); + + if ($result === false) { + require_once 'Zend/Config/Exception.php'; + throw new Zend_Config_Exception('Could not write to file "' . $this->_filename . '"'); + } + } + + /** + * Render a Zend_Config into a config file string. + * + * @since 1.10 + * @todo For 2.0 this should be redone into an abstract method. + * @return string + */ + public function render() + { + return ""; + } +} \ No newline at end of file diff --git a/library/Zend/Config/Writer/Ini.php b/library/Zend/Config/Writer/Ini.php new file mode 100644 index 000000000..7457a0aee --- /dev/null +++ b/library/Zend/Config/Writer/Ini.php @@ -0,0 +1,163 @@ +_nestSeparator = $separator; + + return $this; + } + + /** + * Set if rendering should occour without sections or not. + * + * If set to true, the INI file is rendered without sections completely + * into the global namespace of the INI file. + * + * @param bool $withoutSections + * @return Zend_Config_Writer_Ini + */ + public function setRenderWithoutSections($withoutSections=true) + { + $this->_renderWithoutSections = (bool)$withoutSections; + return $this; + } + + /** + * Render a Zend_Config into a INI config string. + * + * @since 1.10 + * @return string + */ + public function render() + { + $iniString = ''; + $extends = $this->_config->getExtends(); + $sectionName = $this->_config->getSectionName(); + + if($this->_renderWithoutSections == true) { + $iniString .= $this->_addBranch($this->_config); + } else if (is_string($sectionName)) { + $iniString .= '[' . $sectionName . ']' . "\n" + . $this->_addBranch($this->_config) + . "\n"; + } else { + foreach ($this->_config as $sectionName => $data) { + if (!($data instanceof Zend_Config)) { + $iniString .= $sectionName + . ' = ' + . $this->_prepareValue($data) + . "\n"; + } else { + if (isset($extends[$sectionName])) { + $sectionName .= ' : ' . $extends[$sectionName]; + } + + $iniString .= '[' . $sectionName . ']' . "\n" + . $this->_addBranch($data) + . "\n"; + } + } + } + + return $iniString; + } + + /** + * Add a branch to an INI string recursively + * + * @param Zend_Config $config + * @return void + */ + protected function _addBranch(Zend_Config $config, $parents = array()) + { + $iniString = ''; + + foreach ($config as $key => $value) { + $group = array_merge($parents, array($key)); + + if ($value instanceof Zend_Config) { + $iniString .= $this->_addBranch($value, $group); + } else { + $iniString .= implode($this->_nestSeparator, $group) + . ' = ' + . $this->_prepareValue($value) + . "\n"; + } + } + + return $iniString; + } + + /** + * Prepare a value for INI + * + * @param mixed $value + * @return string + */ + protected function _prepareValue($value) + { + if (is_integer($value) || is_float($value)) { + return $value; + } elseif (is_bool($value)) { + return ($value ? 'true' : 'false'); + } elseif (strpos($value, '"') === false) { + return '"' . $value . '"'; + } else { + /** @see Zend_Config_Exception */ + require_once 'Zend/Config/Exception.php'; + throw new Zend_Config_Exception('Value can not contain double quotes "'); + } + } +} diff --git a/library/Zend/Config/Writer/Xml.php b/library/Zend/Config/Writer/Xml.php new file mode 100644 index 000000000..72fc0a0b8 --- /dev/null +++ b/library/Zend/Config/Writer/Xml.php @@ -0,0 +1,127 @@ +'); + $extends = $this->_config->getExtends(); + $sectionName = $this->_config->getSectionName(); + + if (is_string($sectionName)) { + $child = $xml->addChild($sectionName); + + $this->_addBranch($this->_config, $child, $xml); + } else { + foreach ($this->_config as $sectionName => $data) { + if (!($data instanceof Zend_Config)) { + $xml->addChild($sectionName, (string) $data); + } else { + $child = $xml->addChild($sectionName); + + if (isset($extends[$sectionName])) { + $child->addAttribute('zf:extends', $extends[$sectionName], Zend_Config_Xml::XML_NAMESPACE); + } + + $this->_addBranch($data, $child, $xml); + } + } + } + + $dom = dom_import_simplexml($xml)->ownerDocument; + $dom->formatOutput = true; + + $xmlString = $dom->saveXML(); + + return $xmlString; + } + + /** + * Add a branch to an XML object recursively + * + * @param Zend_Config $config + * @param SimpleXMLElement $xml + * @param SimpleXMLElement $parent + * @return void + */ + protected function _addBranch(Zend_Config $config, SimpleXMLElement $xml, SimpleXMLElement $parent) + { + $branchType = null; + + foreach ($config as $key => $value) { + if ($branchType === null) { + if (is_numeric($key)) { + $branchType = 'numeric'; + $branchName = $xml->getName(); + $xml = $parent; + + unset($parent->{$branchName}); + } else { + $branchType = 'string'; + } + } else if ($branchType !== (is_numeric($key) ? 'numeric' : 'string')) { + require_once 'Zend/Config/Exception.php'; + throw new Zend_Config_Exception('Mixing of string and numeric keys is not allowed'); + } + + if ($branchType === 'numeric') { + if ($value instanceof Zend_Config) { + $child = $parent->addChild($branchName); + + $this->_addBranch($value, $child, $parent); + } else { + $parent->addChild($branchName, (string) $value); + } + } else { + if ($value instanceof Zend_Config) { + $child = $xml->addChild($key); + + $this->_addBranch($value, $child, $xml); + } else { + $xml->addChild($key, (string) $value); + } + } + } + } +} diff --git a/library/Zend/Config/Xml.php b/library/Zend/Config/Xml.php new file mode 100644 index 000000000..fa14b9e42 --- /dev/null +++ b/library/Zend/Config/Xml.php @@ -0,0 +1,286 @@ +_skipExtends = (bool) $options['skipExtends']; + } + } + + set_error_handler(array($this, '_loadFileErrorHandler')); // Warnings and errors are suppressed + if (strstr($xml, '_loadFileErrorStr !== null) { + require_once 'Zend/Config/Exception.php'; + throw new Zend_Config_Exception($this->_loadFileErrorStr); + } + + if ($section === null) { + $dataArray = array(); + foreach ($config as $sectionName => $sectionData) { + $dataArray[$sectionName] = $this->_processExtends($config, $sectionName); + } + + parent::__construct($dataArray, $allowModifications); + } else if (is_array($section)) { + $dataArray = array(); + foreach ($section as $sectionName) { + if (!isset($config->$sectionName)) { + require_once 'Zend/Config/Exception.php'; + throw new Zend_Config_Exception("Section '$sectionName' cannot be found in $xml"); + } + + $dataArray = array_merge($this->_processExtends($config, $sectionName), $dataArray); + } + + parent::__construct($dataArray, $allowModifications); + } else { + if (!isset($config->$section)) { + require_once 'Zend/Config/Exception.php'; + throw new Zend_Config_Exception("Section '$section' cannot be found in $xml"); + } + + $dataArray = $this->_processExtends($config, $section); + if (!is_array($dataArray)) { + // Section in the XML file contains just one top level string + $dataArray = array($section => $dataArray); + } + + parent::__construct($dataArray, $allowModifications); + } + + $this->_loadedSection = $section; + } + + /** + * Helper function to process each element in the section and handle + * the "extends" inheritance attribute. + * + * @param SimpleXMLElement $element XML Element to process + * @param string $section Section to process + * @param array $config Configuration which was parsed yet + * @throws Zend_Config_Exception When $section cannot be found + * @return array + */ + protected function _processExtends(SimpleXMLElement $element, $section, array $config = array()) + { + if (!isset($element->$section)) { + require_once 'Zend/Config/Exception.php'; + throw new Zend_Config_Exception("Section '$section' cannot be found"); + } + + $thisSection = $element->$section; + $nsAttributes = $thisSection->attributes(self::XML_NAMESPACE); + + if (isset($thisSection['extends']) || isset($nsAttributes['extends'])) { + $extendedSection = (string) (isset($nsAttributes['extends']) ? $nsAttributes['extends'] : $thisSection['extends']); + $this->_assertValidExtend($section, $extendedSection); + + if (!$this->_skipExtends) { + $config = $this->_processExtends($element, $extendedSection, $config); + } + } + + $config = $this->_arrayMergeRecursive($config, $this->_toArray($thisSection)); + + return $config; + } + + /** + * Returns a string or an associative and possibly multidimensional array from + * a SimpleXMLElement. + * + * @param SimpleXMLElement $xmlObject Convert a SimpleXMLElement into an array + * @return array|string + */ + protected function _toArray(SimpleXMLElement $xmlObject) + { + $config = array(); + $nsAttributes = $xmlObject->attributes(self::XML_NAMESPACE); + + // Search for parent node values + if (count($xmlObject->attributes()) > 0) { + foreach ($xmlObject->attributes() as $key => $value) { + if ($key === 'extends') { + continue; + } + + $value = (string) $value; + + if (array_key_exists($key, $config)) { + if (!is_array($config[$key])) { + $config[$key] = array($config[$key]); + } + + $config[$key][] = $value; + } else { + $config[$key] = $value; + } + } + } + + // Search for local 'const' nodes and replace them + if (count($xmlObject->children(self::XML_NAMESPACE)) > 0) { + if (count($xmlObject->children()) > 0) { + require_once 'Zend/Config/Exception.php'; + throw new Zend_Config_Exception("A node with a 'const' childnode may not have any other children"); + } + + $dom = dom_import_simplexml($xmlObject); + $namespaceChildNodes = array(); + + // We have to store them in an array, as replacing nodes will + // confuse the DOMNodeList later + foreach ($dom->childNodes as $node) { + if ($node instanceof DOMElement && $node->namespaceURI === self::XML_NAMESPACE) { + $namespaceChildNodes[] = $node; + } + } + + foreach ($namespaceChildNodes as $node) { + switch ($node->localName) { + case 'const': + if (!$node->hasAttributeNS(self::XML_NAMESPACE, 'name')) { + require_once 'Zend/Config/Exception.php'; + throw new Zend_Config_Exception("Misssing 'name' attribute in 'const' node"); + } + + $constantName = $node->getAttributeNS(self::XML_NAMESPACE, 'name'); + + if (!defined($constantName)) { + require_once 'Zend/Config/Exception.php'; + throw new Zend_Config_Exception("Constant with name '$constantName' was not defined"); + } + + $constantValue = constant($constantName); + + $dom->replaceChild($dom->ownerDocument->createTextNode($constantValue), $node); + break; + + default: + require_once 'Zend/Config/Exception.php'; + throw new Zend_Config_Exception("Unknown node with name '$node->localName' found"); + } + } + + return (string) simplexml_import_dom($dom); + } + + // Search for children + if (count($xmlObject->children()) > 0) { + foreach ($xmlObject->children() as $key => $value) { + if (count($value->children()) > 0 || count($value->children(self::XML_NAMESPACE)) > 0) { + $value = $this->_toArray($value); + } else if (count($value->attributes()) > 0) { + $attributes = $value->attributes(); + if (isset($attributes['value'])) { + $value = (string) $attributes['value']; + } else { + $value = $this->_toArray($value); + } + } else { + $value = (string) $value; + } + + if (array_key_exists($key, $config)) { + if (!is_array($config[$key]) || !array_key_exists(0, $config[$key])) { + $config[$key] = array($config[$key]); + } + + $config[$key][] = $value; + } else { + $config[$key] = $value; + } + } + } else if (!isset($xmlObject['extends']) && !isset($nsAttributes['extends']) && (count($config) === 0)) { + // Object has no children nor attributes and doesn't use the extends + // attribute: it's a string + $config = (string) $xmlObject; + } + + return $config; + } +} diff --git a/library/Zend/Console/Getopt.php b/library/Zend/Console/Getopt.php new file mode 100644 index 000000000..1e173bdea --- /dev/null +++ b/library/Zend/Console/Getopt.php @@ -0,0 +1,969 @@ + self::MODE_ZEND, + self::CONFIG_DASHDASH => true, + self::CONFIG_IGNORECASE => false, + self::CONFIG_PARSEALL => true, + ); + + /** + * Stores the command-line arguments for the calling applicaion. + * + * @var array + */ + protected $_argv = array(); + + /** + * Stores the name of the calling applicaion. + * + * @var string + */ + protected $_progname = ''; + + /** + * Stores the list of legal options for this application. + * + * @var array + */ + protected $_rules = array(); + + /** + * Stores alternate spellings of legal options. + * + * @var array + */ + protected $_ruleMap = array(); + + /** + * Stores options given by the user in the current invocation + * of the application, as well as parameters given in options. + * + * @var array + */ + protected $_options = array(); + + /** + * Stores the command-line arguments other than options. + * + * @var array + */ + protected $_remainingArgs = array(); + + /** + * State of the options: parsed or not yet parsed? + * + * @var boolean + */ + protected $_parsed = false; + + /** + * The constructor takes one to three parameters. + * + * The first parameter is $rules, which may be a string for + * gnu-style format, or a structured array for Zend-style format. + * + * The second parameter is $argv, and it is optional. If not + * specified, $argv is inferred from the global argv. + * + * The third parameter is an array of configuration parameters + * to control the behavior of this instance of Getopt; it is optional. + * + * @param array $rules + * @param array $argv + * @param array $getoptConfig + * @return void + */ + public function __construct($rules, $argv = null, $getoptConfig = array()) + { + if (!isset($_SERVER['argv'])) { + require_once 'Zend/Console/Getopt/Exception.php'; + if (ini_get('register_argc_argv') == false) { + throw new Zend_Console_Getopt_Exception( + "argv is not available, because ini option 'register_argc_argv' is set Off" + ); + } else { + throw new Zend_Console_Getopt_Exception( + '$_SERVER["argv"] is not set, but Zend_Console_Getopt cannot work without this information.' + ); + } + } + + $this->_progname = $_SERVER['argv'][0]; + $this->setOptions($getoptConfig); + $this->addRules($rules); + if (!is_array($argv)) { + $argv = array_slice($_SERVER['argv'], 1); + } + if (isset($argv)) { + $this->addArguments((array)$argv); + } + } + + /** + * Return the state of the option seen on the command line of the + * current application invocation. This function returns true, or the + * parameter to the option, if any. If the option was not given, + * this function returns null. + * + * The magic __get method works in the context of naming the option + * as a virtual member of this class. + * + * @param string $key + * @return string + */ + public function __get($key) + { + return $this->getOption($key); + } + + /** + * Test whether a given option has been seen. + * + * @param string $key + * @return boolean + */ + public function __isset($key) + { + $this->parse(); + if (isset($this->_ruleMap[$key])) { + $key = $this->_ruleMap[$key]; + return isset($this->_options[$key]); + } + return false; + } + + /** + * Set the value for a given option. + * + * @param string $key + * @param string $value + * @return void + */ + public function __set($key, $value) + { + $this->parse(); + if (isset($this->_ruleMap[$key])) { + $key = $this->_ruleMap[$key]; + $this->_options[$key] = $value; + } + } + + /** + * Return the current set of options and parameters seen as a string. + * + * @return string + */ + public function __toString() + { + return $this->toString(); + } + + /** + * Unset an option. + * + * @param string $key + * @return void + */ + public function __unset($key) + { + $this->parse(); + if (isset($this->_ruleMap[$key])) { + $key = $this->_ruleMap[$key]; + unset($this->_options[$key]); + } + } + + /** + * Define additional command-line arguments. + * These are appended to those defined when the constructor was called. + * + * @param array $argv + * @throws Zend_Console_Getopt_Exception When not given an array as parameter + * @return Zend_Console_Getopt Provides a fluent interface + */ + public function addArguments($argv) + { + if(!is_array($argv)) { + require_once 'Zend/Console/Getopt/Exception.php'; + throw new Zend_Console_Getopt_Exception( + "Parameter #1 to addArguments should be an array"); + } + $this->_argv = array_merge($this->_argv, $argv); + $this->_parsed = false; + return $this; + } + + /** + * Define full set of command-line arguments. + * These replace any currently defined. + * + * @param array $argv + * @throws Zend_Console_Getopt_Exception When not given an array as parameter + * @return Zend_Console_Getopt Provides a fluent interface + */ + public function setArguments($argv) + { + if(!is_array($argv)) { + require_once 'Zend/Console/Getopt/Exception.php'; + throw new Zend_Console_Getopt_Exception( + "Parameter #1 to setArguments should be an array"); + } + $this->_argv = $argv; + $this->_parsed = false; + return $this; + } + + /** + * Define multiple configuration options from an associative array. + * These are not program options, but properties to configure + * the behavior of Zend_Console_Getopt. + * + * @param array $getoptConfig + * @return Zend_Console_Getopt Provides a fluent interface + */ + public function setOptions($getoptConfig) + { + if (isset($getoptConfig)) { + foreach ($getoptConfig as $key => $value) { + $this->setOption($key, $value); + } + } + return $this; + } + + /** + * Define one configuration option as a key/value pair. + * These are not program options, but properties to configure + * the behavior of Zend_Console_Getopt. + * + * @param string $configKey + * @param string $configValue + * @return Zend_Console_Getopt Provides a fluent interface + */ + public function setOption($configKey, $configValue) + { + if ($configKey !== null) { + $this->_getoptConfig[$configKey] = $configValue; + } + return $this; + } + + /** + * Define additional option rules. + * These are appended to the rules defined when the constructor was called. + * + * @param array $rules + * @return Zend_Console_Getopt Provides a fluent interface + */ + public function addRules($rules) + { + $ruleMode = $this->_getoptConfig['ruleMode']; + switch ($this->_getoptConfig['ruleMode']) { + case self::MODE_ZEND: + if (is_array($rules)) { + $this->_addRulesModeZend($rules); + break; + } + // intentional fallthrough + case self::MODE_GNU: + $this->_addRulesModeGnu($rules); + break; + default: + /** + * Call addRulesModeFoo() for ruleMode 'foo'. + * The developer should subclass Getopt and + * provide this method. + */ + $method = '_addRulesMode' . ucfirst($ruleMode); + $this->$method($rules); + } + $this->_parsed = false; + return $this; + } + + /** + * Return the current set of options and parameters seen as a string. + * + * @return string + */ + public function toString() + { + $this->parse(); + $s = array(); + foreach ($this->_options as $flag => $value) { + $s[] = $flag . '=' . ($value === true ? 'true' : $value); + } + return implode(' ', $s); + } + + /** + * Return the current set of options and parameters seen + * as an array of canonical options and parameters. + * + * Clusters have been expanded, and option aliases + * have been mapped to their primary option names. + * + * @return array + */ + public function toArray() + { + $this->parse(); + $s = array(); + foreach ($this->_options as $flag => $value) { + $s[] = $flag; + if ($value !== true) { + $s[] = $value; + } + } + return $s; + } + + /** + * Return the current set of options and parameters seen in Json format. + * + * @return string + */ + public function toJson() + { + $this->parse(); + $j = array(); + foreach ($this->_options as $flag => $value) { + $j['options'][] = array( + 'option' => array( + 'flag' => $flag, + 'parameter' => $value + ) + ); + } + + /** + * @see Zend_Json + */ + require_once 'Zend/Json.php'; + $json = Zend_Json::encode($j); + + return $json; + } + + /** + * Return the current set of options and parameters seen in XML format. + * + * @return string + */ + public function toXml() + { + $this->parse(); + $doc = new DomDocument('1.0', 'utf-8'); + $optionsNode = $doc->createElement('options'); + $doc->appendChild($optionsNode); + foreach ($this->_options as $flag => $value) { + $optionNode = $doc->createElement('option'); + $optionNode->setAttribute('flag', utf8_encode($flag)); + if ($value !== true) { + $optionNode->setAttribute('parameter', utf8_encode($value)); + } + $optionsNode->appendChild($optionNode); + } + $xml = $doc->saveXML(); + return $xml; + } + + /** + * Return a list of options that have been seen in the current argv. + * + * @return array + */ + public function getOptions() + { + $this->parse(); + return array_keys($this->_options); + } + + /** + * Return the state of the option seen on the command line of the + * current application invocation. + * + * This function returns true, or the parameter value to the option, if any. + * If the option was not given, this function returns false. + * + * @param string $flag + * @return mixed + */ + public function getOption($flag) + { + $this->parse(); + if ($this->_getoptConfig[self::CONFIG_IGNORECASE]) { + $flag = strtolower($flag); + } + if (isset($this->_ruleMap[$flag])) { + $flag = $this->_ruleMap[$flag]; + if (isset($this->_options[$flag])) { + return $this->_options[$flag]; + } + } + return null; + } + + /** + * Return the arguments from the command-line following all options found. + * + * @return array + */ + public function getRemainingArgs() + { + $this->parse(); + return $this->_remainingArgs; + } + + /** + * Return a useful option reference, formatted for display in an + * error message. + * + * Note that this usage information is provided in most Exceptions + * generated by this class. + * + * @return string + */ + public function getUsageMessage() + { + $usage = "Usage: {$this->_progname} [ options ]\n"; + $maxLen = 20; + foreach ($this->_rules as $rule) { + $flags = array(); + if (is_array($rule['alias'])) { + foreach ($rule['alias'] as $flag) { + $flags[] = (strlen($flag) == 1 ? '-' : '--') . $flag; + } + } + $linepart['name'] = implode('|', $flags); + if (isset($rule['param']) && $rule['param'] != 'none') { + $linepart['name'] .= ' '; + switch ($rule['param']) { + case 'optional': + $linepart['name'] .= "[ <{$rule['paramType']}> ]"; + break; + case 'required': + $linepart['name'] .= "<{$rule['paramType']}>"; + break; + } + } + if (strlen($linepart['name']) > $maxLen) { + $maxLen = strlen($linepart['name']); + } + $linepart['help'] = ''; + if (isset($rule['help'])) { + $linepart['help'] .= $rule['help']; + } + $lines[] = $linepart; + } + foreach ($lines as $linepart) { + $usage .= sprintf("%s %s\n", + str_pad($linepart['name'], $maxLen), + $linepart['help']); + } + return $usage; + } + + /** + * Define aliases for options. + * + * The parameter $aliasMap is an associative array + * mapping option name (short or long) to an alias. + * + * @param array $aliasMap + * @throws Zend_Console_Getopt_Exception + * @return Zend_Console_Getopt Provides a fluent interface + */ + public function setAliases($aliasMap) + { + foreach ($aliasMap as $flag => $alias) + { + if ($this->_getoptConfig[self::CONFIG_IGNORECASE]) { + $flag = strtolower($flag); + $alias = strtolower($alias); + } + if (!isset($this->_ruleMap[$flag])) { + continue; + } + $flag = $this->_ruleMap[$flag]; + if (isset($this->_rules[$alias]) || isset($this->_ruleMap[$alias])) { + $o = (strlen($alias) == 1 ? '-' : '--') . $alias; + require_once 'Zend/Console/Getopt/Exception.php'; + throw new Zend_Console_Getopt_Exception( + "Option \"$o\" is being defined more than once."); + } + $this->_rules[$flag]['alias'][] = $alias; + $this->_ruleMap[$alias] = $flag; + } + return $this; + } + + /** + * Define help messages for options. + * + * The parameter $help_map is an associative array + * mapping option name (short or long) to the help string. + * + * @param array $helpMap + * @return Zend_Console_Getopt Provides a fluent interface + */ + public function setHelp($helpMap) + { + foreach ($helpMap as $flag => $help) + { + if (!isset($this->_ruleMap[$flag])) { + continue; + } + $flag = $this->_ruleMap[$flag]; + $this->_rules[$flag]['help'] = $help; + } + return $this; + } + + /** + * Parse command-line arguments and find both long and short + * options. + * + * Also find option parameters, and remaining arguments after + * all options have been parsed. + * + * @return Zend_Console_Getopt|null Provides a fluent interface + */ + public function parse() + { + if ($this->_parsed === true) { + return; + } + $argv = $this->_argv; + $this->_options = array(); + $this->_remainingArgs = array(); + while (count($argv) > 0) { + if ($argv[0] == '--') { + array_shift($argv); + if ($this->_getoptConfig[self::CONFIG_DASHDASH]) { + $this->_remainingArgs = array_merge($this->_remainingArgs, $argv); + break; + } + } + if (substr($argv[0], 0, 2) == '--') { + $this->_parseLongOption($argv); + } else if (substr($argv[0], 0, 1) == '-' && ('-' != $argv[0] || count($argv) >1)) { + $this->_parseShortOptionCluster($argv); + } else if($this->_getoptConfig[self::CONFIG_PARSEALL]) { + $this->_remainingArgs[] = array_shift($argv); + } else { + /* + * We should put all other arguments in _remainingArgs and stop parsing + * since CONFIG_PARSEALL is false. + */ + $this->_remainingArgs = array_merge($this->_remainingArgs, $argv); + break; + } + } + $this->_parsed = true; + return $this; + } + + /** + * Parse command-line arguments for a single long option. + * A long option is preceded by a double '--' character. + * Long options may not be clustered. + * + * @param mixed &$argv + * @return void + */ + protected function _parseLongOption(&$argv) + { + $optionWithParam = ltrim(array_shift($argv), '-'); + $l = explode('=', $optionWithParam, 2); + $flag = array_shift($l); + $param = array_shift($l); + if (isset($param)) { + array_unshift($argv, $param); + } + $this->_parseSingleOption($flag, $argv); + } + + /** + * Parse command-line arguments for short options. + * Short options are those preceded by a single '-' character. + * Short options may be clustered. + * + * @param mixed &$argv + * @return void + */ + protected function _parseShortOptionCluster(&$argv) + { + $flagCluster = ltrim(array_shift($argv), '-'); + foreach (str_split($flagCluster) as $flag) { + $this->_parseSingleOption($flag, $argv); + } + } + + /** + * Parse command-line arguments for a single option. + * + * @param string $flag + * @param mixed $argv + * @throws Zend_Console_Getopt_Exception + * @return void + */ + protected function _parseSingleOption($flag, &$argv) + { + if ($this->_getoptConfig[self::CONFIG_IGNORECASE]) { + $flag = strtolower($flag); + } + if (!isset($this->_ruleMap[$flag])) { + require_once 'Zend/Console/Getopt/Exception.php'; + throw new Zend_Console_Getopt_Exception( + "Option \"$flag\" is not recognized.", + $this->getUsageMessage()); + } + $realFlag = $this->_ruleMap[$flag]; + switch ($this->_rules[$realFlag]['param']) { + case 'required': + if (count($argv) > 0) { + $param = array_shift($argv); + $this->_checkParameterType($realFlag, $param); + } else { + require_once 'Zend/Console/Getopt/Exception.php'; + throw new Zend_Console_Getopt_Exception( + "Option \"$flag\" requires a parameter.", + $this->getUsageMessage()); + } + break; + case 'optional': + if (count($argv) > 0 && substr($argv[0], 0, 1) != '-') { + $param = array_shift($argv); + $this->_checkParameterType($realFlag, $param); + } else { + $param = true; + } + break; + default: + $param = true; + } + $this->_options[$realFlag] = $param; + } + + /** + * Return true if the parameter is in a valid format for + * the option $flag. + * Throw an exception in most other cases. + * + * @param string $flag + * @param string $param + * @throws Zend_Console_Getopt_Exception + * @return bool + */ + protected function _checkParameterType($flag, $param) + { + $type = 'string'; + if (isset($this->_rules[$flag]['paramType'])) { + $type = $this->_rules[$flag]['paramType']; + } + switch ($type) { + case 'word': + if (preg_match('/\W/', $param)) { + require_once 'Zend/Console/Getopt/Exception.php'; + throw new Zend_Console_Getopt_Exception( + "Option \"$flag\" requires a single-word parameter, but was given \"$param\".", + $this->getUsageMessage()); + } + break; + case 'integer': + if (preg_match('/\D/', $param)) { + require_once 'Zend/Console/Getopt/Exception.php'; + throw new Zend_Console_Getopt_Exception( + "Option \"$flag\" requires an integer parameter, but was given \"$param\".", + $this->getUsageMessage()); + } + break; + case 'string': + default: + break; + } + return true; + } + + /** + * Define legal options using the gnu-style format. + * + * @param string $rules + * @return void + */ + protected function _addRulesModeGnu($rules) + { + $ruleArray = array(); + + /** + * Options may be single alphanumeric characters. + * Options may have a ':' which indicates a required string parameter. + * No long options or option aliases are supported in GNU style. + */ + preg_match_all('/([a-zA-Z0-9]:?)/', $rules, $ruleArray); + foreach ($ruleArray[1] as $rule) { + $r = array(); + $flag = substr($rule, 0, 1); + if ($this->_getoptConfig[self::CONFIG_IGNORECASE]) { + $flag = strtolower($flag); + } + $r['alias'][] = $flag; + if (substr($rule, 1, 1) == ':') { + $r['param'] = 'required'; + $r['paramType'] = 'string'; + } else { + $r['param'] = 'none'; + } + $this->_rules[$flag] = $r; + $this->_ruleMap[$flag] = $flag; + } + } + + /** + * Define legal options using the Zend-style format. + * + * @param array $rules + * @throws Zend_Console_Getopt_Exception + * @return void + */ + protected function _addRulesModeZend($rules) + { + foreach ($rules as $ruleCode => $helpMessage) + { + // this may have to translate the long parm type if there + // are any complaints that =string will not work (even though that use + // case is not documented) + if (in_array(substr($ruleCode, -2, 1), array('-', '='))) { + $flagList = substr($ruleCode, 0, -2); + $delimiter = substr($ruleCode, -2, 1); + $paramType = substr($ruleCode, -1); + } else { + $flagList = $ruleCode; + $delimiter = $paramType = null; + } + if ($this->_getoptConfig[self::CONFIG_IGNORECASE]) { + $flagList = strtolower($flagList); + } + $flags = explode('|', $flagList); + $rule = array(); + $mainFlag = $flags[0]; + foreach ($flags as $flag) { + if (empty($flag)) { + require_once 'Zend/Console/Getopt/Exception.php'; + throw new Zend_Console_Getopt_Exception( + "Blank flag not allowed in rule \"$ruleCode\"."); + } + if (strlen($flag) == 1) { + if (isset($this->_ruleMap[$flag])) { + require_once 'Zend/Console/Getopt/Exception.php'; + throw new Zend_Console_Getopt_Exception( + "Option \"-$flag\" is being defined more than once."); + } + $this->_ruleMap[$flag] = $mainFlag; + $rule['alias'][] = $flag; + } else { + if (isset($this->_rules[$flag]) || isset($this->_ruleMap[$flag])) { + require_once 'Zend/Console/Getopt/Exception.php'; + throw new Zend_Console_Getopt_Exception( + "Option \"--$flag\" is being defined more than once."); + } + $this->_ruleMap[$flag] = $mainFlag; + $rule['alias'][] = $flag; + } + } + if (isset($delimiter)) { + switch ($delimiter) { + case self::PARAM_REQUIRED: + $rule['param'] = 'required'; + break; + case self::PARAM_OPTIONAL: + default: + $rule['param'] = 'optional'; + } + switch (substr($paramType, 0, 1)) { + case self::TYPE_WORD: + $rule['paramType'] = 'word'; + break; + case self::TYPE_INTEGER: + $rule['paramType'] = 'integer'; + break; + case self::TYPE_STRING: + default: + $rule['paramType'] = 'string'; + } + } else { + $rule['param'] = 'none'; + } + $rule['help'] = $helpMessage; + $this->_rules[$mainFlag] = $rule; + } + } + +} diff --git a/library/Zend/Console/Getopt/Exception.php b/library/Zend/Console/Getopt/Exception.php new file mode 100644 index 000000000..b323d1fac --- /dev/null +++ b/library/Zend/Console/Getopt/Exception.php @@ -0,0 +1,66 @@ +usage = $usage; + parent::__construct($message); + } + + /** + * Returns the usage + * + * @return string + */ + public function getUsageMessage() + { + return $this->usage; + } +} diff --git a/library/Zend/Controller/Action.php b/library/Zend/Controller/Action.php new file mode 100644 index 000000000..562598236 --- /dev/null +++ b/library/Zend/Controller/Action.php @@ -0,0 +1,688 @@ +setRequest($request) + ->setResponse($response) + ->_setInvokeArgs($invokeArgs); + $this->_helper = new Zend_Controller_Action_HelperBroker($this); + $this->init(); + } + + /** + * Initialize object + * + * Called from {@link __construct()} as final step of object instantiation. + * + * @return void + */ + public function init() + { + } + + /** + * Initialize View object + * + * Initializes {@link $view} if not otherwise a Zend_View_Interface. + * + * If {@link $view} is not otherwise set, instantiates a new Zend_View + * object, using the 'views' subdirectory at the same level as the + * controller directory for the current module as the base directory. + * It uses this to set the following: + * - script path = views/scripts/ + * - helper path = views/helpers/ + * - filter path = views/filters/ + * + * @return Zend_View_Interface + * @throws Zend_Controller_Exception if base view directory does not exist + */ + public function initView() + { + if (!$this->getInvokeArg('noViewRenderer') && $this->_helper->hasHelper('viewRenderer')) { + return $this->view; + } + + require_once 'Zend/View/Interface.php'; + if (isset($this->view) && ($this->view instanceof Zend_View_Interface)) { + return $this->view; + } + + $request = $this->getRequest(); + $module = $request->getModuleName(); + $dirs = $this->getFrontController()->getControllerDirectory(); + if (empty($module) || !isset($dirs[$module])) { + $module = $this->getFrontController()->getDispatcher()->getDefaultModule(); + } + $baseDir = dirname($dirs[$module]) . DIRECTORY_SEPARATOR . 'views'; + if (!file_exists($baseDir) || !is_dir($baseDir)) { + require_once 'Zend/Controller/Exception.php'; + throw new Zend_Controller_Exception('Missing base view directory ("' . $baseDir . '")'); + } + + require_once 'Zend/View.php'; + $this->view = new Zend_View(array('basePath' => $baseDir)); + + return $this->view; + } + + /** + * Render a view + * + * Renders a view. By default, views are found in the view script path as + * /.phtml. You may change the script suffix by + * resetting {@link $viewSuffix}. You may omit the controller directory + * prefix by specifying boolean true for $noController. + * + * By default, the rendered contents are appended to the response. You may + * specify the named body content segment to set by specifying a $name. + * + * @see Zend_Controller_Response_Abstract::appendBody() + * @param string|null $action Defaults to action registered in request object + * @param string|null $name Response object named path segment to use; defaults to null + * @param bool $noController Defaults to false; i.e. use controller name as subdir in which to search for view script + * @return void + */ + public function render($action = null, $name = null, $noController = false) + { + if (!$this->getInvokeArg('noViewRenderer') && $this->_helper->hasHelper('viewRenderer')) { + return $this->_helper->viewRenderer->render($action, $name, $noController); + } + + $view = $this->initView(); + $script = $this->getViewScript($action, $noController); + + $this->getResponse()->appendBody( + $view->render($script), + $name + ); + } + + /** + * Render a given view script + * + * Similar to {@link render()}, this method renders a view script. Unlike render(), + * however, it does not autodetermine the view script via {@link getViewScript()}, + * but instead renders the script passed to it. Use this if you know the + * exact view script name and path you wish to use, or if using paths that do not + * conform to the spec defined with getViewScript(). + * + * By default, the rendered contents are appended to the response. You may + * specify the named body content segment to set by specifying a $name. + * + * @param string $script + * @param string $name + * @return void + */ + public function renderScript($script, $name = null) + { + if (!$this->getInvokeArg('noViewRenderer') && $this->_helper->hasHelper('viewRenderer')) { + return $this->_helper->viewRenderer->renderScript($script, $name); + } + + $view = $this->initView(); + $this->getResponse()->appendBody( + $view->render($script), + $name + ); + } + + /** + * Construct view script path + * + * Used by render() to determine the path to the view script. + * + * @param string $action Defaults to action registered in request object + * @param bool $noController Defaults to false; i.e. use controller name as subdir in which to search for view script + * @return string + * @throws Zend_Controller_Exception with bad $action + */ + public function getViewScript($action = null, $noController = null) + { + if (!$this->getInvokeArg('noViewRenderer') && $this->_helper->hasHelper('viewRenderer')) { + $viewRenderer = $this->_helper->getHelper('viewRenderer'); + if (null !== $noController) { + $viewRenderer->setNoController($noController); + } + return $viewRenderer->getViewScript($action); + } + + $request = $this->getRequest(); + if (null === $action) { + $action = $request->getActionName(); + } elseif (!is_string($action)) { + require_once 'Zend/Controller/Exception.php'; + throw new Zend_Controller_Exception('Invalid action specifier for view render'); + } + + if (null === $this->_delimiters) { + $dispatcher = Zend_Controller_Front::getInstance()->getDispatcher(); + $wordDelimiters = $dispatcher->getWordDelimiter(); + $pathDelimiters = $dispatcher->getPathDelimiter(); + $this->_delimiters = array_unique(array_merge($wordDelimiters, (array) $pathDelimiters)); + } + + $action = str_replace($this->_delimiters, '-', $action); + $script = $action . '.' . $this->viewSuffix; + + if (!$noController) { + $controller = $request->getControllerName(); + $controller = str_replace($this->_delimiters, '-', $controller); + $script = $controller . DIRECTORY_SEPARATOR . $script; + } + + return $script; + } + + /** + * Return the Request object + * + * @return Zend_Controller_Request_Abstract + */ + public function getRequest() + { + return $this->_request; + } + + /** + * Set the Request object + * + * @param Zend_Controller_Request_Abstract $request + * @return Zend_Controller_Action + */ + public function setRequest(Zend_Controller_Request_Abstract $request) + { + $this->_request = $request; + return $this; + } + + /** + * Return the Response object + * + * @return Zend_Controller_Response_Abstract + */ + public function getResponse() + { + return $this->_response; + } + + /** + * Set the Response object + * + * @param Zend_Controller_Response_Abstract $response + * @return Zend_Controller_Action + */ + public function setResponse(Zend_Controller_Response_Abstract $response) + { + $this->_response = $response; + return $this; + } + + /** + * Set invocation arguments + * + * @param array $args + * @return Zend_Controller_Action + */ + protected function _setInvokeArgs(array $args = array()) + { + $this->_invokeArgs = $args; + return $this; + } + + /** + * Return the array of constructor arguments (minus the Request object) + * + * @return array + */ + public function getInvokeArgs() + { + return $this->_invokeArgs; + } + + /** + * Return a single invocation argument + * + * @param string $key + * @return mixed + */ + public function getInvokeArg($key) + { + if (isset($this->_invokeArgs[$key])) { + return $this->_invokeArgs[$key]; + } + + return null; + } + + /** + * Get a helper by name + * + * @param string $helperName + * @return Zend_Controller_Action_Helper_Abstract + */ + public function getHelper($helperName) + { + return $this->_helper->{$helperName}; + } + + /** + * Get a clone of a helper by name + * + * @param string $helperName + * @return Zend_Controller_Action_Helper_Abstract + */ + public function getHelperCopy($helperName) + { + return clone $this->_helper->{$helperName}; + } + + /** + * Set the front controller instance + * + * @param Zend_Controller_Front $front + * @return Zend_Controller_Action + */ + public function setFrontController(Zend_Controller_Front $front) + { + $this->_frontController = $front; + return $this; + } + + /** + * Retrieve Front Controller + * + * @return Zend_Controller_Front + */ + public function getFrontController() + { + // Used cache version if found + if (null !== $this->_frontController) { + return $this->_frontController; + } + + // Grab singleton instance, if class has been loaded + if (class_exists('Zend_Controller_Front')) { + $this->_frontController = Zend_Controller_Front::getInstance(); + return $this->_frontController; + } + + // Throw exception in all other cases + require_once 'Zend/Controller/Exception.php'; + throw new Zend_Controller_Exception('Front controller class has not been loaded'); + } + + /** + * Pre-dispatch routines + * + * Called before action method. If using class with + * {@link Zend_Controller_Front}, it may modify the + * {@link $_request Request object} and reset its dispatched flag in order + * to skip processing the current action. + * + * @return void + */ + public function preDispatch() + { + } + + /** + * Post-dispatch routines + * + * Called after action method execution. If using class with + * {@link Zend_Controller_Front}, it may modify the + * {@link $_request Request object} and reset its dispatched flag in order + * to process an additional action. + * + * Common usages for postDispatch() include rendering content in a sitewide + * template, link url correction, setting headers, etc. + * + * @return void + */ + public function postDispatch() + { + } + + /** + * Proxy for undefined methods. Default behavior is to throw an + * exception on undefined methods, however this function can be + * overridden to implement magic (dynamic) actions, or provide run-time + * dispatching. + * + * @param string $methodName + * @param array $args + * @return void + * @throws Zend_Controller_Action_Exception + */ + public function __call($methodName, $args) + { + require_once 'Zend/Controller/Action/Exception.php'; + if ('Action' == substr($methodName, -6)) { + $action = substr($methodName, 0, strlen($methodName) - 6); + throw new Zend_Controller_Action_Exception(sprintf('Action "%s" does not exist and was not trapped in __call()', $action), 404); + } + + throw new Zend_Controller_Action_Exception(sprintf('Method "%s" does not exist and was not trapped in __call()', $methodName), 500); + } + + /** + * Dispatch the requested action + * + * @param string $action Method name of action + * @return void + */ + public function dispatch($action) + { + // Notify helpers of action preDispatch state + $this->_helper->notifyPreDispatch(); + + $this->preDispatch(); + if ($this->getRequest()->isDispatched()) { + if (null === $this->_classMethods) { + $this->_classMethods = get_class_methods($this); + } + + // preDispatch() didn't change the action, so we can continue + if ($this->getInvokeArg('useCaseSensitiveActions') || in_array($action, $this->_classMethods)) { + if ($this->getInvokeArg('useCaseSensitiveActions')) { + trigger_error('Using case sensitive actions without word separators is deprecated; please do not rely on this "feature"'); + } + $this->$action(); + } else { + $this->__call($action, array()); + } + $this->postDispatch(); + } + + // whats actually important here is that this action controller is + // shutting down, regardless of dispatching; notify the helpers of this + // state + $this->_helper->notifyPostDispatch(); + } + + /** + * Call the action specified in the request object, and return a response + * + * Not used in the Action Controller implementation, but left for usage in + * Page Controller implementations. Dispatches a method based on the + * request. + * + * Returns a Zend_Controller_Response_Abstract object, instantiating one + * prior to execution if none exists in the controller. + * + * {@link preDispatch()} is called prior to the action, + * {@link postDispatch()} is called following it. + * + * @param null|Zend_Controller_Request_Abstract $request Optional request + * object to use + * @param null|Zend_Controller_Response_Abstract $response Optional response + * object to use + * @return Zend_Controller_Response_Abstract + */ + public function run(Zend_Controller_Request_Abstract $request = null, Zend_Controller_Response_Abstract $response = null) + { + if (null !== $request) { + $this->setRequest($request); + } else { + $request = $this->getRequest(); + } + + if (null !== $response) { + $this->setResponse($response); + } + + $action = $request->getActionName(); + if (empty($action)) { + $action = 'index'; + } + $action = $action . 'Action'; + + $request->setDispatched(true); + $this->dispatch($action); + + return $this->getResponse(); + } + + /** + * Gets a parameter from the {@link $_request Request object}. If the + * parameter does not exist, NULL will be returned. + * + * If the parameter does not exist and $default is set, then + * $default will be returned instead of NULL. + * + * @param string $paramName + * @param mixed $default + * @return mixed + */ + protected function _getParam($paramName, $default = null) + { + $value = $this->getRequest()->getParam($paramName); + if ((null === $value) && (null !== $default)) { + $value = $default; + } + + return $value; + } + + /** + * Set a parameter in the {@link $_request Request object}. + * + * @param string $paramName + * @param mixed $value + * @return Zend_Controller_Action + */ + protected function _setParam($paramName, $value) + { + $this->getRequest()->setParam($paramName, $value); + + return $this; + } + + /** + * Determine whether a given parameter exists in the + * {@link $_request Request object}. + * + * @param string $paramName + * @return boolean + */ + protected function _hasParam($paramName) + { + return null !== $this->getRequest()->getParam($paramName); + } + + /** + * Return all parameters in the {@link $_request Request object} + * as an associative array. + * + * @return array + */ + protected function _getAllParams() + { + return $this->getRequest()->getParams(); + } + + + /** + * Forward to another controller/action. + * + * It is important to supply the unformatted names, i.e. "article" + * rather than "ArticleController". The dispatcher will do the + * appropriate formatting when the request is received. + * + * If only an action name is provided, forwards to that action in this + * controller. + * + * If an action and controller are specified, forwards to that action and + * controller in this module. + * + * Specifying an action, controller, and module is the most specific way to + * forward. + * + * A fourth argument, $params, will be used to set the request parameters. + * If either the controller or module are unnecessary for forwarding, + * simply pass null values for them before specifying the parameters. + * + * @param string $action + * @param string $controller + * @param string $module + * @param array $params + * @return void + */ + final protected function _forward($action, $controller = null, $module = null, array $params = null) + { + $request = $this->getRequest(); + + if (null !== $params) { + $request->setParams($params); + } + + if (null !== $controller) { + $request->setControllerName($controller); + + // Module should only be reset if controller has been specified + if (null !== $module) { + $request->setModuleName($module); + } + } + + $request->setActionName($action) + ->setDispatched(false); + } + + /** + * Redirect to another URL + * + * Proxies to {@link Zend_Controller_Action_Helper_Redirector::gotoUrl()}. + * + * @param string $url + * @param array $options Options to be used when redirecting + * @return void + */ + protected function _redirect($url, array $options = array()) + { + $this->_helper->redirector->gotoUrl($url, $options); + } +} diff --git a/library/Zend/Controller/Action/Exception.php b/library/Zend/Controller/Action/Exception.php new file mode 100644 index 000000000..f7d0b579f --- /dev/null +++ b/library/Zend/Controller/Action/Exception.php @@ -0,0 +1,38 @@ +_actionController = $actionController; + return $this; + } + + /** + * Retrieve current action controller + * + * @return Zend_Controller_Action + */ + public function getActionController() + { + return $this->_actionController; + } + + /** + * Retrieve front controller instance + * + * @return Zend_Controller_Front + */ + public function getFrontController() + { + return Zend_Controller_Front::getInstance(); + } + + /** + * Hook into action controller initialization + * + * @return void + */ + public function init() + { + } + + /** + * Hook into action controller preDispatch() workflow + * + * @return void + */ + public function preDispatch() + { + } + + /** + * Hook into action controller postDispatch() workflow + * + * @return void + */ + public function postDispatch() + { + } + + /** + * getRequest() - + * + * @return Zend_Controller_Request_Abstract $request + */ + public function getRequest() + { + $controller = $this->getActionController(); + if (null === $controller) { + $controller = $this->getFrontController(); + } + + return $controller->getRequest(); + } + + /** + * getResponse() - + * + * @return Zend_Controller_Response_Abstract $response + */ + public function getResponse() + { + $controller = $this->getActionController(); + if (null === $controller) { + $controller = $this->getFrontController(); + } + + return $controller->getResponse(); + } + + /** + * getName() + * + * @return string + */ + public function getName() + { + $full_class_name = get_class($this); + + if (strpos($full_class_name, '_') !== false) { + $helper_name = strrchr($full_class_name, '_'); + return ltrim($helper_name, '_'); + } else { + return $full_class_name; + } + } +} diff --git a/library/Zend/Controller/Action/Helper/ActionStack.php b/library/Zend/Controller/Action/Helper/ActionStack.php new file mode 100644 index 000000000..17b9862c6 --- /dev/null +++ b/library/Zend/Controller/Action/Helper/ActionStack.php @@ -0,0 +1,138 @@ +hasPlugin('Zend_Controller_Plugin_ActionStack')) { + /** + * @see Zend_Controller_Plugin_ActionStack + */ + require_once 'Zend/Controller/Plugin/ActionStack.php'; + $this->_actionStack = new Zend_Controller_Plugin_ActionStack(); + $front->registerPlugin($this->_actionStack, 97); + } else { + $this->_actionStack = $front->getPlugin('Zend_Controller_Plugin_ActionStack'); + } + } + + /** + * Push onto the stack + * + * @param Zend_Controller_Request_Abstract $next + * @return Zend_Controller_Action_Helper_ActionStack Provides a fluent interface + */ + public function pushStack(Zend_Controller_Request_Abstract $next) + { + $this->_actionStack->pushStack($next); + return $this; + } + + /** + * Push a new action onto the stack + * + * @param string $action + * @param string $controller + * @param string $module + * @param array $params + * @throws Zend_Controller_Action_Exception + * @return Zend_Controller_Action_Helper_ActionStack + */ + public function actionToStack($action, $controller = null, $module = null, array $params = array()) + { + if ($action instanceof Zend_Controller_Request_Abstract) { + return $this->pushStack($action); + } elseif (!is_string($action)) { + /** + * @see Zend_Controller_Action_Exception + */ + require_once 'Zend/Controller/Action/Exception.php'; + throw new Zend_Controller_Action_Exception('ActionStack requires either a request object or minimally a string action'); + } + + $request = $this->getRequest(); + + if ($request instanceof Zend_Controller_Request_Abstract === false){ + /** + * @see Zend_Controller_Action_Exception + */ + require_once 'Zend/Controller/Action/Exception.php'; + throw new Zend_Controller_Action_Exception('Request object not set yet'); + } + + $controller = (null === $controller) ? $request->getControllerName() : $controller; + $module = (null === $module) ? $request->getModuleName() : $module; + + /** + * @see Zend_Controller_Request_Simple + */ + require_once 'Zend/Controller/Request/Simple.php'; + $newRequest = new Zend_Controller_Request_Simple($action, $controller, $module, $params); + + return $this->pushStack($newRequest); + } + + /** + * Perform helper when called as $this->_helper->actionStack() from an action controller + * + * Proxies to {@link simple()} + * + * @param string $action + * @param string $controller + * @param string $module + * @param array $params + * @return boolean + */ + public function direct($action, $controller = null, $module = null, array $params = array()) + { + return $this->actionToStack($action, $controller, $module, $params); + } +} diff --git a/library/Zend/Controller/Action/Helper/AjaxContext.php b/library/Zend/Controller/Action/Helper/AjaxContext.php new file mode 100644 index 000000000..205f10b3a --- /dev/null +++ b/library/Zend/Controller/Action/Helper/AjaxContext.php @@ -0,0 +1,77 @@ +addContext('html', array('suffix' => 'ajax')); + } + + /** + * Initialize AJAX context switching + * + * Checks for XHR requests; if detected, attempts to perform context switch. + * + * @param string $format + * @return void + */ + public function initContext($format = null) + { + $this->_currentContext = null; + + if (!$this->getRequest()->isXmlHttpRequest()) { + return; + } + + return parent::initContext($format); + } +} diff --git a/library/Zend/Controller/Action/Helper/AutoComplete/Abstract.php b/library/Zend/Controller/Action/Helper/AutoComplete/Abstract.php new file mode 100644 index 000000000..b3b6f0c2f --- /dev/null +++ b/library/Zend/Controller/Action/Helper/AutoComplete/Abstract.php @@ -0,0 +1,149 @@ +disableLayout(); + } + + Zend_Controller_Action_HelperBroker::getStaticHelper('viewRenderer')->setNoRender(true); + + return $this; + } + + /** + * Encode data to JSON + * + * @param mixed $data + * @param bool $keepLayouts + * @throws Zend_Controller_Action_Exception + * @return string + */ + public function encodeJson($data, $keepLayouts = false) + { + if ($this->validateData($data)) { + return Zend_Controller_Action_HelperBroker::getStaticHelper('Json')->encodeJson($data, $keepLayouts); + } + + /** + * @see Zend_Controller_Action_Exception + */ + require_once 'Zend/Controller/Action/Exception.php'; + throw new Zend_Controller_Action_Exception('Invalid data passed for autocompletion'); + } + + /** + * Send autocompletion data + * + * Calls prepareAutoCompletion, populates response body with this + * information, and sends response. + * + * @param mixed $data + * @param bool $keepLayouts + * @return string|void + */ + public function sendAutoCompletion($data, $keepLayouts = false) + { + $data = $this->prepareAutoCompletion($data, $keepLayouts); + + $response = $this->getResponse(); + $response->setBody($data); + + if (!$this->suppressExit) { + $response->sendResponse(); + exit; + } + + return $data; + } + + /** + * Strategy pattern: allow calling helper as broker method + * + * Prepares autocompletion data and, if $sendNow is true, immediately sends + * response. + * + * @param mixed $data + * @param bool $sendNow + * @param bool $keepLayouts + * @return string|void + */ + public function direct($data, $sendNow = true, $keepLayouts = false) + { + if ($sendNow) { + return $this->sendAutoCompletion($data, $keepLayouts); + } + + return $this->prepareAutoCompletion($data, $keepLayouts); + } +} diff --git a/library/Zend/Controller/Action/Helper/AutoCompleteDojo.php b/library/Zend/Controller/Action/Helper/AutoCompleteDojo.php new file mode 100644 index 000000000..3894c8763 --- /dev/null +++ b/library/Zend/Controller/Action/Helper/AutoCompleteDojo.php @@ -0,0 +1,87 @@ + $value) { + $items[] = array('label' => $value, 'name' => $value); + } + $data = new Zend_Dojo_Data('name', $items); + } + + if (!$keepLayouts) { + require_once 'Zend/Controller/Action/HelperBroker.php'; + Zend_Controller_Action_HelperBroker::getStaticHelper('viewRenderer')->setNoRender(true); + + require_once 'Zend/Layout.php'; + $layout = Zend_Layout::getMvcInstance(); + if ($layout instanceof Zend_Layout) { + $layout->disableLayout(); + } + } + + $response = Zend_Controller_Front::getInstance()->getResponse(); + $response->setHeader('Content-Type', 'application/json'); + + return $data->toJson(); + } +} diff --git a/library/Zend/Controller/Action/Helper/AutoCompleteScriptaculous.php b/library/Zend/Controller/Action/Helper/AutoCompleteScriptaculous.php new file mode 100644 index 000000000..0125c560b --- /dev/null +++ b/library/Zend/Controller/Action/Helper/AutoCompleteScriptaculous.php @@ -0,0 +1,82 @@ +validateData($data)) { + /** + * @see Zend_Controller_Action_Exception + */ + require_once 'Zend/Controller/Action/Exception.php'; + throw new Zend_Controller_Action_Exception('Invalid data passed for autocompletion'); + } + + $data = (array) $data; + $data = '
    • ' . implode('
    • ', $data) . '
    '; + + if (!$keepLayouts) { + $this->disableLayouts(); + } + + return $data; + } +} diff --git a/library/Zend/Controller/Action/Helper/Cache.php b/library/Zend/Controller/Action/Helper/Cache.php new file mode 100644 index 000000000..d1df4944a --- /dev/null +++ b/library/Zend/Controller/Action/Helper/Cache.php @@ -0,0 +1,279 @@ +getRequest()->getControllerName(); + $actions = array_unique($actions); + if (!isset($this->_caching[$controller])) { + $this->_caching[$controller] = array(); + } + if (!empty($tags)) { + $tags = array_unique($tags); + if (!isset($this->_tags[$controller])) { + $this->_tags[$controller] = array(); + } + } + foreach ($actions as $action) { + $this->_caching[$controller][] = $action; + if (!empty($tags)) { + $this->_tags[$controller][$action] = array(); + foreach ($tags as $tag) { + $this->_tags[$controller][$action][] = $tag; + } + } + } + if ($extension) { + if (!isset($this->_extensions[$controller])) { + $this->_extensions[$controller] = array(); + } + foreach ($actions as $action) { + $this->_extensions[$controller][$action] = $extension; + } + } + } + + /** + * Remove a specific page cache static file based on its + * relative URL from the application's public directory. + * The file extension is not required here; usually matches + * the original REQUEST_URI that was cached. + * + * @param string $relativeUrl + * @param bool $recursive + * @return mixed + */ + public function removePage($relativeUrl, $recursive = false) + { + $cache = $this->getCache(Zend_Cache_Manager::PAGECACHE); + if ($recursive) { + $backend = $cache->getBackend(); + if (($backend instanceof Zend_Cache_Backend) + && method_exists($backend, 'removeRecursively') + ) { + return $backend->removeRecursively($relativeUrl); + } + } + + return $cache->remove($relativeUrl); + } + + /** + * Remove a specific page cache static file based on its + * relative URL from the application's public directory. + * The file extension is not required here; usually matches + * the original REQUEST_URI that was cached. + * + * @param array $tags + * @return mixed + */ + public function removePagesTagged(array $tags) + { + return $this->getCache(Zend_Cache_Manager::PAGECACHE) + ->clean(Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG, $tags); + } + + /** + * Commence page caching for any cacheable actions + * + * @return void + */ + public function preDispatch() + { + $controller = $this->getRequest()->getControllerName(); + $action = $this->getRequest()->getActionName(); + $stats = ob_get_status(true); + foreach ($stats as $status) { + if ($status['name'] == 'Zend_Cache_Frontend_Page::_flush' + || $status['name'] == 'Zend_Cache_Frontend_Capture::_flush') { + $obStarted = true; + } + } + if (!isset($obStarted) && isset($this->_caching[$controller]) && + in_array($action, $this->_caching[$controller])) { + $reqUri = $this->getRequest()->getRequestUri(); + $tags = array(); + if (isset($this->_tags[$controller][$action]) + && !empty($this->_tags[$controller][$action])) { + $tags = array_unique($this->_tags[$controller][$action]); + } + $extension = null; + if (isset($this->_extensions[$controller][$action])) { + $extension = $this->_extensions[$controller][$action]; + } + $this->getCache(Zend_Cache_Manager::PAGECACHE) + ->start($this->_encodeCacheId($reqUri), $tags, $extension); + } + } + + /** + * Encode a Cache ID as hexadecimal. This is a workaround because Backend ID validation + * is trapped in the Frontend classes. Will try to get this reversed for ZF 2.0 + * because it's a major annoyance to have IDs so restricted! + * + * @return string + * @param string $requestUri + */ + protected function _encodeCacheId($requestUri) + { + return bin2hex($requestUri); + } + + /** + * Set an instance of the Cache Manager for this helper + * + * @param Zend_Cache_Manager $manager + * @return void + */ + public function setManager(Zend_Cache_Manager $manager) + { + $this->_manager = $manager; + return $this; + } + + /** + * Get the Cache Manager instance or instantiate the object if not + * exists. Attempts to load from bootstrap if available. + * + * @return Zend_Cache_Manager + */ + public function getManager() + { + if (!is_null($this->_manager)) { + return $this->_manager; + } + $front = Zend_Controller_Front::getInstance(); + if ($front->getParam('bootstrap') + && $front->getParam('bootstrap')->getResource('CacheManager')) { + return $front->getParam('bootstrap') + ->getResource('CacheManager'); + } + $this->_manager = new Zend_Cache_Manager; + return $this->_manager; + } + + /** + * Return a list of actions for the current Controller marked for + * caching + * + * @return array + */ + public function getCacheableActions() + { + return $this->_caching; + } + + /** + * Return a list of tags set for all cacheable actions + * + * @return array + */ + public function getCacheableTags() + { + return $this->_tags; + } + + /** + * Proxy non-matched methods back to Zend_Cache_Manager where + * appropriate + * + * @param string $method + * @param array $args + * @return mixed + */ + public function __call($method, $args) + { + if (method_exists($this->getManager(), $method)) { + return call_user_func_array( + array($this->getManager(), $method), $args + ); + } + throw new Zend_Controller_Action_Exception('Method does not exist:' + . $method); + } + +} diff --git a/library/Zend/Controller/Action/Helper/ContextSwitch.php b/library/Zend/Controller/Action/Helper/ContextSwitch.php new file mode 100644 index 000000000..8cf154d1e --- /dev/null +++ b/library/Zend/Controller/Action/Helper/ContextSwitch.php @@ -0,0 +1,1394 @@ +setConfig($options); + } elseif (is_array($options)) { + $this->setOptions($options); + } + + if (empty($this->_contexts)) { + $this->addContexts(array( + 'json' => array( + 'suffix' => 'json', + 'headers' => array('Content-Type' => 'application/json'), + 'callbacks' => array( + 'init' => 'initJsonContext', + 'post' => 'postJsonContext' + ) + ), + 'xml' => array( + 'suffix' => 'xml', + 'headers' => array('Content-Type' => 'application/xml'), + ) + )); + } + + $this->init(); + } + + /** + * Initialize at start of action controller + * + * Reset the view script suffix to the original state, or store the + * original state. + * + * @return void + */ + public function init() + { + if (null === $this->_viewSuffixOrig) { + $this->_viewSuffixOrig = $this->_getViewRenderer()->getViewSuffix(); + } else { + $this->_getViewRenderer()->setViewSuffix($this->_viewSuffixOrig); + } + } + + /** + * Configure object from array of options + * + * @param array $options + * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface + */ + public function setOptions(array $options) + { + if (isset($options['contexts'])) { + $this->setContexts($options['contexts']); + unset($options['contexts']); + } + + foreach ($options as $key => $value) { + $method = 'set' . ucfirst($key); + if (in_array($method, $this->_unconfigurable)) { + continue; + } + + if (in_array($method, $this->_specialConfig)) { + $method = '_' . $method; + } + + if (method_exists($this, $method)) { + $this->$method($value); + } + } + return $this; + } + + /** + * Set object state from config object + * + * @param Zend_Config $config + * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface + */ + public function setConfig(Zend_Config $config) + { + return $this->setOptions($config->toArray()); + } + + /** + * Strategy pattern: return object + * + * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface + */ + public function direct() + { + return $this; + } + + /** + * Initialize context detection and switching + * + * @param mixed $format + * @throws Zend_Controller_Action_Exception + * @return void + */ + public function initContext($format = null) + { + $this->_currentContext = null; + + $controller = $this->getActionController(); + $request = $this->getRequest(); + $action = $request->getActionName(); + + // Return if no context switching enabled, or no context switching + // enabled for this action + $contexts = $this->getActionContexts($action); + if (empty($contexts)) { + return; + } + + // Return if no context parameter provided + if (!$context = $request->getParam($this->getContextParam())) { + if ($format === null) { + return; + } + $context = $format; + $format = null; + } + + // Check if context allowed by action controller + if (!$this->hasActionContext($action, $context)) { + return; + } + + // Return if invalid context parameter provided and no format or invalid + // format provided + if (!$this->hasContext($context)) { + if (empty($format) || !$this->hasContext($format)) { + + return; + } + } + + // Use provided format if passed + if (!empty($format) && $this->hasContext($format)) { + $context = $format; + } + + $suffix = $this->getSuffix($context); + + $this->_getViewRenderer()->setViewSuffix($suffix); + + $headers = $this->getHeaders($context); + if (!empty($headers)) { + $response = $this->getResponse(); + foreach ($headers as $header => $content) { + $response->setHeader($header, $content); + } + } + + if ($this->getAutoDisableLayout()) { + /** + * @see Zend_Layout + */ + require_once 'Zend/Layout.php'; + $layout = Zend_Layout::getMvcInstance(); + if (null !== $layout) { + $layout->disableLayout(); + } + } + + if (null !== ($callback = $this->getCallback($context, self::TRIGGER_INIT))) { + if (is_string($callback) && method_exists($this, $callback)) { + $this->$callback(); + } elseif (is_string($callback) && function_exists($callback)) { + $callback(); + } elseif (is_array($callback)) { + call_user_func($callback); + } else { + /** + * @see Zend_Controller_Action_Exception + */ + require_once 'Zend/Controller/Action/Exception.php'; + throw new Zend_Controller_Action_Exception(sprintf('Invalid context callback registered for context "%s"', $context)); + } + } + + $this->_currentContext = $context; + } + + /** + * JSON context extra initialization + * + * Turns off viewRenderer auto-rendering + * + * @return void + */ + public function initJsonContext() + { + if (!$this->getAutoJsonSerialization()) { + return; + } + + $viewRenderer = Zend_Controller_Action_HelperBroker::getStaticHelper('viewRenderer'); + $view = $viewRenderer->view; + if ($view instanceof Zend_View_Interface) { + $viewRenderer->setNoRender(true); + } + } + + /** + * Should JSON contexts auto-serialize? + * + * @param boolean $flag + * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface + */ + public function setAutoJsonSerialization($flag) + { + $this->_autoJsonSerialization = (bool) $flag; + return $this; + } + + /** + * Get JSON context auto-serialization flag + * + * @return boolean + */ + public function getAutoJsonSerialization() + { + return $this->_autoJsonSerialization; + } + + /** + * Set suffix from array + * + * @param array $spec + * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface + */ + protected function _setSuffix(array $spec) + { + foreach ($spec as $context => $suffixInfo) { + if (!is_string($context)) { + $context = null; + } + + if (is_string($suffixInfo)) { + $this->setSuffix($context, $suffixInfo); + continue; + } elseif (is_array($suffixInfo)) { + if (isset($suffixInfo['suffix'])) { + $suffix = $suffixInfo['suffix']; + $prependViewRendererSuffix = true; + + if ((null === $context) && isset($suffixInfo['context'])) { + $context = $suffixInfo['context']; + } + + if (isset($suffixInfo['prependViewRendererSuffix'])) { + $prependViewRendererSuffix = $suffixInfo['prependViewRendererSuffix']; + } + + $this->setSuffix($context, $suffix, $prependViewRendererSuffix); + continue; + } + + $count = count($suffixInfo); + switch (true) { + case (($count < 2) && (null === $context)): + /** + * @see Zend_Controller_Action_Exception + */ + require_once 'Zend/Controller/Action/Exception.php'; + throw new Zend_Controller_Action_Exception('Invalid suffix information provided in config'); + case ($count < 2): + $suffix = array_shift($suffixInfo); + $this->setSuffix($context, $suffix); + break; + case (($count < 3) && (null === $context)): + $context = array_shift($suffixInfo); + $suffix = array_shift($suffixInfo); + $this->setSuffix($context, $suffix); + break; + case (($count == 3) && (null === $context)): + $context = array_shift($suffixInfo); + $suffix = array_shift($suffixInfo); + $prependViewRendererSuffix = array_shift($suffixInfo); + $this->setSuffix($context, $suffix, $prependViewRendererSuffix); + break; + case ($count >= 2): + $suffix = array_shift($suffixInfo); + $prependViewRendererSuffix = array_shift($suffixInfo); + $this->setSuffix($context, $suffix, $prependViewRendererSuffix); + break; + } + } + } + return $this; + } + + /** + * Customize view script suffix to use when switching context. + * + * Passing an empty suffix value to the setters disables the view script + * suffix change. + * + * @param string $context Context type for which to set suffix + * @param string $suffix Suffix to use + * @param boolean $prependViewRendererSuffix Whether or not to prepend the new suffix to the viewrenderer suffix + * @throws Zend_Controller_Action_Exception + * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface + */ + public function setSuffix($context, $suffix, $prependViewRendererSuffix = true) + { + if (!isset($this->_contexts[$context])) { + /** + * @see Zend_Controller_Action_Exception + */ + require_once 'Zend/Controller/Action/Exception.php'; + throw new Zend_Controller_Action_Exception(sprintf('Cannot set suffix; invalid context type "%s"', $context)); + } + + if (empty($suffix)) { + $suffix = ''; + } + + if (is_array($suffix)) { + if (isset($suffix['prependViewRendererSuffix'])) { + $prependViewRendererSuffix = $suffix['prependViewRendererSuffix']; + } + if (isset($suffix['suffix'])) { + $suffix = $suffix['suffix']; + } else { + $suffix = ''; + } + } + + $suffix = (string) $suffix; + + if ($prependViewRendererSuffix) { + if (empty($suffix)) { + $suffix = $this->_getViewRenderer()->getViewSuffix(); + } else { + $suffix .= '.' . $this->_getViewRenderer()->getViewSuffix(); + } + } + + $this->_contexts[$context]['suffix'] = $suffix; + return $this; + } + + /** + * Retrieve suffix for given context type + * + * @param string $type Context type + * @throws Zend_Controller_Action_Exception + * @return string + */ + public function getSuffix($type) + { + if (!isset($this->_contexts[$type])) { + /** + * @see Zend_Controller_Action_Exception + */ + require_once 'Zend/Controller/Action/Exception.php'; + throw new Zend_Controller_Action_Exception(sprintf('Cannot retrieve suffix; invalid context type "%s"', $type)); + } + + return $this->_contexts[$type]['suffix']; + } + + /** + * Does the given context exist? + * + * @param string $context + * @param boolean $throwException + * @throws Zend_Controller_Action_Exception if context does not exist and throwException is true + * @return bool + */ + public function hasContext($context, $throwException = false) + { + if (is_string($context)) { + if (isset($this->_contexts[$context])) { + return true; + } + } elseif (is_array($context)) { + $error = false; + foreach ($context as $test) { + if (!isset($this->_contexts[$test])) { + $error = (string) $test; + break; + } + } + if (false === $error) { + return true; + } + $context = $error; + } elseif (true === $context) { + return true; + } + + if ($throwException) { + /** + * @see Zend_Controller_Action_Exception + */ + require_once 'Zend/Controller/Action/Exception.php'; + throw new Zend_Controller_Action_Exception(sprintf('Context "%s" does not exist', $context)); + } + + return false; + } + + /** + * Add header to context + * + * @param string $context + * @param string $header + * @param string $content + * @throws Zend_Controller_Action_Exception + * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface + */ + public function addHeader($context, $header, $content) + { + $context = (string) $context; + $this->hasContext($context, true); + + $header = (string) $header; + $content = (string) $content; + + if (isset($this->_contexts[$context]['headers'][$header])) { + /** + * @see Zend_Controller_Action_Exception + */ + require_once 'Zend/Controller/Action/Exception.php'; + throw new Zend_Controller_Action_Exception(sprintf('Cannot add "%s" header to context "%s": already exists', $header, $context)); + } + + $this->_contexts[$context]['headers'][$header] = $content; + return $this; + } + + /** + * Customize response header to use when switching context + * + * Passing an empty header value to the setters disables the response + * header. + * + * @param string $type Context type for which to set suffix + * @param string $header Header to set + * @param string $content Header content + * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface + */ + public function setHeader($context, $header, $content) + { + $this->hasContext($context, true); + $context = (string) $context; + $header = (string) $header; + $content = (string) $content; + + $this->_contexts[$context]['headers'][$header] = $content; + return $this; + } + + /** + * Add multiple headers at once for a given context + * + * @param string $context + * @param array $headers + * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface + */ + public function addHeaders($context, array $headers) + { + foreach ($headers as $header => $content) { + $this->addHeader($context, $header, $content); + } + + return $this; + } + + /** + * Set headers from context => headers pairs + * + * @param array $options + * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface + */ + protected function _setHeaders(array $options) + { + foreach ($options as $context => $headers) { + if (!is_array($headers)) { + continue; + } + $this->setHeaders($context, $headers); + } + + return $this; + } + + /** + * Set multiple headers at once for a given context + * + * @param string $context + * @param array $headers + * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface + */ + public function setHeaders($context, array $headers) + { + $this->clearHeaders($context); + foreach ($headers as $header => $content) { + $this->setHeader($context, $header, $content); + } + + return $this; + } + + /** + * Retrieve context header + * + * Returns the value of a given header for a given context type + * + * @param string $context + * @param string $header + * @return string|null + */ + public function getHeader($context, $header) + { + $this->hasContext($context, true); + $context = (string) $context; + $header = (string) $header; + if (isset($this->_contexts[$context]['headers'][$header])) { + return $this->_contexts[$context]['headers'][$header]; + } + + return null; + } + + /** + * Retrieve context headers + * + * Returns all headers for a context as key/value pairs + * + * @param string $context + * @return array + */ + public function getHeaders($context) + { + $this->hasContext($context, true); + $context = (string) $context; + return $this->_contexts[$context]['headers']; + } + + /** + * Remove a single header from a context + * + * @param string $context + * @param string $header + * @return boolean + */ + public function removeHeader($context, $header) + { + $this->hasContext($context, true); + $context = (string) $context; + $header = (string) $header; + if (isset($this->_contexts[$context]['headers'][$header])) { + unset($this->_contexts[$context]['headers'][$header]); + return true; + } + + return false; + } + + /** + * Clear all headers for a given context + * + * @param string $context + * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface + */ + public function clearHeaders($context) + { + $this->hasContext($context, true); + $context = (string) $context; + $this->_contexts[$context]['headers'] = array(); + return $this; + } + + /** + * Validate trigger and return in normalized form + * + * @param string $trigger + * @throws Zend_Controller_Action_Exception + * @return string + */ + protected function _validateTrigger($trigger) + { + $trigger = strtoupper($trigger); + if ('TRIGGER_' !== substr($trigger, 0, 8)) { + $trigger = 'TRIGGER_' . $trigger; + } + + if (!in_array($trigger, array(self::TRIGGER_INIT, self::TRIGGER_POST))) { + /** + * @see Zend_Controller_Action_Exception + */ + require_once 'Zend/Controller/Action/Exception.php'; + throw new Zend_Controller_Action_Exception(sprintf('Invalid trigger "%s"', $trigger)); + } + + return $trigger; + } + + /** + * Set a callback for a given context and trigger + * + * @param string $context + * @param string $trigger + * @param string|array $callback + * @throws Zend_Controller_Action_Exception + * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface + */ + public function setCallback($context, $trigger, $callback) + { + $this->hasContext($context, true); + $trigger = $this->_validateTrigger($trigger); + + if (!is_string($callback)) { + if (!is_array($callback) || (2 != count($callback))) { + /** + * @see Zend_Controller_Action_Exception + */ + require_once 'Zend/Controller/Action/Exception.php'; + throw new Zend_Controller_Action_Exception('Invalid callback specified'); + } + } + + $this->_contexts[$context]['callbacks'][$trigger] = $callback; + return $this; + } + + /** + * Set callbacks from array of context => callbacks pairs + * + * @param array $options + * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface + */ + protected function _setCallbacks(array $options) + { + foreach ($options as $context => $callbacks) { + if (!is_array($callbacks)) { + continue; + } + + $this->setCallbacks($context, $callbacks); + } + return $this; + } + + /** + * Set callbacks for a given context + * + * Callbacks should be in trigger/callback pairs. + * + * @param string $context + * @param array $callbacks + * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface + */ + public function setCallbacks($context, array $callbacks) + { + $this->hasContext($context, true); + $context = (string) $context; + if (!isset($this->_contexts[$context]['callbacks'])) { + $this->_contexts[$context]['callbacks'] = array(); + } + + foreach ($callbacks as $trigger => $callback) { + $this->setCallback($context, $trigger, $callback); + } + return $this; + } + + /** + * Get a single callback for a given context and trigger + * + * @param string $context + * @param string $trigger + * @return string|array|null + */ + public function getCallback($context, $trigger) + { + $this->hasContext($context, true); + $trigger = $this->_validateTrigger($trigger); + if (isset($this->_contexts[$context]['callbacks'][$trigger])) { + return $this->_contexts[$context]['callbacks'][$trigger]; + } + + return null; + } + + /** + * Get all callbacks for a given context + * + * @param string $context + * @return array + */ + public function getCallbacks($context) + { + $this->hasContext($context, true); + return $this->_contexts[$context]['callbacks']; + } + + /** + * Clear a callback for a given context and trigger + * + * @param string $context + * @param string $trigger + * @return boolean + */ + public function removeCallback($context, $trigger) + { + $this->hasContext($context, true); + $trigger = $this->_validateTrigger($trigger); + if (isset($this->_contexts[$context]['callbacks'][$trigger])) { + unset($this->_contexts[$context]['callbacks'][$trigger]); + return true; + } + + return false; + } + + /** + * Clear all callbacks for a given context + * + * @param string $context + * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface + */ + public function clearCallbacks($context) + { + $this->hasContext($context, true); + $this->_contexts[$context]['callbacks'] = array(); + return $this; + } + + /** + * Set name of parameter to use when determining context format + * + * @param string $name + * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface + */ + public function setContextParam($name) + { + $this->_contextParam = (string) $name; + return $this; + } + + /** + * Return context format request parameter name + * + * @return string + */ + public function getContextParam() + { + return $this->_contextParam; + } + + /** + * Indicate default context to use when no context format provided + * + * @param string $type + * @throws Zend_Controller_Action_Exception + * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface + */ + public function setDefaultContext($type) + { + if (!isset($this->_contexts[$type])) { + /** + * @see Zend_Controller_Action_Exception + */ + require_once 'Zend/Controller/Action/Exception.php'; + throw new Zend_Controller_Action_Exception(sprintf('Cannot set default context; invalid context type "%s"', $type)); + } + + $this->_defaultContext = $type; + return $this; + } + + /** + * Return default context + * + * @return string + */ + public function getDefaultContext() + { + return $this->_defaultContext; + } + + /** + * Set flag indicating if layout should be disabled + * + * @param boolean $flag + * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface + */ + public function setAutoDisableLayout($flag) + { + $this->_disableLayout = ($flag) ? true : false; + return $this; + } + + /** + * Retrieve auto layout disable flag + * + * @return boolean + */ + public function getAutoDisableLayout() + { + return $this->_disableLayout; + } + + /** + * Add new context + * + * @param string $context Context type + * @param array $spec Context specification + * @throws Zend_Controller_Action_Exception + * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface + */ + public function addContext($context, array $spec) + { + if ($this->hasContext($context)) { + /** + * @see Zend_Controller_Action_Exception + */ + require_once 'Zend/Controller/Action/Exception.php'; + throw new Zend_Controller_Action_Exception(sprintf('Cannot add context "%s"; already exists', $context)); + } + $context = (string) $context; + + $this->_contexts[$context] = array(); + + $this->setSuffix($context, (isset($spec['suffix']) ? $spec['suffix'] : '')) + ->setHeaders($context, (isset($spec['headers']) ? $spec['headers'] : array())) + ->setCallbacks($context, (isset($spec['callbacks']) ? $spec['callbacks'] : array())); + return $this; + } + + /** + * Overwrite existing context + * + * @param string $context Context type + * @param array $spec Context specification + * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface + */ + public function setContext($context, array $spec) + { + $this->removeContext($context); + return $this->addContext($context, $spec); + } + + /** + * Add multiple contexts + * + * @param array $contexts + * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface + */ + public function addContexts(array $contexts) + { + foreach ($contexts as $context => $spec) { + $this->addContext($context, $spec); + } + return $this; + } + + /** + * Set multiple contexts, after first removing all + * + * @param array $contexts + * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface + */ + public function setContexts(array $contexts) + { + $this->clearContexts(); + foreach ($contexts as $context => $spec) { + $this->addContext($context, $spec); + } + return $this; + } + + /** + * Retrieve context specification + * + * @param string $context + * @return array|null + */ + public function getContext($context) + { + if ($this->hasContext($context)) { + return $this->_contexts[(string) $context]; + } + return null; + } + + /** + * Retrieve context definitions + * + * @return array + */ + public function getContexts() + { + return $this->_contexts; + } + + /** + * Remove a context + * + * @param string $context + * @return boolean + */ + public function removeContext($context) + { + if ($this->hasContext($context)) { + unset($this->_contexts[(string) $context]); + return true; + } + return false; + } + + /** + * Remove all contexts + * + * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface + */ + public function clearContexts() + { + $this->_contexts = array(); + return $this; + } + + /** + * Return current context, if any + * + * @return null|string + */ + public function getCurrentContext() + { + return $this->_currentContext; + } + + /** + * Post dispatch processing + * + * Execute postDispatch callback for current context, if available + * + * @throws Zend_Controller_Action_Exception + * @return void + */ + public function postDispatch() + { + $context = $this->getCurrentContext(); + if (null !== $context) { + if (null !== ($callback = $this->getCallback($context, self::TRIGGER_POST))) { + if (is_string($callback) && method_exists($this, $callback)) { + $this->$callback(); + } elseif (is_string($callback) && function_exists($callback)) { + $callback(); + } elseif (is_array($callback)) { + call_user_func($callback); + } else { + /** + * @see Zend_Controller_Action_Exception + */ + require_once 'Zend/Controller/Action/Exception.php'; + throw new Zend_Controller_Action_Exception(sprintf('Invalid postDispatch context callback registered for context "%s"', $context)); + } + } + } + } + + /** + * JSON post processing + * + * JSON serialize view variables to response body + * + * @return void + */ + public function postJsonContext() + { + if (!$this->getAutoJsonSerialization()) { + return; + } + + $viewRenderer = Zend_Controller_Action_HelperBroker::getStaticHelper('viewRenderer'); + $view = $viewRenderer->view; + if ($view instanceof Zend_View_Interface) { + /** + * @see Zend_Json + */ + if(method_exists($view, 'getVars')) { + require_once 'Zend/Json.php'; + $vars = Zend_Json::encode($view->getVars()); + $this->getResponse()->setBody($vars); + } else { + require_once 'Zend/Controller/Action/Exception.php'; + throw new Zend_Controller_Action_Exception('View does not implement the getVars() method needed to encode the view into JSON'); + } + } + } + + /** + * Add one or more contexts to an action + * + * @param string $action + * @param string|array $context + * @return Zend_Controller_Action_Helper_ContextSwitch|void Provides a fluent interface + */ + public function addActionContext($action, $context) + { + $this->hasContext($context, true); + $controller = $this->getActionController(); + if (null === $controller) { + return; + } + $action = (string) $action; + $contextKey = $this->_contextKey; + + if (!isset($controller->$contextKey)) { + $controller->$contextKey = array(); + } + + if (true === $context) { + $contexts = $this->getContexts(); + $controller->{$contextKey}[$action] = array_keys($contexts); + return $this; + } + + $context = (array) $context; + if (!isset($controller->{$contextKey}[$action])) { + $controller->{$contextKey}[$action] = $context; + } else { + $controller->{$contextKey}[$action] = array_merge( + $controller->{$contextKey}[$action], + $context + ); + } + + return $this; + } + + /** + * Set a context as available for a given controller action + * + * @param string $action + * @param string|array $context + * @return Zend_Controller_Action_Helper_ContextSwitch|void Provides a fluent interface + */ + public function setActionContext($action, $context) + { + $this->hasContext($context, true); + $controller = $this->getActionController(); + if (null === $controller) { + return; + } + $action = (string) $action; + $contextKey = $this->_contextKey; + + if (!isset($controller->$contextKey)) { + $controller->$contextKey = array(); + } + + if (true === $context) { + $contexts = $this->getContexts(); + $controller->{$contextKey}[$action] = array_keys($contexts); + } else { + $controller->{$contextKey}[$action] = (array) $context; + } + + return $this; + } + + /** + * Add multiple action/context pairs at once + * + * @param array $contexts + * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface + */ + public function addActionContexts(array $contexts) + { + foreach ($contexts as $action => $context) { + $this->addActionContext($action, $context); + } + return $this; + } + + /** + * Overwrite and set multiple action contexts at once + * + * @param array $contexts + * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface + */ + public function setActionContexts(array $contexts) + { + foreach ($contexts as $action => $context) { + $this->setActionContext($action, $context); + } + return $this; + } + + /** + * Does a particular controller action have the given context(s)? + * + * @param string $action + * @param string|array $context + * @throws Zend_Controller_Action_Exception + * @return boolean + */ + public function hasActionContext($action, $context) + { + $this->hasContext($context, true); + $controller = $this->getActionController(); + if (null === $controller) { + return false; + } + $action = (string) $action; + $contextKey = $this->_contextKey; + + if (!isset($controller->{$contextKey})) { + return false; + } + + $allContexts = $controller->{$contextKey}; + + if (!is_array($allContexts)) { + /** + * @see Zend_Controller_Action_Exception + */ + require_once 'Zend/Controller/Action/Exception.php'; + throw new Zend_Controller_Action_Exception("Invalid contexts found for controller"); + } + + if (!isset($allContexts[$action])) { + return false; + } + + if (true === $allContexts[$action]) { + return true; + } + + $contexts = $allContexts[$action]; + + if (!is_array($contexts)) { + /** + * @see Zend_Controller_Action_Exception + */ + require_once 'Zend/Controller/Action/Exception.php'; + throw new Zend_Controller_Action_Exception(sprintf("Invalid contexts found for action '%s'", $action)); + } + + if (is_string($context) && in_array($context, $contexts)) { + return true; + } elseif (is_array($context)) { + $found = true; + foreach ($context as $test) { + if (!in_array($test, $contexts)) { + $found = false; + break; + } + } + return $found; + } + + return false; + } + + /** + * Get contexts for a given action or all actions in the controller + * + * @param string $action + * @return array + */ + public function getActionContexts($action = null) + { + $controller = $this->getActionController(); + if (null === $controller) { + return array(); + } + $action = (string) $action; + $contextKey = $this->_contextKey; + + if (!isset($controller->$contextKey)) { + return array(); + } + + if (null !== $action) { + if (isset($controller->{$contextKey}[$action])) { + return $controller->{$contextKey}[$action]; + } else { + return array(); + } + } + + return $controller->$contextKey; + } + + /** + * Remove one or more contexts for a given controller action + * + * @param string $action + * @param string|array $context + * @return boolean + */ + public function removeActionContext($action, $context) + { + if ($this->hasActionContext($action, $context)) { + $controller = $this->getActionController(); + $contextKey = $this->_contextKey; + $action = (string) $action; + $contexts = $controller->$contextKey; + $actionContexts = $contexts[$action]; + $contexts = (array) $context; + foreach ($contexts as $context) { + $index = array_search($context, $actionContexts); + if (false !== $index) { + unset($controller->{$contextKey}[$action][$index]); + } + } + return true; + } + return false; + } + + /** + * Clear all contexts for a given controller action or all actions + * + * @param string $action + * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface + */ + public function clearActionContexts($action = null) + { + $controller = $this->getActionController(); + $contextKey = $this->_contextKey; + + if (!isset($controller->$contextKey) || empty($controller->$contextKey)) { + return $this; + } + + if (null === $action) { + $controller->$contextKey = array(); + return $this; + } + + $action = (string) $action; + if (isset($controller->{$contextKey}[$action])) { + unset($controller->{$contextKey}[$action]); + } + + return $this; + } + + /** + * Retrieve ViewRenderer + * + * @return Zend_Controller_Action_Helper_ViewRenderer Provides a fluent interface + */ + protected function _getViewRenderer() + { + if (null === $this->_viewRenderer) { + $this->_viewRenderer = Zend_Controller_Action_HelperBroker::getStaticHelper('viewRenderer'); + } + + return $this->_viewRenderer; + } +} + diff --git a/library/Zend/Controller/Action/Helper/FlashMessenger.php b/library/Zend/Controller/Action/Helper/FlashMessenger.php new file mode 100644 index 000000000..57e339027 --- /dev/null +++ b/library/Zend/Controller/Action/Helper/FlashMessenger.php @@ -0,0 +1,266 @@ +getName()); + foreach (self::$_session as $namespace => $messages) { + self::$_messages[$namespace] = $messages; + unset(self::$_session->{$namespace}); + } + } + } + + /** + * postDispatch() - runs after action is dispatched, in this + * case, it is resetting the namespace in case we have forwarded to a different + * action, Flashmessage will be 'clean' (default namespace) + * + * @return Zend_Controller_Action_Helper_FlashMessenger Provides a fluent interface + */ + public function postDispatch() + { + $this->resetNamespace(); + return $this; + } + + /** + * setNamespace() - change the namespace messages are added to, useful for + * per action controller messaging between requests + * + * @param string $namespace + * @return Zend_Controller_Action_Helper_FlashMessenger Provides a fluent interface + */ + public function setNamespace($namespace = 'default') + { + $this->_namespace = $namespace; + return $this; + } + + /** + * resetNamespace() - reset the namespace to the default + * + * @return Zend_Controller_Action_Helper_FlashMessenger Provides a fluent interface + */ + public function resetNamespace() + { + $this->setNamespace(); + return $this; + } + + /** + * addMessage() - Add a message to flash message + * + * @param string $message + * @return Zend_Controller_Action_Helper_FlashMessenger Provides a fluent interface + */ + public function addMessage($message) + { + if (self::$_messageAdded === false) { + self::$_session->setExpirationHops(1, null, true); + } + + if (!is_array(self::$_session->{$this->_namespace})) { + self::$_session->{$this->_namespace} = array(); + } + + self::$_session->{$this->_namespace}[] = $message; + + return $this; + } + + /** + * hasMessages() - Wether a specific namespace has messages + * + * @return boolean + */ + public function hasMessages() + { + return isset(self::$_messages[$this->_namespace]); + } + + /** + * getMessages() - Get messages from a specific namespace + * + * @return array + */ + public function getMessages() + { + if ($this->hasMessages()) { + return self::$_messages[$this->_namespace]; + } + + return array(); + } + + /** + * Clear all messages from the previous request & current namespace + * + * @return boolean True if messages were cleared, false if none existed + */ + public function clearMessages() + { + if ($this->hasMessages()) { + unset(self::$_messages[$this->_namespace]); + return true; + } + + return false; + } + + /** + * hasCurrentMessages() - check to see if messages have been added to current + * namespace within this request + * + * @return boolean + */ + public function hasCurrentMessages() + { + return isset(self::$_session->{$this->_namespace}); + } + + /** + * getCurrentMessages() - get messages that have been added to the current + * namespace within this request + * + * @return array + */ + public function getCurrentMessages() + { + if ($this->hasCurrentMessages()) { + return self::$_session->{$this->_namespace}; + } + + return array(); + } + + /** + * clear messages from the current request & current namespace + * + * @return boolean + */ + public function clearCurrentMessages() + { + if ($this->hasCurrentMessages()) { + unset(self::$_session->{$this->_namespace}); + return true; + } + + return false; + } + + /** + * getIterator() - complete the IteratorAggregate interface, for iterating + * + * @return ArrayObject + */ + public function getIterator() + { + if ($this->hasMessages()) { + return new ArrayObject($this->getMessages()); + } + + return new ArrayObject(); + } + + /** + * count() - Complete the countable interface + * + * @return int + */ + public function count() + { + if ($this->hasMessages()) { + return count($this->getMessages()); + } + + return 0; + } + + /** + * Strategy pattern: proxy to addMessage() + * + * @param string $message + * @return void + */ + public function direct($message) + { + return $this->addMessage($message); + } +} diff --git a/library/Zend/Controller/Action/Helper/Json.php b/library/Zend/Controller/Action/Helper/Json.php new file mode 100644 index 000000000..2a78fc4b6 --- /dev/null +++ b/library/Zend/Controller/Action/Helper/Json.php @@ -0,0 +1,130 @@ +true|false + * if $keepLayouts and parmas for Zend_Json::encode are required + * then, the array can contains a 'keepLayout'=>true|false + * that will not be passed to Zend_Json::encode method but will be passed + * to Zend_View_Helper_Json + * @throws Zend_Controller_Action_Helper_Json + * @return string + */ + public function encodeJson($data, $keepLayouts = false) + { + /** + * @see Zend_View_Helper_Json + */ + require_once 'Zend/View/Helper/Json.php'; + $jsonHelper = new Zend_View_Helper_Json(); + $data = $jsonHelper->json($data, $keepLayouts); + + if (!$keepLayouts) { + /** + * @see Zend_Controller_Action_HelperBroker + */ + require_once 'Zend/Controller/Action/HelperBroker.php'; + Zend_Controller_Action_HelperBroker::getStaticHelper('viewRenderer')->setNoRender(true); + } + + return $data; + } + + /** + * Encode JSON response and immediately send + * + * @param mixed $data + * @param boolean|array $keepLayouts + * NOTE: if boolean, establish $keepLayouts to true|false + * if array, admit params for Zend_Json::encode as enableJsonExprFinder=>true|false + * if $keepLayouts and parmas for Zend_Json::encode are required + * then, the array can contains a 'keepLayout'=>true|false + * that will not be passed to Zend_Json::encode method but will be passed + * to Zend_View_Helper_Json + * @return string|void + */ + public function sendJson($data, $keepLayouts = false) + { + $data = $this->encodeJson($data, $keepLayouts); + $response = $this->getResponse(); + $response->setBody($data); + + if (!$this->suppressExit) { + $response->sendResponse(); + exit; + } + + return $data; + } + + /** + * Strategy pattern: call helper as helper broker method + * + * Allows encoding JSON. If $sendNow is true, immediately sends JSON + * response. + * + * @param mixed $data + * @param boolean $sendNow + * @param boolean $keepLayouts + * @return string|void + */ + public function direct($data, $sendNow = true, $keepLayouts = false) + { + if ($sendNow) { + return $this->sendJson($data, $keepLayouts); + } + return $this->encodeJson($data, $keepLayouts); + } +} diff --git a/library/Zend/Controller/Action/Helper/Redirector.php b/library/Zend/Controller/Action/Helper/Redirector.php new file mode 100644 index 000000000..04b564788 --- /dev/null +++ b/library/Zend/Controller/Action/Helper/Redirector.php @@ -0,0 +1,531 @@ +_code; + } + + /** + * Validate HTTP status redirect code + * + * @param int $code + * @throws Zend_Controller_Action_Exception on invalid HTTP status code + * @return true + */ + protected function _checkCode($code) + { + $code = (int)$code; + if ((300 > $code) || (307 < $code) || (304 == $code) || (306 == $code)) { + require_once 'Zend/Controller/Action/Exception.php'; + throw new Zend_Controller_Action_Exception('Invalid redirect HTTP status code (' . $code . ')'); + } + + return true; + } + + /** + * Retrieve HTTP status code for {@link _redirect()} behaviour + * + * @param int $code + * @return Zend_Controller_Action_Helper_Redirector Provides a fluent interface + */ + public function setCode($code) + { + $this->_checkCode($code); + $this->_code = $code; + return $this; + } + + /** + * Retrieve flag for whether or not {@link _redirect()} will exit when finished. + * + * @return boolean + */ + public function getExit() + { + return $this->_exit; + } + + /** + * Retrieve exit flag for {@link _redirect()} behaviour + * + * @param boolean $flag + * @return Zend_Controller_Action_Helper_Redirector Provides a fluent interface + */ + public function setExit($flag) + { + $this->_exit = ($flag) ? true : false; + return $this; + } + + /** + * Retrieve flag for whether or not {@link _redirect()} will prepend the + * base URL on relative URLs + * + * @return boolean + */ + public function getPrependBase() + { + return $this->_prependBase; + } + + /** + * Retrieve 'prepend base' flag for {@link _redirect()} behaviour + * + * @param boolean $flag + * @return Zend_Controller_Action_Helper_Redirector Provides a fluent interface + */ + public function setPrependBase($flag) + { + $this->_prependBase = ($flag) ? true : false; + return $this; + } + + /** + * Retrieve flag for whether or not {@link redirectAndExit()} shall close the session before + * exiting. + * + * @return boolean + */ + public function getCloseSessionOnExit() + { + return $this->_closeSessionOnExit; + } + + /** + * Set flag for whether or not {@link redirectAndExit()} shall close the session before exiting. + * + * @param boolean $flag + * @return Zend_Controller_Action_Helper_Redirector Provides a fluent interface + */ + public function setCloseSessionOnExit($flag) + { + $this->_closeSessionOnExit = ($flag) ? true : false; + return $this; + } + + /** + * Return use absolute URI flag + * + * @return boolean + */ + public function getUseAbsoluteUri() + { + return $this->_useAbsoluteUri; + } + + /** + * Set use absolute URI flag + * + * @param boolean $flag + * @return Zend_Controller_Action_Helper_Redirector Provides a fluent interface + */ + public function setUseAbsoluteUri($flag = true) + { + $this->_useAbsoluteUri = ($flag) ? true : false; + return $this; + } + + /** + * Set redirect in response object + * + * @return void + */ + protected function _redirect($url) + { + if ($this->getUseAbsoluteUri() && !preg_match('#^(https?|ftp)://#', $url)) { + $host = (isset($_SERVER['HTTP_HOST'])?$_SERVER['HTTP_HOST']:''); + $proto = (isset($_SERVER['HTTPS'])&&$_SERVER['HTTPS']!=="off") ? 'https' : 'http'; + $port = (isset($_SERVER['SERVER_PORT'])?$_SERVER['SERVER_PORT']:80); + $uri = $proto . '://' . $host; + if ((('http' == $proto) && (80 != $port)) || (('https' == $proto) && (443 != $port))) { + $uri .= ':' . $port; + } + $url = $uri . '/' . ltrim($url, '/'); + } + $this->_redirectUrl = $url; + $this->getResponse()->setRedirect($url, $this->getCode()); + } + + /** + * Retrieve currently set URL for redirect + * + * @return string + */ + public function getRedirectUrl() + { + return $this->_redirectUrl; + } + + /** + * Determine if the baseUrl should be prepended, and prepend if necessary + * + * @param string $url + * @return string + */ + protected function _prependBase($url) + { + if ($this->getPrependBase()) { + $request = $this->getRequest(); + if ($request instanceof Zend_Controller_Request_Http) { + $base = rtrim($request->getBaseUrl(), '/'); + if (!empty($base) && ('/' != $base)) { + $url = $base . '/' . ltrim($url, '/'); + } else { + $url = '/' . ltrim($url, '/'); + } + } + } + + return $url; + } + + /** + * Set a redirect URL of the form /module/controller/action/params + * + * @param string $action + * @param string $controller + * @param string $module + * @param array $params + * @return void + */ + public function setGotoSimple($action, $controller = null, $module = null, array $params = array()) + { + $dispatcher = $this->getFrontController()->getDispatcher(); + $request = $this->getRequest(); + $curModule = $request->getModuleName(); + $useDefaultController = false; + + if (null === $controller && null !== $module) { + $useDefaultController = true; + } + + if (null === $module) { + $module = $curModule; + } + + if ($module == $dispatcher->getDefaultModule()) { + $module = ''; + } + + if (null === $controller && !$useDefaultController) { + $controller = $request->getControllerName(); + if (empty($controller)) { + $controller = $dispatcher->getDefaultControllerName(); + } + } + + $params['module'] = $module; + $params['controller'] = $controller; + $params['action'] = $action; + + $router = $this->getFrontController()->getRouter(); + $url = $router->assemble($params, 'default', true); + + $this->_redirect($url); + } + + /** + * Build a URL based on a route + * + * @param array $urlOptions + * @param string $name Route name + * @param boolean $reset + * @param boolean $encode + * @return void + */ + public function setGotoRoute(array $urlOptions = array(), $name = null, $reset = false, $encode = true) + { + $router = $this->getFrontController()->getRouter(); + $url = $router->assemble($urlOptions, $name, $reset, $encode); + + $this->_redirect($url); + } + + /** + * Set a redirect URL string + * + * By default, emits a 302 HTTP status header, prepends base URL as defined + * in request object if url is relative, and halts script execution by + * calling exit(). + * + * $options is an optional associative array that can be used to control + * redirect behaviour. The available option keys are: + * - exit: boolean flag indicating whether or not to halt script execution when done + * - prependBase: boolean flag indicating whether or not to prepend the base URL when a relative URL is provided + * - code: integer HTTP status code to use with redirect. Should be between 300 and 307. + * + * _redirect() sets the Location header in the response object. If you set + * the exit flag to false, you can override this header later in code + * execution. + * + * If the exit flag is true (true by default), _redirect() will write and + * close the current session, if any. + * + * @param string $url + * @param array $options + * @return void + */ + public function setGotoUrl($url, array $options = array()) + { + // prevent header injections + $url = str_replace(array("\n", "\r"), '', $url); + + if (null !== $options) { + if (isset($options['exit'])) { + $this->setExit(($options['exit']) ? true : false); + } + if (isset($options['prependBase'])) { + $this->setPrependBase(($options['prependBase']) ? true : false); + } + if (isset($options['code'])) { + $this->setCode($options['code']); + } + } + + // If relative URL, decide if we should prepend base URL + if (!preg_match('|^[a-z]+://|', $url)) { + $url = $this->_prependBase($url); + } + + $this->_redirect($url); + } + + /** + * Perform a redirect to an action/controller/module with params + * + * @param string $action + * @param string $controller + * @param string $module + * @param array $params + * @return void + */ + public function gotoSimple($action, $controller = null, $module = null, array $params = array()) + { + $this->setGotoSimple($action, $controller, $module, $params); + + if ($this->getExit()) { + $this->redirectAndExit(); + } + } + + /** + * Perform a redirect to an action/controller/module with params, forcing an immdiate exit + * + * @param mixed $action + * @param mixed $controller + * @param mixed $module + * @param array $params + * @return void + */ + public function gotoSimpleAndExit($action, $controller = null, $module = null, array $params = array()) + { + $this->setGotoSimple($action, $controller, $module, $params); + $this->redirectAndExit(); + } + + /** + * Redirect to a route-based URL + * + * Uses route's assemble method tobuild the URL; route is specified by $name; + * default route is used if none provided. + * + * @param array $urlOptions Array of key/value pairs used to assemble URL + * @param string $name + * @param boolean $reset + * @param boolean $encode + * @return void + */ + public function gotoRoute(array $urlOptions = array(), $name = null, $reset = false, $encode = true) + { + $this->setGotoRoute($urlOptions, $name, $reset, $encode); + + if ($this->getExit()) { + $this->redirectAndExit(); + } + } + + /** + * Redirect to a route-based URL, and immediately exit + * + * Uses route's assemble method tobuild the URL; route is specified by $name; + * default route is used if none provided. + * + * @param array $urlOptions Array of key/value pairs used to assemble URL + * @param string $name + * @param boolean $reset + * @return void + */ + public function gotoRouteAndExit(array $urlOptions = array(), $name = null, $reset = false) + { + $this->setGotoRoute($urlOptions, $name, $reset); + $this->redirectAndExit(); + } + + /** + * Perform a redirect to a url + * + * @param string $url + * @param array $options + * @return void + */ + public function gotoUrl($url, array $options = array()) + { + $this->setGotoUrl($url, $options); + + if ($this->getExit()) { + $this->redirectAndExit(); + } + } + + /** + * Set a URL string for a redirect, perform redirect, and immediately exit + * + * @param string $url + * @param array $options + * @return void + */ + public function gotoUrlAndExit($url, array $options = array()) + { + $this->gotoUrl($url, $options); + $this->redirectAndExit(); + } + + /** + * exit(): Perform exit for redirector + * + * @return void + */ + public function redirectAndExit() + { + if ($this->getCloseSessionOnExit()) { + // Close session, if started + if (class_exists('Zend_Session', false) && Zend_Session::isStarted()) { + Zend_Session::writeClose(); + } elseif (isset($_SESSION)) { + session_write_close(); + } + } + + $this->getResponse()->sendHeaders(); + exit(); + } + + /** + * direct(): Perform helper when called as + * $this->_helper->redirector($action, $controller, $module, $params) + * + * @param string $action + * @param string $controller + * @param string $module + * @param array $params + * @return void + */ + public function direct($action, $controller = null, $module = null, array $params = array()) + { + $this->gotoSimple($action, $controller, $module, $params); + } + + /** + * Overloading + * + * Overloading for old 'goto', 'setGoto', and 'gotoAndExit' methods + * + * @param string $method + * @param array $args + * @return mixed + * @throws Zend_Controller_Action_Exception for invalid methods + */ + public function __call($method, $args) + { + $method = strtolower($method); + if ('goto' == $method) { + return call_user_func_array(array($this, 'gotoSimple'), $args); + } + if ('setgoto' == $method) { + return call_user_func_array(array($this, 'setGotoSimple'), $args); + } + if ('gotoandexit' == $method) { + return call_user_func_array(array($this, 'gotoSimpleAndExit'), $args); + } + + require_once 'Zend/Controller/Action/Exception.php'; + throw new Zend_Controller_Action_Exception(sprintf('Invalid method "%s" called on redirector', $method)); + } +} diff --git a/library/Zend/Controller/Action/Helper/Url.php b/library/Zend/Controller/Action/Helper/Url.php new file mode 100644 index 000000000..9f922e941 --- /dev/null +++ b/library/Zend/Controller/Action/Helper/Url.php @@ -0,0 +1,117 @@ +getRequest(); + + if (null === $controller) { + $controller = $request->getControllerName(); + } + + if (null === $module) { + $module = $request->getModuleName(); + } + + $url = $controller . '/' . $action; + if ($module != $this->getFrontController()->getDispatcher()->getDefaultModule()) { + $url = $module . '/' . $url; + } + + if ('' !== ($baseUrl = $this->getFrontController()->getBaseUrl())) { + $url = $baseUrl . '/' . $url; + } + + if (null !== $params) { + $paramPairs = array(); + foreach ($params as $key => $value) { + $paramPairs[] = urlencode($key) . '/' . urlencode($value); + } + $paramString = implode('/', $paramPairs); + $url .= '/' . $paramString; + } + + $url = '/' . ltrim($url, '/'); + + return $url; + } + + /** + * Assembles a URL based on a given route + * + * This method will typically be used for more complex operations, as it + * ties into the route objects registered with the router. + * + * @param array $urlOptions Options passed to the assemble method of the Route object. + * @param mixed $name The name of a Route to use. If null it will use the current Route + * @param boolean $reset + * @param boolean $encode + * @return string Url for the link href attribute. + */ + public function url($urlOptions = array(), $name = null, $reset = false, $encode = true) + { + $router = $this->getFrontController()->getRouter(); + return $router->assemble($urlOptions, $name, $reset, $encode); + } + + /** + * Perform helper when called as $this->_helper->url() from an action controller + * + * Proxies to {@link simple()} + * + * @param string $action + * @param string $controller + * @param string $module + * @param array $params + * @return string + */ + public function direct($action, $controller = null, $module = null, array $params = null) + { + return $this->simple($action, $controller, $module, $params); + } +} diff --git a/library/Zend/Controller/Action/Helper/ViewRenderer.php b/library/Zend/Controller/Action/Helper/ViewRenderer.php new file mode 100644 index 000000000..4150a2f26 --- /dev/null +++ b/library/Zend/Controller/Action/Helper/ViewRenderer.php @@ -0,0 +1,989 @@ + + * // In your bootstrap: + * Zend_Controller_Action_HelperBroker::addHelper(new Zend_Controller_Action_Helper_ViewRenderer()); + * + * // In your action controller methods: + * $viewHelper = $this->_helper->getHelper('view'); + * + * // Don't use controller subdirectories + * $viewHelper->setNoController(true); + * + * // Specify a different script to render: + * $this->_helper->viewRenderer('form'); + * + * + * + * @uses Zend_Controller_Action_Helper_Abstract + * @package Zend_Controller + * @subpackage Zend_Controller_Action_Helper + * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Controller_Action_Helper_ViewRenderer extends Zend_Controller_Action_Helper_Abstract +{ + /** + * @var Zend_View_Interface + */ + public $view; + + /** + * Word delimiters + * @var array + */ + protected $_delimiters; + + /** + * @var Zend_Filter_Inflector + */ + protected $_inflector; + + /** + * Inflector target + * @var string + */ + protected $_inflectorTarget = ''; + + /** + * Current module directory + * @var string + */ + protected $_moduleDir = ''; + + /** + * Whether or not to autorender using controller name as subdirectory; + * global setting (not reset at next invocation) + * @var boolean + */ + protected $_neverController = false; + + /** + * Whether or not to autorender postDispatch; global setting (not reset at + * next invocation) + * @var boolean + */ + protected $_neverRender = false; + + /** + * Whether or not to use a controller name as a subdirectory when rendering + * @var boolean + */ + protected $_noController = false; + + /** + * Whether or not to autorender postDispatch; per controller/action setting (reset + * at next invocation) + * @var boolean + */ + protected $_noRender = false; + + /** + * Characters representing path delimiters in the controller + * @var string|array + */ + protected $_pathDelimiters; + + /** + * Which named segment of the response to utilize + * @var string + */ + protected $_responseSegment = null; + + /** + * Which action view script to render + * @var string + */ + protected $_scriptAction = null; + + /** + * View object basePath + * @var string + */ + protected $_viewBasePathSpec = ':moduleDir/views'; + + /** + * View script path specification string + * @var string + */ + protected $_viewScriptPathSpec = ':controller/:action.:suffix'; + + /** + * View script path specification string, minus controller segment + * @var string + */ + protected $_viewScriptPathNoControllerSpec = ':action.:suffix'; + + /** + * View script suffix + * @var string + */ + protected $_viewSuffix = 'phtml'; + + /** + * Constructor + * + * Optionally set view object and options. + * + * @param Zend_View_Interface $view + * @param array $options + * @return void + */ + public function __construct(Zend_View_Interface $view = null, array $options = array()) + { + if (null !== $view) { + $this->setView($view); + } + + if (!empty($options)) { + $this->_setOptions($options); + } + } + + /** + * Clone - also make sure the view is cloned. + * + * @return void + */ + public function __clone() + { + if (isset($this->view) && $this->view instanceof Zend_View_Interface) { + $this->view = clone $this->view; + + } + } + + /** + * Set the view object + * + * @param Zend_View_Interface $view + * @return Zend_Controller_Action_Helper_ViewRenderer Provides a fluent interface + */ + public function setView(Zend_View_Interface $view) + { + $this->view = $view; + return $this; + } + + /** + * Get current module name + * + * @return string + */ + public function getModule() + { + $request = $this->getRequest(); + $module = $request->getModuleName(); + if (null === $module) { + $module = $this->getFrontController()->getDispatcher()->getDefaultModule(); + } + + return $module; + } + + /** + * Get module directory + * + * @throws Zend_Controller_Action_Exception + * @return string + */ + public function getModuleDirectory() + { + $module = $this->getModule(); + $moduleDir = $this->getFrontController()->getControllerDirectory($module); + if ((null === $moduleDir) || is_array($moduleDir)) { + /** + * @see Zend_Controller_Action_Exception + */ + require_once 'Zend/Controller/Action/Exception.php'; + throw new Zend_Controller_Action_Exception('ViewRenderer cannot locate module directory for module "' . $module . '"'); + } + $this->_moduleDir = dirname($moduleDir); + return $this->_moduleDir; + } + + /** + * Get inflector + * + * @return Zend_Filter_Inflector + */ + public function getInflector() + { + if (null === $this->_inflector) { + /** + * @see Zend_Filter_Inflector + */ + require_once 'Zend/Filter/Inflector.php'; + /** + * @see Zend_Filter_PregReplace + */ + require_once 'Zend/Filter/PregReplace.php'; + /** + * @see Zend_Filter_Word_UnderscoreToSeparator + */ + require_once 'Zend/Filter/Word/UnderscoreToSeparator.php'; + $this->_inflector = new Zend_Filter_Inflector(); + $this->_inflector->setStaticRuleReference('moduleDir', $this->_moduleDir) // moduleDir must be specified before the less specific 'module' + ->addRules(array( + ':module' => array('Word_CamelCaseToDash', 'StringToLower'), + ':controller' => array('Word_CamelCaseToDash', new Zend_Filter_Word_UnderscoreToSeparator('/'), 'StringToLower', new Zend_Filter_PregReplace('/\./', '-')), + ':action' => array('Word_CamelCaseToDash', new Zend_Filter_PregReplace('#[^a-z0-9' . preg_quote('/', '#') . ']+#i', '-'), 'StringToLower'), + )) + ->setStaticRuleReference('suffix', $this->_viewSuffix) + ->setTargetReference($this->_inflectorTarget); + } + + // Ensure that module directory is current + $this->getModuleDirectory(); + + return $this->_inflector; + } + + /** + * Set inflector + * + * @param Zend_Filter_Inflector $inflector + * @param boolean $reference Whether the moduleDir, target, and suffix should be set as references to ViewRenderer properties + * @return Zend_Controller_Action_Helper_ViewRenderer Provides a fluent interface + */ + public function setInflector(Zend_Filter_Inflector $inflector, $reference = false) + { + $this->_inflector = $inflector; + if ($reference) { + $this->_inflector->setStaticRuleReference('suffix', $this->_viewSuffix) + ->setStaticRuleReference('moduleDir', $this->_moduleDir) + ->setTargetReference($this->_inflectorTarget); + } + return $this; + } + + /** + * Set inflector target + * + * @param string $target + * @return void + */ + protected function _setInflectorTarget($target) + { + $this->_inflectorTarget = (string) $target; + } + + /** + * Set internal module directory representation + * + * @param string $dir + * @return void + */ + protected function _setModuleDir($dir) + { + $this->_moduleDir = (string) $dir; + } + + /** + * Get internal module directory representation + * + * @return string + */ + protected function _getModuleDir() + { + return $this->_moduleDir; + } + + /** + * Generate a class prefix for helper and filter classes + * + * @return string + */ + protected function _generateDefaultPrefix() + { + $default = 'Zend_View'; + if (null === $this->_actionController) { + return $default; + } + + $class = get_class($this->_actionController); + + if (!strstr($class, '_')) { + return $default; + } + + $module = $this->getModule(); + if ('default' == $module) { + return $default; + } + + $prefix = substr($class, 0, strpos($class, '_')) . '_View'; + + return $prefix; + } + + /** + * Retrieve base path based on location of current action controller + * + * @return string + */ + protected function _getBasePath() + { + if (null === $this->_actionController) { + return './views'; + } + + $inflector = $this->getInflector(); + $this->_setInflectorTarget($this->getViewBasePathSpec()); + + $dispatcher = $this->getFrontController()->getDispatcher(); + $request = $this->getRequest(); + + $parts = array( + 'module' => (($moduleName = $request->getModuleName()) != '') ? $dispatcher->formatModuleName($moduleName) : $moduleName, + 'controller' => $request->getControllerName(), + 'action' => $dispatcher->formatActionName($request->getActionName()) + ); + + $path = $inflector->filter($parts); + return $path; + } + + /** + * Set options + * + * @param array $options + * @return Zend_Controller_Action_Helper_ViewRenderer Provides a fluent interface + */ + protected function _setOptions(array $options) + { + foreach ($options as $key => $value) + { + switch ($key) { + case 'neverRender': + case 'neverController': + case 'noController': + case 'noRender': + $property = '_' . $key; + $this->{$property} = ($value) ? true : false; + break; + case 'responseSegment': + case 'scriptAction': + case 'viewBasePathSpec': + case 'viewScriptPathSpec': + case 'viewScriptPathNoControllerSpec': + case 'viewSuffix': + $property = '_' . $key; + $this->{$property} = (string) $value; + break; + default: + break; + } + } + + return $this; + } + + /** + * Initialize the view object + * + * $options may contain the following keys: + * - neverRender - flag dis/enabling postDispatch() autorender (affects all subsequent calls) + * - noController - flag indicating whether or not to look for view scripts in subdirectories named after the controller + * - noRender - flag indicating whether or not to autorender postDispatch() + * - responseSegment - which named response segment to render a view script to + * - scriptAction - what action script to render + * - viewBasePathSpec - specification to use for determining view base path + * - viewScriptPathSpec - specification to use for determining view script paths + * - viewScriptPathNoControllerSpec - specification to use for determining view script paths when noController flag is set + * - viewSuffix - what view script filename suffix to use + * + * @param string $path + * @param string $prefix + * @param array $options + * @throws Zend_Controller_Action_Exception + * @return void + */ + public function initView($path = null, $prefix = null, array $options = array()) + { + if (null === $this->view) { + $this->setView(new Zend_View()); + } + + // Reset some flags every time + $options['noController'] = (isset($options['noController'])) ? $options['noController'] : false; + $options['noRender'] = (isset($options['noRender'])) ? $options['noRender'] : false; + $this->_scriptAction = null; + $this->_responseSegment = null; + + // Set options first; may be used to determine other initializations + $this->_setOptions($options); + + // Get base view path + if (empty($path)) { + $path = $this->_getBasePath(); + if (empty($path)) { + /** + * @see Zend_Controller_Action_Exception + */ + require_once 'Zend/Controller/Action/Exception.php'; + throw new Zend_Controller_Action_Exception('ViewRenderer initialization failed: retrieved view base path is empty'); + } + } + + if (null === $prefix) { + $prefix = $this->_generateDefaultPrefix(); + } + + // Determine if this path has already been registered + $currentPaths = $this->view->getScriptPaths(); + $path = str_replace(array('/', '\\'), '/', $path); + $pathExists = false; + foreach ($currentPaths as $tmpPath) { + $tmpPath = str_replace(array('/', '\\'), '/', $tmpPath); + if (strstr($tmpPath, $path)) { + $pathExists = true; + break; + } + } + if (!$pathExists) { + $this->view->addBasePath($path, $prefix); + } + + // Register view with action controller (unless already registered) + if ((null !== $this->_actionController) && (null === $this->_actionController->view)) { + $this->_actionController->view = $this->view; + $this->_actionController->viewSuffix = $this->_viewSuffix; + } + } + + /** + * init - initialize view + * + * @return void + */ + public function init() + { + if ($this->getFrontController()->getParam('noViewRenderer')) { + return; + } + + $this->initView(); + } + + /** + * Set view basePath specification + * + * Specification can contain one or more of the following: + * - :moduleDir - current module directory + * - :controller - name of current controller in the request + * - :action - name of current action in the request + * - :module - name of current module in the request + * + * @param string $path + * @return Zend_Controller_Action_Helper_ViewRenderer Provides a fluent interface + */ + public function setViewBasePathSpec($path) + { + $this->_viewBasePathSpec = (string) $path; + return $this; + } + + /** + * Retrieve the current view basePath specification string + * + * @return string + */ + public function getViewBasePathSpec() + { + return $this->_viewBasePathSpec; + } + + /** + * Set view script path specification + * + * Specification can contain one or more of the following: + * - :moduleDir - current module directory + * - :controller - name of current controller in the request + * - :action - name of current action in the request + * - :module - name of current module in the request + * + * @param string $path + * @return Zend_Controller_Action_Helper_ViewRenderer Provides a fluent interface + */ + public function setViewScriptPathSpec($path) + { + $this->_viewScriptPathSpec = (string) $path; + return $this; + } + + /** + * Retrieve the current view script path specification string + * + * @return string + */ + public function getViewScriptPathSpec() + { + return $this->_viewScriptPathSpec; + } + + /** + * Set view script path specification (no controller variant) + * + * Specification can contain one or more of the following: + * - :moduleDir - current module directory + * - :controller - name of current controller in the request + * - :action - name of current action in the request + * - :module - name of current module in the request + * + * :controller will likely be ignored in this variant. + * + * @param string $path + * @return Zend_Controller_Action_Helper_ViewRenderer Provides a fluent interface + */ + public function setViewScriptPathNoControllerSpec($path) + { + $this->_viewScriptPathNoControllerSpec = (string) $path; + return $this; + } + + /** + * Retrieve the current view script path specification string (no controller variant) + * + * @return string + */ + public function getViewScriptPathNoControllerSpec() + { + return $this->_viewScriptPathNoControllerSpec; + } + + /** + * Get a view script based on an action and/or other variables + * + * Uses values found in current request if no values passed in $vars. + * + * If {@link $_noController} is set, uses {@link $_viewScriptPathNoControllerSpec}; + * otherwise, uses {@link $_viewScriptPathSpec}. + * + * @param string $action + * @param array $vars + * @return string + */ + public function getViewScript($action = null, array $vars = array()) + { + $request = $this->getRequest(); + if ((null === $action) && (!isset($vars['action']))) { + $action = $this->getScriptAction(); + if (null === $action) { + $action = $request->getActionName(); + } + $vars['action'] = $action; + } elseif (null !== $action) { + $vars['action'] = $action; + } + + $inflector = $this->getInflector(); + if ($this->getNoController() || $this->getNeverController()) { + $this->_setInflectorTarget($this->getViewScriptPathNoControllerSpec()); + } else { + $this->_setInflectorTarget($this->getViewScriptPathSpec()); + } + return $this->_translateSpec($vars); + } + + /** + * Set the neverRender flag (i.e., globally dis/enable autorendering) + * + * @param boolean $flag + * @return Zend_Controller_Action_Helper_ViewRenderer Provides a fluent interface + */ + public function setNeverRender($flag = true) + { + $this->_neverRender = ($flag) ? true : false; + return $this; + } + + /** + * Retrieve neverRender flag value + * + * @return boolean + */ + public function getNeverRender() + { + return $this->_neverRender; + } + + /** + * Set the noRender flag (i.e., whether or not to autorender) + * + * @param boolean $flag + * @return Zend_Controller_Action_Helper_ViewRenderer Provides a fluent interface + */ + public function setNoRender($flag = true) + { + $this->_noRender = ($flag) ? true : false; + return $this; + } + + /** + * Retrieve noRender flag value + * + * @return boolean + */ + public function getNoRender() + { + return $this->_noRender; + } + + /** + * Set the view script to use + * + * @param string $name + * @return Zend_Controller_Action_Helper_ViewRenderer Provides a fluent interface + */ + public function setScriptAction($name) + { + $this->_scriptAction = (string) $name; + return $this; + } + + /** + * Retrieve view script name + * + * @return string + */ + public function getScriptAction() + { + return $this->_scriptAction; + } + + /** + * Set the response segment name + * + * @param string $name + * @return Zend_Controller_Action_Helper_ViewRenderer Provides a fluent interface + */ + public function setResponseSegment($name) + { + if (null === $name) { + $this->_responseSegment = null; + } else { + $this->_responseSegment = (string) $name; + } + + return $this; + } + + /** + * Retrieve named response segment name + * + * @return string + */ + public function getResponseSegment() + { + return $this->_responseSegment; + } + + /** + * Set the noController flag (i.e., whether or not to render into controller subdirectories) + * + * @param boolean $flag + * @return Zend_Controller_Action_Helper_ViewRenderer Provides a fluent interface + */ + public function setNoController($flag = true) + { + $this->_noController = ($flag) ? true : false; + return $this; + } + + /** + * Retrieve noController flag value + * + * @return boolean + */ + public function getNoController() + { + return $this->_noController; + } + + /** + * Set the neverController flag (i.e., whether or not to render into controller subdirectories) + * + * @param boolean $flag + * @return Zend_Controller_Action_Helper_ViewRenderer Provides a fluent interface + */ + public function setNeverController($flag = true) + { + $this->_neverController = ($flag) ? true : false; + return $this; + } + + /** + * Retrieve neverController flag value + * + * @return boolean + */ + public function getNeverController() + { + return $this->_neverController; + } + + /** + * Set view script suffix + * + * @param string $suffix + * @return Zend_Controller_Action_Helper_ViewRenderer Provides a fluent interface + */ + public function setViewSuffix($suffix) + { + $this->_viewSuffix = (string) $suffix; + return $this; + } + + /** + * Get view script suffix + * + * @return string + */ + public function getViewSuffix() + { + return $this->_viewSuffix; + } + + /** + * Set options for rendering a view script + * + * @param string $action View script to render + * @param string $name Response named segment to render to + * @param boolean $noController Whether or not to render within a subdirectory named after the controller + * @return Zend_Controller_Action_Helper_ViewRenderer Provides a fluent interface + */ + public function setRender($action = null, $name = null, $noController = null) + { + if (null !== $action) { + $this->setScriptAction($action); + } + + if (null !== $name) { + $this->setResponseSegment($name); + } + + if (null !== $noController) { + $this->setNoController($noController); + } + + return $this; + } + + /** + * Inflect based on provided vars + * + * Allowed variables are: + * - :moduleDir - current module directory + * - :module - current module name + * - :controller - current controller name + * - :action - current action name + * - :suffix - view script file suffix + * + * @param array $vars + * @return string + */ + protected function _translateSpec(array $vars = array()) + { + $inflector = $this->getInflector(); + $request = $this->getRequest(); + $dispatcher = $this->getFrontController()->getDispatcher(); + $module = $dispatcher->formatModuleName($request->getModuleName()); + $controller = $request->getControllerName(); + $action = $dispatcher->formatActionName($request->getActionName()); + + $params = compact('module', 'controller', 'action'); + foreach ($vars as $key => $value) { + switch ($key) { + case 'module': + case 'controller': + case 'action': + case 'moduleDir': + case 'suffix': + $params[$key] = (string) $value; + break; + default: + break; + } + } + + if (isset($params['suffix'])) { + $origSuffix = $this->getViewSuffix(); + $this->setViewSuffix($params['suffix']); + } + if (isset($params['moduleDir'])) { + $origModuleDir = $this->_getModuleDir(); + $this->_setModuleDir($params['moduleDir']); + } + + $filtered = $inflector->filter($params); + + if (isset($params['suffix'])) { + $this->setViewSuffix($origSuffix); + } + if (isset($params['moduleDir'])) { + $this->_setModuleDir($origModuleDir); + } + + return $filtered; + } + + /** + * Render a view script (optionally to a named response segment) + * + * Sets the noRender flag to true when called. + * + * @param string $script + * @param string $name + * @return void + */ + public function renderScript($script, $name = null) + { + if (null === $name) { + $name = $this->getResponseSegment(); + } + + $this->getResponse()->appendBody( + $this->view->render($script), + $name + ); + + $this->setNoRender(); + } + + /** + * Render a view based on path specifications + * + * Renders a view based on the view script path specifications. + * + * @param string $action + * @param string $name + * @param boolean $noController + * @return void + */ + public function render($action = null, $name = null, $noController = null) + { + $this->setRender($action, $name, $noController); + $path = $this->getViewScript(); + $this->renderScript($path, $name); + } + + /** + * Render a script based on specification variables + * + * Pass an action, and one or more specification variables (view script suffix) + * to determine the view script path, and render that script. + * + * @param string $action + * @param array $vars + * @param string $name + * @return void + */ + public function renderBySpec($action = null, array $vars = array(), $name = null) + { + if (null !== $name) { + $this->setResponseSegment($name); + } + + $path = $this->getViewScript($action, $vars); + + $this->renderScript($path); + } + + /** + * postDispatch - auto render a view + * + * Only autorenders if: + * - _noRender is false + * - action controller is present + * - request has not been re-dispatched (i.e., _forward() has not been called) + * - response is not a redirect + * + * @return void + */ + public function postDispatch() + { + if ($this->_shouldRender()) { + $this->render(); + } + } + + /** + * Should the ViewRenderer render a view script? + * + * @return boolean + */ + protected function _shouldRender() + { + return (!$this->getFrontController()->getParam('noViewRenderer') + && !$this->_neverRender + && !$this->_noRender + && (null !== $this->_actionController) + && $this->getRequest()->isDispatched() + && !$this->getResponse()->isRedirect() + ); + } + + /** + * Use this helper as a method; proxies to setRender() + * + * @param string $action + * @param string $name + * @param boolean $noController + * @return void + */ + public function direct($action = null, $name = null, $noController = null) + { + $this->setRender($action, $name, $noController); + } +} diff --git a/library/Zend/Controller/Action/HelperBroker.php b/library/Zend/Controller/Action/HelperBroker.php new file mode 100644 index 000000000..387b6c3a9 --- /dev/null +++ b/library/Zend/Controller/Action/HelperBroker.php @@ -0,0 +1,381 @@ + 'Zend/Controller/Action/Helper/', + )); + } + return self::$_pluginLoader; + } + + /** + * addPrefix() - Add repository of helpers by prefix + * + * @param string $prefix + */ + static public function addPrefix($prefix) + { + $prefix = rtrim($prefix, '_'); + $path = str_replace('_', DIRECTORY_SEPARATOR, $prefix); + self::getPluginLoader()->addPrefixPath($prefix, $path); + } + + /** + * addPath() - Add path to repositories where Action_Helpers could be found. + * + * @param string $path + * @param string $prefix Optional; defaults to 'Zend_Controller_Action_Helper' + * @return void + */ + static public function addPath($path, $prefix = 'Zend_Controller_Action_Helper') + { + self::getPluginLoader()->addPrefixPath($prefix, $path); + } + + /** + * addHelper() - Add helper objects + * + * @param Zend_Controller_Action_Helper_Abstract $helper + * @return void + */ + static public function addHelper(Zend_Controller_Action_Helper_Abstract $helper) + { + self::getStack()->push($helper); + return; + } + + /** + * resetHelpers() + * + * @return void + */ + static public function resetHelpers() + { + self::$_stack = null; + return; + } + + /** + * Retrieve or initialize a helper statically + * + * Retrieves a helper object statically, loading on-demand if the helper + * does not already exist in the stack. Always returns a helper, unless + * the helper class cannot be found. + * + * @param string $name + * @return Zend_Controller_Action_Helper_Abstract + */ + public static function getStaticHelper($name) + { + $name = self::_normalizeHelperName($name); + $stack = self::getStack(); + + if (!isset($stack->{$name})) { + self::_loadHelper($name); + } + + return $stack->{$name}; + } + + /** + * getExistingHelper() - get helper by name + * + * Static method to retrieve helper object. Only retrieves helpers already + * initialized with the broker (either via addHelper() or on-demand loading + * via getHelper()). + * + * Throws an exception if the referenced helper does not exist in the + * stack; use {@link hasHelper()} to check if the helper is registered + * prior to retrieving it. + * + * @param string $name + * @return Zend_Controller_Action_Helper_Abstract + * @throws Zend_Controller_Action_Exception + */ + public static function getExistingHelper($name) + { + $name = self::_normalizeHelperName($name); + $stack = self::getStack(); + + if (!isset($stack->{$name})) { + require_once 'Zend/Controller/Action/Exception.php'; + throw new Zend_Controller_Action_Exception('Action helper "' . $name . '" has not been registered with the helper broker'); + } + + return $stack->{$name}; + } + + /** + * Return all registered helpers as helper => object pairs + * + * @return array + */ + public static function getExistingHelpers() + { + return self::getStack()->getHelpersByName(); + } + + /** + * Is a particular helper loaded in the broker? + * + * @param string $name + * @return boolean + */ + public static function hasHelper($name) + { + $name = self::_normalizeHelperName($name); + return isset(self::getStack()->{$name}); + } + + /** + * Remove a particular helper from the broker + * + * @param string $name + * @return boolean + */ + public static function removeHelper($name) + { + $name = self::_normalizeHelperName($name); + $stack = self::getStack(); + if (isset($stack->{$name})) { + unset($stack->{$name}); + } + + return false; + } + + /** + * Lazy load the priority stack and return it + * + * @return Zend_Controller_Action_HelperBroker_PriorityStack + */ + public static function getStack() + { + if (self::$_stack == null) { + self::$_stack = new Zend_Controller_Action_HelperBroker_PriorityStack(); + } + + return self::$_stack; + } + + /** + * Constructor + * + * @param Zend_Controller_Action $actionController + * @return void + */ + public function __construct(Zend_Controller_Action $actionController) + { + $this->_actionController = $actionController; + foreach (self::getStack() as $helper) { + $helper->setActionController($actionController); + $helper->init(); + } + } + + /** + * notifyPreDispatch() - called by action controller dispatch method + * + * @return void + */ + public function notifyPreDispatch() + { + foreach (self::getStack() as $helper) { + $helper->preDispatch(); + } + } + + /** + * notifyPostDispatch() - called by action controller dispatch method + * + * @return void + */ + public function notifyPostDispatch() + { + foreach (self::getStack() as $helper) { + $helper->postDispatch(); + } + } + + /** + * getHelper() - get helper by name + * + * @param string $name + * @return Zend_Controller_Action_Helper_Abstract + */ + public function getHelper($name) + { + $name = self::_normalizeHelperName($name); + $stack = self::getStack(); + + if (!isset($stack->{$name})) { + self::_loadHelper($name); + } + + $helper = $stack->{$name}; + + $initialize = false; + if (null === ($actionController = $helper->getActionController())) { + $initialize = true; + } elseif ($actionController !== $this->_actionController) { + $initialize = true; + } + + if ($initialize) { + $helper->setActionController($this->_actionController) + ->init(); + } + + return $helper; + } + + /** + * Method overloading + * + * @param string $method + * @param array $args + * @return mixed + * @throws Zend_Controller_Action_Exception if helper does not have a direct() method + */ + public function __call($method, $args) + { + $helper = $this->getHelper($method); + if (!method_exists($helper, 'direct')) { + require_once 'Zend/Controller/Action/Exception.php'; + throw new Zend_Controller_Action_Exception('Helper "' . $method . '" does not support overloading via direct()'); + } + return call_user_func_array(array($helper, 'direct'), $args); + } + + /** + * Retrieve helper by name as object property + * + * @param string $name + * @return Zend_Controller_Action_Helper_Abstract + */ + public function __get($name) + { + return $this->getHelper($name); + } + + /** + * Normalize helper name for lookups + * + * @param string $name + * @return string + */ + protected static function _normalizeHelperName($name) + { + if (strpos($name, '_') !== false) { + $name = str_replace(' ', '', ucwords(str_replace('_', ' ', $name))); + } + + return ucfirst($name); + } + + /** + * Load a helper + * + * @param string $name + * @return void + */ + protected static function _loadHelper($name) + { + try { + $class = self::getPluginLoader()->load($name); + } catch (Zend_Loader_PluginLoader_Exception $e) { + require_once 'Zend/Controller/Action/Exception.php'; + throw new Zend_Controller_Action_Exception('Action Helper by name ' . $name . ' not found', 0, $e); + } + + $helper = new $class(); + + if (!$helper instanceof Zend_Controller_Action_Helper_Abstract) { + require_once 'Zend/Controller/Action/Exception.php'; + throw new Zend_Controller_Action_Exception('Helper name ' . $name . ' -> class ' . $class . ' is not of type Zend_Controller_Action_Helper_Abstract'); + } + + self::getStack()->push($helper); + } +} diff --git a/library/Zend/Controller/Action/HelperBroker/PriorityStack.php b/library/Zend/Controller/Action/HelperBroker/PriorityStack.php new file mode 100644 index 000000000..bc84c45c2 --- /dev/null +++ b/library/Zend/Controller/Action/HelperBroker/PriorityStack.php @@ -0,0 +1,280 @@ +_helpersByNameRef)) { + return false; + } + + return $this->_helpersByNameRef[$helperName]; + } + + /** + * Magic property overloading for returning if helper is set by name + * + * @param string $helperName The helper name + * @return Zend_Controller_Action_Helper_Abstract + */ + public function __isset($helperName) + { + return array_key_exists($helperName, $this->_helpersByNameRef); + } + + /** + * Magic property overloading for unsetting if helper is exists by name + * + * @param string $helperName The helper name + * @return Zend_Controller_Action_Helper_Abstract + */ + public function __unset($helperName) + { + return $this->offsetUnset($helperName); + } + + /** + * push helper onto the stack + * + * @param Zend_Controller_Action_Helper_Abstract $helper + * @return Zend_Controller_Action_HelperBroker_PriorityStack + */ + public function push(Zend_Controller_Action_Helper_Abstract $helper) + { + $this->offsetSet($this->getNextFreeHigherPriority(), $helper); + return $this; + } + + /** + * Return something iterable + * + * @return array + */ + public function getIterator() + { + return new ArrayObject($this->_helpersByPriority); + } + + /** + * offsetExists() + * + * @param int|string $priorityOrHelperName + * @return Zend_Controller_Action_HelperBroker_PriorityStack + */ + public function offsetExists($priorityOrHelperName) + { + if (is_string($priorityOrHelperName)) { + return array_key_exists($priorityOrHelperName, $this->_helpersByNameRef); + } else { + return array_key_exists($priorityOrHelperName, $this->_helpersByPriority); + } + } + + /** + * offsetGet() + * + * @param int|string $priorityOrHelperName + * @return Zend_Controller_Action_HelperBroker_PriorityStack + */ + public function offsetGet($priorityOrHelperName) + { + if (!$this->offsetExists($priorityOrHelperName)) { + require_once 'Zend/Controller/Action/Exception.php'; + throw new Zend_Controller_Action_Exception('A helper with priority ' . $priorityOrHelperName . ' does not exist.'); + } + + if (is_string($priorityOrHelperName)) { + return $this->_helpersByNameRef[$priorityOrHelperName]; + } else { + return $this->_helpersByPriority[$priorityOrHelperName]; + } + } + + /** + * offsetSet() + * + * @param int $priority + * @param Zend_Controller_Action_Helper_Abstract $helper + * @return Zend_Controller_Action_HelperBroker_PriorityStack + */ + public function offsetSet($priority, $helper) + { + $priority = (int) $priority; + + if (!$helper instanceof Zend_Controller_Action_Helper_Abstract) { + require_once 'Zend/Controller/Action/Exception.php'; + throw new Zend_Controller_Action_Exception('$helper must extend Zend_Controller_Action_Helper_Abstract.'); + } + + if (array_key_exists($helper->getName(), $this->_helpersByNameRef)) { + // remove any object with the same name to retain BC compailitbility + // @todo At ZF 2.0 time throw an exception here. + $this->offsetUnset($helper->getName()); + } + + if (array_key_exists($priority, $this->_helpersByPriority)) { + $priority = $this->getNextFreeHigherPriority($priority); // ensures LIFO + trigger_error("A helper with the same priority already exists, reassigning to $priority", E_USER_WARNING); + } + + $this->_helpersByPriority[$priority] = $helper; + $this->_helpersByNameRef[$helper->getName()] = $helper; + + if ($priority == ($nextFreeDefault = $this->getNextFreeHigherPriority($this->_nextDefaultPriority))) { + $this->_nextDefaultPriority = $nextFreeDefault; + } + + krsort($this->_helpersByPriority); // always make sure priority and LIFO are both enforced + return $this; + } + + /** + * offsetUnset() + * + * @param int|string $priorityOrHelperName Priority integer or the helper name + * @return Zend_Controller_Action_HelperBroker_PriorityStack + */ + public function offsetUnset($priorityOrHelperName) + { + if (!$this->offsetExists($priorityOrHelperName)) { + require_once 'Zend/Controller/Action/Exception.php'; + throw new Zend_Controller_Action_Exception('A helper with priority or name ' . $priorityOrHelperName . ' does not exist.'); + } + + if (is_string($priorityOrHelperName)) { + $helperName = $priorityOrHelperName; + $helper = $this->_helpersByNameRef[$helperName]; + $priority = array_search($helper, $this->_helpersByPriority, true); + } else { + $priority = $priorityOrHelperName; + $helperName = $this->_helpersByPriority[$priorityOrHelperName]->getName(); + } + + unset($this->_helpersByNameRef[$helperName]); + unset($this->_helpersByPriority[$priority]); + return $this; + } + + /** + * return the count of helpers + * + * @return int + */ + public function count() + { + return count($this->_helpersByPriority); + } + + /** + * Find the next free higher priority. If an index is given, it will + * find the next free highest priority after it. + * + * @param int $indexPriority OPTIONAL + * @return int + */ + public function getNextFreeHigherPriority($indexPriority = null) + { + if ($indexPriority == null) { + $indexPriority = $this->_nextDefaultPriority; + } + + $priorities = array_keys($this->_helpersByPriority); + + while (in_array($indexPriority, $priorities)) { + $indexPriority++; + } + + return $indexPriority; + } + + /** + * Find the next free lower priority. If an index is given, it will + * find the next free lower priority before it. + * + * @param int $indexPriority + * @return int + */ + public function getNextFreeLowerPriority($indexPriority = null) + { + if ($indexPriority == null) { + $indexPriority = $this->_nextDefaultPriority; + } + + $priorities = array_keys($this->_helpersByPriority); + + while (in_array($indexPriority, $priorities)) { + $indexPriority--; + } + + return $indexPriority; + } + + /** + * return the highest priority + * + * @return int + */ + public function getHighestPriority() + { + return max(array_keys($this->_helpersByPriority)); + } + + /** + * return the lowest priority + * + * @return int + */ + public function getLowestPriority() + { + return min(array_keys($this->_helpersByPriority)); + } + + /** + * return the helpers referenced by name + * + * @return array + */ + public function getHelpersByName() + { + return $this->_helpersByNameRef; + } + +} diff --git a/library/Zend/Controller/Action/Interface.php b/library/Zend/Controller/Action/Interface.php new file mode 100644 index 000000000..eb00ada36 --- /dev/null +++ b/library/Zend/Controller/Action/Interface.php @@ -0,0 +1,69 @@ +setParams($params); + } + + /** + * Formats a string into a controller name. This is used to take a raw + * controller name, such as one stored inside a Zend_Controller_Request_Abstract + * object, and reformat it to a proper class name that a class extending + * Zend_Controller_Action would use. + * + * @param string $unformatted + * @return string + */ + public function formatControllerName($unformatted) + { + return ucfirst($this->_formatName($unformatted)) . 'Controller'; + } + + /** + * Formats a string into an action name. This is used to take a raw + * action name, such as one that would be stored inside a Zend_Controller_Request_Abstract + * object, and reformat into a proper method name that would be found + * inside a class extending Zend_Controller_Action. + * + * @param string $unformatted + * @return string + */ + public function formatActionName($unformatted) + { + $formatted = $this->_formatName($unformatted, true); + return strtolower(substr($formatted, 0, 1)) . substr($formatted, 1) . 'Action'; + } + + /** + * Verify delimiter + * + * Verify a delimiter to use in controllers or actions. May be a single + * string or an array of strings. + * + * @param string|array $spec + * @return array + * @throws Zend_Controller_Dispatcher_Exception with invalid delimiters + */ + public function _verifyDelimiter($spec) + { + if (is_string($spec)) { + return (array) $spec; + } elseif (is_array($spec)) { + $allStrings = true; + foreach ($spec as $delim) { + if (!is_string($delim)) { + $allStrings = false; + break; + } + } + + if (!$allStrings) { + require_once 'Zend/Controller/Dispatcher/Exception.php'; + throw new Zend_Controller_Dispatcher_Exception('Word delimiter array must contain only strings'); + } + + return $spec; + } + + require_once 'Zend/Controller/Dispatcher/Exception.php'; + throw new Zend_Controller_Dispatcher_Exception('Invalid word delimiter'); + } + + /** + * Retrieve the word delimiter character(s) used in + * controller or action names + * + * @return array + */ + public function getWordDelimiter() + { + return $this->_wordDelimiter; + } + + /** + * Set word delimiter + * + * Set the word delimiter to use in controllers and actions. May be a + * single string or an array of strings. + * + * @param string|array $spec + * @return Zend_Controller_Dispatcher_Abstract + */ + public function setWordDelimiter($spec) + { + $spec = $this->_verifyDelimiter($spec); + $this->_wordDelimiter = $spec; + + return $this; + } + + /** + * Retrieve the path delimiter character(s) used in + * controller names + * + * @return array + */ + public function getPathDelimiter() + { + return $this->_pathDelimiter; + } + + /** + * Set path delimiter + * + * Set the path delimiter to use in controllers. May be a single string or + * an array of strings. + * + * @param string $spec + * @return Zend_Controller_Dispatcher_Abstract + */ + public function setPathDelimiter($spec) + { + if (!is_string($spec)) { + require_once 'Zend/Controller/Dispatcher/Exception.php'; + throw new Zend_Controller_Dispatcher_Exception('Invalid path delimiter'); + } + $this->_pathDelimiter = $spec; + + return $this; + } + + /** + * Formats a string from a URI into a PHP-friendly name. + * + * By default, replaces words separated by the word separator character(s) + * with camelCaps. If $isAction is false, it also preserves replaces words + * separated by the path separation character with an underscore, making + * the following word Title cased. All non-alphanumeric characters are + * removed. + * + * @param string $unformatted + * @param boolean $isAction Defaults to false + * @return string + */ + protected function _formatName($unformatted, $isAction = false) + { + // preserve directories + if (!$isAction) { + $segments = explode($this->getPathDelimiter(), $unformatted); + } else { + $segments = (array) $unformatted; + } + + foreach ($segments as $key => $segment) { + $segment = str_replace($this->getWordDelimiter(), ' ', strtolower($segment)); + $segment = preg_replace('/[^a-z0-9 ]/', '', $segment); + $segments[$key] = str_replace(' ', '', ucwords($segment)); + } + + return implode('_', $segments); + } + + /** + * Retrieve front controller instance + * + * @return Zend_Controller_Front + */ + public function getFrontController() + { + if (null === $this->_frontController) { + require_once 'Zend/Controller/Front.php'; + $this->_frontController = Zend_Controller_Front::getInstance(); + } + + return $this->_frontController; + } + + /** + * Set front controller instance + * + * @param Zend_Controller_Front $controller + * @return Zend_Controller_Dispatcher_Abstract + */ + public function setFrontController(Zend_Controller_Front $controller) + { + $this->_frontController = $controller; + return $this; + } + + /** + * Add or modify a parameter to use when instantiating an action controller + * + * @param string $name + * @param mixed $value + * @return Zend_Controller_Dispatcher_Abstract + */ + public function setParam($name, $value) + { + $name = (string) $name; + $this->_invokeParams[$name] = $value; + return $this; + } + + /** + * Set parameters to pass to action controller constructors + * + * @param array $params + * @return Zend_Controller_Dispatcher_Abstract + */ + public function setParams(array $params) + { + $this->_invokeParams = array_merge($this->_invokeParams, $params); + return $this; + } + + /** + * Retrieve a single parameter from the controller parameter stack + * + * @param string $name + * @return mixed + */ + public function getParam($name) + { + if(isset($this->_invokeParams[$name])) { + return $this->_invokeParams[$name]; + } + + return null; + } + + /** + * Retrieve action controller instantiation parameters + * + * @return array + */ + public function getParams() + { + return $this->_invokeParams; + } + + /** + * Clear the controller parameter stack + * + * By default, clears all parameters. If a parameter name is given, clears + * only that parameter; if an array of parameter names is provided, clears + * each. + * + * @param null|string|array single key or array of keys for params to clear + * @return Zend_Controller_Dispatcher_Abstract + */ + public function clearParams($name = null) + { + if (null === $name) { + $this->_invokeParams = array(); + } elseif (is_string($name) && isset($this->_invokeParams[$name])) { + unset($this->_invokeParams[$name]); + } elseif (is_array($name)) { + foreach ($name as $key) { + if (is_string($key) && isset($this->_invokeParams[$key])) { + unset($this->_invokeParams[$key]); + } + } + } + + return $this; + } + + /** + * Set response object to pass to action controllers + * + * @param Zend_Controller_Response_Abstract|null $response + * @return Zend_Controller_Dispatcher_Abstract + */ + public function setResponse(Zend_Controller_Response_Abstract $response = null) + { + $this->_response = $response; + return $this; + } + + /** + * Return the registered response object + * + * @return Zend_Controller_Response_Abstract|null + */ + public function getResponse() + { + return $this->_response; + } + + /** + * Set the default controller (minus any formatting) + * + * @param string $controller + * @return Zend_Controller_Dispatcher_Abstract + */ + public function setDefaultControllerName($controller) + { + $this->_defaultController = (string) $controller; + return $this; + } + + /** + * Retrieve the default controller name (minus formatting) + * + * @return string + */ + public function getDefaultControllerName() + { + return $this->_defaultController; + } + + /** + * Set the default action (minus any formatting) + * + * @param string $action + * @return Zend_Controller_Dispatcher_Abstract + */ + public function setDefaultAction($action) + { + $this->_defaultAction = (string) $action; + return $this; + } + + /** + * Retrieve the default action name (minus formatting) + * + * @return string + */ + public function getDefaultAction() + { + return $this->_defaultAction; + } + + /** + * Set the default module + * + * @param string $module + * @return Zend_Controller_Dispatcher_Abstract + */ + public function setDefaultModule($module) + { + $this->_defaultModule = (string) $module; + return $this; + } + + /** + * Retrieve the default module + * + * @return string + */ + public function getDefaultModule() + { + return $this->_defaultModule; + } +} diff --git a/library/Zend/Controller/Dispatcher/Exception.php b/library/Zend/Controller/Dispatcher/Exception.php new file mode 100644 index 000000000..7a58cf8ac --- /dev/null +++ b/library/Zend/Controller/Dispatcher/Exception.php @@ -0,0 +1,37 @@ +_curModule = $this->getDefaultModule(); + } + + /** + * Add a single path to the controller directory stack + * + * @param string $path + * @param string $module + * @return Zend_Controller_Dispatcher_Standard + */ + public function addControllerDirectory($path, $module = null) + { + if (null === $module) { + $module = $this->_defaultModule; + } + + $module = (string) $module; + $path = rtrim((string) $path, '/\\'); + + $this->_controllerDirectory[$module] = $path; + return $this; + } + + /** + * Set controller directory + * + * @param array|string $directory + * @return Zend_Controller_Dispatcher_Standard + */ + public function setControllerDirectory($directory, $module = null) + { + $this->_controllerDirectory = array(); + + if (is_string($directory)) { + $this->addControllerDirectory($directory, $module); + } elseif (is_array($directory)) { + foreach ((array) $directory as $module => $path) { + $this->addControllerDirectory($path, $module); + } + } else { + require_once 'Zend/Controller/Exception.php'; + throw new Zend_Controller_Exception('Controller directory spec must be either a string or an array'); + } + + return $this; + } + + /** + * Return the currently set directories for Zend_Controller_Action class + * lookup + * + * If a module is specified, returns just that directory. + * + * @param string $module Module name + * @return array|string Returns array of all directories by default, single + * module directory if module argument provided + */ + public function getControllerDirectory($module = null) + { + if (null === $module) { + return $this->_controllerDirectory; + } + + $module = (string) $module; + if (array_key_exists($module, $this->_controllerDirectory)) { + return $this->_controllerDirectory[$module]; + } + + return null; + } + + /** + * Remove a controller directory by module name + * + * @param string $module + * @return bool + */ + public function removeControllerDirectory($module) + { + $module = (string) $module; + if (array_key_exists($module, $this->_controllerDirectory)) { + unset($this->_controllerDirectory[$module]); + return true; + } + return false; + } + + /** + * Format the module name. + * + * @param string $unformatted + * @return string + */ + public function formatModuleName($unformatted) + { + if (($this->_defaultModule == $unformatted) && !$this->getParam('prefixDefaultModule')) { + return $unformatted; + } + + return ucfirst($this->_formatName($unformatted)); + } + + /** + * Format action class name + * + * @param string $moduleName Name of the current module + * @param string $className Name of the action class + * @return string Formatted class name + */ + public function formatClassName($moduleName, $className) + { + return $this->formatModuleName($moduleName) . '_' . $className; + } + + /** + * Convert a class name to a filename + * + * @param string $class + * @return string + */ + public function classToFilename($class) + { + return str_replace('_', DIRECTORY_SEPARATOR, $class) . '.php'; + } + + /** + * Returns TRUE if the Zend_Controller_Request_Abstract object can be + * dispatched to a controller. + * + * Use this method wisely. By default, the dispatcher will fall back to the + * default controller (either in the module specified or the global default) + * if a given controller does not exist. This method returning false does + * not necessarily indicate the dispatcher will not still dispatch the call. + * + * @param Zend_Controller_Request_Abstract $action + * @return boolean + */ + public function isDispatchable(Zend_Controller_Request_Abstract $request) + { + $className = $this->getControllerClass($request); + if (!$className) { + return false; + } + + if (class_exists($className, false)) { + return true; + } + + $fileSpec = $this->classToFilename($className); + $dispatchDir = $this->getDispatchDirectory(); + $test = $dispatchDir . DIRECTORY_SEPARATOR . $fileSpec; + return Zend_Loader::isReadable($test); + } + + /** + * Dispatch to a controller/action + * + * By default, if a controller is not dispatchable, dispatch() will throw + * an exception. If you wish to use the default controller instead, set the + * param 'useDefaultControllerAlways' via {@link setParam()}. + * + * @param Zend_Controller_Request_Abstract $request + * @param Zend_Controller_Response_Abstract $response + * @return void + * @throws Zend_Controller_Dispatcher_Exception + */ + public function dispatch(Zend_Controller_Request_Abstract $request, Zend_Controller_Response_Abstract $response) + { + $this->setResponse($response); + + /** + * Get controller class + */ + if (!$this->isDispatchable($request)) { + $controller = $request->getControllerName(); + if (!$this->getParam('useDefaultControllerAlways') && !empty($controller)) { + require_once 'Zend/Controller/Dispatcher/Exception.php'; + throw new Zend_Controller_Dispatcher_Exception('Invalid controller specified (' . $request->getControllerName() . ')'); + } + + $className = $this->getDefaultControllerClass($request); + } else { + $className = $this->getControllerClass($request); + if (!$className) { + $className = $this->getDefaultControllerClass($request); + } + } + + /** + * Load the controller class file + */ + $className = $this->loadClass($className); + + /** + * Instantiate controller with request, response, and invocation + * arguments; throw exception if it's not an action controller + */ + $controller = new $className($request, $this->getResponse(), $this->getParams()); + if (!($controller instanceof Zend_Controller_Action_Interface) && + !($controller instanceof Zend_Controller_Action)) { + require_once 'Zend/Controller/Dispatcher/Exception.php'; + throw new Zend_Controller_Dispatcher_Exception( + 'Controller "' . $className . '" is not an instance of Zend_Controller_Action_Interface' + ); + } + + /** + * Retrieve the action name + */ + $action = $this->getActionMethod($request); + + /** + * Dispatch the method call + */ + $request->setDispatched(true); + + // by default, buffer output + $disableOb = $this->getParam('disableOutputBuffering'); + $obLevel = ob_get_level(); + if (empty($disableOb)) { + ob_start(); + } + + try { + $controller->dispatch($action); + } catch (Exception $e) { + // Clean output buffer on error + $curObLevel = ob_get_level(); + if ($curObLevel > $obLevel) { + do { + ob_get_clean(); + $curObLevel = ob_get_level(); + } while ($curObLevel > $obLevel); + } + throw $e; + } + + if (empty($disableOb)) { + $content = ob_get_clean(); + $response->appendBody($content); + } + + // Destroy the page controller instance and reflection objects + $controller = null; + } + + /** + * Load a controller class + * + * Attempts to load the controller class file from + * {@link getControllerDirectory()}. If the controller belongs to a + * module, looks for the module prefix to the controller class. + * + * @param string $className + * @return string Class name loaded + * @throws Zend_Controller_Dispatcher_Exception if class not loaded + */ + public function loadClass($className) + { + $finalClass = $className; + if (($this->_defaultModule != $this->_curModule) + || $this->getParam('prefixDefaultModule')) + { + $finalClass = $this->formatClassName($this->_curModule, $className); + } + if (class_exists($finalClass, false)) { + return $finalClass; + } + + $dispatchDir = $this->getDispatchDirectory(); + $loadFile = $dispatchDir . DIRECTORY_SEPARATOR . $this->classToFilename($className); + + if (Zend_Loader::isReadable($loadFile)) { + include_once $loadFile; + } else { + require_once 'Zend/Controller/Dispatcher/Exception.php'; + throw new Zend_Controller_Dispatcher_Exception('Cannot load controller class "' . $className . '" from file "' . $loadFile . "'"); + } + + if (!class_exists($finalClass, false)) { + require_once 'Zend/Controller/Dispatcher/Exception.php'; + throw new Zend_Controller_Dispatcher_Exception('Invalid controller class ("' . $finalClass . '")'); + } + + return $finalClass; + } + + /** + * Get controller class name + * + * Try request first; if not found, try pulling from request parameter; + * if still not found, fallback to default + * + * @param Zend_Controller_Request_Abstract $request + * @return string|false Returns class name on success + */ + public function getControllerClass(Zend_Controller_Request_Abstract $request) + { + $controllerName = $request->getControllerName(); + if (empty($controllerName)) { + if (!$this->getParam('useDefaultControllerAlways')) { + return false; + } + $controllerName = $this->getDefaultControllerName(); + $request->setControllerName($controllerName); + } + + $className = $this->formatControllerName($controllerName); + + $controllerDirs = $this->getControllerDirectory(); + $module = $request->getModuleName(); + if ($this->isValidModule($module)) { + $this->_curModule = $module; + $this->_curDirectory = $controllerDirs[$module]; + } elseif ($this->isValidModule($this->_defaultModule)) { + $request->setModuleName($this->_defaultModule); + $this->_curModule = $this->_defaultModule; + $this->_curDirectory = $controllerDirs[$this->_defaultModule]; + } else { + require_once 'Zend/Controller/Exception.php'; + throw new Zend_Controller_Exception('No default module defined for this application'); + } + + return $className; + } + + /** + * Determine if a given module is valid + * + * @param string $module + * @return bool + */ + public function isValidModule($module) + { + if (!is_string($module)) { + return false; + } + + $module = strtolower($module); + $controllerDir = $this->getControllerDirectory(); + foreach (array_keys($controllerDir) as $moduleName) { + if ($module == strtolower($moduleName)) { + return true; + } + } + + return false; + } + + /** + * Retrieve default controller class + * + * Determines whether the default controller to use lies within the + * requested module, or if the global default should be used. + * + * By default, will only use the module default unless that controller does + * not exist; if this is the case, it falls back to the default controller + * in the default module. + * + * @param Zend_Controller_Request_Abstract $request + * @return string + */ + public function getDefaultControllerClass(Zend_Controller_Request_Abstract $request) + { + $controller = $this->getDefaultControllerName(); + $default = $this->formatControllerName($controller); + $request->setControllerName($controller) + ->setActionName(null); + + $module = $request->getModuleName(); + $controllerDirs = $this->getControllerDirectory(); + $this->_curModule = $this->_defaultModule; + $this->_curDirectory = $controllerDirs[$this->_defaultModule]; + if ($this->isValidModule($module)) { + $found = false; + if (class_exists($default, false)) { + $found = true; + } else { + $moduleDir = $controllerDirs[$module]; + $fileSpec = $moduleDir . DIRECTORY_SEPARATOR . $this->classToFilename($default); + if (Zend_Loader::isReadable($fileSpec)) { + $found = true; + $this->_curDirectory = $moduleDir; + } + } + if ($found) { + $request->setModuleName($module); + $this->_curModule = $this->formatModuleName($module); + } + } else { + $request->setModuleName($this->_defaultModule); + } + + return $default; + } + + /** + * Return the value of the currently selected dispatch directory (as set by + * {@link getController()}) + * + * @return string + */ + public function getDispatchDirectory() + { + return $this->_curDirectory; + } + + /** + * Determine the action name + * + * First attempt to retrieve from request; then from request params + * using action key; default to default action + * + * Returns formatted action name + * + * @param Zend_Controller_Request_Abstract $request + * @return string + */ + public function getActionMethod(Zend_Controller_Request_Abstract $request) + { + $action = $request->getActionName(); + if (empty($action)) { + $action = $this->getDefaultAction(); + $request->setActionName($action); + } + + return $this->formatActionName($action); + } +} diff --git a/library/Zend/Controller/Exception.php b/library/Zend/Controller/Exception.php new file mode 100644 index 000000000..2cd0ff09d --- /dev/null +++ b/library/Zend/Controller/Exception.php @@ -0,0 +1,35 @@ +_plugins = new Zend_Controller_Plugin_Broker(); + } + + /** + * Enforce singleton; disallow cloning + * + * @return void + */ + private function __clone() + { + } + + /** + * Singleton instance + * + * @return Zend_Controller_Front + */ + public static function getInstance() + { + if (null === self::$_instance) { + self::$_instance = new self(); + } + + return self::$_instance; + } + + /** + * Resets all object properties of the singleton instance + * + * Primarily used for testing; could be used to chain front controllers. + * + * Also resets action helper broker, clearing all registered helpers. + * + * @return void + */ + public function resetInstance() + { + $reflection = new ReflectionObject($this); + foreach ($reflection->getProperties() as $property) { + $name = $property->getName(); + switch ($name) { + case '_instance': + break; + case '_controllerDir': + case '_invokeParams': + $this->{$name} = array(); + break; + case '_plugins': + $this->{$name} = new Zend_Controller_Plugin_Broker(); + break; + case '_throwExceptions': + case '_returnResponse': + $this->{$name} = false; + break; + case '_moduleControllerDirectoryName': + $this->{$name} = 'controllers'; + break; + default: + $this->{$name} = null; + break; + } + } + Zend_Controller_Action_HelperBroker::resetHelpers(); + } + + /** + * Convenience feature, calls setControllerDirectory()->setRouter()->dispatch() + * + * In PHP 5.1.x, a call to a static method never populates $this -- so run() + * may actually be called after setting up your front controller. + * + * @param string|array $controllerDirectory Path to Zend_Controller_Action + * controller classes or array of such paths + * @return void + * @throws Zend_Controller_Exception if called from an object instance + */ + public static function run($controllerDirectory) + { + self::getInstance() + ->setControllerDirectory($controllerDirectory) + ->dispatch(); + } + + /** + * Add a controller directory to the controller directory stack + * + * If $args is presented and is a string, uses it for the array key mapping + * to the directory specified. + * + * @param string $directory + * @param string $module Optional argument; module with which to associate directory. If none provided, assumes 'default' + * @return Zend_Controller_Front + * @throws Zend_Controller_Exception if directory not found or readable + */ + public function addControllerDirectory($directory, $module = null) + { + $this->getDispatcher()->addControllerDirectory($directory, $module); + return $this; + } + + /** + * Set controller directory + * + * Stores controller directory(ies) in dispatcher. May be an array of + * directories or a string containing a single directory. + * + * @param string|array $directory Path to Zend_Controller_Action controller + * classes or array of such paths + * @param string $module Optional module name to use with string $directory + * @return Zend_Controller_Front + */ + public function setControllerDirectory($directory, $module = null) + { + $this->getDispatcher()->setControllerDirectory($directory, $module); + return $this; + } + + /** + * Retrieve controller directory + * + * Retrieves: + * - Array of all controller directories if no $name passed + * - String path if $name passed and exists as a key in controller directory array + * - null if $name passed but does not exist in controller directory keys + * + * @param string $name Default null + * @return array|string|null + */ + public function getControllerDirectory($name = null) + { + return $this->getDispatcher()->getControllerDirectory($name); + } + + /** + * Remove a controller directory by module name + * + * @param string $module + * @return bool + */ + public function removeControllerDirectory($module) + { + return $this->getDispatcher()->removeControllerDirectory($module); + } + + /** + * Specify a directory as containing modules + * + * Iterates through the directory, adding any subdirectories as modules; + * the subdirectory within each module named after {@link $_moduleControllerDirectoryName} + * will be used as the controller directory path. + * + * @param string $path + * @return Zend_Controller_Front + */ + public function addModuleDirectory($path) + { + try{ + $dir = new DirectoryIterator($path); + } catch(Exception $e) { + require_once 'Zend/Controller/Exception.php'; + throw new Zend_Controller_Exception("Directory $path not readable", 0, $e); + } + foreach ($dir as $file) { + if ($file->isDot() || !$file->isDir()) { + continue; + } + + $module = $file->getFilename(); + + // Don't use SCCS directories as modules + if (preg_match('/^[^a-z]/i', $module) || ('CVS' == $module)) { + continue; + } + + $moduleDir = $file->getPathname() . DIRECTORY_SEPARATOR . $this->getModuleControllerDirectoryName(); + $this->addControllerDirectory($moduleDir, $module); + } + + return $this; + } + + /** + * Return the path to a module directory (but not the controllers directory within) + * + * @param string $module + * @return string|null + */ + public function getModuleDirectory($module = null) + { + if (null === $module) { + $request = $this->getRequest(); + if (null !== $request) { + $module = $this->getRequest()->getModuleName(); + } + if (empty($module)) { + $module = $this->getDispatcher()->getDefaultModule(); + } + } + + $controllerDir = $this->getControllerDirectory($module); + + if ((null === $controllerDir) || !is_string($controllerDir)) { + return null; + } + + return dirname($controllerDir); + } + + /** + * Set the directory name within a module containing controllers + * + * @param string $name + * @return Zend_Controller_Front + */ + public function setModuleControllerDirectoryName($name = 'controllers') + { + $this->_moduleControllerDirectoryName = (string) $name; + + return $this; + } + + /** + * Return the directory name within a module containing controllers + * + * @return string + */ + public function getModuleControllerDirectoryName() + { + return $this->_moduleControllerDirectoryName; + } + + /** + * Set the default controller (unformatted string) + * + * @param string $controller + * @return Zend_Controller_Front + */ + public function setDefaultControllerName($controller) + { + $dispatcher = $this->getDispatcher(); + $dispatcher->setDefaultControllerName($controller); + return $this; + } + + /** + * Retrieve the default controller (unformatted string) + * + * @return string + */ + public function getDefaultControllerName() + { + return $this->getDispatcher()->getDefaultControllerName(); + } + + /** + * Set the default action (unformatted string) + * + * @param string $action + * @return Zend_Controller_Front + */ + public function setDefaultAction($action) + { + $dispatcher = $this->getDispatcher(); + $dispatcher->setDefaultAction($action); + return $this; + } + + /** + * Retrieve the default action (unformatted string) + * + * @return string + */ + public function getDefaultAction() + { + return $this->getDispatcher()->getDefaultAction(); + } + + /** + * Set the default module name + * + * @param string $module + * @return Zend_Controller_Front + */ + public function setDefaultModule($module) + { + $dispatcher = $this->getDispatcher(); + $dispatcher->setDefaultModule($module); + return $this; + } + + /** + * Retrieve the default module + * + * @return string + */ + public function getDefaultModule() + { + return $this->getDispatcher()->getDefaultModule(); + } + + /** + * Set request class/object + * + * Set the request object. The request holds the request environment. + * + * If a class name is provided, it will instantiate it + * + * @param string|Zend_Controller_Request_Abstract $request + * @throws Zend_Controller_Exception if invalid request class + * @return Zend_Controller_Front + */ + public function setRequest($request) + { + if (is_string($request)) { + if (!class_exists($request)) { + require_once 'Zend/Loader.php'; + Zend_Loader::loadClass($request); + } + $request = new $request(); + } + if (!$request instanceof Zend_Controller_Request_Abstract) { + require_once 'Zend/Controller/Exception.php'; + throw new Zend_Controller_Exception('Invalid request class'); + } + + $this->_request = $request; + + return $this; + } + + /** + * Return the request object. + * + * @return null|Zend_Controller_Request_Abstract + */ + public function getRequest() + { + return $this->_request; + } + + /** + * Set router class/object + * + * Set the router object. The router is responsible for mapping + * the request to a controller and action. + * + * If a class name is provided, instantiates router with any parameters + * registered via {@link setParam()} or {@link setParams()}. + * + * @param string|Zend_Controller_Router_Interface $router + * @throws Zend_Controller_Exception if invalid router class + * @return Zend_Controller_Front + */ + public function setRouter($router) + { + if (is_string($router)) { + if (!class_exists($router)) { + require_once 'Zend/Loader.php'; + Zend_Loader::loadClass($router); + } + $router = new $router(); + } + + if (!$router instanceof Zend_Controller_Router_Interface) { + require_once 'Zend/Controller/Exception.php'; + throw new Zend_Controller_Exception('Invalid router class'); + } + + $router->setFrontController($this); + $this->_router = $router; + + return $this; + } + + /** + * Return the router object. + * + * Instantiates a Zend_Controller_Router_Rewrite object if no router currently set. + * + * @return Zend_Controller_Router_Interface + */ + public function getRouter() + { + if (null == $this->_router) { + require_once 'Zend/Controller/Router/Rewrite.php'; + $this->setRouter(new Zend_Controller_Router_Rewrite()); + } + + return $this->_router; + } + + /** + * Set the base URL used for requests + * + * Use to set the base URL segment of the REQUEST_URI to use when + * determining PATH_INFO, etc. Examples: + * - /admin + * - /myapp + * - /subdir/index.php + * + * Note that the URL should not include the full URI. Do not use: + * - http://example.com/admin + * - http://example.com/myapp + * - http://example.com/subdir/index.php + * + * If a null value is passed, this can be used as well for autodiscovery (default). + * + * @param string $base + * @return Zend_Controller_Front + * @throws Zend_Controller_Exception for non-string $base + */ + public function setBaseUrl($base = null) + { + if (!is_string($base) && (null !== $base)) { + require_once 'Zend/Controller/Exception.php'; + throw new Zend_Controller_Exception('Rewrite base must be a string'); + } + + $this->_baseUrl = $base; + + if ((null !== ($request = $this->getRequest())) && (method_exists($request, 'setBaseUrl'))) { + $request->setBaseUrl($base); + } + + return $this; + } + + /** + * Retrieve the currently set base URL + * + * @return string + */ + public function getBaseUrl() + { + $request = $this->getRequest(); + if ((null !== $request) && method_exists($request, 'getBaseUrl')) { + return $request->getBaseUrl(); + } + + return $this->_baseUrl; + } + + /** + * Set the dispatcher object. The dispatcher is responsible for + * taking a Zend_Controller_Dispatcher_Token object, instantiating the controller, and + * call the action method of the controller. + * + * @param Zend_Controller_Dispatcher_Interface $dispatcher + * @return Zend_Controller_Front + */ + public function setDispatcher(Zend_Controller_Dispatcher_Interface $dispatcher) + { + $this->_dispatcher = $dispatcher; + return $this; + } + + /** + * Return the dispatcher object. + * + * @return Zend_Controller_Dispatcher_Interface + */ + public function getDispatcher() + { + /** + * Instantiate the default dispatcher if one was not set. + */ + if (!$this->_dispatcher instanceof Zend_Controller_Dispatcher_Interface) { + require_once 'Zend/Controller/Dispatcher/Standard.php'; + $this->_dispatcher = new Zend_Controller_Dispatcher_Standard(); + } + return $this->_dispatcher; + } + + /** + * Set response class/object + * + * Set the response object. The response is a container for action + * responses and headers. Usage is optional. + * + * If a class name is provided, instantiates a response object. + * + * @param string|Zend_Controller_Response_Abstract $response + * @throws Zend_Controller_Exception if invalid response class + * @return Zend_Controller_Front + */ + public function setResponse($response) + { + if (is_string($response)) { + if (!class_exists($response)) { + require_once 'Zend/Loader.php'; + Zend_Loader::loadClass($response); + } + $response = new $response(); + } + if (!$response instanceof Zend_Controller_Response_Abstract) { + require_once 'Zend/Controller/Exception.php'; + throw new Zend_Controller_Exception('Invalid response class'); + } + + $this->_response = $response; + + return $this; + } + + /** + * Return the response object. + * + * @return null|Zend_Controller_Response_Abstract + */ + public function getResponse() + { + return $this->_response; + } + + /** + * Add or modify a parameter to use when instantiating an action controller + * + * @param string $name + * @param mixed $value + * @return Zend_Controller_Front + */ + public function setParam($name, $value) + { + $name = (string) $name; + $this->_invokeParams[$name] = $value; + return $this; + } + + /** + * Set parameters to pass to action controller constructors + * + * @param array $params + * @return Zend_Controller_Front + */ + public function setParams(array $params) + { + $this->_invokeParams = array_merge($this->_invokeParams, $params); + return $this; + } + + /** + * Retrieve a single parameter from the controller parameter stack + * + * @param string $name + * @return mixed + */ + public function getParam($name) + { + if(isset($this->_invokeParams[$name])) { + return $this->_invokeParams[$name]; + } + + return null; + } + + /** + * Retrieve action controller instantiation parameters + * + * @return array + */ + public function getParams() + { + return $this->_invokeParams; + } + + /** + * Clear the controller parameter stack + * + * By default, clears all parameters. If a parameter name is given, clears + * only that parameter; if an array of parameter names is provided, clears + * each. + * + * @param null|string|array single key or array of keys for params to clear + * @return Zend_Controller_Front + */ + public function clearParams($name = null) + { + if (null === $name) { + $this->_invokeParams = array(); + } elseif (is_string($name) && isset($this->_invokeParams[$name])) { + unset($this->_invokeParams[$name]); + } elseif (is_array($name)) { + foreach ($name as $key) { + if (is_string($key) && isset($this->_invokeParams[$key])) { + unset($this->_invokeParams[$key]); + } + } + } + + return $this; + } + + /** + * Register a plugin. + * + * @param Zend_Controller_Plugin_Abstract $plugin + * @param int $stackIndex Optional; stack index for plugin + * @return Zend_Controller_Front + */ + public function registerPlugin(Zend_Controller_Plugin_Abstract $plugin, $stackIndex = null) + { + $this->_plugins->registerPlugin($plugin, $stackIndex); + return $this; + } + + /** + * Unregister a plugin. + * + * @param string|Zend_Controller_Plugin_Abstract $plugin Plugin class or object to unregister + * @return Zend_Controller_Front + */ + public function unregisterPlugin($plugin) + { + $this->_plugins->unregisterPlugin($plugin); + return $this; + } + + /** + * Is a particular plugin registered? + * + * @param string $class + * @return bool + */ + public function hasPlugin($class) + { + return $this->_plugins->hasPlugin($class); + } + + /** + * Retrieve a plugin or plugins by class + * + * @param string $class + * @return false|Zend_Controller_Plugin_Abstract|array + */ + public function getPlugin($class) + { + return $this->_plugins->getPlugin($class); + } + + /** + * Retrieve all plugins + * + * @return array + */ + public function getPlugins() + { + return $this->_plugins->getPlugins(); + } + + /** + * Set the throwExceptions flag and retrieve current status + * + * Set whether exceptions encounted in the dispatch loop should be thrown + * or caught and trapped in the response object. + * + * Default behaviour is to trap them in the response object; call this + * method to have them thrown. + * + * Passing no value will return the current value of the flag; passing a + * boolean true or false value will set the flag and return the current + * object instance. + * + * @param boolean $flag Defaults to null (return flag state) + * @return boolean|Zend_Controller_Front Used as a setter, returns object; as a getter, returns boolean + */ + public function throwExceptions($flag = null) + { + if ($flag !== null) { + $this->_throwExceptions = (bool) $flag; + return $this; + } + + return $this->_throwExceptions; + } + + /** + * Set whether {@link dispatch()} should return the response without first + * rendering output. By default, output is rendered and dispatch() returns + * nothing. + * + * @param boolean $flag + * @return boolean|Zend_Controller_Front Used as a setter, returns object; as a getter, returns boolean + */ + public function returnResponse($flag = null) + { + if (true === $flag) { + $this->_returnResponse = true; + return $this; + } elseif (false === $flag) { + $this->_returnResponse = false; + return $this; + } + + return $this->_returnResponse; + } + + /** + * Dispatch an HTTP request to a controller/action. + * + * @param Zend_Controller_Request_Abstract|null $request + * @param Zend_Controller_Response_Abstract|null $response + * @return void|Zend_Controller_Response_Abstract Returns response object if returnResponse() is true + */ + public function dispatch(Zend_Controller_Request_Abstract $request = null, Zend_Controller_Response_Abstract $response = null) + { + if (!$this->getParam('noErrorHandler') && !$this->_plugins->hasPlugin('Zend_Controller_Plugin_ErrorHandler')) { + // Register with stack index of 100 + require_once 'Zend/Controller/Plugin/ErrorHandler.php'; + $this->_plugins->registerPlugin(new Zend_Controller_Plugin_ErrorHandler(), 100); + } + + if (!$this->getParam('noViewRenderer') && !Zend_Controller_Action_HelperBroker::hasHelper('viewRenderer')) { + require_once 'Zend/Controller/Action/Helper/ViewRenderer.php'; + Zend_Controller_Action_HelperBroker::getStack()->offsetSet(-80, new Zend_Controller_Action_Helper_ViewRenderer()); + } + + /** + * Instantiate default request object (HTTP version) if none provided + */ + if (null !== $request) { + $this->setRequest($request); + } elseif ((null === $request) && (null === ($request = $this->getRequest()))) { + require_once 'Zend/Controller/Request/Http.php'; + $request = new Zend_Controller_Request_Http(); + $this->setRequest($request); + } + + /** + * Set base URL of request object, if available + */ + if (is_callable(array($this->_request, 'setBaseUrl'))) { + if (null !== $this->_baseUrl) { + $this->_request->setBaseUrl($this->_baseUrl); + } + } + + /** + * Instantiate default response object (HTTP version) if none provided + */ + if (null !== $response) { + $this->setResponse($response); + } elseif ((null === $this->_response) && (null === ($this->_response = $this->getResponse()))) { + require_once 'Zend/Controller/Response/Http.php'; + $response = new Zend_Controller_Response_Http(); + $this->setResponse($response); + } + + /** + * Register request and response objects with plugin broker + */ + $this->_plugins + ->setRequest($this->_request) + ->setResponse($this->_response); + + /** + * Initialize router + */ + $router = $this->getRouter(); + $router->setParams($this->getParams()); + + /** + * Initialize dispatcher + */ + $dispatcher = $this->getDispatcher(); + $dispatcher->setParams($this->getParams()) + ->setResponse($this->_response); + + // Begin dispatch + try { + /** + * Route request to controller/action, if a router is provided + */ + + /** + * Notify plugins of router startup + */ + $this->_plugins->routeStartup($this->_request); + + try { + $router->route($this->_request); + } catch (Exception $e) { + if ($this->throwExceptions()) { + throw $e; + } + + $this->_response->setException($e); + } + + /** + * Notify plugins of router completion + */ + $this->_plugins->routeShutdown($this->_request); + + /** + * Notify plugins of dispatch loop startup + */ + $this->_plugins->dispatchLoopStartup($this->_request); + + /** + * Attempt to dispatch the controller/action. If the $this->_request + * indicates that it needs to be dispatched, move to the next + * action in the request. + */ + do { + $this->_request->setDispatched(true); + + /** + * Notify plugins of dispatch startup + */ + $this->_plugins->preDispatch($this->_request); + + /** + * Skip requested action if preDispatch() has reset it + */ + if (!$this->_request->isDispatched()) { + continue; + } + + /** + * Dispatch request + */ + try { + $dispatcher->dispatch($this->_request, $this->_response); + } catch (Exception $e) { + if ($this->throwExceptions()) { + throw $e; + } + $this->_response->setException($e); + } + + /** + * Notify plugins of dispatch completion + */ + $this->_plugins->postDispatch($this->_request); + } while (!$this->_request->isDispatched()); + } catch (Exception $e) { + if ($this->throwExceptions()) { + throw $e; + } + + $this->_response->setException($e); + } + + /** + * Notify plugins of dispatch loop completion + */ + try { + $this->_plugins->dispatchLoopShutdown(); + } catch (Exception $e) { + if ($this->throwExceptions()) { + throw $e; + } + + $this->_response->setException($e); + } + + if ($this->returnResponse()) { + return $this->_response; + } + + $this->_response->sendResponse(); + } +} diff --git a/library/Zend/Controller/Plugin/Abstract.php b/library/Zend/Controller/Plugin/Abstract.php new file mode 100644 index 000000000..007d2f630 --- /dev/null +++ b/library/Zend/Controller/Plugin/Abstract.php @@ -0,0 +1,151 @@ +_request = $request; + return $this; + } + + /** + * Get request object + * + * @return Zend_Controller_Request_Abstract $request + */ + public function getRequest() + { + return $this->_request; + } + + /** + * Set response object + * + * @param Zend_Controller_Response_Abstract $response + * @return Zend_Controller_Plugin_Abstract + */ + public function setResponse(Zend_Controller_Response_Abstract $response) + { + $this->_response = $response; + return $this; + } + + /** + * Get response object + * + * @return Zend_Controller_Response_Abstract $response + */ + public function getResponse() + { + return $this->_response; + } + + /** + * Called before Zend_Controller_Front begins evaluating the + * request against its routes. + * + * @param Zend_Controller_Request_Abstract $request + * @return void + */ + public function routeStartup(Zend_Controller_Request_Abstract $request) + {} + + /** + * Called after Zend_Controller_Router exits. + * + * Called after Zend_Controller_Front exits from the router. + * + * @param Zend_Controller_Request_Abstract $request + * @return void + */ + public function routeShutdown(Zend_Controller_Request_Abstract $request) + {} + + /** + * Called before Zend_Controller_Front enters its dispatch loop. + * + * @param Zend_Controller_Request_Abstract $request + * @return void + */ + public function dispatchLoopStartup(Zend_Controller_Request_Abstract $request) + {} + + /** + * Called before an action is dispatched by Zend_Controller_Dispatcher. + * + * This callback allows for proxy or filter behavior. By altering the + * request and resetting its dispatched flag (via + * {@link Zend_Controller_Request_Abstract::setDispatched() setDispatched(false)}), + * the current action may be skipped. + * + * @param Zend_Controller_Request_Abstract $request + * @return void + */ + public function preDispatch(Zend_Controller_Request_Abstract $request) + {} + + /** + * Called after an action is dispatched by Zend_Controller_Dispatcher. + * + * This callback allows for proxy or filter behavior. By altering the + * request and resetting its dispatched flag (via + * {@link Zend_Controller_Request_Abstract::setDispatched() setDispatched(false)}), + * a new action may be specified for dispatching. + * + * @param Zend_Controller_Request_Abstract $request + * @return void + */ + public function postDispatch(Zend_Controller_Request_Abstract $request) + {} + + /** + * Called before Zend_Controller_Front exits its dispatch loop. + * + * @return void + */ + public function dispatchLoopShutdown() + {} +} diff --git a/library/Zend/Controller/Plugin/ActionStack.php b/library/Zend/Controller/Plugin/ActionStack.php new file mode 100644 index 000000000..a209d95b8 --- /dev/null +++ b/library/Zend/Controller/Plugin/ActionStack.php @@ -0,0 +1,280 @@ +setRegistry($registry); + + if (null !== $key) { + $this->setRegistryKey($key); + } else { + $key = $this->getRegistryKey(); + } + + $registry[$key] = array(); + } + + /** + * Set registry object + * + * @param Zend_Registry $registry + * @return Zend_Controller_Plugin_ActionStack + */ + public function setRegistry(Zend_Registry $registry) + { + $this->_registry = $registry; + return $this; + } + + /** + * Retrieve registry object + * + * @return Zend_Registry + */ + public function getRegistry() + { + return $this->_registry; + } + + /** + * Retrieve registry key + * + * @return string + */ + public function getRegistryKey() + { + return $this->_registryKey; + } + + /** + * Set registry key + * + * @param string $key + * @return Zend_Controller_Plugin_ActionStack + */ + public function setRegistryKey($key) + { + $this->_registryKey = (string) $key; + return $this; + } + + /** + * Set clearRequestParams flag + * + * @param bool $clearRequestParams + * @return Zend_Controller_Plugin_ActionStack + */ + public function setClearRequestParams($clearRequestParams) + { + $this->_clearRequestParams = (bool) $clearRequestParams; + return $this; + } + + /** + * Retrieve clearRequestParams flag + * + * @return bool + */ + public function getClearRequestParams() + { + return $this->_clearRequestParams; + } + + /** + * Retrieve action stack + * + * @return array + */ + public function getStack() + { + $registry = $this->getRegistry(); + $stack = $registry[$this->getRegistryKey()]; + return $stack; + } + + /** + * Save stack to registry + * + * @param array $stack + * @return Zend_Controller_Plugin_ActionStack + */ + protected function _saveStack(array $stack) + { + $registry = $this->getRegistry(); + $registry[$this->getRegistryKey()] = $stack; + return $this; + } + + /** + * Push an item onto the stack + * + * @param Zend_Controller_Request_Abstract $next + * @return Zend_Controller_Plugin_ActionStack + */ + public function pushStack(Zend_Controller_Request_Abstract $next) + { + $stack = $this->getStack(); + array_push($stack, $next); + return $this->_saveStack($stack); + } + + /** + * Pop an item off the action stack + * + * @return false|Zend_Controller_Request_Abstract + */ + public function popStack() + { + $stack = $this->getStack(); + if (0 == count($stack)) { + return false; + } + + $next = array_pop($stack); + $this->_saveStack($stack); + + if (!$next instanceof Zend_Controller_Request_Abstract) { + require_once 'Zend/Controller/Exception.php'; + throw new Zend_Controller_Exception('ArrayStack should only contain request objects'); + } + $action = $next->getActionName(); + if (empty($action)) { + return $this->popStack($stack); + } + + $request = $this->getRequest(); + $controller = $next->getControllerName(); + if (empty($controller)) { + $next->setControllerName($request->getControllerName()); + } + + $module = $next->getModuleName(); + if (empty($module)) { + $next->setModuleName($request->getModuleName()); + } + + return $next; + } + + /** + * postDispatch() plugin hook -- check for actions in stack, and dispatch if any found + * + * @param Zend_Controller_Request_Abstract $request + * @return void + */ + public function postDispatch(Zend_Controller_Request_Abstract $request) + { + // Don't move on to next request if this is already an attempt to + // forward + if (!$request->isDispatched()) { + return; + } + + $this->setRequest($request); + $stack = $this->getStack(); + if (empty($stack)) { + return; + } + $next = $this->popStack(); + if (!$next) { + return; + } + + $this->forward($next); + } + + /** + * Forward request with next action + * + * @param array $next + * @return void + */ + public function forward(Zend_Controller_Request_Abstract $next) + { + $request = $this->getRequest(); + if ($this->getClearRequestParams()) { + $request->clearParams(); + } + + $request->setModuleName($next->getModuleName()) + ->setControllerName($next->getControllerName()) + ->setActionName($next->getActionName()) + ->setParams($next->getParams()) + ->setDispatched(false); + } +} diff --git a/library/Zend/Controller/Plugin/Broker.php b/library/Zend/Controller/Plugin/Broker.php new file mode 100644 index 000000000..9c2f1aaa3 --- /dev/null +++ b/library/Zend/Controller/Plugin/Broker.php @@ -0,0 +1,363 @@ +_plugins, true)) { + require_once 'Zend/Controller/Exception.php'; + throw new Zend_Controller_Exception('Plugin already registered'); + } + + $stackIndex = (int) $stackIndex; + + if ($stackIndex) { + if (isset($this->_plugins[$stackIndex])) { + require_once 'Zend/Controller/Exception.php'; + throw new Zend_Controller_Exception('Plugin with stackIndex "' . $stackIndex . '" already registered'); + } + $this->_plugins[$stackIndex] = $plugin; + } else { + $stackIndex = count($this->_plugins); + while (isset($this->_plugins[$stackIndex])) { + ++$stackIndex; + } + $this->_plugins[$stackIndex] = $plugin; + } + + $request = $this->getRequest(); + if ($request) { + $this->_plugins[$stackIndex]->setRequest($request); + } + $response = $this->getResponse(); + if ($response) { + $this->_plugins[$stackIndex]->setResponse($response); + } + + ksort($this->_plugins); + + return $this; + } + + /** + * Unregister a plugin. + * + * @param string|Zend_Controller_Plugin_Abstract $plugin Plugin object or class name + * @return Zend_Controller_Plugin_Broker + */ + public function unregisterPlugin($plugin) + { + if ($plugin instanceof Zend_Controller_Plugin_Abstract) { + // Given a plugin object, find it in the array + $key = array_search($plugin, $this->_plugins, true); + if (false === $key) { + require_once 'Zend/Controller/Exception.php'; + throw new Zend_Controller_Exception('Plugin never registered.'); + } + unset($this->_plugins[$key]); + } elseif (is_string($plugin)) { + // Given a plugin class, find all plugins of that class and unset them + foreach ($this->_plugins as $key => $_plugin) { + $type = get_class($_plugin); + if ($plugin == $type) { + unset($this->_plugins[$key]); + } + } + } + return $this; + } + + /** + * Is a plugin of a particular class registered? + * + * @param string $class + * @return bool + */ + public function hasPlugin($class) + { + foreach ($this->_plugins as $plugin) { + $type = get_class($plugin); + if ($class == $type) { + return true; + } + } + + return false; + } + + /** + * Retrieve a plugin or plugins by class + * + * @param string $class Class name of plugin(s) desired + * @return false|Zend_Controller_Plugin_Abstract|array Returns false if none found, plugin if only one found, and array of plugins if multiple plugins of same class found + */ + public function getPlugin($class) + { + $found = array(); + foreach ($this->_plugins as $plugin) { + $type = get_class($plugin); + if ($class == $type) { + $found[] = $plugin; + } + } + + switch (count($found)) { + case 0: + return false; + case 1: + return $found[0]; + default: + return $found; + } + } + + /** + * Retrieve all plugins + * + * @return array + */ + public function getPlugins() + { + return $this->_plugins; + } + + /** + * Set request object, and register with each plugin + * + * @param Zend_Controller_Request_Abstract $request + * @return Zend_Controller_Plugin_Broker + */ + public function setRequest(Zend_Controller_Request_Abstract $request) + { + $this->_request = $request; + + foreach ($this->_plugins as $plugin) { + $plugin->setRequest($request); + } + + return $this; + } + + /** + * Get request object + * + * @return Zend_Controller_Request_Abstract $request + */ + public function getRequest() + { + return $this->_request; + } + + /** + * Set response object + * + * @param Zend_Controller_Response_Abstract $response + * @return Zend_Controller_Plugin_Broker + */ + public function setResponse(Zend_Controller_Response_Abstract $response) + { + $this->_response = $response; + + foreach ($this->_plugins as $plugin) { + $plugin->setResponse($response); + } + + + return $this; + } + + /** + * Get response object + * + * @return Zend_Controller_Response_Abstract $response + */ + public function getResponse() + { + return $this->_response; + } + + + /** + * Called before Zend_Controller_Front begins evaluating the + * request against its routes. + * + * @param Zend_Controller_Request_Abstract $request + * @return void + */ + public function routeStartup(Zend_Controller_Request_Abstract $request) + { + foreach ($this->_plugins as $plugin) { + try { + $plugin->routeStartup($request); + } catch (Exception $e) { + if (Zend_Controller_Front::getInstance()->throwExceptions()) { + throw $e; + } else { + $this->getResponse()->setException($e); + } + } + } + } + + + /** + * Called before Zend_Controller_Front exits its iterations over + * the route set. + * + * @param Zend_Controller_Request_Abstract $request + * @return void + */ + public function routeShutdown(Zend_Controller_Request_Abstract $request) + { + foreach ($this->_plugins as $plugin) { + try { + $plugin->routeShutdown($request); + } catch (Exception $e) { + if (Zend_Controller_Front::getInstance()->throwExceptions()) { + throw $e; + } else { + $this->getResponse()->setException($e); + } + } + } + } + + + /** + * Called before Zend_Controller_Front enters its dispatch loop. + * + * During the dispatch loop, Zend_Controller_Front keeps a + * Zend_Controller_Request_Abstract object, and uses + * Zend_Controller_Dispatcher to dispatch the + * Zend_Controller_Request_Abstract object to controllers/actions. + * + * @param Zend_Controller_Request_Abstract $request + * @return void + */ + public function dispatchLoopStartup(Zend_Controller_Request_Abstract $request) + { + foreach ($this->_plugins as $plugin) { + try { + $plugin->dispatchLoopStartup($request); + } catch (Exception $e) { + if (Zend_Controller_Front::getInstance()->throwExceptions()) { + throw $e; + } else { + $this->getResponse()->setException($e); + } + } + } + } + + + /** + * Called before an action is dispatched by Zend_Controller_Dispatcher. + * + * @param Zend_Controller_Request_Abstract $request + * @return void + */ + public function preDispatch(Zend_Controller_Request_Abstract $request) + { + foreach ($this->_plugins as $plugin) { + try { + $plugin->preDispatch($request); + } catch (Exception $e) { + if (Zend_Controller_Front::getInstance()->throwExceptions()) { + throw $e; + } else { + $this->getResponse()->setException($e); + } + } + } + } + + + /** + * Called after an action is dispatched by Zend_Controller_Dispatcher. + * + * @param Zend_Controller_Request_Abstract $request + * @return void + */ + public function postDispatch(Zend_Controller_Request_Abstract $request) + { + foreach ($this->_plugins as $plugin) { + try { + $plugin->postDispatch($request); + } catch (Exception $e) { + if (Zend_Controller_Front::getInstance()->throwExceptions()) { + throw $e; + } else { + $this->getResponse()->setException($e); + } + } + } + } + + + /** + * Called before Zend_Controller_Front exits its dispatch loop. + * + * @param Zend_Controller_Request_Abstract $request + * @return void + */ + public function dispatchLoopShutdown() + { + foreach ($this->_plugins as $plugin) { + try { + $plugin->dispatchLoopShutdown(); + } catch (Exception $e) { + if (Zend_Controller_Front::getInstance()->throwExceptions()) { + throw $e; + } else { + $this->getResponse()->setException($e); + } + } + } + } +} diff --git a/library/Zend/Controller/Plugin/ErrorHandler.php b/library/Zend/Controller/Plugin/ErrorHandler.php new file mode 100644 index 000000000..1e70e66ab --- /dev/null +++ b/library/Zend/Controller/Plugin/ErrorHandler.php @@ -0,0 +1,289 @@ +setErrorHandler($options); + } + + /** + * setErrorHandler() - setup the error handling options + * + * @param array $options + * @return Zend_Controller_Plugin_ErrorHandler + */ + public function setErrorHandler(Array $options = array()) + { + if (isset($options['module'])) { + $this->setErrorHandlerModule($options['module']); + } + if (isset($options['controller'])) { + $this->setErrorHandlerController($options['controller']); + } + if (isset($options['action'])) { + $this->setErrorHandlerAction($options['action']); + } + return $this; + } + + /** + * Set the module name for the error handler + * + * @param string $module + * @return Zend_Controller_Plugin_ErrorHandler + */ + public function setErrorHandlerModule($module) + { + $this->_errorModule = (string) $module; + return $this; + } + + /** + * Retrieve the current error handler module + * + * @return string + */ + public function getErrorHandlerModule() + { + if (null === $this->_errorModule) { + $this->_errorModule = Zend_Controller_Front::getInstance()->getDispatcher()->getDefaultModule(); + } + return $this->_errorModule; + } + + /** + * Set the controller name for the error handler + * + * @param string $controller + * @return Zend_Controller_Plugin_ErrorHandler + */ + public function setErrorHandlerController($controller) + { + $this->_errorController = (string) $controller; + return $this; + } + + /** + * Retrieve the current error handler controller + * + * @return string + */ + public function getErrorHandlerController() + { + return $this->_errorController; + } + + /** + * Set the action name for the error handler + * + * @param string $action + * @return Zend_Controller_Plugin_ErrorHandler + */ + public function setErrorHandlerAction($action) + { + $this->_errorAction = (string) $action; + return $this; + } + + /** + * Retrieve the current error handler action + * + * @return string + */ + public function getErrorHandlerAction() + { + return $this->_errorAction; + } + + /** + * Route shutdown hook -- Ccheck for router exceptions + * + * @param Zend_Controller_Request_Abstract $request + */ + public function routeShutdown(Zend_Controller_Request_Abstract $request) + { + $this->_handleError($request); + } + + /** + * Post dispatch hook -- check for exceptions and dispatch error handler if + * necessary + * + * @param Zend_Controller_Request_Abstract $request + */ + public function postDispatch(Zend_Controller_Request_Abstract $request) + { + $this->_handleError($request); + } + + /** + * Handle errors and exceptions + * + * If the 'noErrorHandler' front controller flag has been set, + * returns early. + * + * @param Zend_Controller_Request_Abstract $request + * @return void + */ + protected function _handleError(Zend_Controller_Request_Abstract $request) + { + $frontController = Zend_Controller_Front::getInstance(); + if ($frontController->getParam('noErrorHandler')) { + return; + } + + $response = $this->getResponse(); + + if ($this->_isInsideErrorHandlerLoop) { + $exceptions = $response->getException(); + if (count($exceptions) > $this->_exceptionCountAtFirstEncounter) { + // Exception thrown by error handler; tell the front controller to throw it + $frontController->throwExceptions(true); + throw array_pop($exceptions); + } + } + + // check for an exception AND allow the error handler controller the option to forward + if (($response->isException()) && (!$this->_isInsideErrorHandlerLoop)) { + $this->_isInsideErrorHandlerLoop = true; + + // Get exception information + $error = new ArrayObject(array(), ArrayObject::ARRAY_AS_PROPS); + $exceptions = $response->getException(); + $exception = $exceptions[0]; + $exceptionType = get_class($exception); + $error->exception = $exception; + switch ($exceptionType) { + case 'Zend_Controller_Router_Exception': + if (404 == $exception->getCode()) { + $error->type = self::EXCEPTION_NO_ROUTE; + } else { + $error->type = self::EXCEPTION_OTHER; + } + break; + case 'Zend_Controller_Dispatcher_Exception': + $error->type = self::EXCEPTION_NO_CONTROLLER; + break; + case 'Zend_Controller_Action_Exception': + if (404 == $exception->getCode()) { + $error->type = self::EXCEPTION_NO_ACTION; + } else { + $error->type = self::EXCEPTION_OTHER; + } + break; + default: + $error->type = self::EXCEPTION_OTHER; + break; + } + + // Keep a copy of the original request + $error->request = clone $request; + + // get a count of the number of exceptions encountered + $this->_exceptionCountAtFirstEncounter = count($exceptions); + + // Forward to the error handler + $request->setParam('error_handler', $error) + ->setModuleName($this->getErrorHandlerModule()) + ->setControllerName($this->getErrorHandlerController()) + ->setActionName($this->getErrorHandlerAction()) + ->setDispatched(false); + } + } +} diff --git a/library/Zend/Controller/Plugin/PutHandler.php b/library/Zend/Controller/Plugin/PutHandler.php new file mode 100644 index 000000000..84eb49c07 --- /dev/null +++ b/library/Zend/Controller/Plugin/PutHandler.php @@ -0,0 +1,60 @@ +_request->isPut()) { + $putParams = array(); + parse_str($this->_request->getRawBody(), $putParams); + $request->setParams($putParams); + } + } +} diff --git a/library/Zend/Controller/Request/Abstract.php b/library/Zend/Controller/Request/Abstract.php new file mode 100644 index 000000000..1d18e0c5e --- /dev/null +++ b/library/Zend/Controller/Request/Abstract.php @@ -0,0 +1,356 @@ +_module) { + $this->_module = $this->getParam($this->getModuleKey()); + } + + return $this->_module; + } + + /** + * Set the module name to use + * + * @param string $value + * @return Zend_Controller_Request_Abstract + */ + public function setModuleName($value) + { + $this->_module = $value; + return $this; + } + + /** + * Retrieve the controller name + * + * @return string + */ + public function getControllerName() + { + if (null === $this->_controller) { + $this->_controller = $this->getParam($this->getControllerKey()); + } + + return $this->_controller; + } + + /** + * Set the controller name to use + * + * @param string $value + * @return Zend_Controller_Request_Abstract + */ + public function setControllerName($value) + { + $this->_controller = $value; + return $this; + } + + /** + * Retrieve the action name + * + * @return string + */ + public function getActionName() + { + if (null === $this->_action) { + $this->_action = $this->getParam($this->getActionKey()); + } + + return $this->_action; + } + + /** + * Set the action name + * + * @param string $value + * @return Zend_Controller_Request_Abstract + */ + public function setActionName($value) + { + $this->_action = $value; + /** + * @see ZF-3465 + */ + if (null === $value) { + $this->setParam($this->getActionKey(), $value); + } + return $this; + } + + /** + * Retrieve the module key + * + * @return string + */ + public function getModuleKey() + { + return $this->_moduleKey; + } + + /** + * Set the module key + * + * @param string $key + * @return Zend_Controller_Request_Abstract + */ + public function setModuleKey($key) + { + $this->_moduleKey = (string) $key; + return $this; + } + + /** + * Retrieve the controller key + * + * @return string + */ + public function getControllerKey() + { + return $this->_controllerKey; + } + + /** + * Set the controller key + * + * @param string $key + * @return Zend_Controller_Request_Abstract + */ + public function setControllerKey($key) + { + $this->_controllerKey = (string) $key; + return $this; + } + + /** + * Retrieve the action key + * + * @return string + */ + public function getActionKey() + { + return $this->_actionKey; + } + + /** + * Set the action key + * + * @param string $key + * @return Zend_Controller_Request_Abstract + */ + public function setActionKey($key) + { + $this->_actionKey = (string) $key; + return $this; + } + + /** + * Get an action parameter + * + * @param string $key + * @param mixed $default Default value to use if key not found + * @return mixed + */ + public function getParam($key, $default = null) + { + $key = (string) $key; + if (isset($this->_params[$key])) { + return $this->_params[$key]; + } + + return $default; + } + + /** + * Retrieve only user params (i.e, any param specific to the object and not the environment) + * + * @return array + */ + public function getUserParams() + { + return $this->_params; + } + + /** + * Retrieve a single user param (i.e, a param specific to the object and not the environment) + * + * @param string $key + * @param string $default Default value to use if key not found + * @return mixed + */ + public function getUserParam($key, $default = null) + { + if (isset($this->_params[$key])) { + return $this->_params[$key]; + } + + return $default; + } + + /** + * Set an action parameter + * + * A $value of null will unset the $key if it exists + * + * @param string $key + * @param mixed $value + * @return Zend_Controller_Request_Abstract + */ + public function setParam($key, $value) + { + $key = (string) $key; + + if ((null === $value) && isset($this->_params[$key])) { + unset($this->_params[$key]); + } elseif (null !== $value) { + $this->_params[$key] = $value; + } + + return $this; + } + + /** + * Get all action parameters + * + * @return array + */ + public function getParams() + { + return $this->_params; + } + + /** + * Set action parameters en masse; does not overwrite + * + * Null values will unset the associated key. + * + * @param array $array + * @return Zend_Controller_Request_Abstract + */ + public function setParams(array $array) + { + $this->_params = $this->_params + (array) $array; + + foreach ($this->_params as $key => $value) { + if (null === $value) { + unset($this->_params[$key]); + } + } + + return $this; + } + + /** + * Unset all user parameters + * + * @return Zend_Controller_Request_Abstract + */ + public function clearParams() + { + $this->_params = array(); + return $this; + } + + /** + * Set flag indicating whether or not request has been dispatched + * + * @param boolean $flag + * @return Zend_Controller_Request_Abstract + */ + public function setDispatched($flag = true) + { + $this->_dispatched = $flag ? true : false; + return $this; + } + + /** + * Determine if the request has been dispatched + * + * @return boolean + */ + public function isDispatched() + { + return $this->_dispatched; + } +} diff --git a/library/Zend/Controller/Request/Apache404.php b/library/Zend/Controller/Request/Apache404.php new file mode 100644 index 000000000..3039860d1 --- /dev/null +++ b/library/Zend/Controller/Request/Apache404.php @@ -0,0 +1,82 @@ +_requestUri = $requestUri; + return $this; + } +} diff --git a/library/Zend/Controller/Request/Exception.php b/library/Zend/Controller/Request/Exception.php new file mode 100644 index 000000000..838fa58c5 --- /dev/null +++ b/library/Zend/Controller/Request/Exception.php @@ -0,0 +1,37 @@ +valid()) { + $path = $uri->getPath(); + $query = $uri->getQuery(); + if (!empty($query)) { + $path .= '?' . $query; + } + + $this->setRequestUri($path); + } else { + require_once 'Zend/Controller/Request/Exception.php'; + throw new Zend_Controller_Request_Exception('Invalid URI provided to constructor'); + } + } else { + $this->setRequestUri(); + } + } + + /** + * Access values contained in the superglobals as public members + * Order of precedence: 1. GET, 2. POST, 3. COOKIE, 4. SERVER, 5. ENV + * + * @see http://msdn.microsoft.com/en-us/library/system.web.httprequest.item.aspx + * @param string $key + * @return mixed + */ + public function __get($key) + { + switch (true) { + case isset($this->_params[$key]): + return $this->_params[$key]; + case isset($_GET[$key]): + return $_GET[$key]; + case isset($_POST[$key]): + return $_POST[$key]; + case isset($_COOKIE[$key]): + return $_COOKIE[$key]; + case ($key == 'REQUEST_URI'): + return $this->getRequestUri(); + case ($key == 'PATH_INFO'): + return $this->getPathInfo(); + case isset($_SERVER[$key]): + return $_SERVER[$key]; + case isset($_ENV[$key]): + return $_ENV[$key]; + default: + return null; + } + } + + /** + * Alias to __get + * + * @param string $key + * @return mixed + */ + public function get($key) + { + return $this->__get($key); + } + + /** + * Set values + * + * In order to follow {@link __get()}, which operates on a number of + * superglobals, setting values through overloading is not allowed and will + * raise an exception. Use setParam() instead. + * + * @param string $key + * @param mixed $value + * @return void + * @throws Zend_Controller_Request_Exception + */ + public function __set($key, $value) + { + require_once 'Zend/Controller/Request/Exception.php'; + throw new Zend_Controller_Request_Exception('Setting values in superglobals not allowed; please use setParam()'); + } + + /** + * Alias to __set() + * + * @param string $key + * @param mixed $value + * @return void + */ + public function set($key, $value) + { + return $this->__set($key, $value); + } + + /** + * Check to see if a property is set + * + * @param string $key + * @return boolean + */ + public function __isset($key) + { + switch (true) { + case isset($this->_params[$key]): + return true; + case isset($_GET[$key]): + return true; + case isset($_POST[$key]): + return true; + case isset($_COOKIE[$key]): + return true; + case isset($_SERVER[$key]): + return true; + case isset($_ENV[$key]): + return true; + default: + return false; + } + } + + /** + * Alias to __isset() + * + * @param string $key + * @return boolean + */ + public function has($key) + { + return $this->__isset($key); + } + + /** + * Set GET values + * + * @param string|array $spec + * @param null|mixed $value + * @return Zend_Controller_Request_Http + */ + public function setQuery($spec, $value = null) + { + if ((null === $value) && !is_array($spec)) { + require_once 'Zend/Controller/Exception.php'; + throw new Zend_Controller_Exception('Invalid value passed to setQuery(); must be either array of values or key/value pair'); + } + if ((null === $value) && is_array($spec)) { + foreach ($spec as $key => $value) { + $this->setQuery($key, $value); + } + return $this; + } + $_GET[(string) $spec] = $value; + return $this; + } + + /** + * Retrieve a member of the $_GET superglobal + * + * If no $key is passed, returns the entire $_GET array. + * + * @todo How to retrieve from nested arrays + * @param string $key + * @param mixed $default Default value to use if key not found + * @return mixed Returns null if key does not exist + */ + public function getQuery($key = null, $default = null) + { + if (null === $key) { + return $_GET; + } + + return (isset($_GET[$key])) ? $_GET[$key] : $default; + } + + /** + * Set POST values + * + * @param string|array $spec + * @param null|mixed $value + * @return Zend_Controller_Request_Http + */ + public function setPost($spec, $value = null) + { + if ((null === $value) && !is_array($spec)) { + require_once 'Zend/Controller/Exception.php'; + throw new Zend_Controller_Exception('Invalid value passed to setPost(); must be either array of values or key/value pair'); + } + if ((null === $value) && is_array($spec)) { + foreach ($spec as $key => $value) { + $this->setPost($key, $value); + } + return $this; + } + $_POST[(string) $spec] = $value; + return $this; + } + + /** + * Retrieve a member of the $_POST superglobal + * + * If no $key is passed, returns the entire $_POST array. + * + * @todo How to retrieve from nested arrays + * @param string $key + * @param mixed $default Default value to use if key not found + * @return mixed Returns null if key does not exist + */ + public function getPost($key = null, $default = null) + { + if (null === $key) { + return $_POST; + } + + return (isset($_POST[$key])) ? $_POST[$key] : $default; + } + + /** + * Retrieve a member of the $_COOKIE superglobal + * + * If no $key is passed, returns the entire $_COOKIE array. + * + * @todo How to retrieve from nested arrays + * @param string $key + * @param mixed $default Default value to use if key not found + * @return mixed Returns null if key does not exist + */ + public function getCookie($key = null, $default = null) + { + if (null === $key) { + return $_COOKIE; + } + + return (isset($_COOKIE[$key])) ? $_COOKIE[$key] : $default; + } + + /** + * Retrieve a member of the $_SERVER superglobal + * + * If no $key is passed, returns the entire $_SERVER array. + * + * @param string $key + * @param mixed $default Default value to use if key not found + * @return mixed Returns null if key does not exist + */ + public function getServer($key = null, $default = null) + { + if (null === $key) { + return $_SERVER; + } + + return (isset($_SERVER[$key])) ? $_SERVER[$key] : $default; + } + + /** + * Retrieve a member of the $_ENV superglobal + * + * If no $key is passed, returns the entire $_ENV array. + * + * @param string $key + * @param mixed $default Default value to use if key not found + * @return mixed Returns null if key does not exist + */ + public function getEnv($key = null, $default = null) + { + if (null === $key) { + return $_ENV; + } + + return (isset($_ENV[$key])) ? $_ENV[$key] : $default; + } + + /** + * Set the REQUEST_URI on which the instance operates + * + * If no request URI is passed, uses the value in $_SERVER['REQUEST_URI'], + * $_SERVER['HTTP_X_REWRITE_URL'], or $_SERVER['ORIG_PATH_INFO'] + $_SERVER['QUERY_STRING']. + * + * @param string $requestUri + * @return Zend_Controller_Request_Http + */ + public function setRequestUri($requestUri = null) + { + if ($requestUri === null) { + if (isset($_SERVER['HTTP_X_REWRITE_URL'])) { // check this first so IIS will catch + $requestUri = $_SERVER['HTTP_X_REWRITE_URL']; + } elseif ( + // IIS7 with URL Rewrite: make sure we get the unencoded url (double slash problem) + isset($_SERVER['IIS_WasUrlRewritten']) + && $_SERVER['IIS_WasUrlRewritten'] == '1' + && isset($_SERVER['UNENCODED_URL']) + && $_SERVER['UNENCODED_URL'] != '' + ) { + $requestUri = $_SERVER['UNENCODED_URL']; + } elseif (isset($_SERVER['REQUEST_URI'])) { + $requestUri = $_SERVER['REQUEST_URI']; + // Http proxy reqs setup request uri with scheme and host [and port] + the url path, only use url path + $schemeAndHttpHost = $this->getScheme() . '://' . $this->getHttpHost(); + if (strpos($requestUri, $schemeAndHttpHost) === 0) { + $requestUri = substr($requestUri, strlen($schemeAndHttpHost)); + } + } elseif (isset($_SERVER['ORIG_PATH_INFO'])) { // IIS 5.0, PHP as CGI + $requestUri = $_SERVER['ORIG_PATH_INFO']; + if (!empty($_SERVER['QUERY_STRING'])) { + $requestUri .= '?' . $_SERVER['QUERY_STRING']; + } + } else { + return $this; + } + } elseif (!is_string($requestUri)) { + return $this; + } else { + // Set GET items, if available + if (false !== ($pos = strpos($requestUri, '?'))) { + // Get key => value pairs and set $_GET + $query = substr($requestUri, $pos + 1); + parse_str($query, $vars); + $this->setQuery($vars); + } + } + + $this->_requestUri = $requestUri; + return $this; + } + + /** + * Returns the REQUEST_URI taking into account + * platform differences between Apache and IIS + * + * @return string + */ + public function getRequestUri() + { + if (empty($this->_requestUri)) { + $this->setRequestUri(); + } + + return $this->_requestUri; + } + + /** + * Set the base URL of the request; i.e., the segment leading to the script name + * + * E.g.: + * - /admin + * - /myapp + * - /subdir/index.php + * + * Do not use the full URI when providing the base. The following are + * examples of what not to use: + * - http://example.com/admin (should be just /admin) + * - http://example.com/subdir/index.php (should be just /subdir/index.php) + * + * If no $baseUrl is provided, attempts to determine the base URL from the + * environment, using SCRIPT_FILENAME, SCRIPT_NAME, PHP_SELF, and + * ORIG_SCRIPT_NAME in its determination. + * + * @param mixed $baseUrl + * @return Zend_Controller_Request_Http + */ + public function setBaseUrl($baseUrl = null) + { + if ((null !== $baseUrl) && !is_string($baseUrl)) { + return $this; + } + + if ($baseUrl === null) { + $filename = (isset($_SERVER['SCRIPT_FILENAME'])) ? basename($_SERVER['SCRIPT_FILENAME']) : ''; + + if (isset($_SERVER['SCRIPT_NAME']) && basename($_SERVER['SCRIPT_NAME']) === $filename) { + $baseUrl = $_SERVER['SCRIPT_NAME']; + } elseif (isset($_SERVER['PHP_SELF']) && basename($_SERVER['PHP_SELF']) === $filename) { + $baseUrl = $_SERVER['PHP_SELF']; + } elseif (isset($_SERVER['ORIG_SCRIPT_NAME']) && basename($_SERVER['ORIG_SCRIPT_NAME']) === $filename) { + $baseUrl = $_SERVER['ORIG_SCRIPT_NAME']; // 1and1 shared hosting compatibility + } else { + // Backtrack up the script_filename to find the portion matching + // php_self + $path = isset($_SERVER['PHP_SELF']) ? $_SERVER['PHP_SELF'] : ''; + $file = isset($_SERVER['SCRIPT_FILENAME']) ? $_SERVER['SCRIPT_FILENAME'] : ''; + $segs = explode('/', trim($file, '/')); + $segs = array_reverse($segs); + $index = 0; + $last = count($segs); + $baseUrl = ''; + do { + $seg = $segs[$index]; + $baseUrl = '/' . $seg . $baseUrl; + ++$index; + } while (($last > $index) && (false !== ($pos = strpos($path, $baseUrl))) && (0 != $pos)); + } + + // Does the baseUrl have anything in common with the request_uri? + $requestUri = $this->getRequestUri(); + + if (0 === strpos($requestUri, $baseUrl)) { + // full $baseUrl matches + $this->_baseUrl = $baseUrl; + return $this; + } + + if (0 === strpos($requestUri, dirname($baseUrl))) { + // directory portion of $baseUrl matches + $this->_baseUrl = rtrim(dirname($baseUrl), '/'); + return $this; + } + + $truncatedRequestUri = $requestUri; + if (($pos = strpos($requestUri, '?')) !== false) { + $truncatedRequestUri = substr($requestUri, 0, $pos); + } + + $basename = basename($baseUrl); + if (empty($basename) || !strpos($truncatedRequestUri, $basename)) { + // no match whatsoever; set it blank + $this->_baseUrl = ''; + return $this; + } + + // If using mod_rewrite or ISAPI_Rewrite strip the script filename + // out of baseUrl. $pos !== 0 makes sure it is not matching a value + // from PATH_INFO or QUERY_STRING + if ((strlen($requestUri) >= strlen($baseUrl)) + && ((false !== ($pos = strpos($requestUri, $baseUrl))) && ($pos !== 0))) + { + $baseUrl = substr($requestUri, 0, $pos + strlen($baseUrl)); + } + } + + $this->_baseUrl = rtrim($baseUrl, '/'); + return $this; + } + + /** + * Everything in REQUEST_URI before PATH_INFO + *
    + * + * @return string + */ + public function getBaseUrl() + { + if (null === $this->_baseUrl) { + $this->setBaseUrl(); + } + + return $this->_baseUrl; + } + + /** + * Set the base path for the URL + * + * @param string|null $basePath + * @return Zend_Controller_Request_Http + */ + public function setBasePath($basePath = null) + { + if ($basePath === null) { + $filename = (isset($_SERVER['SCRIPT_FILENAME'])) + ? basename($_SERVER['SCRIPT_FILENAME']) + : ''; + + $baseUrl = $this->getBaseUrl(); + if (empty($baseUrl)) { + $this->_basePath = ''; + return $this; + } + + if (basename($baseUrl) === $filename) { + $basePath = dirname($baseUrl); + } else { + $basePath = $baseUrl; + } + } + + if (substr(PHP_OS, 0, 3) === 'WIN') { + $basePath = str_replace('\\', '/', $basePath); + } + + $this->_basePath = rtrim($basePath, '/'); + return $this; + } + + /** + * Everything in REQUEST_URI before PATH_INFO not including the filename + * + * + * @return string + */ + public function getBasePath() + { + if (null === $this->_basePath) { + $this->setBasePath(); + } + + return $this->_basePath; + } + + /** + * Set the PATH_INFO string + * + * @param string|null $pathInfo + * @return Zend_Controller_Request_Http + */ + public function setPathInfo($pathInfo = null) + { + if ($pathInfo === null) { + $baseUrl = $this->getBaseUrl(); + + if (null === ($requestUri = $this->getRequestUri())) { + return $this; + } + + // Remove the query string from REQUEST_URI + if ($pos = strpos($requestUri, '?')) { + $requestUri = substr($requestUri, 0, $pos); + } + + if (null !== $baseUrl + && ((!empty($baseUrl) && 0 === strpos($requestUri, $baseUrl)) + || empty($baseUrl)) + && false === ($pathInfo = substr($requestUri, strlen($baseUrl))) + ){ + // If substr() returns false then PATH_INFO is set to an empty string + $pathInfo = ''; + } elseif (null === $baseUrl + || (!empty($baseUrl) && false === strpos($requestUri, $baseUrl)) + ) { + $pathInfo = $requestUri; + } + } + + $this->_pathInfo = (string) $pathInfo; + return $this; + } + + /** + * Returns everything between the BaseUrl and QueryString. + * This value is calculated instead of reading PATH_INFO + * directly from $_SERVER due to cross-platform differences. + * + * @return string + */ + public function getPathInfo() + { + if (empty($this->_pathInfo)) { + $this->setPathInfo(); + } + + return $this->_pathInfo; + } + + /** + * Set allowed parameter sources + * + * Can be empty array, or contain one or more of '_GET' or '_POST'. + * + * @param array $paramSoures + * @return Zend_Controller_Request_Http + */ + public function setParamSources(array $paramSources = array()) + { + $this->_paramSources = $paramSources; + return $this; + } + + /** + * Get list of allowed parameter sources + * + * @return array + */ + public function getParamSources() + { + return $this->_paramSources; + } + + /** + * Set a userland parameter + * + * Uses $key to set a userland parameter. If $key is an alias, the actual + * key will be retrieved and used to set the parameter. + * + * @param mixed $key + * @param mixed $value + * @return Zend_Controller_Request_Http + */ + public function setParam($key, $value) + { + $key = (null !== ($alias = $this->getAlias($key))) ? $alias : $key; + parent::setParam($key, $value); + return $this; + } + + /** + * Retrieve a parameter + * + * Retrieves a parameter from the instance. Priority is in the order of + * userland parameters (see {@link setParam()}), $_GET, $_POST. If a + * parameter matching the $key is not found, null is returned. + * + * If the $key is an alias, the actual key aliased will be used. + * + * @param mixed $key + * @param mixed $default Default value to use if key not found + * @return mixed + */ + public function getParam($key, $default = null) + { + $keyName = (null !== ($alias = $this->getAlias($key))) ? $alias : $key; + + $paramSources = $this->getParamSources(); + if (isset($this->_params[$keyName])) { + return $this->_params[$keyName]; + } elseif (in_array('_GET', $paramSources) && (isset($_GET[$keyName]))) { + return $_GET[$keyName]; + } elseif (in_array('_POST', $paramSources) && (isset($_POST[$keyName]))) { + return $_POST[$keyName]; + } + + return $default; + } + + /** + * Retrieve an array of parameters + * + * Retrieves a merged array of parameters, with precedence of userland + * params (see {@link setParam()}), $_GET, $_POST (i.e., values in the + * userland params will take precedence over all others). + * + * @return array + */ + public function getParams() + { + $return = $this->_params; + $paramSources = $this->getParamSources(); + if (in_array('_GET', $paramSources) + && isset($_GET) + && is_array($_GET) + ) { + $return += $_GET; + } + if (in_array('_POST', $paramSources) + && isset($_POST) + && is_array($_POST) + ) { + $return += $_POST; + } + return $return; + } + + /** + * Set parameters + * + * Set one or more parameters. Parameters are set as userland parameters, + * using the keys specified in the array. + * + * @param array $params + * @return Zend_Controller_Request_Http + */ + public function setParams(array $params) + { + foreach ($params as $key => $value) { + $this->setParam($key, $value); + } + return $this; + } + + /** + * Set a key alias + * + * Set an alias used for key lookups. $name specifies the alias, $target + * specifies the actual key to use. + * + * @param string $name + * @param string $target + * @return Zend_Controller_Request_Http + */ + public function setAlias($name, $target) + { + $this->_aliases[$name] = $target; + return $this; + } + + /** + * Retrieve an alias + * + * Retrieve the actual key represented by the alias $name. + * + * @param string $name + * @return string|null Returns null when no alias exists + */ + public function getAlias($name) + { + if (isset($this->_aliases[$name])) { + return $this->_aliases[$name]; + } + + return null; + } + + /** + * Retrieve the list of all aliases + * + * @return array + */ + public function getAliases() + { + return $this->_aliases; + } + + /** + * Return the method by which the request was made + * + * @return string + */ + public function getMethod() + { + return $this->getServer('REQUEST_METHOD'); + } + + /** + * Was the request made by POST? + * + * @return boolean + */ + public function isPost() + { + if ('POST' == $this->getMethod()) { + return true; + } + + return false; + } + + /** + * Was the request made by GET? + * + * @return boolean + */ + public function isGet() + { + if ('GET' == $this->getMethod()) { + return true; + } + + return false; + } + + /** + * Was the request made by PUT? + * + * @return boolean + */ + public function isPut() + { + if ('PUT' == $this->getMethod()) { + return true; + } + + return false; + } + + /** + * Was the request made by DELETE? + * + * @return boolean + */ + public function isDelete() + { + if ('DELETE' == $this->getMethod()) { + return true; + } + + return false; + } + + /** + * Was the request made by HEAD? + * + * @return boolean + */ + public function isHead() + { + if ('HEAD' == $this->getMethod()) { + return true; + } + + return false; + } + + /** + * Was the request made by OPTIONS? + * + * @return boolean + */ + public function isOptions() + { + if ('OPTIONS' == $this->getMethod()) { + return true; + } + + return false; + } + + /** + * Is the request a Javascript XMLHttpRequest? + * + * Should work with Prototype/Script.aculo.us, possibly others. + * + * @return boolean + */ + public function isXmlHttpRequest() + { + return ($this->getHeader('X_REQUESTED_WITH') == 'XMLHttpRequest'); + } + + /** + * Is this a Flash request? + * + * @return boolean + */ + public function isFlashRequest() + { + $header = strtolower($this->getHeader('USER_AGENT')); + return (strstr($header, ' flash')) ? true : false; + } + + /** + * Is https secure request + * + * @return boolean + */ + public function isSecure() + { + return ($this->getScheme() === self::SCHEME_HTTPS); + } + + /** + * Return the raw body of the request, if present + * + * @return string|false Raw body, or false if not present + */ + public function getRawBody() + { + if (null === $this->_rawBody) { + $body = file_get_contents('php://input'); + + if (strlen(trim($body)) > 0) { + $this->_rawBody = $body; + } else { + $this->_rawBody = false; + } + } + return $this->_rawBody; + } + + /** + * Return the value of the given HTTP header. Pass the header name as the + * plain, HTTP-specified header name. Ex.: Ask for 'Accept' to get the + * Accept header, 'Accept-Encoding' to get the Accept-Encoding header. + * + * @param string $header HTTP header name + * @return string|false HTTP header value, or false if not found + * @throws Zend_Controller_Request_Exception + */ + public function getHeader($header) + { + if (empty($header)) { + require_once 'Zend/Controller/Request/Exception.php'; + throw new Zend_Controller_Request_Exception('An HTTP header name is required'); + } + + // Try to get it from the $_SERVER array first + $temp = 'HTTP_' . strtoupper(str_replace('-', '_', $header)); + if (!empty($_SERVER[$temp])) { + return $_SERVER[$temp]; + } + + // This seems to be the only way to get the Authorization header on + // Apache + if (function_exists('apache_request_headers')) { + $headers = apache_request_headers(); + if (!empty($headers[$header])) { + return $headers[$header]; + } + } + + return false; + } + + /** + * Get the request URI scheme + * + * @return string + */ + public function getScheme() + { + return ($this->getServer('HTTPS') == 'on') ? self::SCHEME_HTTPS : self::SCHEME_HTTP; + } + + /** + * Get the HTTP host. + * + * "Host" ":" host [ ":" port ] ; Section 3.2.2 + * Note the HTTP Host header is not the same as the URI host. + * It includes the port while the URI host doesn't. + * + * @return string + */ + public function getHttpHost() + { + $host = $this->getServer('HTTP_HOST'); + if (!empty($host)) { + return $host; + } + + $scheme = $this->getScheme(); + $name = $this->getServer('SERVER_NAME'); + $port = $this->getServer('SERVER_PORT'); + + if (($scheme == self::SCHEME_HTTP && $port == 80) || ($scheme == self::SCHEME_HTTPS && $port == 443)) { + return $name; + } else { + return $name . ':' . $port; + } + } + + /** + * Get the client's IP addres + * + * @param boolean $checkProxy + * @return string + */ + public function getClientIp($checkProxy = true) + { + if ($checkProxy && $this->getServer('HTTP_CLIENT_IP') != null) { + $ip = $this->getServer('HTTP_CLIENT_IP'); + } else if ($checkProxy && $this->getServer('HTTP_X_FORWARDED_FOR') != null) { + $ip = $this->getServer('HTTP_X_FORWARDED_FOR'); + } else { + $ip = $this->getServer('REMOTE_ADDR'); + } + + return $ip; + } +} diff --git a/library/Zend/Controller/Request/HttpTestCase.php b/library/Zend/Controller/Request/HttpTestCase.php new file mode 100644 index 000000000..90434d842 --- /dev/null +++ b/library/Zend/Controller/Request/HttpTestCase.php @@ -0,0 +1,276 @@ +_rawBody = (string) $content; + return $this; + } + + /** + * Get RAW POST body + * + * @return string|null + */ + public function getRawBody() + { + return $this->_rawBody; + } + + /** + * Clear raw POST body + * + * @return Zend_Controller_Request_HttpTestCase + */ + public function clearRawBody() + { + $this->_rawBody = null; + return $this; + } + + /** + * Set a cookie + * + * @param string $key + * @param mixed $value + * @return Zend_Controller_Request_HttpTestCase + */ + public function setCookie($key, $value) + { + $_COOKIE[(string) $key] = $value; + return $this; + } + + /** + * Set multiple cookies at once + * + * @param array $cookies + * @return void + */ + public function setCookies(array $cookies) + { + foreach ($cookies as $key => $value) { + $_COOKIE[$key] = $value; + } + return $this; + } + + /** + * Clear all cookies + * + * @return Zend_Controller_Request_HttpTestCase + */ + public function clearCookies() + { + $_COOKIE = array(); + return $this; + } + + /** + * Set request method + * + * @param string $type + * @return Zend_Controller_Request_HttpTestCase + */ + public function setMethod($type) + { + $type = strtoupper(trim((string) $type)); + if (!in_array($type, $this->_validMethodTypes)) { + require_once 'Zend/Controller/Exception.php'; + throw new Zend_Controller_Exception('Invalid request method specified'); + } + $this->_method = $type; + return $this; + } + + /** + * Get request method + * + * @return string|null + */ + public function getMethod() + { + return $this->_method; + } + + /** + * Set a request header + * + * @param string $key + * @param string $value + * @return Zend_Controller_Request_HttpTestCase + */ + public function setHeader($key, $value) + { + $key = $this->_normalizeHeaderName($key); + $this->_headers[$key] = (string) $value; + return $this; + } + + /** + * Set request headers + * + * @param array $headers + * @return Zend_Controller_Request_HttpTestCase + */ + public function setHeaders(array $headers) + { + foreach ($headers as $key => $value) { + $this->setHeader($key, $value); + } + return $this; + } + + /** + * Get request header + * + * @param string $header + * @param mixed $default + * @return string|null + */ + public function getHeader($header, $default = null) + { + $header = $this->_normalizeHeaderName($header); + if (array_key_exists($header, $this->_headers)) { + return $this->_headers[$header]; + } + return $default; + } + + /** + * Get all request headers + * + * @return array + */ + public function getHeaders() + { + return $this->_headers; + } + + /** + * Clear request headers + * + * @return Zend_Controller_Request_HttpTestCase + */ + public function clearHeaders() + { + $this->_headers = array(); + return $this; + } + + /** + * Get REQUEST_URI + * + * @return null|string + */ + public function getRequestUri() + { + return $this->_requestUri; + } + + /** + * Normalize a header name for setting and retrieval + * + * @param string $name + * @return string + */ + protected function _normalizeHeaderName($name) + { + $name = strtoupper((string) $name); + $name = str_replace('-', '_', $name); + return $name; + } +} diff --git a/library/Zend/Controller/Request/Simple.php b/library/Zend/Controller/Request/Simple.php new file mode 100644 index 000000000..866f76bdb --- /dev/null +++ b/library/Zend/Controller/Request/Simple.php @@ -0,0 +1,55 @@ +setActionName($action); + } + + if ($controller) { + $this->setControllerName($controller); + } + + if ($module) { + $this->setModuleName($module); + } + + if ($params) { + $this->setParams($params); + } + } + +} diff --git a/library/Zend/Controller/Response/Abstract.php b/library/Zend/Controller/Response/Abstract.php new file mode 100644 index 000000000..df862ba3f --- /dev/null +++ b/library/Zend/Controller/Response/Abstract.php @@ -0,0 +1,794 @@ +canSendHeaders(true); + $name = $this->_normalizeHeader($name); + $value = (string) $value; + + if ($replace) { + foreach ($this->_headers as $key => $header) { + if ($name == $header['name']) { + unset($this->_headers[$key]); + } + } + } + + $this->_headers[] = array( + 'name' => $name, + 'value' => $value, + 'replace' => $replace + ); + + return $this; + } + + /** + * Set redirect URL + * + * Sets Location header and response code. Forces replacement of any prior + * redirects. + * + * @param string $url + * @param int $code + * @return Zend_Controller_Response_Abstract + */ + public function setRedirect($url, $code = 302) + { + $this->canSendHeaders(true); + $this->setHeader('Location', $url, true) + ->setHttpResponseCode($code); + + return $this; + } + + /** + * Is this a redirect? + * + * @return boolean + */ + public function isRedirect() + { + return $this->_isRedirect; + } + + /** + * Return array of headers; see {@link $_headers} for format + * + * @return array + */ + public function getHeaders() + { + return $this->_headers; + } + + /** + * Clear headers + * + * @return Zend_Controller_Response_Abstract + */ + public function clearHeaders() + { + $this->_headers = array(); + + return $this; + } + + /** + * Clears the specified HTTP header + * + * @param string $name + * @return Zend_Controller_Response_Abstract + */ + public function clearHeader($name) + { + if (! count($this->_headers)) { + return $this; + } + + foreach ($this->_headers as $index => $header) { + if ($name == $header['name']) { + unset($this->_headers[$index]); + } + } + + return $this; + } + + /** + * Set raw HTTP header + * + * Allows setting non key => value headers, such as status codes + * + * @param string $value + * @return Zend_Controller_Response_Abstract + */ + public function setRawHeader($value) + { + $this->canSendHeaders(true); + if ('Location' == substr($value, 0, 8)) { + $this->_isRedirect = true; + } + $this->_headersRaw[] = (string) $value; + return $this; + } + + /** + * Retrieve all {@link setRawHeader() raw HTTP headers} + * + * @return array + */ + public function getRawHeaders() + { + return $this->_headersRaw; + } + + /** + * Clear all {@link setRawHeader() raw HTTP headers} + * + * @return Zend_Controller_Response_Abstract + */ + public function clearRawHeaders() + { + $this->_headersRaw = array(); + return $this; + } + + /** + * Clears the specified raw HTTP header + * + * @param string $headerRaw + * @return Zend_Controller_Response_Abstract + */ + public function clearRawHeader($headerRaw) + { + if (! count($this->_headersRaw)) { + return $this; + } + + $key = array_search($headerRaw, $this->_headersRaw); + unset($this->_headersRaw[$key]); + + return $this; + } + + /** + * Clear all headers, normal and raw + * + * @return Zend_Controller_Response_Abstract + */ + public function clearAllHeaders() + { + return $this->clearHeaders() + ->clearRawHeaders(); + } + + /** + * Set HTTP response code to use with headers + * + * @param int $code + * @return Zend_Controller_Response_Abstract + */ + public function setHttpResponseCode($code) + { + if (!is_int($code) || (100 > $code) || (599 < $code)) { + require_once 'Zend/Controller/Response/Exception.php'; + throw new Zend_Controller_Response_Exception('Invalid HTTP response code'); + } + + if ((300 <= $code) && (307 >= $code)) { + $this->_isRedirect = true; + } else { + $this->_isRedirect = false; + } + + $this->_httpResponseCode = $code; + return $this; + } + + /** + * Retrieve HTTP response code + * + * @return int + */ + public function getHttpResponseCode() + { + return $this->_httpResponseCode; + } + + /** + * Can we send headers? + * + * @param boolean $throw Whether or not to throw an exception if headers have been sent; defaults to false + * @return boolean + * @throws Zend_Controller_Response_Exception + */ + public function canSendHeaders($throw = false) + { + $ok = headers_sent($file, $line); + if ($ok && $throw && $this->headersSentThrowsException) { + require_once 'Zend/Controller/Response/Exception.php'; + throw new Zend_Controller_Response_Exception('Cannot send headers; headers already sent in ' . $file . ', line ' . $line); + } + + return !$ok; + } + + /** + * Send all headers + * + * Sends any headers specified. If an {@link setHttpResponseCode() HTTP response code} + * has been specified, it is sent with the first header. + * + * @return Zend_Controller_Response_Abstract + */ + public function sendHeaders() + { + // Only check if we can send headers if we have headers to send + if (count($this->_headersRaw) || count($this->_headers) || (200 != $this->_httpResponseCode)) { + $this->canSendHeaders(true); + } elseif (200 == $this->_httpResponseCode) { + // Haven't changed the response code, and we have no headers + return $this; + } + + $httpCodeSent = false; + + foreach ($this->_headersRaw as $header) { + if (!$httpCodeSent && $this->_httpResponseCode) { + header($header, true, $this->_httpResponseCode); + $httpCodeSent = true; + } else { + header($header); + } + } + + foreach ($this->_headers as $header) { + if (!$httpCodeSent && $this->_httpResponseCode) { + header($header['name'] . ': ' . $header['value'], $header['replace'], $this->_httpResponseCode); + $httpCodeSent = true; + } else { + header($header['name'] . ': ' . $header['value'], $header['replace']); + } + } + + if (!$httpCodeSent) { + header('HTTP/1.1 ' . $this->_httpResponseCode); + $httpCodeSent = true; + } + + return $this; + } + + /** + * Set body content + * + * If $name is not passed, or is not a string, resets the entire body and + * sets the 'default' key to $content. + * + * If $name is a string, sets the named segment in the body array to + * $content. + * + * @param string $content + * @param null|string $name + * @return Zend_Controller_Response_Abstract + */ + public function setBody($content, $name = null) + { + if ((null === $name) || !is_string($name)) { + $this->_body = array('default' => (string) $content); + } else { + $this->_body[$name] = (string) $content; + } + + return $this; + } + + /** + * Append content to the body content + * + * @param string $content + * @param null|string $name + * @return Zend_Controller_Response_Abstract + */ + public function appendBody($content, $name = null) + { + if ((null === $name) || !is_string($name)) { + if (isset($this->_body['default'])) { + $this->_body['default'] .= (string) $content; + } else { + return $this->append('default', $content); + } + } elseif (isset($this->_body[$name])) { + $this->_body[$name] .= (string) $content; + } else { + return $this->append($name, $content); + } + + return $this; + } + + /** + * Clear body array + * + * With no arguments, clears the entire body array. Given a $name, clears + * just that named segment; if no segment matching $name exists, returns + * false to indicate an error. + * + * @param string $name Named segment to clear + * @return boolean + */ + public function clearBody($name = null) + { + if (null !== $name) { + $name = (string) $name; + if (isset($this->_body[$name])) { + unset($this->_body[$name]); + return true; + } + + return false; + } + + $this->_body = array(); + return true; + } + + /** + * Return the body content + * + * If $spec is false, returns the concatenated values of the body content + * array. If $spec is boolean true, returns the body content array. If + * $spec is a string and matches a named segment, returns the contents of + * that segment; otherwise, returns null. + * + * @param boolean $spec + * @return string|array|null + */ + public function getBody($spec = false) + { + if (false === $spec) { + ob_start(); + $this->outputBody(); + return ob_get_clean(); + } elseif (true === $spec) { + return $this->_body; + } elseif (is_string($spec) && isset($this->_body[$spec])) { + return $this->_body[$spec]; + } + + return null; + } + + /** + * Append a named body segment to the body content array + * + * If segment already exists, replaces with $content and places at end of + * array. + * + * @param string $name + * @param string $content + * @return Zend_Controller_Response_Abstract + */ + public function append($name, $content) + { + if (!is_string($name)) { + require_once 'Zend/Controller/Response/Exception.php'; + throw new Zend_Controller_Response_Exception('Invalid body segment key ("' . gettype($name) . '")'); + } + + if (isset($this->_body[$name])) { + unset($this->_body[$name]); + } + $this->_body[$name] = (string) $content; + return $this; + } + + /** + * Prepend a named body segment to the body content array + * + * If segment already exists, replaces with $content and places at top of + * array. + * + * @param string $name + * @param string $content + * @return void + */ + public function prepend($name, $content) + { + if (!is_string($name)) { + require_once 'Zend/Controller/Response/Exception.php'; + throw new Zend_Controller_Response_Exception('Invalid body segment key ("' . gettype($name) . '")'); + } + + if (isset($this->_body[$name])) { + unset($this->_body[$name]); + } + + $new = array($name => (string) $content); + $this->_body = $new + $this->_body; + + return $this; + } + + /** + * Insert a named segment into the body content array + * + * @param string $name + * @param string $content + * @param string $parent + * @param boolean $before Whether to insert the new segment before or + * after the parent. Defaults to false (after) + * @return Zend_Controller_Response_Abstract + */ + public function insert($name, $content, $parent = null, $before = false) + { + if (!is_string($name)) { + require_once 'Zend/Controller/Response/Exception.php'; + throw new Zend_Controller_Response_Exception('Invalid body segment key ("' . gettype($name) . '")'); + } + + if ((null !== $parent) && !is_string($parent)) { + require_once 'Zend/Controller/Response/Exception.php'; + throw new Zend_Controller_Response_Exception('Invalid body segment parent key ("' . gettype($parent) . '")'); + } + + if (isset($this->_body[$name])) { + unset($this->_body[$name]); + } + + if ((null === $parent) || !isset($this->_body[$parent])) { + return $this->append($name, $content); + } + + $ins = array($name => (string) $content); + $keys = array_keys($this->_body); + $loc = array_search($parent, $keys); + if (!$before) { + // Increment location if not inserting before + ++$loc; + } + + if (0 === $loc) { + // If location of key is 0, we're prepending + $this->_body = $ins + $this->_body; + } elseif ($loc >= (count($this->_body))) { + // If location of key is maximal, we're appending + $this->_body = $this->_body + $ins; + } else { + // Otherwise, insert at location specified + $pre = array_slice($this->_body, 0, $loc); + $post = array_slice($this->_body, $loc); + $this->_body = $pre + $ins + $post; + } + + return $this; + } + + /** + * Echo the body segments + * + * @return void + */ + public function outputBody() + { + $body = implode('', $this->_body); + echo $body; + } + + /** + * Register an exception with the response + * + * @param Exception $e + * @return Zend_Controller_Response_Abstract + */ + public function setException(Exception $e) + { + $this->_exceptions[] = $e; + return $this; + } + + /** + * Retrieve the exception stack + * + * @return array + */ + public function getException() + { + return $this->_exceptions; + } + + /** + * Has an exception been registered with the response? + * + * @return boolean + */ + public function isException() + { + return !empty($this->_exceptions); + } + + /** + * Does the response object contain an exception of a given type? + * + * @param string $type + * @return boolean + */ + public function hasExceptionOfType($type) + { + foreach ($this->_exceptions as $e) { + if ($e instanceof $type) { + return true; + } + } + + return false; + } + + /** + * Does the response object contain an exception with a given message? + * + * @param string $message + * @return boolean + */ + public function hasExceptionOfMessage($message) + { + foreach ($this->_exceptions as $e) { + if ($message == $e->getMessage()) { + return true; + } + } + + return false; + } + + /** + * Does the response object contain an exception with a given code? + * + * @param int $code + * @return boolean + */ + public function hasExceptionOfCode($code) + { + $code = (int) $code; + foreach ($this->_exceptions as $e) { + if ($code == $e->getCode()) { + return true; + } + } + + return false; + } + + /** + * Retrieve all exceptions of a given type + * + * @param string $type + * @return false|array + */ + public function getExceptionByType($type) + { + $exceptions = array(); + foreach ($this->_exceptions as $e) { + if ($e instanceof $type) { + $exceptions[] = $e; + } + } + + if (empty($exceptions)) { + $exceptions = false; + } + + return $exceptions; + } + + /** + * Retrieve all exceptions of a given message + * + * @param string $message + * @return false|array + */ + public function getExceptionByMessage($message) + { + $exceptions = array(); + foreach ($this->_exceptions as $e) { + if ($message == $e->getMessage()) { + $exceptions[] = $e; + } + } + + if (empty($exceptions)) { + $exceptions = false; + } + + return $exceptions; + } + + /** + * Retrieve all exceptions of a given code + * + * @param mixed $code + * @return void + */ + public function getExceptionByCode($code) + { + $code = (int) $code; + $exceptions = array(); + foreach ($this->_exceptions as $e) { + if ($code == $e->getCode()) { + $exceptions[] = $e; + } + } + + if (empty($exceptions)) { + $exceptions = false; + } + + return $exceptions; + } + + /** + * Whether or not to render exceptions (off by default) + * + * If called with no arguments or a null argument, returns the value of the + * flag; otherwise, sets it and returns the current value. + * + * @param boolean $flag Optional + * @return boolean + */ + public function renderExceptions($flag = null) + { + if (null !== $flag) { + $this->_renderExceptions = $flag ? true : false; + } + + return $this->_renderExceptions; + } + + /** + * Send the response, including all headers, rendering exceptions if so + * requested. + * + * @return void + */ + public function sendResponse() + { + $this->sendHeaders(); + + if ($this->isException() && $this->renderExceptions()) { + $exceptions = ''; + foreach ($this->getException() as $e) { + $exceptions .= $e->__toString() . "\n"; + } + echo $exceptions; + return; + } + + $this->outputBody(); + } + + /** + * Magic __toString functionality + * + * Proxies to {@link sendResponse()} and returns response value as string + * using output buffering. + * + * @return string + */ + public function __toString() + { + ob_start(); + $this->sendResponse(); + return ob_get_clean(); + } +} diff --git a/library/Zend/Controller/Response/Cli.php b/library/Zend/Controller/Response/Cli.php new file mode 100644 index 000000000..40aea080a --- /dev/null +++ b/library/Zend/Controller/Response/Cli.php @@ -0,0 +1,68 @@ +isException() && $this->renderExceptions()) { + $exceptions = ''; + foreach ($this->getException() as $e) { + $exceptions .= $e->__toString() . "\n"; + } + return $exceptions; + } + + return $this->_body; + } +} diff --git a/library/Zend/Controller/Response/Exception.php b/library/Zend/Controller/Response/Exception.php new file mode 100644 index 000000000..58b6fdda6 --- /dev/null +++ b/library/Zend/Controller/Response/Exception.php @@ -0,0 +1,36 @@ +_headersRaw as $header) { + $headers[] = $header; + } + foreach ($this->_headers as $header) { + $name = $header['name']; + $key = strtolower($name); + if (array_key_exists($name, $headers)) { + if ($header['replace']) { + $headers[$key] = $header['name'] . ': ' . $header['value']; + } + } else { + $headers[$key] = $header['name'] . ': ' . $header['value']; + } + } + return $headers; + } + + /** + * Can we send headers? + * + * @param bool $throw + * @return void + */ + public function canSendHeaders($throw = false) + { + return true; + } + + /** + * Return the concatenated body segments + * + * @return string + */ + public function outputBody() + { + $fullContent = ''; + foreach ($this->_body as $content) { + $fullContent .= $content; + } + return $fullContent; + } + + /** + * Get body and/or body segments + * + * @param bool|string $spec + * @return string|array|null + */ + public function getBody($spec = false) + { + if (false === $spec) { + return $this->outputBody(); + } elseif (true === $spec) { + return $this->_body; + } elseif (is_string($spec) && isset($this->_body[$spec])) { + return $this->_body[$spec]; + } + + return null; + } + + /** + * "send" Response + * + * Concats all response headers, and then final body (separated by two + * newlines) + * + * @return string + */ + public function sendResponse() + { + $headers = $this->sendHeaders(); + $content = implode("\n", $headers) . "\n\n"; + + if ($this->isException() && $this->renderExceptions()) { + $exceptions = ''; + foreach ($this->getException() as $e) { + $exceptions .= $e->__toString() . "\n"; + } + $content .= $exceptions; + } else { + $content .= $this->outputBody(); + } + + return $content; + } +} diff --git a/library/Zend/Controller/Router/Abstract.php b/library/Zend/Controller/Router/Abstract.php new file mode 100644 index 000000000..fb60ebd0c --- /dev/null +++ b/library/Zend/Controller/Router/Abstract.php @@ -0,0 +1,170 @@ +setParams($params); + } + + /** + * Add or modify a parameter to use when instantiating an action controller + * + * @param string $name + * @param mixed $value + * @return Zend_Controller_Router + */ + public function setParam($name, $value) + { + $name = (string) $name; + $this->_invokeParams[$name] = $value; + return $this; + } + + /** + * Set parameters to pass to action controller constructors + * + * @param array $params + * @return Zend_Controller_Router + */ + public function setParams(array $params) + { + $this->_invokeParams = array_merge($this->_invokeParams, $params); + return $this; + } + + /** + * Retrieve a single parameter from the controller parameter stack + * + * @param string $name + * @return mixed + */ + public function getParam($name) + { + if(isset($this->_invokeParams[$name])) { + return $this->_invokeParams[$name]; + } + + return null; + } + + /** + * Retrieve action controller instantiation parameters + * + * @return array + */ + public function getParams() + { + return $this->_invokeParams; + } + + /** + * Clear the controller parameter stack + * + * By default, clears all parameters. If a parameter name is given, clears + * only that parameter; if an array of parameter names is provided, clears + * each. + * + * @param null|string|array single key or array of keys for params to clear + * @return Zend_Controller_Router + */ + public function clearParams($name = null) + { + if (null === $name) { + $this->_invokeParams = array(); + } elseif (is_string($name) && isset($this->_invokeParams[$name])) { + unset($this->_invokeParams[$name]); + } elseif (is_array($name)) { + foreach ($name as $key) { + if (is_string($key) && isset($this->_invokeParams[$key])) { + unset($this->_invokeParams[$key]); + } + } + } + + return $this; + } + + /** + * Retrieve Front Controller + * + * @return Zend_Controller_Front + */ + public function getFrontController() + { + // Used cache version if found + if (null !== $this->_frontController) { + return $this->_frontController; + } + + require_once 'Zend/Controller/Front.php'; + $this->_frontController = Zend_Controller_Front::getInstance(); + return $this->_frontController; + } + + /** + * Set Front Controller + * + * @param Zend_Controller_Front $controller + * @return Zend_Controller_Router_Interface + */ + public function setFrontController(Zend_Controller_Front $controller) + { + $this->_frontController = $controller; + return $this; + } + +} diff --git a/library/Zend/Controller/Router/Exception.php b/library/Zend/Controller/Router/Exception.php new file mode 100644 index 000000000..f4bf757de --- /dev/null +++ b/library/Zend/Controller/Router/Exception.php @@ -0,0 +1,36 @@ +hasRoute('default')) { + $dispatcher = $this->getFrontController()->getDispatcher(); + $request = $this->getFrontController()->getRequest(); + + require_once 'Zend/Controller/Router/Route/Module.php'; + $compat = new Zend_Controller_Router_Route_Module(array(), $dispatcher, $request); + + $this->_routes = array_merge(array('default' => $compat), $this->_routes); + } + + return $this; + } + + /** + * Add route to the route chain + * + * If route contains method setRequest(), it is initialized with a request object + * + * @param string $name Name of the route + * @param Zend_Controller_Router_Route_Interface $route Instance of the route + * @return Zend_Controller_Router_Rewrite + */ + public function addRoute($name, Zend_Controller_Router_Route_Interface $route) + { + if (method_exists($route, 'setRequest')) { + $route->setRequest($this->getFrontController()->getRequest()); + } + + $this->_routes[$name] = $route; + + return $this; + } + + /** + * Add routes to the route chain + * + * @param array $routes Array of routes with names as keys and routes as values + * @return Zend_Controller_Router_Rewrite + */ + public function addRoutes($routes) { + foreach ($routes as $name => $route) { + $this->addRoute($name, $route); + } + + return $this; + } + + /** + * Create routes out of Zend_Config configuration + * + * Example INI: + * routes.archive.route = "archive/:year/*" + * routes.archive.defaults.controller = archive + * routes.archive.defaults.action = show + * routes.archive.defaults.year = 2000 + * routes.archive.reqs.year = "\d+" + * + * routes.news.type = "Zend_Controller_Router_Route_Static" + * routes.news.route = "news" + * routes.news.defaults.controller = "news" + * routes.news.defaults.action = "list" + * + * And finally after you have created a Zend_Config with above ini: + * $router = new Zend_Controller_Router_Rewrite(); + * $router->addConfig($config, 'routes'); + * + * @param Zend_Config $config Configuration object + * @param string $section Name of the config section containing route's definitions + * @throws Zend_Controller_Router_Exception + * @return Zend_Controller_Router_Rewrite + */ + public function addConfig(Zend_Config $config, $section = null) + { + if ($section !== null) { + if ($config->{$section} === null) { + require_once 'Zend/Controller/Router/Exception.php'; + throw new Zend_Controller_Router_Exception("No route configuration in section '{$section}'"); + } + + $config = $config->{$section}; + } + + foreach ($config as $name => $info) { + $route = $this->_getRouteFromConfig($info); + + if ($route instanceof Zend_Controller_Router_Route_Chain) { + if (!isset($info->chain)) { + require_once 'Zend/Controller/Router/Exception.php'; + throw new Zend_Controller_Router_Exception("No chain defined"); + } + + if ($info->chain instanceof Zend_Config) { + $childRouteNames = $info->chain; + } else { + $childRouteNames = explode(',', $info->chain); + } + + foreach ($childRouteNames as $childRouteName) { + $childRoute = $this->getRoute(trim($childRouteName)); + $route->chain($childRoute); + } + + $this->addRoute($name, $route); + } elseif (isset($info->chains) && $info->chains instanceof Zend_Config) { + $this->_addChainRoutesFromConfig($name, $route, $info->chains); + } else { + $this->addRoute($name, $route); + } + } + + return $this; + } + + /** + * Get a route frm a config instance + * + * @param Zend_Config $info + * @return Zend_Controller_Router_Route_Interface + */ + protected function _getRouteFromConfig(Zend_Config $info) + { + $class = (isset($info->type)) ? $info->type : 'Zend_Controller_Router_Route'; + if (!class_exists($class)) { + require_once 'Zend/Loader.php'; + Zend_Loader::loadClass($class); + } + + $route = call_user_func(array($class, 'getInstance'), $info); + + if (isset($info->abstract) && $info->abstract && method_exists($route, 'isAbstract')) { + $route->isAbstract(true); + } + + return $route; + } + + /** + * Add chain routes from a config route + * + * @param string $name + * @param Zend_Controller_Router_Route_Interface $route + * @param Zend_Config $childRoutesInfo + * @return void + */ + protected function _addChainRoutesFromConfig($name, + Zend_Controller_Router_Route_Interface $route, + Zend_Config $childRoutesInfo) + { + foreach ($childRoutesInfo as $childRouteName => $childRouteInfo) { + if (is_string($childRouteInfo)) { + $childRouteName = $childRouteInfo; + $childRoute = $this->getRoute($childRouteName); + } else { + $childRoute = $this->_getRouteFromConfig($childRouteInfo); + } + + if ($route instanceof Zend_Controller_Router_Route_Chain) { + $chainRoute = clone $route; + $chainRoute->chain($childRoute); + } else { + $chainRoute = $route->chain($childRoute); + } + + $chainName = $name . $this->_chainNameSeparator . $childRouteName; + + if (isset($childRouteInfo->chains)) { + $this->_addChainRoutesFromConfig($chainName, $chainRoute, $childRouteInfo->chains); + } else { + $this->addRoute($chainName, $chainRoute); + } + } + } + + /** + * Remove a route from the route chain + * + * @param string $name Name of the route + * @throws Zend_Controller_Router_Exception + * @return Zend_Controller_Router_Rewrite + */ + public function removeRoute($name) + { + if (!isset($this->_routes[$name])) { + require_once 'Zend/Controller/Router/Exception.php'; + throw new Zend_Controller_Router_Exception("Route $name is not defined"); + } + + unset($this->_routes[$name]); + + return $this; + } + + /** + * Remove all standard default routes + * + * @param Zend_Controller_Router_Route_Interface Route + * @return Zend_Controller_Router_Rewrite + */ + public function removeDefaultRoutes() + { + $this->_useDefaultRoutes = false; + + return $this; + } + + /** + * Check if named route exists + * + * @param string $name Name of the route + * @return boolean + */ + public function hasRoute($name) + { + return isset($this->_routes[$name]); + } + + /** + * Retrieve a named route + * + * @param string $name Name of the route + * @throws Zend_Controller_Router_Exception + * @return Zend_Controller_Router_Route_Interface Route object + */ + public function getRoute($name) + { + if (!isset($this->_routes[$name])) { + require_once 'Zend/Controller/Router/Exception.php'; + throw new Zend_Controller_Router_Exception("Route $name is not defined"); + } + + return $this->_routes[$name]; + } + + /** + * Retrieve a currently matched route + * + * @throws Zend_Controller_Router_Exception + * @return Zend_Controller_Router_Route_Interface Route object + */ + public function getCurrentRoute() + { + if (!isset($this->_currentRoute)) { + require_once 'Zend/Controller/Router/Exception.php'; + throw new Zend_Controller_Router_Exception("Current route is not defined"); + } + return $this->getRoute($this->_currentRoute); + } + + /** + * Retrieve a name of currently matched route + * + * @throws Zend_Controller_Router_Exception + * @return Zend_Controller_Router_Route_Interface Route object + */ + public function getCurrentRouteName() + { + if (!isset($this->_currentRoute)) { + require_once 'Zend/Controller/Router/Exception.php'; + throw new Zend_Controller_Router_Exception("Current route is not defined"); + } + return $this->_currentRoute; + } + + /** + * Retrieve an array of routes added to the route chain + * + * @return array All of the defined routes + */ + public function getRoutes() + { + return $this->_routes; + } + + /** + * Find a matching route to the current PATH_INFO and inject + * returning values to the Request object. + * + * @throws Zend_Controller_Router_Exception + * @return Zend_Controller_Request_Abstract Request object + */ + public function route(Zend_Controller_Request_Abstract $request) + { + if (!$request instanceof Zend_Controller_Request_Http) { + require_once 'Zend/Controller/Router/Exception.php'; + throw new Zend_Controller_Router_Exception('Zend_Controller_Router_Rewrite requires a Zend_Controller_Request_Http-based request object'); + } + + if ($this->_useDefaultRoutes) { + $this->addDefaultRoutes(); + } + + // Find the matching route + $routeMatched = false; + + foreach (array_reverse($this->_routes) as $name => $route) { + // TODO: Should be an interface method. Hack for 1.0 BC + if (method_exists($route, 'isAbstract') && $route->isAbstract()) { + continue; + } + + // TODO: Should be an interface method. Hack for 1.0 BC + if (!method_exists($route, 'getVersion') || $route->getVersion() == 1) { + $match = $request->getPathInfo(); + } else { + $match = $request; + } + + if ($params = $route->match($match)) { + $this->_setRequestParams($request, $params); + $this->_currentRoute = $name; + $routeMatched = true; + break; + } + } + + if (!$routeMatched) { + require_once 'Zend/Controller/Router/Exception.php'; + throw new Zend_Controller_Router_Exception('No route matched the request', 404); + } + + if($this->_useCurrentParamsAsGlobal) { + $params = $request->getParams(); + foreach($params as $param => $value) { + $this->setGlobalParam($param, $value); + } + } + + return $request; + + } + + protected function _setRequestParams($request, $params) + { + foreach ($params as $param => $value) { + + $request->setParam($param, $value); + + if ($param === $request->getModuleKey()) { + $request->setModuleName($value); + } + if ($param === $request->getControllerKey()) { + $request->setControllerName($value); + } + if ($param === $request->getActionKey()) { + $request->setActionName($value); + } + + } + } + + /** + * Generates a URL path that can be used in URL creation, redirection, etc. + * + * @param array $userParams Options passed by a user used to override parameters + * @param mixed $name The name of a Route to use + * @param bool $reset Whether to reset to the route defaults ignoring URL params + * @param bool $encode Tells to encode URL parts on output + * @throws Zend_Controller_Router_Exception + * @return string Resulting absolute URL path + */ + public function assemble($userParams, $name = null, $reset = false, $encode = true) + { + if ($name == null) { + try { + $name = $this->getCurrentRouteName(); + } catch (Zend_Controller_Router_Exception $e) { + $name = 'default'; + } + } + + $params = array_merge($this->_globalParams, $userParams); + + $route = $this->getRoute($name); + $url = $route->assemble($params, $reset, $encode); + + if (!preg_match('|^[a-z]+://|', $url)) { + $url = rtrim($this->getFrontController()->getBaseUrl(), '/') . '/' . $url; + } + + return $url; + } + + /** + * Set a global parameter + * + * @param string $name + * @param mixed $value + * @return Zend_Controller_Router_Rewrite + */ + public function setGlobalParam($name, $value) + { + $this->_globalParams[$name] = $value; + + return $this; + } + + /** + * Set the separator to use with chain names + * + * @param string $separator The separator to use + * @return Zend_Controller_Router_Rewrite + */ + public function setChainNameSeparator($separator) { + $this->_chainNameSeparator = $separator; + + return $this; + } + + /** + * Get the separator to use for chain names + * + * @return string + */ + public function getChainNameSeparator() { + return $this->_chainNameSeparator; + } + + /** + * Determines/returns whether to use the request parameters as global parameters. + * + * @param boolean|null $use + * Null/unset when you want to retrieve the current state. + * True when request parameters should be global, false otherwise + * @return boolean|Zend_Controller_Router_Rewrite + * Returns a boolean if first param isn't set, returns an + * instance of Zend_Controller_Router_Rewrite otherwise. + * + */ + public function useRequestParametersAsGlobal($use = null) { + if($use === null) { + return $this->_useCurrentParamsAsGlobal; + } + + $this->_useCurrentParamsAsGlobal = (bool) $use; + + return $this; + } +} diff --git a/library/Zend/Controller/Router/Route.php b/library/Zend/Controller/Router/Route.php new file mode 100644 index 000000000..1239db085 --- /dev/null +++ b/library/Zend/Controller/Router/Route.php @@ -0,0 +1,559 @@ +reqs instanceof Zend_Config) ? $config->reqs->toArray() : array(); + $defs = ($config->defaults instanceof Zend_Config) ? $config->defaults->toArray() : array(); + return new self($config->route, $defs, $reqs); + } + + /** + * Prepares the route for mapping by splitting (exploding) it + * to a corresponding atomic parts. These parts are assigned + * a position which is later used for matching and preparing values. + * + * @param string $route Map used to match with later submitted URL path + * @param array $defaults Defaults for map variables with keys as variable names + * @param array $reqs Regular expression requirements for variables (keys as variable names) + * @param Zend_Translate $translator Translator to use for this instance + */ + public function __construct($route, $defaults = array(), $reqs = array(), Zend_Translate $translator = null, $locale = null) + { + $route = trim($route, $this->_urlDelimiter); + $this->_defaults = (array) $defaults; + $this->_requirements = (array) $reqs; + $this->_translator = $translator; + $this->_locale = $locale; + + if ($route !== '') { + foreach (explode($this->_urlDelimiter, $route) as $pos => $part) { + if (substr($part, 0, 1) == $this->_urlVariable && substr($part, 1, 1) != $this->_urlVariable) { + $name = substr($part, 1); + + if (substr($name, 0, 1) === '@' && substr($name, 1, 1) !== '@') { + $name = substr($name, 1); + $this->_translatable[] = $name; + $this->_isTranslated = true; + } + + $this->_parts[$pos] = (isset($reqs[$name]) ? $reqs[$name] : $this->_defaultRegex); + $this->_variables[$pos] = $name; + } else { + if (substr($part, 0, 1) == $this->_urlVariable) { + $part = substr($part, 1); + } + + if (substr($part, 0, 1) === '@' && substr($part, 1, 1) !== '@') { + $this->_isTranslated = true; + } + + $this->_parts[$pos] = $part; + + if ($part !== '*') { + $this->_staticCount++; + } + } + } + } + } + + /** + * Matches a user submitted path with parts defined by a map. Assigns and + * returns an array of variables on a successful match. + * + * @param string $path Path used to match against this routing map + * @return array|false An array of assigned values or a false on a mismatch + */ + public function match($path, $partial = false) + { + if ($this->_isTranslated) { + $translateMessages = $this->getTranslator()->getMessages(); + } + + $pathStaticCount = 0; + $values = array(); + $matchedPath = ''; + + if (!$partial) { + $path = trim($path, $this->_urlDelimiter); + } + + if ($path !== '') { + $path = explode($this->_urlDelimiter, $path); + + foreach ($path as $pos => $pathPart) { + // Path is longer than a route, it's not a match + if (!array_key_exists($pos, $this->_parts)) { + if ($partial) { + break; + } else { + return false; + } + } + + $matchedPath .= $pathPart . $this->_urlDelimiter; + + // If it's a wildcard, get the rest of URL as wildcard data and stop matching + if ($this->_parts[$pos] == '*') { + $count = count($path); + for($i = $pos; $i < $count; $i+=2) { + $var = urldecode($path[$i]); + if (!isset($this->_wildcardData[$var]) && !isset($this->_defaults[$var]) && !isset($values[$var])) { + $this->_wildcardData[$var] = (isset($path[$i+1])) ? urldecode($path[$i+1]) : null; + } + } + + $matchedPath = implode($this->_urlDelimiter, $path); + break; + } + + $name = isset($this->_variables[$pos]) ? $this->_variables[$pos] : null; + $pathPart = urldecode($pathPart); + + // Translate value if required + $part = $this->_parts[$pos]; + if ($this->_isTranslated && (substr($part, 0, 1) === '@' && substr($part, 1, 1) !== '@' && $name === null) || $name !== null && in_array($name, $this->_translatable)) { + if (substr($part, 0, 1) === '@') { + $part = substr($part, 1); + } + + if (($originalPathPart = array_search($pathPart, $translateMessages)) !== false) { + $pathPart = $originalPathPart; + } + } + + if (substr($part, 0, 2) === '@@') { + $part = substr($part, 1); + } + + // If it's a static part, match directly + if ($name === null && $part != $pathPart) { + return false; + } + + // If it's a variable with requirement, match a regex. If not - everything matches + if ($part !== null && !preg_match($this->_regexDelimiter . '^' . $part . '$' . $this->_regexDelimiter . 'iu', $pathPart)) { + return false; + } + + // If it's a variable store it's value for later + if ($name !== null) { + $values[$name] = $pathPart; + } else { + $pathStaticCount++; + } + } + } + + // Check if all static mappings have been matched + if ($this->_staticCount != $pathStaticCount) { + return false; + } + + $return = $values + $this->_wildcardData + $this->_defaults; + + // Check if all map variables have been initialized + foreach ($this->_variables as $var) { + if (!array_key_exists($var, $return)) { + return false; + } + } + + $this->setMatchedPath(rtrim($matchedPath, $this->_urlDelimiter)); + + $this->_values = $values; + + return $return; + + } + + /** + * Assembles user submitted parameters forming a URL path defined by this route + * + * @param array $data An array of variable and value pairs used as parameters + * @param boolean $reset Whether or not to set route defaults with those provided in $data + * @return string Route path with user submitted parameters + */ + public function assemble($data = array(), $reset = false, $encode = false, $partial = false) + { + if ($this->_isTranslated) { + $translator = $this->getTranslator(); + + if (isset($data['@locale'])) { + $locale = $data['@locale']; + unset($data['@locale']); + } else { + $locale = $this->getLocale(); + } + } + + $url = array(); + $flag = false; + + foreach ($this->_parts as $key => $part) { + $name = isset($this->_variables[$key]) ? $this->_variables[$key] : null; + + $useDefault = false; + if (isset($name) && array_key_exists($name, $data) && $data[$name] === null) { + $useDefault = true; + } + + if (isset($name)) { + if (isset($data[$name]) && !$useDefault) { + $value = $data[$name]; + unset($data[$name]); + } elseif (!$reset && !$useDefault && isset($this->_values[$name])) { + $value = $this->_values[$name]; + } elseif (!$reset && !$useDefault && isset($this->_wildcardData[$name])) { + $value = $this->_wildcardData[$name]; + } elseif (isset($this->_defaults[$name])) { + $value = $this->_defaults[$name]; + } else { + require_once 'Zend/Controller/Router/Exception.php'; + throw new Zend_Controller_Router_Exception($name . ' is not specified'); + } + + if ($this->_isTranslated && in_array($name, $this->_translatable)) { + $url[$key] = $translator->translate($value, $locale); + } else { + $url[$key] = $value; + } + } elseif ($part != '*') { + if ($this->_isTranslated && substr($part, 0, 1) === '@') { + if (substr($part, 1, 1) !== '@') { + $url[$key] = $translator->translate(substr($part, 1), $locale); + } else { + $url[$key] = substr($part, 1); + } + } else { + if (substr($part, 0, 2) === '@@') { + $part = substr($part, 1); + } + + $url[$key] = $part; + } + } else { + if (!$reset) $data += $this->_wildcardData; + $defaults = $this->getDefaults(); + foreach ($data as $var => $value) { + if ($value !== null && (!isset($defaults[$var]) || $value != $defaults[$var])) { + $url[$key++] = $var; + $url[$key++] = $value; + $flag = true; + } + } + } + } + + $return = ''; + + foreach (array_reverse($url, true) as $key => $value) { + $defaultValue = null; + + if (isset($this->_variables[$key])) { + $defaultValue = $this->getDefault($this->_variables[$key]); + + if ($this->_isTranslated && $defaultValue !== null && isset($this->_translatable[$this->_variables[$key]])) { + $defaultValue = $translator->translate($defaultValue, $locale); + } + } + + if ($flag || $value !== $defaultValue || $partial) { + if ($encode) $value = urlencode($value); + $return = $this->_urlDelimiter . $value . $return; + $flag = true; + } + } + + return trim($return, $this->_urlDelimiter); + + } + + /** + * Return a single parameter of route's defaults + * + * @param string $name Array key of the parameter + * @return string Previously set default + */ + public function getDefault($name) { + if (isset($this->_defaults[$name])) { + return $this->_defaults[$name]; + } + return null; + } + + /** + * Return an array of defaults + * + * @return array Route defaults + */ + public function getDefaults() { + return $this->_defaults; + } + + /** + * Get all variables which are used by the route + * + * @return array + */ + public function getVariables() + { + return $this->_variables; + } + + /** + * Set a default translator + * + * @param Zend_Translate $translator + * @return void + */ + public static function setDefaultTranslator(Zend_Translate $translator = null) + { + self::$_defaultTranslator = $translator; + } + + /** + * Get the default translator + * + * @return Zend_Translate + */ + public static function getDefaultTranslator() + { + return self::$_defaultTranslator; + } + + /** + * Set a translator + * + * @param Zend_Translate $translator + * @return void + */ + public function setTranslator(Zend_Translate $translator) + { + $this->_translator = $translator; + } + + /** + * Get the translator + * + * @throws Zend_Controller_Router_Exception When no translator can be found + * @return Zend_Translate + */ + public function getTranslator() + { + if ($this->_translator !== null) { + return $this->_translator; + } else if (($translator = self::getDefaultTranslator()) !== null) { + return $translator; + } else { + try { + $translator = Zend_Registry::get('Zend_Translate'); + } catch (Zend_Exception $e) { + $translator = null; + } + + if ($translator instanceof Zend_Translate) { + return $translator; + } + } + + require_once 'Zend/Controller/Router/Exception.php'; + throw new Zend_Controller_Router_Exception('Could not find a translator'); + } + + /** + * Set a default locale + * + * @param mixed $locale + * @return void + */ + public static function setDefaultLocale($locale = null) + { + self::$_defaultLocale = $locale; + } + + /** + * Get the default locale + * + * @return mixed + */ + public static function getDefaultLocale() + { + return self::$_defaultLocale; + } + + /** + * Set a locale + * + * @param mixed $locale + * @return void + */ + public function setLocale($locale) + { + $this->_locale = $locale; + } + + /** + * Get the locale + * + * @return mixed + */ + public function getLocale() + { + if ($this->_locale !== null) { + return $this->_locale; + } else if (($locale = self::getDefaultLocale()) !== null) { + return $locale; + } else { + try { + $locale = Zend_Registry::get('Zend_Locale'); + } catch (Zend_Exception $e) { + $locale = null; + } + + if ($locale !== null) { + return $locale; + } + } + + return null; + } +} diff --git a/library/Zend/Controller/Router/Route/Abstract.php b/library/Zend/Controller/Router/Route/Abstract.php new file mode 100644 index 000000000..426a285e1 --- /dev/null +++ b/library/Zend/Controller/Router/Route/Abstract.php @@ -0,0 +1,117 @@ +_matchedPath = $path; + } + + /** + * Get partially matched path + * + * @return string + */ + public function getMatchedPath() + { + return $this->_matchedPath; + } + + /** + * Check or set wether this is an abstract route or not + * + * @param boolean $flag + * @return boolean + */ + public function isAbstract($flag = null) + { + if ($flag !== null) { + $this->_isAbstract = $flag; + } + + return $this->_isAbstract; + } + + /** + * Create a new chain + * + * @param Zend_Controller_Router_Route_Abstract $route + * @param string $separator + * @return Zend_Controller_Router_Route_Chain + */ + public function chain(Zend_Controller_Router_Route_Abstract $route, $separator = '/') + { + require_once 'Zend/Controller/Router/Route/Chain.php'; + + $chain = new Zend_Controller_Router_Route_Chain(); + $chain->chain($this)->chain($route, $separator); + + return $chain; + } + +} diff --git a/library/Zend/Controller/Router/Route/Chain.php b/library/Zend/Controller/Router/Route/Chain.php new file mode 100644 index 000000000..6de98fb0a --- /dev/null +++ b/library/Zend/Controller/Router/Route/Chain.php @@ -0,0 +1,169 @@ +defaults instanceof Zend_Config) ? $config->defaults->toArray() : array(); + return new self($config->route, $defs); + } + + /** + * Add a route to this chain + * + * @param Zend_Controller_Router_Route_Abstract $route + * @param string $separator + * @return Zend_Controller_Router_Route_Chain + */ + public function chain(Zend_Controller_Router_Route_Abstract $route, $separator = '/') + { + $this->_routes[] = $route; + $this->_separators[] = $separator; + + return $this; + + } + + /** + * Matches a user submitted path with a previously defined route. + * Assigns and returns an array of defaults on a successful match. + * + * @param Zend_Controller_Request_Http $request Request to get the path info from + * @return array|false An array of assigned values or a false on a mismatch + */ + public function match($request, $partial = null) + { + $path = trim($request->getPathInfo(), '/'); + $subPath = $path; + $values = array(); + + foreach ($this->_routes as $key => $route) { + if ($key > 0 && $matchedPath !== null) { + $separator = substr($subPath, 0, strlen($this->_separators[$key])); + + if ($separator !== $this->_separators[$key]) { + return false; + } + + $subPath = substr($subPath, strlen($separator)); + } + + // TODO: Should be an interface method. Hack for 1.0 BC + if (!method_exists($route, 'getVersion') || $route->getVersion() == 1) { + $match = $subPath; + } else { + $request->setPathInfo($subPath); + $match = $request; + } + + $res = $route->match($match, true); + if ($res === false) { + return false; + } + + $matchedPath = $route->getMatchedPath(); + + if ($matchedPath !== null) { + $subPath = substr($subPath, strlen($matchedPath)); + $separator = substr($subPath, 0, strlen($this->_separators[$key])); + } + + $values = $res + $values; + } + + $request->setPathInfo($path); + + if ($subPath !== '' && $subPath !== false) { + return false; + } + + return $values; + } + + /** + * Assembles a URL path defined by this route + * + * @param array $data An array of variable and value pairs used as parameters + * @return string Route path with user submitted parameters + */ + public function assemble($data = array(), $reset = false, $encode = false) + { + $value = ''; + $numRoutes = count($this->_routes); + + foreach ($this->_routes as $key => $route) { + if ($key > 0) { + $value .= $this->_separators[$key]; + } + + $value .= $route->assemble($data, $reset, $encode, (($numRoutes - 1) > $key)); + + if (method_exists($route, 'getVariables')) { + $variables = $route->getVariables(); + + foreach ($variables as $variable) { + $data[$variable] = null; + } + } + } + + return $value; + } + + /** + * Set the request object for this and the child routes + * + * @param Zend_Controller_Request_Abstract|null $request + * @return void + */ + public function setRequest(Zend_Controller_Request_Abstract $request = null) + { + $this->_request = $request; + + foreach ($this->_routes as $route) { + if (method_exists($route, 'setRequest')) { + $route->setRequest($request); + } + } + } + +} diff --git a/library/Zend/Controller/Router/Route/Hostname.php b/library/Zend/Controller/Router/Route/Hostname.php new file mode 100644 index 000000000..082977111 --- /dev/null +++ b/library/Zend/Controller/Router/Route/Hostname.php @@ -0,0 +1,342 @@ +_request = $request; + } + + /** + * Get the request object + * + * @return Zend_Controller_Request_Abstract $request + */ + public function getRequest() + { + if ($this->_request === null) { + require_once 'Zend/Controller/Front.php'; + $this->_request = Zend_Controller_Front::getInstance()->getRequest(); + } + + return $this->_request; + } + + /** + * Instantiates route based on passed Zend_Config structure + * + * @param Zend_Config $config Configuration object + */ + public static function getInstance(Zend_Config $config) + { + $reqs = ($config->reqs instanceof Zend_Config) ? $config->reqs->toArray() : array(); + $defs = ($config->defaults instanceof Zend_Config) ? $config->defaults->toArray() : array(); + $scheme = (isset($config->scheme)) ? $config->scheme : null; + return new self($config->route, $defs, $reqs, $scheme); + } + + /** + * Prepares the route for mapping by splitting (exploding) it + * to a corresponding atomic parts. These parts are assigned + * a position which is later used for matching and preparing values. + * + * @param string $route Map used to match with later submitted hostname + * @param array $defaults Defaults for map variables with keys as variable names + * @param array $reqs Regular expression requirements for variables (keys as variable names) + * @param string $scheme + */ + public function __construct($route, $defaults = array(), $reqs = array(), $scheme = null) + { + $route = trim($route, '.'); + $this->_defaults = (array) $defaults; + $this->_requirements = (array) $reqs; + $this->_scheme = $scheme; + + if ($route != '') { + foreach (explode('.', $route) as $pos => $part) { + if (substr($part, 0, 1) == $this->_hostVariable) { + $name = substr($part, 1); + $this->_parts[$pos] = (isset($reqs[$name]) ? $reqs[$name] : $this->_defaultRegex); + $this->_variables[$pos] = $name; + } else { + $this->_parts[$pos] = $part; + $this->_staticCount++; + } + } + } + } + + /** + * Matches a user submitted path with parts defined by a map. Assigns and + * returns an array of variables on a successful match. + * + * @param Zend_Controller_Request_Http $request Request to get the host from + * @return array|false An array of assigned values or a false on a mismatch + */ + public function match($request) + { + // Check the scheme if required + if ($this->_scheme !== null) { + $scheme = $request->getScheme(); + + if ($scheme !== $this->_scheme) { + return false; + } + } + + // Get the host and remove unnecessary port information + $host = $request->getHttpHost(); + if (preg_match('#:\d+$#', $host, $result) === 1) { + $host = substr($host, 0, -strlen($result[0])); + } + + $hostStaticCount = 0; + $values = array(); + + $host = trim($host, '.'); + + if ($host != '') { + $host = explode('.', $host); + + foreach ($host as $pos => $hostPart) { + // Host is longer than a route, it's not a match + if (!array_key_exists($pos, $this->_parts)) { + return false; + } + + $name = isset($this->_variables[$pos]) ? $this->_variables[$pos] : null; + $hostPart = urldecode($hostPart); + + // If it's a static part, match directly + if ($name === null && $this->_parts[$pos] != $hostPart) { + return false; + } + + // If it's a variable with requirement, match a regex. If not - everything matches + if ($this->_parts[$pos] !== null && !preg_match($this->_regexDelimiter . '^' . $this->_parts[$pos] . '$' . $this->_regexDelimiter . 'iu', $hostPart)) { + return false; + } + + // If it's a variable store it's value for later + if ($name !== null) { + $values[$name] = $hostPart; + } else { + $hostStaticCount++; + } + } + } + + // Check if all static mappings have been matched + if ($this->_staticCount != $hostStaticCount) { + return false; + } + + $return = $values + $this->_defaults; + + // Check if all map variables have been initialized + foreach ($this->_variables as $var) { + if (!array_key_exists($var, $return)) { + return false; + } + } + + $this->_values = $values; + + return $return; + + } + + /** + * Assembles user submitted parameters forming a hostname defined by this route + * + * @param array $data An array of variable and value pairs used as parameters + * @param boolean $reset Whether or not to set route defaults with those provided in $data + * @return string Route path with user submitted parameters + */ + public function assemble($data = array(), $reset = false, $encode = false, $partial = false) + { + $host = array(); + $flag = false; + + foreach ($this->_parts as $key => $part) { + $name = isset($this->_variables[$key]) ? $this->_variables[$key] : null; + + $useDefault = false; + if (isset($name) && array_key_exists($name, $data) && $data[$name] === null) { + $useDefault = true; + } + + if (isset($name)) { + if (isset($data[$name]) && !$useDefault) { + $host[$key] = $data[$name]; + unset($data[$name]); + } elseif (!$reset && !$useDefault && isset($this->_values[$name])) { + $host[$key] = $this->_values[$name]; + } elseif (isset($this->_defaults[$name])) { + $host[$key] = $this->_defaults[$name]; + } else { + require_once 'Zend/Controller/Router/Exception.php'; + throw new Zend_Controller_Router_Exception($name . ' is not specified'); + } + } else { + $host[$key] = $part; + } + } + + $return = ''; + + foreach (array_reverse($host, true) as $key => $value) { + if ($flag || !isset($this->_variables[$key]) || $value !== $this->getDefault($this->_variables[$key]) || $partial) { + if ($encode) $value = urlencode($value); + $return = '.' . $value . $return; + $flag = true; + } + } + + $url = trim($return, '.'); + + if ($this->_scheme !== null) { + $scheme = $this->_scheme; + } else { + $request = $this->getRequest(); + if ($request instanceof Zend_Controller_Request_Http) { + $scheme = $request->getScheme(); + } else { + $scheme = 'http'; + } + } + + $hostname = implode('.', $host); + $url = $scheme . '://' . $url; + + return $url; + } + + /** + * Return a single parameter of route's defaults + * + * @param string $name Array key of the parameter + * @return string Previously set default + */ + public function getDefault($name) { + if (isset($this->_defaults[$name])) { + return $this->_defaults[$name]; + } + return null; + } + + /** + * Return an array of defaults + * + * @return array Route defaults + */ + public function getDefaults() { + return $this->_defaults; + } + + /** + * Get all variables which are used by the route + * + * @return array + */ + public function getVariables() + { + return $this->_variables; + } +} diff --git a/library/Zend/Controller/Router/Route/Interface.php b/library/Zend/Controller/Router/Route/Interface.php new file mode 100644 index 000000000..22a6aeec0 --- /dev/null +++ b/library/Zend/Controller/Router/Route/Interface.php @@ -0,0 +1,37 @@ +defaults instanceof Zend_Config) ? $config->defaults->toArray() : array(); + $dispatcher = $frontController->getDispatcher(); + $request = $frontController->getRequest(); + + return new self($defs, $dispatcher, $request); + } + + /** + * Constructor + * + * @param array $defaults Defaults for map variables with keys as variable names + * @param Zend_Controller_Dispatcher_Interface $dispatcher Dispatcher object + * @param Zend_Controller_Request_Abstract $request Request object + */ + public function __construct(array $defaults = array(), + Zend_Controller_Dispatcher_Interface $dispatcher = null, + Zend_Controller_Request_Abstract $request = null) + { + $this->_defaults = $defaults; + + if (isset($request)) { + $this->_request = $request; + } + + if (isset($dispatcher)) { + $this->_dispatcher = $dispatcher; + } + } + + /** + * Set request keys based on values in request object + * + * @return void + */ + protected function _setRequestKeys() + { + if (null !== $this->_request) { + $this->_moduleKey = $this->_request->getModuleKey(); + $this->_controllerKey = $this->_request->getControllerKey(); + $this->_actionKey = $this->_request->getActionKey(); + } + + if (null !== $this->_dispatcher) { + $this->_defaults += array( + $this->_controllerKey => $this->_dispatcher->getDefaultControllerName(), + $this->_actionKey => $this->_dispatcher->getDefaultAction(), + $this->_moduleKey => $this->_dispatcher->getDefaultModule() + ); + } + + $this->_keysSet = true; + } + + /** + * Matches a user submitted path. Assigns and returns an array of variables + * on a successful match. + * + * If a request object is registered, it uses its setModuleName(), + * setControllerName(), and setActionName() accessors to set those values. + * Always returns the values as an array. + * + * @param string $path Path used to match against this routing map + * @return array An array of assigned values or a false on a mismatch + */ + public function match($path, $partial = false) + { + $this->_setRequestKeys(); + + $values = array(); + $params = array(); + + if (!$partial) { + $path = trim($path, self::URI_DELIMITER); + } else { + $matchedPath = $path; + } + + if ($path != '') { + $path = explode(self::URI_DELIMITER, $path); + + if ($this->_dispatcher && $this->_dispatcher->isValidModule($path[0])) { + $values[$this->_moduleKey] = array_shift($path); + $this->_moduleValid = true; + } + + if (count($path) && !empty($path[0])) { + $values[$this->_controllerKey] = array_shift($path); + } + + if (count($path) && !empty($path[0])) { + $values[$this->_actionKey] = array_shift($path); + } + + if ($numSegs = count($path)) { + for ($i = 0; $i < $numSegs; $i = $i + 2) { + $key = urldecode($path[$i]); + $val = isset($path[$i + 1]) ? urldecode($path[$i + 1]) : null; + $params[$key] = (isset($params[$key]) ? (array_merge((array) $params[$key], array($val))): $val); + } + } + } + + if ($partial) { + $this->setMatchedPath($matchedPath); + } + + $this->_values = $values + $params; + + return $this->_values + $this->_defaults; + } + + /** + * Assembles user submitted parameters forming a URL path defined by this route + * + * @param array $data An array of variable and value pairs used as parameters + * @param bool $reset Weither to reset the current params + * @return string Route path with user submitted parameters + */ + public function assemble($data = array(), $reset = false, $encode = true, $partial = false) + { + if (!$this->_keysSet) { + $this->_setRequestKeys(); + } + + $params = (!$reset) ? $this->_values : array(); + + foreach ($data as $key => $value) { + if ($value !== null) { + $params[$key] = $value; + } elseif (isset($params[$key])) { + unset($params[$key]); + } + } + + $params += $this->_defaults; + + $url = ''; + + if ($this->_moduleValid || array_key_exists($this->_moduleKey, $data)) { + if ($params[$this->_moduleKey] != $this->_defaults[$this->_moduleKey]) { + $module = $params[$this->_moduleKey]; + } + } + unset($params[$this->_moduleKey]); + + $controller = $params[$this->_controllerKey]; + unset($params[$this->_controllerKey]); + + $action = $params[$this->_actionKey]; + unset($params[$this->_actionKey]); + + foreach ($params as $key => $value) { + $key = ($encode) ? urlencode($key) : $key; + if (is_array($value)) { + foreach ($value as $arrayValue) { + $arrayValue = ($encode) ? urlencode($arrayValue) : $arrayValue; + $url .= '/' . $key; + $url .= '/' . $arrayValue; + } + } else { + if ($encode) $value = urlencode($value); + $url .= '/' . $key; + $url .= '/' . $value; + } + } + + if (!empty($url) || $action !== $this->_defaults[$this->_actionKey]) { + if ($encode) $action = urlencode($action); + $url = '/' . $action . $url; + } + + if (!empty($url) || $controller !== $this->_defaults[$this->_controllerKey]) { + if ($encode) $controller = urlencode($controller); + $url = '/' . $controller . $url; + } + + if (isset($module)) { + if ($encode) $module = urlencode($module); + $url = '/' . $module . $url; + } + + return ltrim($url, self::URI_DELIMITER); + } + + /** + * Return a single parameter of route's defaults + * + * @param string $name Array key of the parameter + * @return string Previously set default + */ + public function getDefault($name) { + if (isset($this->_defaults[$name])) { + return $this->_defaults[$name]; + } + } + + /** + * Return an array of defaults + * + * @return array Route defaults + */ + public function getDefaults() { + return $this->_defaults; + } + +} diff --git a/library/Zend/Controller/Router/Route/Regex.php b/library/Zend/Controller/Router/Route/Regex.php new file mode 100644 index 000000000..160f7cdab --- /dev/null +++ b/library/Zend/Controller/Router/Route/Regex.php @@ -0,0 +1,269 @@ +defaults instanceof Zend_Config) ? $config->defaults->toArray() : array(); + $map = ($config->map instanceof Zend_Config) ? $config->map->toArray() : array(); + $reverse = (isset($config->reverse)) ? $config->reverse : null; + return new self($config->route, $defs, $map, $reverse); + } + + public function __construct($route, $defaults = array(), $map = array(), $reverse = null) + { + $this->_regex = $route; + $this->_defaults = (array) $defaults; + $this->_map = (array) $map; + $this->_reverse = $reverse; + } + + public function getVersion() { + return 1; + } + + /** + * Matches a user submitted path with a previously defined route. + * Assigns and returns an array of defaults on a successful match. + * + * @param string $path Path used to match against this routing map + * @return array|false An array of assigned values or a false on a mismatch + */ + public function match($path, $partial = false) + { + if (!$partial) { + $path = trim(urldecode($path), '/'); + $regex = '#^' . $this->_regex . '$#i'; + } else { + $regex = '#^' . $this->_regex . '#i'; + } + + $res = preg_match($regex, $path, $values); + + if ($res === 0) { + return false; + } + + if ($partial) { + $this->setMatchedPath($values[0]); + } + + // array_filter_key()? Why isn't this in a standard PHP function set yet? :) + foreach ($values as $i => $value) { + if (!is_int($i) || $i === 0) { + unset($values[$i]); + } + } + + $this->_values = $values; + + $values = $this->_getMappedValues($values); + $defaults = $this->_getMappedValues($this->_defaults, false, true); + $return = $values + $defaults; + + return $return; + } + + /** + * Maps numerically indexed array values to it's associative mapped counterpart. + * Or vice versa. Uses user provided map array which consists of index => name + * parameter mapping. If map is not found, it returns original array. + * + * Method strips destination type of keys form source array. Ie. if source array is + * indexed numerically then every associative key will be stripped. Vice versa if reversed + * is set to true. + * + * @param array $values Indexed or associative array of values to map + * @param boolean $reversed False means translation of index to association. True means reverse. + * @param boolean $preserve Should wrong type of keys be preserved or stripped. + * @return array An array of mapped values + */ + protected function _getMappedValues($values, $reversed = false, $preserve = false) + { + if (count($this->_map) == 0) { + return $values; + } + + $return = array(); + + foreach ($values as $key => $value) { + if (is_int($key) && !$reversed) { + if (array_key_exists($key, $this->_map)) { + $index = $this->_map[$key]; + } elseif (false === ($index = array_search($key, $this->_map))) { + $index = $key; + } + $return[$index] = $values[$key]; + } elseif ($reversed) { + $index = $key; + if (!is_int($key)) { + if (array_key_exists($key, $this->_map)) { + $index = $this->_map[$key]; + } else { + $index = array_search($key, $this->_map, true); + } + } + if (false !== $index) { + $return[$index] = $values[$key]; + } + } elseif ($preserve) { + $return[$key] = $value; + } + } + + return $return; + } + + /** + * Assembles a URL path defined by this route + * + * @param array $data An array of name (or index) and value pairs used as parameters + * @return string Route path with user submitted parameters + */ + public function assemble($data = array(), $reset = false, $encode = false, $partial = false) + { + if ($this->_reverse === null) { + require_once 'Zend/Controller/Router/Exception.php'; + throw new Zend_Controller_Router_Exception('Cannot assemble. Reversed route is not specified.'); + } + + $defaultValuesMapped = $this->_getMappedValues($this->_defaults, true, false); + $matchedValuesMapped = $this->_getMappedValues($this->_values, true, false); + $dataValuesMapped = $this->_getMappedValues($data, true, false); + + // handle resets, if so requested (By null value) to do so + if (($resetKeys = array_search(null, $dataValuesMapped, true)) !== false) { + foreach ((array) $resetKeys as $resetKey) { + if (isset($matchedValuesMapped[$resetKey])) { + unset($matchedValuesMapped[$resetKey]); + unset($dataValuesMapped[$resetKey]); + } + } + } + + // merge all the data together, first defaults, then values matched, then supplied + $mergedData = $defaultValuesMapped; + $mergedData = $this->_arrayMergeNumericKeys($mergedData, $matchedValuesMapped); + $mergedData = $this->_arrayMergeNumericKeys($mergedData, $dataValuesMapped); + + if ($encode) { + foreach ($mergedData as $key => &$value) { + $value = urlencode($value); + } + } + + ksort($mergedData); + + $return = @vsprintf($this->_reverse, $mergedData); + + if ($return === false) { + require_once 'Zend/Controller/Router/Exception.php'; + throw new Zend_Controller_Router_Exception('Cannot assemble. Too few arguments?'); + } + + return $return; + + } + + /** + * Return a single parameter of route's defaults + * + * @param string $name Array key of the parameter + * @return string Previously set default + */ + public function getDefault($name) { + if (isset($this->_defaults[$name])) { + return $this->_defaults[$name]; + } + } + + /** + * Return an array of defaults + * + * @return array Route defaults + */ + public function getDefaults() { + return $this->_defaults; + } + + /** + * Get all variables which are used by the route + * + * @return array + */ + public function getVariables() + { + $variables = array(); + + foreach ($this->_map as $key => $value) { + if (is_numeric($key)) { + $variables[] = $value; + } else { + $variables[] = $key; + } + } + + return $variables; + } + + /** + * _arrayMergeNumericKeys() - allows for a strict key (numeric's included) array_merge. + * php's array_merge() lacks the ability to merge with numeric keys. + * + * @param array $array1 + * @param array $array2 + * @return array + */ + protected function _arrayMergeNumericKeys(Array $array1, Array $array2) + { + $returnArray = $array1; + foreach ($array2 as $array2Index => $array2Value) { + $returnArray[$array2Index] = $array2Value; + } + return $returnArray; + } + + +} diff --git a/library/Zend/Controller/Router/Route/Static.php b/library/Zend/Controller/Router/Route/Static.php new file mode 100644 index 000000000..178704227 --- /dev/null +++ b/library/Zend/Controller/Router/Route/Static.php @@ -0,0 +1,125 @@ +defaults instanceof Zend_Config) ? $config->defaults->toArray() : array(); + return new self($config->route, $defs); + } + + /** + * Prepares the route for mapping. + * + * @param string $route Map used to match with later submitted URL path + * @param array $defaults Defaults for map variables with keys as variable names + */ + public function __construct($route, $defaults = array()) + { + $this->_route = trim($route, '/'); + $this->_defaults = (array) $defaults; + } + + /** + * Matches a user submitted path with a previously defined route. + * Assigns and returns an array of defaults on a successful match. + * + * @param string $path Path used to match against this routing map + * @return array|false An array of assigned values or a false on a mismatch + */ + public function match($path, $partial = false) + { + if ($partial) { + if (substr($path, 0, strlen($this->_route)) === $this->_route) { + $this->setMatchedPath($this->_route); + return $this->_defaults; + } + } else { + if (trim($path, '/') == $this->_route) { + return $this->_defaults; + } + } + + return false; + } + + /** + * Assembles a URL path defined by this route + * + * @param array $data An array of variable and value pairs used as parameters + * @return string Route path with user submitted parameters + */ + public function assemble($data = array(), $reset = false, $encode = false, $partial = false) + { + return $this->_route; + } + + /** + * Return a single parameter of route's defaults + * + * @param string $name Array key of the parameter + * @return string Previously set default + */ + public function getDefault($name) { + if (isset($this->_defaults[$name])) { + return $this->_defaults[$name]; + } + return null; + } + + /** + * Return an array of defaults + * + * @return array Route defaults + */ + public function getDefaults() { + return $this->_defaults; + } + +} diff --git a/library/Zend/Crypt.php b/library/Zend/Crypt.php new file mode 100644 index 000000000..49bb134f0 --- /dev/null +++ b/library/Zend/Crypt.php @@ -0,0 +1,167 @@ +setPrime($prime); + $this->setGenerator($generator); + if (!is_null($privateKey)) { + $this->setPrivateKey($privateKey, $privateKeyType); + } + $this->setBigIntegerMath(); + } + + /** + * Generate own public key. If a private number has not already been + * set, one will be generated at this stage. + * + * @return Zend_Crypt_DiffieHellman + */ + public function generateKeys() + { + if (function_exists('openssl_dh_compute_key') && self::$useOpenssl !== false) { + $details = array(); + $details['p'] = $this->getPrime(); + $details['g'] = $this->getGenerator(); + if ($this->hasPrivateKey()) { + $details['priv_key'] = $this->getPrivateKey(); + } + $opensslKeyResource = openssl_pkey_new( array('dh' => $details) ); + $data = openssl_pkey_get_details($opensslKeyResource); + $this->setPrivateKey($data['dh']['priv_key'], self::BINARY); + $this->setPublicKey($data['dh']['pub_key'], self::BINARY); + } else { + // Private key is lazy generated in the absence of PHP 5.3's ext/openssl + $publicKey = $this->_math->powmod($this->getGenerator(), $this->getPrivateKey(), $this->getPrime()); + $this->setPublicKey($publicKey); + } + return $this; + } + + /** + * Setter for the value of the public number + * + * @param string $number + * @param string $type + * @return Zend_Crypt_DiffieHellman + */ + public function setPublicKey($number, $type = self::NUMBER) + { + if ($type == self::BINARY) { + $number = $this->_math->fromBinary($number); + } + if (!preg_match("/^\d+$/", $number)) { + require_once('Zend/Crypt/DiffieHellman/Exception.php'); + throw new Zend_Crypt_DiffieHellman_Exception('invalid parameter; not a positive natural number'); + } + $this->_publicKey = (string) $number; + return $this; + } + + /** + * Returns own public key for communication to the second party to this + * transaction. + * + * @param string $type + * @return string + */ + public function getPublicKey($type = self::NUMBER) + { + if (is_null($this->_publicKey)) { + require_once 'Zend/Crypt/DiffieHellman/Exception.php'; + throw new Zend_Crypt_DiffieHellman_Exception('A public key has not yet been generated using a prior call to generateKeys()'); + } + if ($type == self::BINARY) { + return $this->_math->toBinary($this->_publicKey); + } elseif ($type == self::BTWOC) { + return $this->_math->btwoc($this->_math->toBinary($this->_publicKey)); + } + return $this->_publicKey; + } + + /** + * Compute the shared secret key based on the public key received from the + * the second party to this transaction. This should agree to the secret + * key the second party computes on our own public key. + * Once in agreement, the key is known to only to both parties. + * By default, the function expects the public key to be in binary form + * which is the typical format when being transmitted. + * + * If you need the binary form of the shared secret key, call + * getSharedSecretKey() with the optional parameter for Binary output. + * + * @param string $publicKey + * @param string $type + * @return mixed + */ + public function computeSecretKey($publicKey, $type = self::NUMBER, $output = self::NUMBER) + { + if ($type == self::BINARY) { + $publicKey = $this->_math->fromBinary($publicKey); + } + if (!preg_match("/^\d+$/", $publicKey)) { + require_once('Zend/Crypt/DiffieHellman/Exception.php'); + throw new Zend_Crypt_DiffieHellman_Exception('invalid parameter; not a positive natural number'); + } + if (function_exists('openssl_dh_compute_key') && self::$useOpenssl !== false) { + $this->_secretKey = openssl_dh_compute_key($publicKey, $this->getPublicKey()); + } else { + $this->_secretKey = $this->_math->powmod($publicKey, $this->getPrivateKey(), $this->getPrime()); + } + return $this->getSharedSecretKey($output); + } + + /** + * Return the computed shared secret key from the DiffieHellman transaction + * + * @param string $type + * @return string + */ + public function getSharedSecretKey($type = self::NUMBER) + { + if (!isset($this->_secretKey)) { + require_once('Zend/Crypt/DiffieHellman/Exception.php'); + throw new Zend_Crypt_DiffieHellman_Exception('A secret key has not yet been computed; call computeSecretKey()'); + } + if ($type == self::BINARY) { + return $this->_math->toBinary($this->_secretKey); + } elseif ($type == self::BTWOC) { + return $this->_math->btwoc($this->_math->toBinary($this->_secretKey)); + } + return $this->_secretKey; + } + + /** + * Setter for the value of the prime number + * + * @param string $number + * @return Zend_Crypt_DiffieHellman + */ + public function setPrime($number) + { + if (!preg_match("/^\d+$/", $number) || $number < 11) { + require_once('Zend/Crypt/DiffieHellman/Exception.php'); + throw new Zend_Crypt_DiffieHellman_Exception('invalid parameter; not a positive natural number or too small: should be a large natural number prime'); + } + $this->_prime = (string) $number; + return $this; + } + + /** + * Getter for the value of the prime number + * + * @return string + */ + public function getPrime() + { + if (!isset($this->_prime)) { + require_once('Zend/Crypt/DiffieHellman/Exception.php'); + throw new Zend_Crypt_DiffieHellman_Exception('No prime number has been set'); + } + return $this->_prime; + } + + + /** + * Setter for the value of the generator number + * + * @param string $number + * @return Zend_Crypt_DiffieHellman + */ + public function setGenerator($number) + { + if (!preg_match("/^\d+$/", $number) || $number < 2) { + require_once('Zend/Crypt/DiffieHellman/Exception.php'); + throw new Zend_Crypt_DiffieHellman_Exception('invalid parameter; not a positive natural number greater than 1'); + } + $this->_generator = (string) $number; + return $this; + } + + /** + * Getter for the value of the generator number + * + * @return string + */ + public function getGenerator() + { + if (!isset($this->_generator)) { + require_once('Zend/Crypt/DiffieHellman/Exception.php'); + throw new Zend_Crypt_DiffieHellman_Exception('No generator number has been set'); + } + return $this->_generator; + } + + /** + * Setter for the value of the private number + * + * @param string $number + * @param string $type + * @return Zend_Crypt_DiffieHellman + */ + public function setPrivateKey($number, $type = self::NUMBER) + { + if ($type == self::BINARY) { + $number = $this->_math->fromBinary($number); + } + if (!preg_match("/^\d+$/", $number)) { + require_once('Zend/Crypt/DiffieHellman/Exception.php'); + throw new Zend_Crypt_DiffieHellman_Exception('invalid parameter; not a positive natural number'); + } + $this->_privateKey = (string) $number; + return $this; + } + + /** + * Getter for the value of the private number + * + * @param string $type + * @return string + */ + public function getPrivateKey($type = self::NUMBER) + { + if (!$this->hasPrivateKey()) { + $this->setPrivateKey($this->_generatePrivateKey()); + } + if ($type == self::BINARY) { + return $this->_math->toBinary($this->_privateKey); + } elseif ($type == self::BTWOC) { + return $this->_math->btwoc($this->_math->toBinary($this->_privateKey)); + } + return $this->_privateKey; + } + + /** + * Check whether a private key currently exists. + * + * @return boolean + */ + public function hasPrivateKey() + { + return isset($this->_privateKey); + } + + /** + * Setter to pass an extension parameter which is used to create + * a specific BigInteger instance for a specific extension type. + * Allows manual setting of the class in case of an extension + * problem or bug. + * + * @param string $extension + * @return void + */ + public function setBigIntegerMath($extension = null) + { + /** + * @see Zend_Crypt_Math + */ + require_once 'Zend/Crypt/Math.php'; + $this->_math = new Zend_Crypt_Math($extension); + } + + /** + * In the event a private number/key has not been set by the user, + * or generated by ext/openssl, a best attempt will be made to + * generate a random key. Having a random number generator installed + * on linux/bsd is highly recommended! The alternative is not recommended + * for production unless without any other option. + * + * @return string + */ + protected function _generatePrivateKey() + { + $rand = $this->_math->rand($this->getGenerator(), $this->getPrime()); + return $rand; + } + +} \ No newline at end of file diff --git a/library/Zend/Crypt/DiffieHellman/Exception.php b/library/Zend/Crypt/DiffieHellman/Exception.php new file mode 100644 index 000000000..fcb6d740b --- /dev/null +++ b/library/Zend/Crypt/DiffieHellman/Exception.php @@ -0,0 +1,36 @@ +80 using internal algo) + * @todo Check if mhash() is a required alternative (will be PECL-only soon) + * @category Zend + * @package Zend_Crypt + * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Crypt_Hmac extends Zend_Crypt +{ + + /** + * The key to use for the hash + * + * @var string + */ + protected static $_key = null; + + /** + * pack() format to be used for current hashing method + * + * @var string + */ + protected static $_packFormat = null; + + /** + * Hashing algorithm; can be the md5/sha1 functions or any algorithm name + * listed in the output of PHP 5.1.2+ hash_algos(). + * + * @var string + */ + protected static $_hashAlgorithm = 'md5'; + + /** + * List of algorithms supported my mhash() + * + * @var array + */ + protected static $_supportedMhashAlgorithms = array('adler32',' crc32', 'crc32b', 'gost', + 'haval128', 'haval160', 'haval192', 'haval256', 'md4', 'md5', 'ripemd160', + 'sha1', 'sha256', 'tiger', 'tiger128', 'tiger160'); + + /** + * Constants representing the output mode of the hash algorithm + */ + const STRING = 'string'; + const BINARY = 'binary'; + + /** + * Performs a HMAC computation given relevant details such as Key, Hashing + * algorithm, the data to compute MAC of, and an output format of String, + * Binary notation or BTWOC. + * + * @param string $key + * @param string $hash + * @param string $data + * @param string $output + * @param boolean $internal + * @return string + */ + public static function compute($key, $hash, $data, $output = self::STRING) + { + // set the key + if (!isset($key) || empty($key)) { + require_once 'Zend/Crypt/Hmac/Exception.php'; + throw new Zend_Crypt_Hmac_Exception('provided key is null or empty'); + } + self::$_key = $key; + + // set the hash + self::_setHashAlgorithm($hash); + + // perform hashing and return + return self::_hash($data, $output); + } + + /** + * Setter for the hash method. + * + * @param string $hash + * @return Zend_Crypt_Hmac + */ + protected static function _setHashAlgorithm($hash) + { + if (!isset($hash) || empty($hash)) { + require_once 'Zend/Crypt/Hmac/Exception.php'; + throw new Zend_Crypt_Hmac_Exception('provided hash string is null or empty'); + } + + $hash = strtolower($hash); + $hashSupported = false; + + if (function_exists('hash_algos') && in_array($hash, hash_algos())) { + $hashSupported = true; + } + + if ($hashSupported === false && function_exists('mhash') && in_array($hash, self::$_supportedAlgosMhash)) { + $hashSupported = true; + } + + if ($hashSupported === false) { + require_once 'Zend/Crypt/Hmac/Exception.php'; + throw new Zend_Crypt_Hmac_Exception('hash algorithm provided is not supported on this PHP installation; please enable the hash or mhash extensions'); + } + self::$_hashAlgorithm = $hash; + } + + /** + * Perform HMAC and return the keyed data + * + * @param string $data + * @param string $output + * @param bool $internal Option to not use hash() functions for testing + * @return string + */ + protected static function _hash($data, $output = self::STRING, $internal = false) + { + if (function_exists('hash_hmac')) { + if ($output == self::BINARY) { + return hash_hmac(self::$_hashAlgorithm, $data, self::$_key, 1); + } + return hash_hmac(self::$_hashAlgorithm, $data, self::$_key); + } + + if (function_exists('mhash')) { + if ($output == self::BINARY) { + return mhash(self::_getMhashDefinition(self::$_hashAlgorithm), $data, self::$_key); + } + $bin = mhash(self::_getMhashDefinition(self::$_hashAlgorithm), $data, self::$_key); + return bin2hex($bin); + } + } + + /** + * Since MHASH accepts an integer constant representing the hash algorithm + * we need to make a small detour to get the correct integer matching our + * algorithm's name. + * + * @param string $hashAlgorithm + * @return integer + */ + protected static function _getMhashDefinition($hashAlgorithm) + { + for ($i = 0; $i <= mhash_count(); $i++) + { + $types[mhash_get_hash_name($i)] = $i; + } + return $types[strtoupper($hashAlgorithm)]; + } + +} \ No newline at end of file diff --git a/library/Zend/Crypt/Hmac/Exception.php b/library/Zend/Crypt/Hmac/Exception.php new file mode 100644 index 000000000..8ed2e5d51 --- /dev/null +++ b/library/Zend/Crypt/Hmac/Exception.php @@ -0,0 +1,36 @@ + 127) { + return "\x00" . $long; + } + return $long; + } + + /** + * Translate a binary form into a big integer string + * + * @param string $binary + * @return string + */ + public function fromBinary($binary) { + return $this->_math->binaryToInteger($binary); + } + + /** + * Translate a big integer string into a binary form + * + * @param string $integer + * @return string + */ + public function toBinary($integer) + { + return $this->_math->integerToBinary($integer); + } + +} diff --git a/library/Zend/Crypt/Math/BigInteger.php b/library/Zend/Crypt/Math/BigInteger.php new file mode 100644 index 000000000..bb02dbb39 --- /dev/null +++ b/library/Zend/Crypt/Math/BigInteger.php @@ -0,0 +1,117 @@ +_loadAdapter($extension); + } + + /** + * Redirect all public method calls to the wrapped extension object. + * + * @param string $methodName + * @param array $args + * @throws Zend_Crypt_Math_BigInteger_Exception + */ + public function __call($methodName, $args) + { + if(!method_exists($this->_math, $methodName)) { + require_once 'Zend/Crypt/Math/BigInteger/Exception.php'; + throw new Zend_Crypt_Math_BigInteger_Exception('invalid method call: ' . get_class($this->_math) . '::' . $methodName . '() does not exist'); + } + return call_user_func_array(array($this->_math, $methodName), $args); + } + + /** + * @param string $extension + * @throws Zend_Crypt_Math_BigInteger_Exception + */ + protected function _loadAdapter($extension = null) + { + if (is_null($extension)) { + if (extension_loaded('gmp')) { + $extension = 'gmp'; + //} elseif (extension_loaded('big_int')) { + // $extension = 'big_int'; + } else { + $extension = 'bcmath'; + } + } + if($extension == 'gmp' && extension_loaded('gmp')) { + require_once 'Zend/Crypt/Math/BigInteger/Gmp.php'; + $this->_math = new Zend_Crypt_Math_BigInteger_Gmp(); + //} elseif($extension == 'bigint' && extension_loaded('big_int')) { + // require_once 'Zend/Crypt_Math/BigInteger/Bigint.php'; + // $this->_math = new Zend_Crypt_Math_BigInteger_Bigint(); + } elseif ($extension == 'bcmath') { + require_once 'Zend/Crypt/Math/BigInteger/Bcmath.php'; + $this->_math = new Zend_Crypt_Math_BigInteger_Bcmath(); + } else { + require_once 'Zend/Crypt/Math/BigInteger/Exception.php'; + throw new Zend_Crypt_Math_BigInteger_Exception($extension . ' big integer precision math support not detected'); + } + } + +} \ No newline at end of file diff --git a/library/Zend/Crypt/Math/BigInteger/Bcmath.php b/library/Zend/Crypt/Math/BigInteger/Bcmath.php new file mode 100644 index 000000000..092e5b4c2 --- /dev/null +++ b/library/Zend/Crypt/Math/BigInteger/Bcmath.php @@ -0,0 +1,203 @@ + 0) { + $return = chr(bcmod($operand, 256)) . $return; + $operand = bcdiv($operand, 256); + } + if (ord($return[0]) > 127) { + $return = chr(0) . $return; + } + return $return; + } + + /**public function integerToBinary($operand) + { + $return = ''; + while(bccomp($operand, '0')) { + $return .= chr(bcmod($operand, '256')); + $operand = bcdiv($operand, '256'); + } + return $return; + }**/ // Prior version for referenced offset + + + public function hexToDecimal($operand) + { + $return = '0'; + while(strlen($hex)) { + $hex = hexdec(substr($operand, 0, 4)); + $dec = bcadd(bcmul($return, 65536), $hex); + $operand = substr($operand, 4); + } + return $return; + } + +} \ No newline at end of file diff --git a/library/Zend/Crypt/Math/BigInteger/Exception.php b/library/Zend/Crypt/Math/BigInteger/Exception.php new file mode 100644 index 000000000..201242db2 --- /dev/null +++ b/library/Zend/Crypt/Math/BigInteger/Exception.php @@ -0,0 +1,36 @@ + '7') { + $bigInt = '00' . $bigInt; + } + $return = pack("H*", $bigInt); + return $return; + } + + + public function hexToDecimal($operand) + { + $return = '0'; + while(strlen($hex)) { + $hex = hexdec(substr($operand, 0, 4)); + $dec = gmp_add(gmp_mul($return, 65536), $hex); + $operand = substr($operand, 4); + } + return $return; + } + +} \ No newline at end of file diff --git a/library/Zend/Crypt/Math/BigInteger/Interface.php b/library/Zend/Crypt/Math/BigInteger/Interface.php new file mode 100644 index 000000000..f0cce8b5d --- /dev/null +++ b/library/Zend/Crypt/Math/BigInteger/Interface.php @@ -0,0 +1,51 @@ +setOptions($options); + } + } + + public function setOptions(array $options) + { + if (isset($options['passPhrase'])) { + $this->_passPhrase = $options['passPhrase']; + } + foreach ($options as $option=>$value) { + switch ($option) { + case 'pemString': + $this->setPemString($value); + break; + case 'pemPath': + $this->setPemPath($value); + break; + case 'certificateString': + $this->setCertificateString($value); + break; + case 'certificatePath': + $this->setCertificatePath($value); + break; + case 'hashAlgorithm': + $this->setHashAlgorithm($value); + break; + } + } + } + + public function getPrivateKey() + { + return $this->_privateKey; + } + + public function getPublicKey() + { + return $this->_publicKey; + } + + /** + * @param string $data + * @param Zend_Crypt_Rsa_Key_Private $privateKey + * @param string $format + * @return string + */ + public function sign($data, Zend_Crypt_Rsa_Key_Private $privateKey = null, $format = null) + { + $signature = ''; + if (isset($privateKey)) { + $opensslKeyResource = $privateKey->getOpensslKeyResource(); + } else { + $opensslKeyResource = $this->_privateKey->getOpensslKeyResource(); + } + $result = openssl_sign( + $data, $signature, + $opensslKeyResource, + $this->getHashAlgorithm() + ); + if ($format == self::BASE64) { + return base64_encode($signature); + } + return $signature; + } + + /** + * @param string $data + * @param string $signature + * @param string $format + * @return string + */ + public function verifySignature($data, $signature, $format = null) + { + if ($format == self::BASE64) { + $signature = base64_decode($signature); + } + $result = openssl_verify($data, $signature, + $this->getPublicKey()->getOpensslKeyResource(), + $this->getHashAlgorithm()); + return $result; + } + + /** + * @param string $data + * @param Zend_Crypt_Rsa_Key $key + * @param string $format + * @return string + */ + public function encrypt($data, Zend_Crypt_Rsa_Key $key, $format = null) + { + $encrypted = ''; + $function = 'openssl_public_encrypt'; + if ($key instanceof Zend_Crypt_Rsa_Key_Private) { + $function = 'openssl_private_encrypt'; + } + $function($data, $encrypted, $key->getOpensslKeyResource()); + if ($format == self::BASE64) { + return base64_encode($encrypted); + } + return $encrypted; + } + + /** + * @param string $data + * @param Zend_Crypt_Rsa_Key $key + * @param string $format + * @return string + */ + public function decrypt($data, Zend_Crypt_Rsa_Key $key, $format = null) + { + $decrypted = ''; + if ($format == self::BASE64) { + $data = base64_decode($data); + } + $function = 'openssl_private_decrypt'; + if ($key instanceof Zend_Crypt_Rsa_Key_Public) { + $function = 'openssl_public_decrypt'; + } + $function($data, $decrypted, $key->getOpensslKeyResource()); + return $decrypted; + } + + public function generateKeys(array $configargs = null) + { + $config = null; + $passPhrase = null; + if (!is_null($configargs)) { + if (isset($configargs['passPhrase'])) { + $passPhrase = $configargs['passPhrase']; + unset($configargs['passPhrase']); + } + $config = $this->_parseConfigArgs($configargs); + } + $privateKey = null; + $publicKey = null; + $resource = openssl_pkey_new($config); + // above fails on PHP 5.3 + openssl_pkey_export($resource, $private, $passPhrase); + $privateKey = new Zend_Crypt_Rsa_Key_Private($private, $passPhrase); + $details = openssl_pkey_get_details($resource); + $publicKey = new Zend_Crypt_Rsa_Key_Public($details['key']); + $return = new ArrayObject(array( + 'privateKey'=>$privateKey, + 'publicKey'=>$publicKey + ), ArrayObject::ARRAY_AS_PROPS); + return $return; + } + + /** + * @param string $value + */ + public function setPemString($value) + { + $this->_pemString = $value; + $this->_privateKey = new Zend_Crypt_Rsa_Key_Private($this->_pemString, $this->_passPhrase); + $this->_publicKey = $this->_privateKey->getPublicKey(); + } + + public function setPemPath($value) + { + $this->_pemPath = $value; + $this->setPemString(file_get_contents($this->_pemPath)); + } + + public function setCertificateString($value) + { + $this->_certificateString = $value; + $this->_publicKey = new Zend_Crypt_Rsa_Key_Public($this->_certificateString, $this->_passPhrase); + } + + public function setCertificatePath($value) + { + $this->_certificatePath = $value; + $this->setCertificateString(file_get_contents($this->_certificatePath)); + } + + public function setHashAlgorithm($name) + { + switch ($name) { + case 'md2': + $this->_hashAlgorithm = OPENSSL_ALGO_MD2; + break; + case 'md4': + $this->_hashAlgorithm = OPENSSL_ALGO_MD4; + break; + case 'md5': + $this->_hashAlgorithm = OPENSSL_ALGO_MD5; + break; + } + } + + /** + * @return string + */ + public function getPemString() + { + return $this->_pemString; + } + + public function getPemPath() + { + return $this->_pemPath; + } + + public function getCertificateString() + { + return $this->_certificateString; + } + + public function getCertificatePath() + { + return $this->_certificatePath; + } + + public function getHashAlgorithm() + { + return $this->_hashAlgorithm; + } + + protected function _parseConfigArgs(array $config = null) + { + $configs = array(); + if (isset($config['privateKeyBits'])) { + $configs['private_key_bits'] = $config['privateKeyBits']; + } + if (!empty($configs)) { + return $configs; + } + return null; + } + +} \ No newline at end of file diff --git a/library/Zend/Crypt/Rsa/Key.php b/library/Zend/Crypt/Rsa/Key.php new file mode 100644 index 000000000..b4176742d --- /dev/null +++ b/library/Zend/Crypt/Rsa/Key.php @@ -0,0 +1,95 @@ +_opensslKeyResource; + } + + /** + * @return string + * @throws Zend_Crypt_Exception + */ + public function toString() + { + if (!empty($this->_pemString)) { + return $this->_pemString; + } elseif (!empty($this->_certificateString)) { + return $this->_certificateString; + } + /** + * @see Zend_Crypt_Exception + */ + require_once 'Zend/Crypt/Exception.php'; + throw new Zend_Crypt_Exception('No public key string representation is available'); + } + + /** + * @return string + */ + public function __toString() + { + return $this->toString(); + } + + public function count() + { + return $this->_details['bits']; + } + + public function getType() + { + return $this->_details['type']; + } +} \ No newline at end of file diff --git a/library/Zend/Crypt/Rsa/Key/Private.php b/library/Zend/Crypt/Rsa/Key/Private.php new file mode 100644 index 000000000..632f6f01e --- /dev/null +++ b/library/Zend/Crypt/Rsa/Key/Private.php @@ -0,0 +1,75 @@ +_pemString = $pemString; + $this->_parse($passPhrase); + } + + /** + * @param string $passPhrase + * @throws Zend_Crypt_Exception + */ + protected function _parse($passPhrase) + { + $result = openssl_get_privatekey($this->_pemString, $passPhrase); + if (!$result) { + /** + * @see Zend_Crypt_Exception + */ + require_once 'Zend/Crypt/Exception.php'; + throw new Zend_Crypt_Exception('Unable to load private key'); + } + $this->_opensslKeyResource = $result; + $this->_details = openssl_pkey_get_details($this->_opensslKeyResource); + } + + public function getPublicKey() + { + if (is_null($this->_publicKey)) { + /** + * @see Zend_Crypt_Rsa_Key_Public + */ + require_once 'Zend/Crypt/Rsa/Key/Public.php'; + $this->_publicKey = new Zend_Crypt_Rsa_Key_Public($this->_details['key']); + } + return $this->_publicKey; + } + +} \ No newline at end of file diff --git a/library/Zend/Crypt/Rsa/Key/Public.php b/library/Zend/Crypt/Rsa/Key/Public.php new file mode 100644 index 000000000..567100a0e --- /dev/null +++ b/library/Zend/Crypt/Rsa/Key/Public.php @@ -0,0 +1,74 @@ +_parse($string); + } + + /** + * @param string $string + * @throws Zend_Crypt_Exception + */ + protected function _parse($string) + { + if (preg_match("/^-----BEGIN CERTIFICATE-----/", $string)) { + $this->_certificateString = $string; + } else { + $this->_pemString = $string; + } + $result = openssl_get_publickey($string); + if (!$result) { + /** + * @see Zend_Crypt_Exception + */ + require_once 'Zend/Crypt/Exception.php'; + throw new Zend_Crypt_Exception('Unable to load public key'); + } + //openssl_pkey_export($result, $public); + //$this->_pemString = $public; + $this->_opensslKeyResource = $result; + $this->_details = openssl_pkey_get_details($this->_opensslKeyResource); + } + + public function getCertificate() + { + return $this->_certificateString; + } + +} \ No newline at end of file diff --git a/library/Zend/Currency.php b/library/Zend/Currency.php new file mode 100644 index 000000000..6906695f2 --- /dev/null +++ b/library/Zend/Currency.php @@ -0,0 +1,890 @@ + Position for the currency sign + * 'script' => Script for the output + * 'format' => Locale for numeric output + * 'display' => Currency detail to show + * 'precision' => Precision for the currency + * 'name' => Name for this currency + * 'currency' => 3 lettered international abbreviation + * 'symbol' => Currency symbol + * 'locale' => Locale for this currency + * 'value' => Money value + * 'service' => Exchange service to use + * + * @var array + * @see Zend_Locale + */ + protected $_options = array( + 'position' => self::STANDARD, + 'script' => null, + 'format' => null, + 'display' => self::NO_SYMBOL, + 'precision' => 2, + 'name' => null, + 'currency' => null, + 'symbol' => null, + 'locale' => null, + 'value' => 0, + 'service' => null + ); + + /** + * Creates a currency instance. Every supressed parameter is used from the actual or the given locale. + * + * @param string|array $options OPTIONAL Options array or currency short name + * when string is given + * @param string|Zend_Locale $locale OPTIONAL locale name + * @throws Zend_Currency_Exception When currency is invalid + */ + public function __construct($options = null, $locale = null) + { + if (is_array($options)) { + $this->setLocale($locale); + $this->setFormat($options); + } else if (Zend_Locale::isLocale($options, false, false)) { + $this->setLocale($options); + $options = $locale; + } else { + $this->setLocale($locale); + } + + // Get currency details + if (!isset($this->_options['currency']) || !is_array($options)) { + $this->_options['currency'] = self::getShortName($options, $this->_options['locale']); + } + + if (!isset($this->_options['name']) || !is_array($options)) { + $this->_options['name'] = self::getName($options, $this->_options['locale']); + } + + if (!isset($this->_options['symbol']) || !is_array($options)) { + $this->_options['symbol'] = self::getSymbol($options, $this->_options['locale']); + } + + if (($this->_options['currency'] === null) and ($this->_options['name'] === null)) { + require_once 'Zend/Currency/Exception.php'; + throw new Zend_Currency_Exception("Currency '$options' not found"); + } + + // Get the format + if (!empty($this->_options['symbol'])) { + $this->_options['display'] = self::USE_SYMBOL; + } else if (!empty($this->_options['currency'])) { + $this->_options['display'] = self::USE_SHORTNAME; + } + } + + /** + * Returns a localized currency string + * + * @param integer|float $value OPTIONAL Currency value + * @param array $options OPTIONAL options to set temporary + * @throws Zend_Currency_Exception When the value is not a number + * @return string + */ + public function toCurrency($value = null, array $options = array()) + { + if ($value === null) { + if (is_array($value) && isset($options['value'])) { + $value = $options['value']; + } else { + $value = $this->_options['value']; + } + } + + if (is_array($value)) { + $options += $value; + if (isset($options['value'])) { + $value = $options['value']; + } + } + + // Validate the passed number + if (!(isset($value)) or (is_numeric($value) === false)) { + require_once 'Zend/Currency/Exception.php'; + throw new Zend_Currency_Exception("Value '$value' has to be numeric"); + } + + if (isset($options['currency'])) { + if (!isset($options['locale'])) { + $options['locale'] = $this->_options['locale']; + } + + $options['currency'] = self::getShortName($options['currency'], $options['locale']); + $options['name'] = self::getName($options['currency'], $options['locale']); + $options['symbol'] = self::getSymbol($options['currency'], $options['locale']); + } + + $options = $this->_checkOptions($options) + $this->_options; + + // Format the number + $format = $options['format']; + $locale = $options['locale']; + if (empty($format)) { + $format = Zend_Locale_Data::getContent($locale, 'currencynumber'); + } else if (Zend_Locale::isLocale($format, true, false)) { + $locale = $format; + $format = Zend_Locale_Data::getContent($format, 'currencynumber'); + } + + $original = $value; + $value = Zend_Locale_Format::toNumber($value, array('locale' => $locale, + 'number_format' => $format, + 'precision' => $options['precision'])); + + if ($options['position'] !== self::STANDARD) { + $value = str_replace('¤', '', $value); + $space = ''; + if (iconv_strpos($value, ' ') !== false) { + $value = str_replace(' ', '', $value); + $space = ' '; + } + + if ($options['position'] == self::LEFT) { + $value = '¤' . $space . $value; + } else { + $value = $value . $space . '¤'; + } + } + + // Localize the number digits + if (empty($options['script']) === false) { + $value = Zend_Locale_Format::convertNumerals($value, 'Latn', $options['script']); + } + + // Get the sign to be placed next to the number + if (is_numeric($options['display']) === false) { + $sign = $options['display']; + } else { + switch($options['display']) { + case self::USE_SYMBOL: + $sign = $this->_extractPattern($options['symbol'], $original); + break; + + case self::USE_SHORTNAME: + $sign = $options['currency']; + break; + + case self::USE_NAME: + $sign = $options['name']; + break; + + default: + $sign = ''; + $value = str_replace(' ', '', $value); + break; + } + } + + $value = str_replace('¤', $sign, $value); + return $value; + } + + /** + * Internal method to extract the currency pattern + * when a choice is given based on the given value + * + * @param string $pattern + * @param float|integer $value + * @return string + */ + private function _extractPattern($pattern, $value) + { + if (strpos($pattern, '|') === false) { + return $pattern; + } + + $patterns = explode('|', $pattern); + $token = $pattern; + $value = trim(str_replace('¤', '', $value)); + krsort($patterns); + foreach($patterns as $content) { + if (strpos($content, '<') !== false) { + $check = iconv_substr($content, 0, iconv_strpos($content, '<')); + $token = iconv_substr($content, iconv_strpos($content, '<') + 1); + if ($check < $value) { + return $token; + } + } else { + $check = iconv_substr($content, 0, iconv_strpos($content, '≤')); + $token = iconv_substr($content, iconv_strpos($content, '≤') + 1); + if ($check <= $value) { + return $token; + } + } + + } + + return $token; + } + + /** + * Sets the formating options of the localized currency string + * If no parameter is passed, the standard setting of the + * actual set locale will be used + * + * @param array $options (Optional) Options to set + * @return Zend_Currency + */ + public function setFormat(array $options = array()) + { + $this->_options = $this->_checkOptions($options) + $this->_options; + return $this; + } + + /** + * Internal function for checking static given locale parameter + * + * @param string $currency (Optional) Currency name + * @param string|Zend_Locale $locale (Optional) Locale to display informations + * @throws Zend_Currency_Exception When locale contains no region + * @return string The extracted locale representation as string + */ + private function _checkParams($currency = null, $locale = null) + { + // Manage the params + if ((empty($locale)) and (!empty($currency)) and + (Zend_Locale::isLocale($currency, true, false))) { + $locale = $currency; + $currency = null; + } + + // Validate the locale and get the country short name + $country = null; + if ((Zend_Locale::isLocale($locale, true, false)) and (strlen($locale) > 4)) { + $country = substr($locale, (strpos($locale, '_') + 1)); + } else { + require_once 'Zend/Currency/Exception.php'; + throw new Zend_Currency_Exception("No region found within the locale '" . (string) $locale . "'"); + } + + // Get the available currencies for this country + $data = Zend_Locale_Data::getContent($locale, 'currencytoregion', $country); + if ((empty($currency) === false) and (empty($data) === false)) { + $abbreviation = $currency; + } else { + $abbreviation = $data; + } + + return array('locale' => $locale, 'currency' => $currency, 'name' => $abbreviation, 'country' => $country); + } + + /** + * Returns the actual or details of other currency symbols, + * when no symbol is available it returns the currency shortname (f.e. FIM for Finnian Mark) + * + * @param string $currency (Optional) Currency name + * @param string|Zend_Locale $locale (Optional) Locale to display informations + * @return string + */ + public function getSymbol($currency = null, $locale = null) + { + if (($currency === null) and ($locale === null)) { + return $this->_options['symbol']; + } + + $params = self::_checkParams($currency, $locale); + + // Get the symbol + $symbol = Zend_Locale_Data::getContent($params['locale'], 'currencysymbol', $params['currency']); + if (empty($symbol) === true) { + $symbol = Zend_Locale_Data::getContent($params['locale'], 'currencysymbol', $params['name']); + } + + if (empty($symbol) === true) { + return null; + } + + return $symbol; + } + + /** + * Returns the actual or details of other currency shortnames + * + * @param string $currency OPTIONAL Currency's name + * @param string|Zend_Locale $locale OPTIONAL The locale + * @return string + */ + public function getShortName($currency = null, $locale = null) + { + if (($currency === null) and ($locale === null)) { + return $this->_options['currency']; + } + + $params = self::_checkParams($currency, $locale); + + // Get the shortname + if (empty($params['currency']) === true) { + return $params['name']; + } + + $list = Zend_Locale_Data::getContent($params['locale'], 'currencytoname', $params['currency']); + if (empty($list) === true) { + $list = Zend_Locale_Data::getContent($params['locale'], 'nametocurrency', $params['currency']); + if (empty($list) === false) { + $list = $params['currency']; + } + } + + if (empty($list) === true) { + return null; + } + + return $list; + } + + /** + * Returns the actual or details of other currency names + * + * @param string $currency (Optional) Currency's short name + * @param string|Zend_Locale $locale (Optional) The locale + * @return string + */ + public function getName($currency = null, $locale = null) + { + if (($currency === null) and ($locale === null)) { + return $this->_options['name']; + } + + $params = self::_checkParams($currency, $locale); + + // Get the name + $name = Zend_Locale_Data::getContent($params['locale'], 'nametocurrency', $params['currency']); + if (empty($name) === true) { + $name = Zend_Locale_Data::getContent($params['locale'], 'nametocurrency', $params['name']); + } + + if (empty($name) === true) { + return null; + } + + return $name; + } + + /** + * Returns a list of regions where this currency is or was known + * + * @param string $currency OPTIONAL Currency's short name + * @throws Zend_Currency_Exception When no currency was defined + * @return array List of regions + */ + public function getRegionList($currency = null) + { + if ($currency === null) { + $currency = $this->_options['currency']; + } + + if (empty($currency) === true) { + require_once 'Zend/Currency/Exception.php'; + throw new Zend_Currency_Exception('No currency defined'); + } + + $data = Zend_Locale_Data::getContent('', 'regiontocurrency', $currency); + + $result = explode(' ', $data); + return $result; + } + + /** + * Returns a list of currencies which are used in this region + * a region name should be 2 charachters only (f.e. EG, DE, US) + * If no region is given, the actual region is used + * + * @param string $region OPTIONAL Region to return the currencies for + * @return array List of currencies + */ + public function getCurrencyList($region = null) + { + if (empty($region) === true) { + if (strlen($this->_options['locale']) > 4) { + $region = substr($this->_options['locale'], (strpos($this->_options['locale'], '_') + 1)); + } + } + + return Zend_Locale_Data::getList('', 'regiontocurrency', $region); + } + + /** + * Returns the actual currency name + * + * @return string + */ + public function toString() + { + return $this->toCurrency(); + } + + /** + * Returns the currency name + * + * @return string + */ + public function __toString() + { + return $this->toString(); + } + + /** + * Returns the set cache + * + * @return Zend_Cache_Core The set cache + */ + public static function getCache() + { + $cache = Zend_Locale_Data::getCache(); + return $cache; + } + + /** + * Sets a cache for Zend_Currency + * + * @param Zend_Cache_Core $cache Cache to set + * @return void + */ + public static function setCache(Zend_Cache_Core $cache) + { + Zend_Locale_Data::setCache($cache); + } + + /** + * Returns true when a cache is set + * + * @return boolean + */ + public static function hasCache() + { + return Zend_Locale_Data::hasCache(); + } + + /** + * Removes any set cache + * + * @return void + */ + public static function removeCache() + { + Zend_Locale_Data::removeCache(); + } + + /** + * Clears all set cache data + * + * @return void + */ + public static function clearCache() + { + Zend_Locale_Data::clearCache(); + } + + /** + * Sets a new locale for data retreivement + * Example: 'de_XX' will be set to 'de' because 'de_XX' does not exist + * 'xx_YY' will be set to 'root' because 'xx' does not exist + * + * @param string|Zend_Locale $locale (Optional) Locale for parsing input + * @throws Zend_Currency_Exception When the given locale does not exist + * @return Zend_Currency Provides fluent interface + */ + public function setLocale($locale = null) + { + require_once 'Zend/Locale.php'; + try { + $locale = Zend_Locale::findLocale($locale); + if (strlen($locale) > 4) { + $this->_options['locale'] = $locale; + } else { + require_once 'Zend/Currency/Exception.php'; + throw new Zend_Currency_Exception("No region found within the locale '" . (string) $locale . "'"); + } + } catch (Zend_Locale_Exception $e) { + require_once 'Zend/Currency/Exception.php'; + throw new Zend_Currency_Exception($e->getMessage()); + } + + // Get currency details + $this->_options['currency'] = $this->getShortName(null, $this->_options['locale']); + $this->_options['name'] = $this->getName(null, $this->_options['locale']); + $this->_options['symbol'] = $this->getSymbol(null, $this->_options['locale']); + + return $this; + } + + /** + * Returns the actual set locale + * + * @return string + */ + public function getLocale() + { + return $this->_options['locale']; + } + + /** + * Returns the value + * + * @return float + */ + public function getValue() + { + return $this->_options['value']; + } + + /** + * Adds a currency + * + * @param float|integer|Zend_Currency $value Add this value to currency + * @param string|Zend_Currency $currency The currency to add + * @return Zend_Currency + */ + public function setValue($value, $currency = null) + { + $this->_options['value'] = $this->_exchangeCurrency($value, $currency); + return $this; + } + + /** + * Adds a currency + * + * @param float|integer|Zend_Currency $value Add this value to currency + * @param string|Zend_Currency $currency The currency to add + * @return Zend_Currency + */ + public function add($value, $currency = null) + { + $value = $this->_exchangeCurrency($value, $currency); + $this->_options['value'] += (float) $value; + return $this; + } + + /** + * Substracts a currency + * + * @param float|integer|Zend_Currency $value Substracts this value from currency + * @param string|Zend_Currency $currency The currency to substract + * @return Zend_Currency + */ + public function sub($value, $currency = null) + { + $value = $this->_exchangeCurrency($value, $currency); + $this->_options['value'] -= (float) $value; + return $this; + } + + /** + * Divides a currency + * + * @param float|integer|Zend_Currency $value Divides this value from currency + * @param string|Zend_Currency $currency The currency to divide + * @return Zend_Currency + */ + public function div($value, $currency = null) + { + $value = $this->_exchangeCurrency($value, $currency); + $this->_options['value'] /= (float) $value; + return $this; + } + + /** + * Multiplies a currency + * + * @param float|integer|Zend_Currency $value Multiplies this value from currency + * @param string|Zend_Currency $currency The currency to multiply + * @return Zend_Currency + */ + public function mul($value, $currency = null) + { + $value = $this->_exchangeCurrency($value, $currency); + $this->_options['value'] *= (float) $value; + return $this; + } + + /** + * Calculates the modulo from a currency + * + * @param float|integer|Zend_Currency $value Calculate modulo from this value + * @param string|Zend_Currency $currency The currency to calculate the modulo + * @return Zend_Currency + */ + public function mod($value, $currency = null) + { + $value = $this->_exchangeCurrency($value, $currency); + $this->_options['value'] %= (float) $value; + return $this; + } + + /** + * Compares two currencies + * + * @param float|integer|Zend_Currency $value Compares the currency with this value + * @param string|Zend_Currency $currency The currency to compare this value from + * @return Zend_Currency + */ + public function compare($value, $currency = null) + { + $value = $this->_exchangeCurrency($value, $currency); + $value = $this->_options['value'] - $value; + if ($value < 0) { + return -1; + } else if ($value > 0) { + return 1; + } + + return 0; + } + + /** + * Returns true when the two currencies are equal + * + * @param float|integer|Zend_Currency $value Compares the currency with this value + * @param string|Zend_Currency $currency The currency to compare this value from + * @return boolean + */ + public function equals($value, $currency = null) + { + $value = $this->_exchangeCurrency($value, $currency); + if ($this->_options['value'] == $value) { + return true; + } + + return false; + } + + /** + * Returns true when the currency is more than the given value + * + * @param float|integer|Zend_Currency $value Compares the currency with this value + * @param string|Zend_Currency $currency The currency to compare this value from + * @return boolean + */ + public function isMore($value, $currency = null) + { + $value = $this->_exchangeCurrency($value, $currency); + if ($this->_options['value'] > $value) { + return true; + } + + return false; + } + + /** + * Returns true when the currency is less than the given value + * + * @param float|integer|Zend_Currency $value Compares the currency with this value + * @param string|Zend_Currency $currency The currency to compare this value from + * @return boolean + */ + public function isLess($value, $currency = null) + { + $value = $this->_exchangeCurrency($value, $currency); + if ($this->_options['value'] < $value) { + return true; + } + + return false; + + } + + /** + * Internal method which calculates the exchanges currency + * + * @param float|integer|Zend_Currency $value Compares the currency with this value + * @param string|Zend_Currency $currency The currency to compare this value from + * @return unknown + */ + protected function _exchangeCurrency($value, $currency) + { + if ($value instanceof Zend_Currency) { + $currency = $value->getShortName(); + $value = $value->getValue(); + } else { + $currency = $this->getShortName($currency, $this->getLocale()); + } + + $rate = 1; + if ($currency !== $this->getShortName()) { + $service = $this->getService(); + if (!($service instanceof Zend_Currency_CurrencyInterface)) { + require_once 'Zend/Currency/Exception.php'; + throw new Zend_Currency_Exception('No exchange service applied'); + } + + $rate = $service->getRate($currency, $this->getShortName()); + } + + $value *= $rate; + return $value; + } + + /** + * Returns the set service class + * + * @return Zend_Service + */ + public function getService() + { + return $this->_options['service']; + } + + /** + * Sets a new exchange service + * + * @param string|Zend_Currency_CurrencyInterface $service Service class + * @return Zend_Currency + */ + public function setService($service) + { + if (is_string($service)) { + require_once 'Zend/Loader.php'; + if (!class_exists($service)) { + $file = str_replace('_', DIRECTORY_SEPARATOR, $service) . '.php'; + if (Zend_Loader::isReadable($file)) { + Zend_Loader::loadClass($class); + } + } + + $service = new $service; + } + + if (!($service instanceof Zend_Currency_CurrencyInterface)) { + require_once 'Zend/Currency/Exception.php'; + throw new Zend_Currency_Exception('A currency service must implement Zend_Currency_CurrencyInterface'); + } + + $this->_options['service'] = $service; + return $this; + } + + /** + * Internal method for checking the options array + * + * @param array $options Options to check + * @throws Zend_Currency_Exception On unknown position + * @throws Zend_Currency_Exception On unknown locale + * @throws Zend_Currency_Exception On unknown display + * @throws Zend_Currency_Exception On precision not between -1 and 30 + * @throws Zend_Currency_Exception On problem with script conversion + * @throws Zend_Currency_Exception On unknown options + * @return array + */ + protected function _checkOptions(array $options = array()) + { + if (count($options) === 0) { + return $this->_options; + } + + foreach ($options as $name => $value) { + $name = strtolower($name); + if ($name !== 'format') { + if (gettype($value) === 'string') { + $value = strtolower($value); + } + } + + switch($name) { + case 'position': + if (($value !== self::STANDARD) and ($value !== self::RIGHT) and ($value !== self::LEFT)) { + require_once 'Zend/Currency/Exception.php'; + throw new Zend_Currency_Exception("Unknown position '" . $value . "'"); + } + + break; + + case 'format': + if ((empty($value) === false) and (Zend_Locale::isLocale($value, null, false) === false)) { + if (!is_string($value) || (strpos($value, '0') === false)) { + require_once 'Zend/Currency/Exception.php'; + throw new Zend_Currency_Exception("'" . + ((gettype($value) === 'object') ? get_class($value) : $value) + . "' is no format token"); + } + } + break; + + case 'display': + if (is_numeric($value) and ($value !== self::NO_SYMBOL) and ($value !== self::USE_SYMBOL) and + ($value !== self::USE_SHORTNAME) and ($value !== self::USE_NAME)) { + require_once 'Zend/Currency/Exception.php'; + throw new Zend_Currency_Exception("Unknown display '$value'"); + } + break; + + case 'precision': + if ($value === null) { + $value = -1; + } + + if (($value < -1) or ($value > 30)) { + require_once 'Zend/Currency/Exception.php'; + throw new Zend_Currency_Exception("'$value' precision has to be between -1 and 30."); + } + break; + + case 'script': + try { + Zend_Locale_Format::convertNumerals(0, $options['script']); + } catch (Zend_Locale_Exception $e) { + require_once 'Zend/Currency/Exception.php'; + throw new Zend_Currency_Exception($e->getMessage()); + } + break; + + default: + break; + } + } + + return $options; + } +} diff --git a/library/Zend/Currency/CurrencyInterface.php b/library/Zend/Currency/CurrencyInterface.php new file mode 100644 index 000000000..a331f7c4b --- /dev/null +++ b/library/Zend/Currency/CurrencyInterface.php @@ -0,0 +1,39 @@ + 'iso', // format for date strings 'iso' or 'php' + 'fix_dst' => true, // fix dst on summer/winter time change + 'extend_month' => false, // false - addMonth like SQL, true like excel + 'cache' => null, // cache to set + 'timesync' => null // timesync server to set + ); + + // Class wide Date Constants + const DAY = 'dd'; + const DAY_SHORT = 'd'; + const DAY_SUFFIX = 'SS'; + const DAY_OF_YEAR = 'D'; + const WEEKDAY = 'EEEE'; + const WEEKDAY_SHORT = 'EEE'; + const WEEKDAY_NARROW = 'E'; + const WEEKDAY_NAME = 'EE'; + const WEEKDAY_8601 = 'eee'; + const WEEKDAY_DIGIT = 'e'; + const WEEK = 'ww'; + const MONTH = 'MM'; + const MONTH_SHORT = 'M'; + const MONTH_DAYS = 'ddd'; + const MONTH_NAME = 'MMMM'; + const MONTH_NAME_SHORT = 'MMM'; + const MONTH_NAME_NARROW = 'MMMMM'; + const YEAR = 'y'; + const YEAR_SHORT = 'yy'; + const YEAR_8601 = 'Y'; + const YEAR_SHORT_8601 = 'YY'; + const LEAPYEAR = 'l'; + const MERIDIEM = 'a'; + const SWATCH = 'B'; + const HOUR = 'HH'; + const HOUR_SHORT = 'H'; + const HOUR_AM = 'hh'; + const HOUR_SHORT_AM = 'h'; + const MINUTE = 'mm'; + const MINUTE_SHORT = 'm'; + const SECOND = 'ss'; + const SECOND_SHORT = 's'; + const MILLISECOND = 'S'; + const TIMEZONE_NAME = 'zzzz'; + const DAYLIGHT = 'I'; + const GMT_DIFF = 'Z'; + const GMT_DIFF_SEP = 'ZZZZ'; + const TIMEZONE = 'z'; + const TIMEZONE_SECS = 'X'; + const ISO_8601 = 'c'; + const RFC_2822 = 'r'; + const TIMESTAMP = 'U'; + const ERA = 'G'; + const ERA_NAME = 'GGGG'; + const ERA_NARROW = 'GGGGG'; + const DATES = 'F'; + const DATE_FULL = 'FFFFF'; + const DATE_LONG = 'FFFF'; + const DATE_MEDIUM = 'FFF'; + const DATE_SHORT = 'FF'; + const TIMES = 'WW'; + const TIME_FULL = 'TTTTT'; + const TIME_LONG = 'TTTT'; + const TIME_MEDIUM = 'TTT'; + const TIME_SHORT = 'TT'; + const DATETIME = 'K'; + const DATETIME_FULL = 'KKKKK'; + const DATETIME_LONG = 'KKKK'; + const DATETIME_MEDIUM = 'KKK'; + const DATETIME_SHORT = 'KK'; + const ATOM = 'OOO'; + const COOKIE = 'CCC'; + const RFC_822 = 'R'; + const RFC_850 = 'RR'; + const RFC_1036 = 'RRR'; + const RFC_1123 = 'RRRR'; + const RFC_3339 = 'RRRRR'; + const RSS = 'SSS'; + const W3C = 'WWW'; + + /** + * Generates the standard date object, could be a unix timestamp, localized date, + * string, integer, array and so on. Also parts of dates or time are supported + * Always set the default timezone: http://php.net/date_default_timezone_set + * For example, in your bootstrap: date_default_timezone_set('America/Los_Angeles'); + * For detailed instructions please look in the docu. + * + * @param string|integer|Zend_Date|array $date OPTIONAL Date value or value of date part to set + * ,depending on $part. If null the actual time is set + * @param string $part OPTIONAL Defines the input format of $date + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date + * @throws Zend_Date_Exception + */ + public function __construct($date = null, $part = null, $locale = null) + { + if (is_object($date) and !($date instanceof Zend_TimeSync_Protocol) and + !($date instanceof Zend_Date)) { + if ($locale instanceof Zend_Locale) { + $locale = $date; + $date = null; + $part = null; + } else { + $date = (string) $date; + } + } + + if (($date !== null) and !is_array($date) and !($date instanceof Zend_TimeSync_Protocol) and + !($date instanceof Zend_Date) and !defined($date) and Zend_Locale::isLocale($date, true, false)) { + $locale = $date; + $date = null; + $part = null; + } else if (($part !== null) and !defined($part) and Zend_Locale::isLocale($part, true, false)) { + $locale = $part; + $part = null; + } + + $this->setLocale($locale); + if (is_string($date) && ($part === null) && (strlen($date) <= 5)) { + $part = $date; + $date = null; + } + + if ($date === null) { + if ($part === null) { + $date = time(); + } else if ($part !== self::TIMESTAMP) { + $date = self::now($locale); + $date = $date->get($part); + } + } + + if ($date instanceof Zend_TimeSync_Protocol) { + $date = $date->getInfo(); + $date = $this->_getTime($date['offset']); + $part = null; + } else if (parent::$_defaultOffset != 0) { + $date = $this->_getTime(parent::$_defaultOffset); + } + + // set the timezone and offset for $this + $zone = @date_default_timezone_get(); + $this->setTimezone($zone); + + // try to get timezone from date-string + if (!is_int($date)) { + $zone = $this->getTimezoneFromString($date); + $this->setTimezone($zone); + } + + // set datepart + if (($part !== null && $part !== self::TIMESTAMP) or (!is_numeric($date))) { + // switch off dst handling for value setting + $this->setUnixTimestamp($this->getGmtOffset()); + $this->set($date, $part, $this->_locale); + + // DST fix + if (is_array($date) === true) { + if (!isset($date['hour'])) { + $date['hour'] = 0; + } + + $hour = $this->toString('H', 'iso', true); + $hour = $date['hour'] - $hour; + switch ($hour) { + case 1 : + case -23 : + $this->addTimestamp(3600); + break; + case -1 : + case 23 : + $this->subTimestamp(3600); + break; + case 2 : + case -22 : + $this->addTimestamp(7200); + break; + case -2 : + case 22 : + $this->subTimestamp(7200); + break; + } + } + } else { + $this->setUnixTimestamp($date); + } + } + + /** + * Sets class wide options, if no option was given, the actual set options will be returned + * + * @param array $options Options to set + * @throws Zend_Date_Exception + * @return Options array if no option was given + */ + public static function setOptions(array $options = array()) + { + if (empty($options)) { + return self::$_options; + } + + foreach ($options as $name => $value) { + $name = strtolower($name); + + if (array_key_exists($name, self::$_options)) { + switch($name) { + case 'format_type' : + if ((strtolower($value) != 'php') && (strtolower($value) != 'iso')) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("Unknown format type ($value) for dates, only 'iso' and 'php' supported", 0, null, $value); + } + break; + case 'fix_dst' : + if (!is_bool($value)) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("'fix_dst' has to be boolean", 0, null, $value); + } + break; + case 'extend_month' : + if (!is_bool($value)) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("'extend_month' has to be boolean", 0, null, $value); + } + break; + case 'cache' : + if ($value === null) { + parent::$_cache = null; + } else { + if (!$value instanceof Zend_Cache_Core) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("Instance of Zend_Cache expected"); + } + + parent::$_cache = $value; + Zend_Locale_Data::setCache($value); + } + break; + case 'timesync' : + if ($value === null) { + parent::$_defaultOffset = 0; + } else { + if (!$value instanceof Zend_TimeSync_Protocol) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("Instance of Zend_TimeSync expected"); + } + + $date = $value->getInfo(); + parent::$_defaultOffset = $date['offset']; + } + break; + } + self::$_options[$name] = $value; + } + else { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("Unknown option: $name = $value"); + } + } + } + + /** + * Returns this object's internal UNIX timestamp (equivalent to Zend_Date::TIMESTAMP). + * If the timestamp is too large for integers, then the return value will be a string. + * This function does not return the timestamp as an object. + * Use clone() or copyPart() instead. + * + * @return integer|string UNIX timestamp + */ + public function getTimestamp() + { + return $this->getUnixTimestamp(); + } + + /** + * Returns the calculated timestamp + * HINT: timestamps are always GMT + * + * @param string $calc Type of calculation to make + * @param string|integer|array|Zend_Date $stamp Timestamp to calculate, when null the actual timestamp is calculated + * @return Zend_Date|integer + * @throws Zend_Date_Exception + */ + private function _timestamp($calc, $stamp) + { + if ($stamp instanceof Zend_Date) { + // extract timestamp from object + $stamp = $stamp->getTimestamp(); + } + + if (is_array($stamp)) { + if (isset($stamp['timestamp']) === true) { + $stamp = $stamp['timestamp']; + } else { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception('no timestamp given in array'); + } + } + + if ($calc === 'set') { + $return = $this->setUnixTimestamp($stamp); + } else { + $return = $this->_calcdetail($calc, $stamp, self::TIMESTAMP, null); + } + if ($calc != 'cmp') { + return $this; + } + return $return; + } + + /** + * Sets a new timestamp + * + * @param integer|string|array|Zend_Date $timestamp Timestamp to set + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function setTimestamp($timestamp) + { + return $this->_timestamp('set', $timestamp); + } + + /** + * Adds a timestamp + * + * @param integer|string|array|Zend_Date $timestamp Timestamp to add + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function addTimestamp($timestamp) + { + return $this->_timestamp('add', $timestamp); + } + + /** + * Subtracts a timestamp + * + * @param integer|string|array|Zend_Date $timestamp Timestamp to sub + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function subTimestamp($timestamp) + { + return $this->_timestamp('sub', $timestamp); + } + + /** + * Compares two timestamps, returning the difference as integer + * + * @param integer|string|array|Zend_Date $timestamp Timestamp to compare + * @return integer 0 = equal, 1 = later, -1 = earlier + * @throws Zend_Date_Exception + */ + public function compareTimestamp($timestamp) + { + return $this->_timestamp('cmp', $timestamp); + } + + /** + * Returns a string representation of the object + * Supported format tokens are: + * G - era, y - year, Y - ISO year, M - month, w - week of year, D - day of year, d - day of month + * E - day of week, e - number of weekday (1-7), h - hour 1-12, H - hour 0-23, m - minute, s - second + * A - milliseconds of day, z - timezone, Z - timezone offset, S - fractional second, a - period of day + * + * Additionally format tokens but non ISO conform are: + * SS - day suffix, eee - php number of weekday(0-6), ddd - number of days per month + * l - Leap year, B - swatch internet time, I - daylight saving time, X - timezone offset in seconds + * r - RFC2822 format, U - unix timestamp + * + * Not supported ISO tokens are + * u - extended year, Q - quarter, q - quarter, L - stand alone month, W - week of month + * F - day of week of month, g - modified julian, c - stand alone weekday, k - hour 0-11, K - hour 1-24 + * v - wall zone + * + * @param string $format OPTIONAL Rule for formatting output. If null the default date format is used + * @param string $type OPTIONAL Type for the format string which overrides the standard setting + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return string + */ + public function toString($format = null, $type = null, $locale = null) + { + if (is_object($format)) { + if ($format instanceof Zend_Locale) { + $locale = $format; + $format = null; + } else { + $format = (string) $format; + } + } + + if (is_object($type)) { + if ($type instanceof Zend_Locale) { + $locale = $type; + $type = null; + } else { + $type = (string) $type; + } + } + + if (($format !== null) and !defined($format) and + ($format != 'ee') and ($format != 'ss') and Zend_Locale::isLocale($format, null, false)) { + $locale = $format; + $format = null; + } + + if (($type !== null) and ($type != 'php') and ($type != 'iso') and + Zend_Locale::isLocale($type, null, false)) { + $locale = $type; + $type = null; + } + + if ($locale === null) { + $locale = $this->getLocale(); + } + + if ($format === null) { + $format = Zend_Locale_Format::getDateFormat($locale) . ' ' . Zend_Locale_Format::getTimeFormat($locale); + } else if (((self::$_options['format_type'] == 'php') && ($type === null)) or ($type == 'php')) { + $format = Zend_Locale_Format::convertPhpToIsoFormat($format); + } + + return $this->date($this->_toToken($format, $locale), $this->getUnixTimestamp(), false); + } + + /** + * Returns a string representation of the date which is equal with the timestamp + * + * @return string + */ + public function __toString() + { + return $this->toString(null, $this->_locale); + } + + /** + * Returns a integer representation of the object + * But returns false when the given part is no value f.e. Month-Name + * + * @param string|integer|Zend_Date $part OPTIONAL Defines the date or datepart to return as integer + * @return integer|false + */ + public function toValue($part = null) + { + $result = $this->get($part); + if (is_numeric($result)) { + return intval("$result"); + } else { + return false; + } + } + + /** + * Returns an array representation of the object + * + * @return array + */ + public function toArray() + { + return array('day' => $this->toString(self::DAY_SHORT, 'iso'), + 'month' => $this->toString(self::MONTH_SHORT, 'iso'), + 'year' => $this->toString(self::YEAR, 'iso'), + 'hour' => $this->toString(self::HOUR_SHORT, 'iso'), + 'minute' => $this->toString(self::MINUTE_SHORT, 'iso'), + 'second' => $this->toString(self::SECOND_SHORT, 'iso'), + 'timezone' => $this->toString(self::TIMEZONE, 'iso'), + 'timestamp' => $this->toString(self::TIMESTAMP, 'iso'), + 'weekday' => $this->toString(self::WEEKDAY_8601, 'iso'), + 'dayofyear' => $this->toString(self::DAY_OF_YEAR, 'iso'), + 'week' => $this->toString(self::WEEK, 'iso'), + 'gmtsecs' => $this->toString(self::TIMEZONE_SECS, 'iso')); + } + + /** + * Returns a representation of a date or datepart + * This could be for example a localized monthname, the time without date, + * the era or only the fractional seconds. There are about 50 different supported date parts. + * For a complete list of supported datepart values look into the docu + * + * @param string $part OPTIONAL Part of the date to return, if null the timestamp is returned + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return string date or datepart + */ + public function get($part = null, $locale = null) + { + if ($locale === null) { + $locale = $this->getLocale(); + } + + if (($part !== null) and !defined($part) and + ($part != 'ee') and ($part != 'ss') and Zend_Locale::isLocale($part, null, false)) { + $locale = $part; + $part = null; + } + + if ($part === null) { + $part = self::TIMESTAMP; + } else if (self::$_options['format_type'] == 'php') { + $part = Zend_Locale_Format::convertPhpToIsoFormat($part); + } + + return $this->date($this->_toToken($part, $locale), $this->getUnixTimestamp(), false); + } + + /** + * Internal method to apply tokens + * + * @param string $part + * @param string $locale + * @return string + */ + private function _toToken($part, $locale) { + // get format tokens + $comment = false; + $format = ''; + $orig = ''; + for ($i = 0; $i < strlen($part); ++$i) { + if ($part[$i] == "'") { + $comment = $comment ? false : true; + if (isset($part[$i+1]) && ($part[$i+1] == "'")) { + $comment = $comment ? false : true; + $format .= "\\'"; + ++$i; + } + + $orig = ''; + continue; + } + + if ($comment) { + $format .= '\\' . $part[$i]; + $orig = ''; + } else { + $orig .= $part[$i]; + if (!isset($part[$i+1]) || (isset($orig[0]) && ($orig[0] != $part[$i+1]))) { + $format .= $this->_parseIsoToDate($orig, $locale); + $orig = ''; + } + } + } + + return $format; + } + + /** + * Internal parsing method + * + * @param string $token + * @param string $locale + * @return string + */ + private function _parseIsoToDate($token, $locale) { + switch($token) { + case self::DAY : + return 'd'; + break; + + case self::WEEKDAY_SHORT : + $weekday = strtolower($this->date('D', $this->getUnixTimestamp(), false)); + $day = Zend_Locale_Data::getContent($locale, 'day', array('gregorian', 'format', 'wide', $weekday)); + return $this->_toComment(iconv_substr($day, 0, 3, 'UTF-8')); + break; + + case self::DAY_SHORT : + return 'j'; + break; + + case self::WEEKDAY : + $weekday = strtolower($this->date('D', $this->getUnixTimestamp(), false)); + return $this->_toComment(Zend_Locale_Data::getContent($locale, 'day', array('gregorian', 'format', 'wide', $weekday))); + break; + + case self::WEEKDAY_8601 : + return 'N'; + break; + + case 'ee' : + return $this->_toComment(str_pad($this->date('N', $this->getUnixTimestamp(), false), 2, '0', STR_PAD_LEFT)); + break; + + case self::DAY_SUFFIX : + return 'S'; + break; + + case self::WEEKDAY_DIGIT : + return 'w'; + break; + + case self::DAY_OF_YEAR : + return 'z'; + break; + + case 'DDD' : + return $this->_toComment(str_pad($this->date('z', $this->getUnixTimestamp(), false), 3, '0', STR_PAD_LEFT)); + break; + + case 'DD' : + return $this->_toComment(str_pad($this->date('z', $this->getUnixTimestamp(), false), 2, '0', STR_PAD_LEFT)); + break; + + case self::WEEKDAY_NARROW : + case 'EEEEE' : + $weekday = strtolower($this->date('D', $this->getUnixTimestamp(), false)); + $day = Zend_Locale_Data::getContent($locale, 'day', array('gregorian', 'format', 'abbreviated', $weekday)); + return $this->_toComment(iconv_substr($day, 0, 1, 'UTF-8')); + break; + + case self::WEEKDAY_NAME : + $weekday = strtolower($this->date('D', $this->getUnixTimestamp(), false)); + return $this->_toComment(Zend_Locale_Data::getContent($locale, 'day', array('gregorian', 'format', 'abbreviated', $weekday))); + break; + + case 'w' : + $week = $this->date('W', $this->getUnixTimestamp(), false); + return $this->_toComment(($week[0] == '0') ? $week[1] : $week); + break; + + case self::WEEK : + return 'W'; + break; + + case self::MONTH_NAME : + $month = $this->date('n', $this->getUnixTimestamp(), false); + return $this->_toComment(Zend_Locale_Data::getContent($locale, 'month', array('gregorian', 'format', 'wide', $month))); + break; + + case self::MONTH : + return 'm'; + break; + + case self::MONTH_NAME_SHORT : + $month = $this->date('n', $this->getUnixTimestamp(), false); + return $this->_toComment(Zend_Locale_Data::getContent($locale, 'month', array('gregorian', 'format', 'abbreviated', $month))); + break; + + case self::MONTH_SHORT : + return 'n'; + break; + + case self::MONTH_DAYS : + return 't'; + break; + + case self::MONTH_NAME_NARROW : + $month = $this->date('n', $this->getUnixTimestamp(), false); + $mon = Zend_Locale_Data::getContent($locale, 'month', array('gregorian', 'format', 'abbreviated', $month)); + return $this->_toComment(iconv_substr($mon, 0, 1, 'UTF-8')); + break; + + case self::LEAPYEAR : + return 'L'; + break; + + case self::YEAR_8601 : + return 'o'; + break; + + case self::YEAR : + return 'Y'; + break; + + case self::YEAR_SHORT : + return 'y'; + break; + + case self::YEAR_SHORT_8601 : + return $this->_toComment(substr($this->date('o', $this->getUnixTimestamp(), false), -2, 2)); + break; + + case self::MERIDIEM : + $am = $this->date('a', $this->getUnixTimestamp(), false); + if ($am == 'am') { + return $this->_toComment(Zend_Locale_Data::getContent($locale, 'am')); + } + + return $this->_toComment(Zend_Locale_Data::getContent($locale, 'pm')); + break; + + case self::SWATCH : + return 'B'; + break; + + case self::HOUR_SHORT_AM : + return 'g'; + break; + + case self::HOUR_SHORT : + return 'G'; + break; + + case self::HOUR_AM : + return 'h'; + break; + + case self::HOUR : + return 'H'; + break; + + case self::MINUTE : + return $this->_toComment(str_pad($this->date('i', $this->getUnixTimestamp(), false), 2, '0', STR_PAD_LEFT)); + break; + + case self::SECOND : + return $this->_toComment(str_pad($this->date('s', $this->getUnixTimestamp(), false), 2, '0', STR_PAD_LEFT)); + break; + + case self::MINUTE_SHORT : + return 'i'; + break; + + case self::SECOND_SHORT : + return 's'; + break; + + case self::MILLISECOND : + return $this->_toComment($this->getMilliSecond()); + break; + + case self::TIMEZONE_NAME : + case 'vvvv' : + return 'e'; + break; + + case self::DAYLIGHT : + return 'I'; + break; + + case self::GMT_DIFF : + case 'ZZ' : + case 'ZZZ' : + return 'O'; + break; + + case self::GMT_DIFF_SEP : + return 'P'; + break; + + case self::TIMEZONE : + case 'v' : + case 'zz' : + case 'zzz' : + return 'T'; + break; + + case self::TIMEZONE_SECS : + return 'Z'; + break; + + case self::ISO_8601 : + return 'c'; + break; + + case self::RFC_2822 : + return 'r'; + break; + + case self::TIMESTAMP : + return 'U'; + break; + + case self::ERA : + case 'GG' : + case 'GGG' : + $year = $this->date('Y', $this->getUnixTimestamp(), false); + if ($year < 0) { + return $this->_toComment(Zend_Locale_Data::getContent($locale, 'era', array('gregorian', 'Abbr', '0'))); + } + + return $this->_toComment(Zend_Locale_Data::getContent($locale, 'era', array('gregorian', 'Abbr', '1'))); + break; + + case self::ERA_NARROW : + $year = $this->date('Y', $this->getUnixTimestamp(), false); + if ($year < 0) { + return $this->_toComment(iconv_substr(Zend_Locale_Data::getContent($locale, 'era', array('gregorian', 'Abbr', '0')), 0, 1, 'UTF-8')) . '.'; + } + + return $this->_toComment(iconv_substr(Zend_Locale_Data::getContent($locale, 'era', array('gregorian', 'Abbr', '1')), 0, 1, 'UTF-8')) . '.'; + break; + + case self::ERA_NAME : + $year = $this->date('Y', $this->getUnixTimestamp(), false); + if ($year < 0) { + return $this->_toComment(Zend_Locale_Data::getContent($locale, 'era', array('gregorian', 'Names', '0'))); + } + + return $this->_toComment(Zend_Locale_Data::getContent($locale, 'era', array('gregorian', 'Names', '1'))); + break; + + case self::DATES : + return $this->_toToken(Zend_Locale_Format::getDateFormat($locale), $locale); + break; + + case self::DATE_FULL : + return $this->_toToken(Zend_Locale_Data::getContent($locale, 'date', array('gregorian', 'full')), $locale); + break; + + case self::DATE_LONG : + return $this->_toToken(Zend_Locale_Data::getContent($locale, 'date', array('gregorian', 'long')), $locale); + break; + + case self::DATE_MEDIUM : + return $this->_toToken(Zend_Locale_Data::getContent($locale, 'date', array('gregorian', 'medium')), $locale); + break; + + case self::DATE_SHORT : + return $this->_toToken(Zend_Locale_Data::getContent($locale, 'date', array('gregorian', 'short')), $locale); + break; + + case self::TIMES : + return $this->_toToken(Zend_Locale_Format::getTimeFormat($locale), $locale); + break; + + case self::TIME_FULL : + return $this->_toToken(Zend_Locale_Data::getContent($locale, 'time', 'full'), $locale); + break; + + case self::TIME_LONG : + return $this->_toToken(Zend_Locale_Data::getContent($locale, 'time', 'long'), $locale); + break; + + case self::TIME_MEDIUM : + return $this->_toToken(Zend_Locale_Data::getContent($locale, 'time', 'medium'), $locale); + break; + + case self::TIME_SHORT : + return $this->_toToken(Zend_Locale_Data::getContent($locale, 'time', 'short'), $locale); + break; + + case self::DATETIME : + return $this->_toToken(Zend_Locale_Format::getDateTimeFormat($locale), $locale); + break; + + case self::DATETIME_FULL : + return $this->_toToken(Zend_Locale_Data::getContent($locale, 'datetime', array('gregorian', 'full')), $locale); + break; + + case self::DATETIME_LONG : + return $this->_toToken(Zend_Locale_Data::getContent($locale, 'datetime', array('gregorian', 'long')), $locale); + break; + + case self::DATETIME_MEDIUM : + return $this->_toToken(Zend_Locale_Data::getContent($locale, 'datetime', array('gregorian', 'medium')), $locale); + break; + + case self::DATETIME_SHORT : + return $this->_toToken(Zend_Locale_Data::getContent($locale, 'datetime', array('gregorian', 'short')), $locale); + break; + + case self::ATOM : + return 'Y\-m\-d\TH\:i\:sP'; + break; + + case self::COOKIE : + return 'l\, d\-M\-y H\:i\:s e'; + break; + + case self::RFC_822 : + return 'D\, d M y H\:i\:s O'; + break; + + case self::RFC_850 : + return 'l\, d\-M\-y H\:i\:s e'; + break; + + case self::RFC_1036 : + return 'D\, d M y H\:i\:s O'; + break; + + case self::RFC_1123 : + return 'D\, d M Y H\:i\:s O'; + break; + + case self::RFC_3339 : + return 'Y\-m\-d\TH\:i\:sP'; + break; + + case self::RSS : + return 'D\, d M Y H\:i\:s O'; + break; + + case self::W3C : + return 'Y\-m\-d\TH\:i\:sP'; + break; + } + + if ($token == '') { + return ''; + } + + switch ($token[0]) { + case 'y' : + if ((strlen($token) == 4) && (abs($this->getUnixTimestamp()) <= 0x7FFFFFFF)) { + return 'Y'; + } + + $length = iconv_strlen($token, 'UTF-8'); + return $this->_toComment(str_pad($this->date('Y', $this->getUnixTimestamp(), false), $length, '0', STR_PAD_LEFT)); + break; + + case 'Y' : + if ((strlen($token) == 4) && (abs($this->getUnixTimestamp()) <= 0x7FFFFFFF)) { + return 'o'; + } + + $length = iconv_strlen($token, 'UTF-8'); + return $this->_toComment(str_pad($this->date('o', $this->getUnixTimestamp(), false), $length, '0', STR_PAD_LEFT)); + break; + + case 'A' : + $length = iconv_strlen($token, 'UTF-8'); + $result = substr($this->getMilliSecond(), 0, 3); + $result += $this->date('s', $this->getUnixTimestamp(), false) * 1000; + $result += $this->date('i', $this->getUnixTimestamp(), false) * 60000; + $result += $this->date('H', $this->getUnixTimestamp(), false) * 3600000; + + return $this->_toComment(str_pad($result, $length, '0', STR_PAD_LEFT)); + break; + } + + return $this->_toComment($token); + } + + /** + * Private function to make a comment of a token + * + * @param string $token + * @return string + */ + private function _toComment($token) + { + $token = str_split($token); + $result = ''; + foreach ($token as $tok) { + $result .= '\\' . $tok; + } + + return $result; + } + + /** + * Return digit from standard names (english) + * Faster implementation than locale aware searching + * + * @param string $name + * @return integer Number of this month + * @throws Zend_Date_Exception + */ + private function _getDigitFromName($name) + { + switch($name) { + case "Jan": + return 1; + + case "Feb": + return 2; + + case "Mar": + return 3; + + case "Apr": + return 4; + + case "May": + return 5; + + case "Jun": + return 6; + + case "Jul": + return 7; + + case "Aug": + return 8; + + case "Sep": + return 9; + + case "Oct": + return 10; + + case "Nov": + return 11; + + case "Dec": + return 12; + + default: + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception('Month ($name) is not a known month'); + } + } + + /** + * Counts the exact year number + * < 70 - 2000 added, >70 < 100 - 1900, others just returned + * + * @param integer $value year number + * @return integer Number of year + */ + public static function getFullYear($value) + { + if ($value >= 0) { + if ($value < 70) { + $value += 2000; + } else if ($value < 100) { + $value += 1900; + } + } + return $value; + } + + /** + * Sets the given date as new date or a given datepart as new datepart returning the new datepart + * This could be for example a localized dayname, the date without time, + * the month or only the seconds. There are about 50 different supported date parts. + * For a complete list of supported datepart values look into the docu + * + * @param string|integer|array|Zend_Date $date Date or datepart to set + * @param string $part OPTIONAL Part of the date to set, if null the timestamp is set + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function set($date, $part = null, $locale = null) + { + if (self::$_options['format_type'] == 'php') { + $part = Zend_Locale_Format::convertPhpToIsoFormat($part); + } + + $zone = $this->getTimezoneFromString($date); + $this->setTimezone($zone); + + $this->_calculate('set', $date, $part, $locale); + return $this; + } + + /** + * Adds a date or datepart to the existing date, by extracting $part from $date, + * and modifying this object by adding that part. The $part is then extracted from + * this object and returned as an integer or numeric string (for large values, or $part's + * corresponding to pre-defined formatted date strings). + * This could be for example a ISO 8601 date, the hour the monthname or only the minute. + * There are about 50 different supported date parts. + * For a complete list of supported datepart values look into the docu. + * + * @param string|integer|array|Zend_Date $date Date or datepart to add + * @param string $part OPTIONAL Part of the date to add, if null the timestamp is added + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function add($date, $part = self::TIMESTAMP, $locale = null) + { + if (self::$_options['format_type'] == 'php') { + $part = Zend_Locale_Format::convertPhpToIsoFormat($part); + } + + $this->_calculate('add', $date, $part, $locale); + return $this; + } + + /** + * Subtracts a date from another date. + * This could be for example a RFC2822 date, the time, + * the year or only the timestamp. There are about 50 different supported date parts. + * For a complete list of supported datepart values look into the docu + * Be aware: Adding -2 Months is not equal to Subtracting 2 Months !!! + * + * @param string|integer|array|Zend_Date $date Date or datepart to subtract + * @param string $part OPTIONAL Part of the date to sub, if null the timestamp is subtracted + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function sub($date, $part = self::TIMESTAMP, $locale = null) + { + if (self::$_options['format_type'] == 'php') { + $part = Zend_Locale_Format::convertPhpToIsoFormat($part); + } + + $this->_calculate('sub', $date, $part, $locale); + return $this; + } + + /** + * Compares a date or datepart with the existing one. + * Returns -1 if earlier, 0 if equal and 1 if later. + * + * @param string|integer|array|Zend_Date $date Date or datepart to compare with the date object + * @param string $part OPTIONAL Part of the date to compare, if null the timestamp is subtracted + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return integer 0 = equal, 1 = later, -1 = earlier + * @throws Zend_Date_Exception + */ + public function compare($date, $part = null, $locale = null) + { + if (self::$_options['format_type'] == 'php') { + $part = Zend_Locale_Format::convertPhpToIsoFormat($part); + } + + $compare = $this->_calculate('cmp', $date, $part, $locale); + + if ($compare > 0) { + return 1; + } else if ($compare < 0) { + return -1; + } + return 0; + } + + /** + * Returns a new instance of Zend_Date with the selected part copied. + * To make an exact copy, use PHP's clone keyword. + * For a complete list of supported date part values look into the docu. + * If a date part is copied, all other date parts are set to standard values. + * For example: If only YEAR is copied, the returned date object is equal to + * 01-01-YEAR 00:00:00 (01-01-1970 00:00:00 is equal to timestamp 0) + * If only HOUR is copied, the returned date object is equal to + * 01-01-1970 HOUR:00:00 (so $this contains a timestamp equal to a timestamp of 0 plus HOUR). + * + * @param string $part Part of the date to compare, if null the timestamp is subtracted + * @param string|Zend_Locale $locale OPTIONAL New object's locale. No adjustments to timezone are made. + * @return Zend_Date New clone with requested part + */ + public function copyPart($part, $locale = null) + { + $clone = clone $this; // copy all instance variables + $clone->setUnixTimestamp(0); // except the timestamp + if ($locale != null) { + $clone->setLocale($locale); // set an other locale if selected + } + $clone->set($this, $part); + return $clone; + } + + /** + * Internal function, returns the offset of a given timezone + * + * @param string $zone + * @return integer + */ + public function getTimezoneFromString($zone) + { + if (is_array($zone)) { + return $this->getTimezone(); + } + + if ($zone instanceof Zend_Date) { + return $zone->getTimezone(); + } + + $match = array(); + preg_match('/\dZ$/', $zone, $match); + if (!empty($match)) { + return "Etc/UTC"; + } + + preg_match('/([+-]\d{2}):{0,1}\d{2}/', $zone, $match); + if (!empty($match) and ($match[count($match) - 1] <= 12) and ($match[count($match) - 1] >= -12)) { + $zone = "Etc/GMT"; + $zone .= ($match[count($match) - 1] < 0) ? "+" : "-"; + $zone .= (int) abs($match[count($match) - 1]); + return $zone; + } + + preg_match('/([[:alpha:]\/]{3,30})(?!.*([[:alpha:]\/]{3,30}))/', $zone, $match); + try { + if (!empty($match) and (!is_int($match[count($match) - 1]))) { + $oldzone = $this->getTimezone(); + $this->setTimezone($match[count($match) - 1]); + $result = $this->getTimezone(); + $this->setTimezone($oldzone); + if ($result !== $oldzone) { + return $match[count($match) - 1]; + } + } + } catch (Exception $e) { + // fall through + } + + return $this->getTimezone(); + } + + /** + * Calculates the date or object + * + * @param string $calc Calculation to make + * @param string|integer $date Date for calculation + * @param string|integer $comp Second date for calculation + * @param boolean|integer $dst Use dst correction if option is set + * @return integer|string|Zend_Date new timestamp or Zend_Date depending on calculation + */ + private function _assign($calc, $date, $comp = 0, $dst = false) + { + switch ($calc) { + case 'set' : + if (!empty($comp)) { + $this->setUnixTimestamp(call_user_func(Zend_Locale_Math::$sub, $this->getUnixTimestamp(), $comp)); + } + $this->setUnixTimestamp(call_user_func(Zend_Locale_Math::$add, $this->getUnixTimestamp(), $date)); + $value = $this->getUnixTimestamp(); + break; + case 'add' : + $this->setUnixTimestamp(call_user_func(Zend_Locale_Math::$add, $this->getUnixTimestamp(), $date)); + $value = $this->getUnixTimestamp(); + break; + case 'sub' : + $this->setUnixTimestamp(call_user_func(Zend_Locale_Math::$sub, $this->getUnixTimestamp(), $date)); + $value = $this->getUnixTimestamp(); + break; + default : + // cmp - compare + return call_user_func(Zend_Locale_Math::$comp, $comp, $date); + break; + } + + // dst-correction if 'fix_dst' = true and dst !== false but only for non UTC and non GMT + if ((self::$_options['fix_dst'] === true) and ($dst !== false) and ($this->_dst === true)) { + $hour = $this->toString(self::HOUR, 'iso'); + if ($hour != $dst) { + if (($dst == ($hour + 1)) or ($dst == ($hour - 23))) { + $value += 3600; + } else if (($dst == ($hour - 1)) or ($dst == ($hour + 23))) { + $value -= 3600; + } + $this->setUnixTimestamp($value); + } + } + return $this->getUnixTimestamp(); + } + + + /** + * Calculates the date or object + * + * @param string $calc Calculation to make, one of: 'add'|'sub'|'cmp'|'copy'|'set' + * @param string|integer|array|Zend_Date $date Date or datepart to calculate with + * @param string $part Part of the date to calculate, if null the timestamp is used + * @param string|Zend_Locale $locale Locale for parsing input + * @return integer|string|Zend_Date new timestamp + * @throws Zend_Date_Exception + */ + private function _calculate($calc, $date, $part, $locale) + { + if ($date === null) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception('parameter $date must be set, null is not allowed'); + } + + if (($part !== null) && (strlen($part) !== 2) && (Zend_Locale::isLocale($part, null, false))) { + $locale = $part; + $part = null; + } + + if ($locale === null) { + $locale = $this->getLocale(); + } + + $locale = (string) $locale; + + // Create date parts + $year = $this->toString(self::YEAR, 'iso'); + $month = $this->toString(self::MONTH_SHORT, 'iso'); + $day = $this->toString(self::DAY_SHORT, 'iso'); + $hour = $this->toString(self::HOUR_SHORT, 'iso'); + $minute = $this->toString(self::MINUTE_SHORT, 'iso'); + $second = $this->toString(self::SECOND_SHORT, 'iso'); + // If object extract value + if ($date instanceof Zend_Date) { + $date = $date->toString($part, 'iso', $locale); + } + + if (is_array($date) === true) { + if (empty($part) === false) { + switch($part) { + // Fall through + case self::DAY: + case self::DAY_SHORT: + if (isset($date['day']) === true) { + $date = $date['day']; + } + break; + // Fall through + case self::WEEKDAY_SHORT: + case self::WEEKDAY: + case self::WEEKDAY_8601: + case self::WEEKDAY_DIGIT: + case self::WEEKDAY_NARROW: + case self::WEEKDAY_NAME: + if (isset($date['weekday']) === true) { + $date = $date['weekday']; + $part = self::WEEKDAY_DIGIT; + } + break; + case self::DAY_OF_YEAR: + if (isset($date['day_of_year']) === true) { + $date = $date['day_of_year']; + } + break; + // Fall through + case self::MONTH: + case self::MONTH_SHORT: + case self::MONTH_NAME: + case self::MONTH_NAME_SHORT: + case self::MONTH_NAME_NARROW: + if (isset($date['month']) === true) { + $date = $date['month']; + } + break; + // Fall through + case self::YEAR: + case self::YEAR_SHORT: + case self::YEAR_8601: + case self::YEAR_SHORT_8601: + if (isset($date['year']) === true) { + $date = $date['year']; + } + break; + // Fall through + case self::HOUR: + case self::HOUR_AM: + case self::HOUR_SHORT: + case self::HOUR_SHORT_AM: + if (isset($date['hour']) === true) { + $date = $date['hour']; + } + break; + // Fall through + case self::MINUTE: + case self::MINUTE_SHORT: + if (isset($date['minute']) === true) { + $date = $date['minute']; + } + break; + // Fall through + case self::SECOND: + case self::SECOND_SHORT: + if (isset($date['second']) === true) { + $date = $date['second']; + } + break; + // Fall through + case self::TIMEZONE: + case self::TIMEZONE_NAME: + if (isset($date['timezone']) === true) { + $date = $date['timezone']; + } + break; + case self::TIMESTAMP: + if (isset($date['timestamp']) === true) { + $date = $date['timestamp']; + } + break; + case self::WEEK: + if (isset($date['week']) === true) { + $date = $date['week']; + } + break; + case self::TIMEZONE_SECS: + if (isset($date['gmtsecs']) === true) { + $date = $date['gmtsecs']; + } + break; + default: + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("datepart for part ($part) not found in array"); + break; + } + } else { + $hours = 0; + if (isset($date['hour']) === true) { + $hours = $date['hour']; + } + $minutes = 0; + if (isset($date['minute']) === true) { + $minutes = $date['minute']; + } + $seconds = 0; + if (isset($date['second']) === true) { + $seconds = $date['second']; + } + $months = 0; + if (isset($date['month']) === true) { + $months = $date['month']; + } + $days = 0; + if (isset($date['day']) === true) { + $days = $date['day']; + } + $years = 0; + if (isset($date['year']) === true) { + $years = $date['year']; + } + return $this->_assign($calc, $this->mktime($hours, $minutes, $seconds, $months, $days, $years, true), + $this->mktime($hour, $minute, $second, $month, $day, $year, true), $hour); + } + } + + // $date as object, part of foreign date as own date + switch($part) { + + // day formats + case self::DAY: + if (is_numeric($date)) { + return $this->_assign($calc, $this->mktime(0, 0, 0, 1, 1 + intval($date), 1970, true), + $this->mktime(0, 0, 0, 1, 1 + intval($day), 1970, true), $hour); + } + + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, day expected", 0, null, $date); + break; + + case self::WEEKDAY_SHORT: + $daylist = Zend_Locale_Data::getList($locale, 'day'); + $weekday = (int) $this->toString(self::WEEKDAY_DIGIT, 'iso', $locale); + $cnt = 0; + + foreach ($daylist as $key => $value) { + if (strtoupper(iconv_substr($value, 0, 3, 'UTF-8')) == strtoupper($date)) { + $found = $cnt; + break; + } + ++$cnt; + } + + // Weekday found + if ($cnt < 7) { + return $this->_assign($calc, $this->mktime(0, 0, 0, 1, 1 + $found, 1970, true), + $this->mktime(0, 0, 0, 1, 1 + $weekday, 1970, true), $hour); + } + + // Weekday not found + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, weekday expected", 0, null, $date); + break; + + case self::DAY_SHORT: + if (is_numeric($date)) { + return $this->_assign($calc, $this->mktime(0, 0, 0, 1, 1 + intval($date), 1970, true), + $this->mktime(0, 0, 0, 1, 1 + intval($day), 1970, true), $hour); + } + + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, day expected", 0, null, $date); + break; + + case self::WEEKDAY: + $daylist = Zend_Locale_Data::getList($locale, 'day'); + $weekday = (int) $this->toString(self::WEEKDAY_DIGIT, 'iso', $locale); + $cnt = 0; + + foreach ($daylist as $key => $value) { + if (strtoupper($value) == strtoupper($date)) { + $found = $cnt; + break; + } + ++$cnt; + } + + // Weekday found + if ($cnt < 7) { + return $this->_assign($calc, $this->mktime(0, 0, 0, 1, 1 + $found, 1970, true), + $this->mktime(0, 0, 0, 1, 1 + $weekday, 1970, true), $hour); + } + + // Weekday not found + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, weekday expected", 0, null, $date); + break; + + case self::WEEKDAY_8601: + $weekday = (int) $this->toString(self::WEEKDAY_8601, 'iso', $locale); + if ((intval($date) > 0) and (intval($date) < 8)) { + return $this->_assign($calc, $this->mktime(0, 0, 0, 1, 1 + intval($date), 1970, true), + $this->mktime(0, 0, 0, 1, 1 + $weekday, 1970, true), $hour); + } + + // Weekday not found + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, weekday expected", 0, null, $date); + break; + + case self::DAY_SUFFIX: + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception('day suffix not supported', 0, null, $date); + break; + + case self::WEEKDAY_DIGIT: + $weekday = (int) $this->toString(self::WEEKDAY_DIGIT, 'iso', $locale); + if (is_numeric($date) and (intval($date) >= 0) and (intval($date) < 7)) { + return $this->_assign($calc, $this->mktime(0, 0, 0, 1, 1 + $date, 1970, true), + $this->mktime(0, 0, 0, 1, 1 + $weekday, 1970, true), $hour); + } + + // Weekday not found + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, weekday expected", 0, null, $date); + break; + + case self::DAY_OF_YEAR: + if (is_numeric($date)) { + if (($calc == 'add') || ($calc == 'sub')) { + $year = 1970; + ++$date; + ++$day; + } + + return $this->_assign($calc, $this->mktime(0, 0, 0, 1, $date, $year, true), + $this->mktime(0, 0, 0, $month, $day, $year, true), $hour); + } + + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, day expected", 0, null, $date); + break; + + case self::WEEKDAY_NARROW: + $daylist = Zend_Locale_Data::getList($locale, 'day', array('gregorian', 'format', 'abbreviated')); + $weekday = (int) $this->toString(self::WEEKDAY_DIGIT, 'iso', $locale); + $cnt = 0; + foreach ($daylist as $key => $value) { + if (strtoupper(iconv_substr($value, 0, 1, 'UTF-8')) == strtoupper($date)) { + $found = $cnt; + break; + } + ++$cnt; + } + + // Weekday found + if ($cnt < 7) { + return $this->_assign($calc, $this->mktime(0, 0, 0, 1, 1 + $found, 1970, true), + $this->mktime(0, 0, 0, 1, 1 + $weekday, 1970, true), $hour); + } + + // Weekday not found + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, weekday expected", 0, null, $date); + break; + + case self::WEEKDAY_NAME: + $daylist = Zend_Locale_Data::getList($locale, 'day', array('gregorian', 'format', 'abbreviated')); + $weekday = (int) $this->toString(self::WEEKDAY_DIGIT, 'iso', $locale); + $cnt = 0; + foreach ($daylist as $key => $value) { + if (strtoupper($value) == strtoupper($date)) { + $found = $cnt; + break; + } + ++$cnt; + } + + // Weekday found + if ($cnt < 7) { + return $this->_assign($calc, $this->mktime(0, 0, 0, 1, 1 + $found, 1970, true), + $this->mktime(0, 0, 0, 1, 1 + $weekday, 1970, true), $hour); + } + + // Weekday not found + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, weekday expected", 0, null, $date); + break; + + // week formats + case self::WEEK: + if (is_numeric($date)) { + $week = (int) $this->toString(self::WEEK, 'iso', $locale); + return $this->_assign($calc, parent::mktime(0, 0, 0, 1, 1 + ($date * 7), 1970, true), + parent::mktime(0, 0, 0, 1, 1 + ($week * 7), 1970, true), $hour); + } + + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, week expected", 0, null, $date); + break; + + // month formats + case self::MONTH_NAME: + $monthlist = Zend_Locale_Data::getList($locale, 'month'); + $cnt = 0; + foreach ($monthlist as $key => $value) { + if (strtoupper($value) == strtoupper($date)) { + $found = $key; + break; + } + ++$cnt; + } + $date = array_search($date, $monthlist); + + // Monthname found + if ($cnt < 12) { + $fixday = 0; + if ($calc == 'add') { + $date += $found; + $calc = 'set'; + if (self::$_options['extend_month'] == false) { + $parts = $this->getDateParts($this->mktime($hour, $minute, $second, $date, $day, $year, false)); + if ($parts['mday'] != $day) { + $fixday = ($parts['mday'] < $day) ? -$parts['mday'] : ($parts['mday'] - $day); + } + } + } else if ($calc == 'sub') { + $date = $month - $found; + $calc = 'set'; + if (self::$_options['extend_month'] == false) { + $parts = $this->getDateParts($this->mktime($hour, $minute, $second, $date, $day, $year, false)); + if ($parts['mday'] != $day) { + $fixday = ($parts['mday'] < $day) ? -$parts['mday'] : ($parts['mday'] - $day); + } + } + } + return $this->_assign($calc, $this->mktime(0, 0, 0, $date, $day + $fixday, $year, true), + $this->mktime(0, 0, 0, $month, $day, $year, true), $hour); + } + + // Monthname not found + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, month expected", 0, null, $date); + break; + + case self::MONTH: + if (is_numeric($date)) { + $fixday = 0; + if ($calc == 'add') { + $date += $month; + $calc = 'set'; + if (self::$_options['extend_month'] == false) { + $parts = $this->getDateParts($this->mktime($hour, $minute, $second, $date, $day, $year, false)); + if ($parts['mday'] != $day) { + $fixday = ($parts['mday'] < $day) ? -$parts['mday'] : ($parts['mday'] - $day); + } + } + } else if ($calc == 'sub') { + $date = $month - $date; + $calc = 'set'; + if (self::$_options['extend_month'] == false) { + $parts = $this->getDateParts($this->mktime($hour, $minute, $second, $date, $day, $year, false)); + if ($parts['mday'] != $day) { + $fixday = ($parts['mday'] < $day) ? -$parts['mday'] : ($parts['mday'] - $day); + } + } + } + return $this->_assign($calc, $this->mktime(0, 0, 0, $date, $day + $fixday, $year, true), + $this->mktime(0, 0, 0, $month, $day, $year, true), $hour); + } + + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, month expected", 0, null, $date); + break; + + case self::MONTH_NAME_SHORT: + $monthlist = Zend_Locale_Data::getList($locale, 'month', array('gregorian', 'format', 'abbreviated')); + $cnt = 0; + foreach ($monthlist as $key => $value) { + if (strtoupper($value) == strtoupper($date)) { + $found = $key; + break; + } + ++$cnt; + } + $date = array_search($date, $monthlist); + + // Monthname found + if ($cnt < 12) { + $fixday = 0; + if ($calc == 'add') { + $date += $found; + $calc = 'set'; + if (self::$_options['extend_month'] === false) { + $parts = $this->getDateParts($this->mktime($hour, $minute, $second, $date, $day, $year, false)); + if ($parts['mday'] != $day) { + $fixday = ($parts['mday'] < $day) ? -$parts['mday'] : ($parts['mday'] - $day); + } + } + } else if ($calc == 'sub') { + $date = $month - $found; + $calc = 'set'; + if (self::$_options['extend_month'] === false) { + $parts = $this->getDateParts($this->mktime($hour, $minute, $second, $date, $day, $year, false)); + if ($parts['mday'] != $day) { + $fixday = ($parts['mday'] < $day) ? -$parts['mday'] : ($parts['mday'] - $day); + } + } + } + return $this->_assign($calc, $this->mktime(0, 0, 0, $date, $day + $fixday, $year, true), + $this->mktime(0, 0, 0, $month, $day, $year, true), $hour); + } + + // Monthname not found + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, month expected", 0, null, $date); + break; + + case self::MONTH_SHORT: + if (is_numeric($date) === true) { + $fixday = 0; + if ($calc === 'add') { + $date += $month; + $calc = 'set'; + if (self::$_options['extend_month'] === false) { + $parts = $this->getDateParts($this->mktime($hour, $minute, $second, $date, $day, $year, false)); + if ($parts['mday'] != $day) { + $fixday = ($parts['mday'] < $day) ? -$parts['mday'] : ($parts['mday'] - $day); + } + } + } else if ($calc === 'sub') { + $date = $month - $date; + $calc = 'set'; + if (self::$_options['extend_month'] === false) { + $parts = $this->getDateParts($this->mktime($hour, $minute, $second, $date, $day, $year, false)); + if ($parts['mday'] != $day) { + $fixday = ($parts['mday'] < $day) ? -$parts['mday'] : ($parts['mday'] - $day); + } + } + } + + return $this->_assign($calc, $this->mktime(0, 0, 0, $date, $day + $fixday, $year, true), + $this->mktime(0, 0, 0, $month, $day, $year, true), $hour); + } + + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, month expected", 0, null, $date); + break; + + case self::MONTH_DAYS: + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception('month days not supported', 0, null, $date); + break; + + case self::MONTH_NAME_NARROW: + $monthlist = Zend_Locale_Data::getList($locale, 'month', array('gregorian', 'stand-alone', 'narrow')); + $cnt = 0; + foreach ($monthlist as $key => $value) { + if (strtoupper($value) === strtoupper($date)) { + $found = $key; + break; + } + ++$cnt; + } + $date = array_search($date, $monthlist); + + // Monthname found + if ($cnt < 12) { + $fixday = 0; + if ($calc === 'add') { + $date += $found; + $calc = 'set'; + if (self::$_options['extend_month'] === false) { + $parts = $this->getDateParts($this->mktime($hour, $minute, $second, $date, $day, $year, false)); + if ($parts['mday'] != $day) { + $fixday = ($parts['mday'] < $day) ? -$parts['mday'] : ($parts['mday'] - $day); + } + } + } else if ($calc === 'sub') { + $date = $month - $found; + $calc = 'set'; + if (self::$_options['extend_month'] === false) { + $parts = $this->getDateParts($this->mktime($hour, $minute, $second, $date, $day, $year, false)); + if ($parts['mday'] != $day) { + $fixday = ($parts['mday'] < $day) ? -$parts['mday'] : ($parts['mday'] - $day); + } + } + } + return $this->_assign($calc, $this->mktime(0, 0, 0, $date, $day + $fixday, $year, true), + $this->mktime(0, 0, 0, $month, $day, $year, true), $hour); + } + + // Monthname not found + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, month expected", 0, null, $date); + break; + + // year formats + case self::LEAPYEAR: + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception('leap year not supported', 0, null, $date); + break; + + case self::YEAR_8601: + if (is_numeric($date)) { + if ($calc === 'add') { + $date += $year; + $calc = 'set'; + } else if ($calc === 'sub') { + $date = $year - $date; + $calc = 'set'; + } + + return $this->_assign($calc, $this->mktime(0, 0, 0, $month, $day, intval($date), true), + $this->mktime(0, 0, 0, $month, $day, $year, true), false); + } + + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, year expected", 0, null, $date); + break; + + case self::YEAR: + if (is_numeric($date)) { + if ($calc === 'add') { + $date += $year; + $calc = 'set'; + } else if ($calc === 'sub') { + $date = $year - $date; + $calc = 'set'; + } + + return $this->_assign($calc, $this->mktime(0, 0, 0, $month, $day, intval($date), true), + $this->mktime(0, 0, 0, $month, $day, $year, true), false); + } + + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, year expected", 0, null, $date); + break; + + case self::YEAR_SHORT: + if (is_numeric($date)) { + $date = intval($date); + if (($calc == 'set') || ($calc == 'cmp')) { + $date = self::getFullYear($date); + } + if ($calc === 'add') { + $date += $year; + $calc = 'set'; + } else if ($calc === 'sub') { + $date = $year - $date; + $calc = 'set'; + } + + return $this->_assign($calc, $this->mktime(0, 0, 0, $month, $day, $date, true), + $this->mktime(0, 0, 0, $month, $day, $year, true), false); + } + + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, year expected", 0, null, $date); + break; + + case self::YEAR_SHORT_8601: + if (is_numeric($date)) { + $date = intval($date); + if (($calc === 'set') || ($calc === 'cmp')) { + $date = self::getFullYear($date); + } + if ($calc === 'add') { + $date += $year; + $calc = 'set'; + } else if ($calc === 'sub') { + $date = $year - $date; + $calc = 'set'; + } + + return $this->_assign($calc, $this->mktime(0, 0, 0, $month, $day, $date, true), + $this->mktime(0, 0, 0, $month, $day, $year, true), false); + } + + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, year expected", 0, null, $date); + break; + + // time formats + case self::MERIDIEM: + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception('meridiem not supported', 0, null, $date); + break; + + case self::SWATCH: + if (is_numeric($date)) { + $rest = intval($date); + $hours = floor($rest * 24 / 1000); + $rest = $rest - ($hours * 1000 / 24); + $minutes = floor($rest * 1440 / 1000); + $rest = $rest - ($minutes * 1000 / 1440); + $seconds = floor($rest * 86400 / 1000); + return $this->_assign($calc, $this->mktime($hours, $minutes, $seconds, 1, 1, 1970, true), + $this->mktime($hour, $minute, $second, 1, 1, 1970, true), false); + } + + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, swatchstamp expected", 0, null, $date); + break; + + case self::HOUR_SHORT_AM: + if (is_numeric($date)) { + return $this->_assign($calc, $this->mktime(intval($date), 0, 0, 1, 1, 1970, true), + $this->mktime($hour, 0, 0, 1, 1, 1970, true), false); + } + + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, hour expected", 0, null, $date); + break; + + case self::HOUR_SHORT: + if (is_numeric($date)) { + return $this->_assign($calc, $this->mktime(intval($date), 0, 0, 1, 1, 1970, true), + $this->mktime($hour, 0, 0, 1, 1, 1970, true), false); + } + + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, hour expected", 0, null, $date); + break; + + case self::HOUR_AM: + if (is_numeric($date)) { + return $this->_assign($calc, $this->mktime(intval($date), 0, 0, 1, 1, 1970, true), + $this->mktime($hour, 0, 0, 1, 1, 1970, true), false); + } + + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, hour expected", 0, null, $date); + break; + + case self::HOUR: + if (is_numeric($date)) { + return $this->_assign($calc, $this->mktime(intval($date), 0, 0, 1, 1, 1970, true), + $this->mktime($hour, 0, 0, 1, 1, 1970, true), false); + } + + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, hour expected", 0, null, $date); + break; + + case self::MINUTE: + if (is_numeric($date)) { + return $this->_assign($calc, $this->mktime(0, intval($date), 0, 1, 1, 1970, true), + $this->mktime(0, $minute, 0, 1, 1, 1970, true), false); + } + + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, minute expected", 0, null, $date); + break; + + case self::SECOND: + if (is_numeric($date)) { + return $this->_assign($calc, $this->mktime(0, 0, intval($date), 1, 1, 1970, true), + $this->mktime(0, 0, $second, 1, 1, 1970, true), false); + } + + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, second expected", 0, null, $date); + break; + + case self::MILLISECOND: + if (is_numeric($date)) { + switch($calc) { + case 'set' : + return $this->setMillisecond($date); + break; + case 'add' : + return $this->addMillisecond($date); + break; + case 'sub' : + return $this->subMillisecond($date); + break; + } + + return $this->compareMillisecond($date); + } + + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, milliseconds expected", 0, null, $date); + break; + + case self::MINUTE_SHORT: + if (is_numeric($date)) { + return $this->_assign($calc, $this->mktime(0, intval($date), 0, 1, 1, 1970, true), + $this->mktime(0, $minute, 0, 1, 1, 1970, true), false); + } + + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, minute expected", 0, null, $date); + break; + + case self::SECOND_SHORT: + if (is_numeric($date)) { + return $this->_assign($calc, $this->mktime(0, 0, intval($date), 1, 1, 1970, true), + $this->mktime(0, 0, $second, 1, 1, 1970, true), false); + } + + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, second expected", 0, null, $date); + break; + + // timezone formats + // break intentionally omitted + case self::TIMEZONE_NAME: + case self::TIMEZONE: + case self::TIMEZONE_SECS: + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception('timezone not supported', 0, null, $date); + break; + + case self::DAYLIGHT: + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception('daylight not supported', 0, null, $date); + break; + + case self::GMT_DIFF: + case self::GMT_DIFF_SEP: + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception('gmtdiff not supported', 0, null, $date); + break; + + // date strings + case self::ISO_8601: + // (-)YYYY-MM-dd + preg_match('/^(-{0,1}\d{4})-(\d{2})-(\d{2})/', $date, $datematch); + // (-)YY-MM-dd + if (empty($datematch)) { + preg_match('/^(-{0,1}\d{2})-(\d{2})-(\d{2})/', $date, $datematch); + } + // (-)YYYYMMdd + if (empty($datematch)) { + preg_match('/^(-{0,1}\d{4})(\d{2})(\d{2})/', $date, $datematch); + } + // (-)YYMMdd + if (empty($datematch)) { + preg_match('/^(-{0,1}\d{2})(\d{2})(\d{2})/', $date, $datematch); + } + $tmpdate = $date; + if (!empty($datematch)) { + $dateMatchCharCount = iconv_strlen($datematch[0], 'UTF-8'); + $tmpdate = iconv_substr($date, + $dateMatchCharCount, + iconv_strlen($date, 'UTF-8') - $dateMatchCharCount, + 'UTF-8'); + } + // (T)hh:mm:ss + preg_match('/[T,\s]{0,1}(\d{2}):(\d{2}):(\d{2})/', $tmpdate, $timematch); + if (empty($timematch)) { + preg_match('/[T,\s]{0,1}(\d{2})(\d{2})(\d{2})/', $tmpdate, $timematch); + } + if (empty($datematch) and empty($timematch)) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("unsupported ISO8601 format ($date)", 0, null, $date); + } + if (!empty($timematch)) { + $timeMatchCharCount = iconv_strlen($timematch[0], 'UTF-8'); + $tmpdate = iconv_substr($tmpdate, + $timeMatchCharCount, + iconv_strlen($tmpdate, 'UTF-8') - $timeMatchCharCount, + 'UTF-8'); + } + if (empty($datematch)) { + $datematch[1] = 1970; + $datematch[2] = 1; + $datematch[3] = 1; + } else if (iconv_strlen($datematch[1], 'UTF-8') == 2) { + $datematch[1] = self::getFullYear($datematch[1]); + } + if (empty($timematch)) { + $timematch[1] = 0; + $timematch[2] = 0; + $timematch[3] = 0; + } + + if (($calc == 'set') || ($calc == 'cmp')) { + --$datematch[2]; + --$month; + --$datematch[3]; + --$day; + $datematch[1] -= 1970; + $year -= 1970; + } + return $this->_assign($calc, $this->mktime($timematch[1], $timematch[2], $timematch[3], 1 + $datematch[2], 1 + $datematch[3], 1970 + $datematch[1], false), + $this->mktime($hour, $minute, $second, 1 + $month, 1 + $day, 1970 + $year, false), false); + break; + + case self::RFC_2822: + $result = preg_match('/^\w{3},\s(\d{1,2})\s(\w{3})\s(\d{4})\s(\d{2}):(\d{2}):{0,1}(\d{0,2})\s([+-]{1}\d{4})$/', $date, $match); + if (!$result) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("no RFC 2822 format ($date)", 0, null, $date); + } + + $months = $this->_getDigitFromName($match[2]); + + if (($calc == 'set') || ($calc == 'cmp')) { + --$months; + --$month; + --$match[1]; + --$day; + $match[3] -= 1970; + $year -= 1970; + } + return $this->_assign($calc, $this->mktime($match[4], $match[5], $match[6], 1 + $months, 1 + $match[1], 1970 + $match[3], false), + $this->mktime($hour, $minute, $second, 1 + $month, 1 + $day, 1970 + $year, false), false); + break; + + case self::TIMESTAMP: + if (is_numeric($date)) { + return $this->_assign($calc, $date, $this->getUnixTimestamp()); + } + + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, timestamp expected", 0, null, $date); + break; + + // additional formats + // break intentionally omitted + case self::ERA: + case self::ERA_NAME: + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception('era not supported', 0, null, $date); + break; + + case self::DATES: + try { + $parsed = Zend_Locale_Format::getDate($date, array('locale' => $locale, 'format_type' => 'iso', 'fix_date' => true)); + + if (($calc == 'set') || ($calc == 'cmp')) { + --$parsed['month']; + --$month; + --$parsed['day']; + --$day; + $parsed['year'] -= 1970; + $year -= 1970; + } + + return $this->_assign($calc, $this->mktime(0, 0, 0, 1 + $parsed['month'], 1 + $parsed['day'], 1970 + $parsed['year'], true), + $this->mktime(0, 0, 0, 1 + $month, 1 + $day, 1970 + $year, true), $hour); + } catch (Zend_Locale_Exception $e) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception($e->getMessage(), 0, $e, $date); + } + break; + + case self::DATE_FULL: + try { + $format = Zend_Locale_Data::getContent($locale, 'date', array('gregorian', 'full')); + $parsed = Zend_Locale_Format::getDate($date, array('date_format' => $format, 'format_type' => 'iso', 'locale' => $locale)); + + if (($calc == 'set') || ($calc == 'cmp')) { + --$parsed['month']; + --$month; + --$parsed['day']; + --$day; + $parsed['year'] -= 1970; + $year -= 1970; + } + return $this->_assign($calc, $this->mktime(0, 0, 0, 1 + $parsed['month'], 1 + $parsed['day'], 1970 + $parsed['year'], true), + $this->mktime(0, 0, 0, 1 + $month, 1 + $day, 1970 + $year, true), $hour); + } catch (Zend_Locale_Exception $e) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception($e->getMessage(), 0, $e, $date); + } + break; + + case self::DATE_LONG: + try { + $format = Zend_Locale_Data::getContent($locale, 'date', array('gregorian', 'long')); + $parsed = Zend_Locale_Format::getDate($date, array('date_format' => $format, 'format_type' => 'iso', 'locale' => $locale)); + + if (($calc == 'set') || ($calc == 'cmp')){ + --$parsed['month']; + --$month; + --$parsed['day']; + --$day; + $parsed['year'] -= 1970; + $year -= 1970; + } + return $this->_assign($calc, $this->mktime(0, 0, 0, 1 + $parsed['month'], 1 + $parsed['day'], 1970 + $parsed['year'], true), + $this->mktime(0, 0, 0, 1 + $month, 1 + $day, 1970 + $year, true), $hour); + } catch (Zend_Locale_Exception $e) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception($e->getMessage(), 0, $e, $date); + } + break; + + case self::DATE_MEDIUM: + try { + $format = Zend_Locale_Data::getContent($locale, 'date', array('gregorian', 'medium')); + $parsed = Zend_Locale_Format::getDate($date, array('date_format' => $format, 'format_type' => 'iso', 'locale' => $locale)); + + if (($calc == 'set') || ($calc == 'cmp')) { + --$parsed['month']; + --$month; + --$parsed['day']; + --$day; + $parsed['year'] -= 1970; + $year -= 1970; + } + return $this->_assign($calc, $this->mktime(0, 0, 0, 1 + $parsed['month'], 1 + $parsed['day'], 1970 + $parsed['year'], true), + $this->mktime(0, 0, 0, 1 + $month, 1 + $day, 1970 + $year, true), $hour); + } catch (Zend_Locale_Exception $e) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception($e->getMessage(), 0, $e, $date); + } + break; + + case self::DATE_SHORT: + try { + $format = Zend_Locale_Data::getContent($locale, 'date', array('gregorian', 'short')); + $parsed = Zend_Locale_Format::getDate($date, array('date_format' => $format, 'format_type' => 'iso', 'locale' => $locale)); + + $parsed['year'] = self::getFullYear($parsed['year']); + + if (($calc == 'set') || ($calc == 'cmp')) { + --$parsed['month']; + --$month; + --$parsed['day']; + --$day; + $parsed['year'] -= 1970; + $year -= 1970; + } + return $this->_assign($calc, $this->mktime(0, 0, 0, 1 + $parsed['month'], 1 + $parsed['day'], 1970 + $parsed['year'], true), + $this->mktime(0, 0, 0, 1 + $month, 1 + $day, 1970 + $year, true), $hour); + } catch (Zend_Locale_Exception $e) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception($e->getMessage(), 0, $e, $date); + } + break; + + case self::TIMES: + try { + if ($calc != 'set') { + $month = 1; + $day = 1; + $year = 1970; + } + $parsed = Zend_Locale_Format::getTime($date, array('locale' => $locale, 'format_type' => 'iso', 'fix_date' => true)); + return $this->_assign($calc, $this->mktime($parsed['hour'], $parsed['minute'], $parsed['second'], $month, $day, $year, true), + $this->mktime($hour, $minute, $second, $month, $day, $year, true), false); + } catch (Zend_Locale_Exception $e) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception($e->getMessage(), 0, $e, $date); + } + break; + + case self::TIME_FULL: + try { + $format = Zend_Locale_Data::getContent($locale, 'time', array('gregorian', 'full')); + $parsed = Zend_Locale_Format::getTime($date, array('date_format' => $format, 'format_type' => 'iso', 'locale' => $locale)); + if ($calc != 'set') { + $month = 1; + $day = 1; + $year = 1970; + } + + if (!isset($parsed['second'])) { + $parsed['second'] = 0; + } + + return $this->_assign($calc, $this->mktime($parsed['hour'], $parsed['minute'], $parsed['second'], $month, $day, $year, true), + $this->mktime($hour, $minute, $second, $month, $day, $year, true), false); + } catch (Zend_Locale_Exception $e) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception($e->getMessage(), 0, $e, $date); + } + break; + + case self::TIME_LONG: + try { + $format = Zend_Locale_Data::getContent($locale, 'time', array('gregorian', 'long')); + $parsed = Zend_Locale_Format::getTime($date, array('date_format' => $format, 'format_type' => 'iso', 'locale' => $locale)); + if ($calc != 'set') { + $month = 1; + $day = 1; + $year = 1970; + } + return $this->_assign($calc, $this->mktime($parsed['hour'], $parsed['minute'], $parsed['second'], $month, $day, $year, true), + $this->mktime($hour, $minute, $second, $month, $day, $year, true), false); + } catch (Zend_Locale_Exception $e) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception($e->getMessage(), 0, $e, $date); + } + break; + + case self::TIME_MEDIUM: + try { + $format = Zend_Locale_Data::getContent($locale, 'time', array('gregorian', 'medium')); + $parsed = Zend_Locale_Format::getTime($date, array('date_format' => $format, 'format_type' => 'iso', 'locale' => $locale)); + if ($calc != 'set') { + $month = 1; + $day = 1; + $year = 1970; + } + return $this->_assign($calc, $this->mktime($parsed['hour'], $parsed['minute'], $parsed['second'], $month, $day, $year, true), + $this->mktime($hour, $minute, $second, $month, $day, $year, true), false); + } catch (Zend_Locale_Exception $e) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception($e->getMessage(), 0, $e, $date); + } + break; + + case self::TIME_SHORT: + try { + $format = Zend_Locale_Data::getContent($locale, 'time', array('gregorian', 'short')); + $parsed = Zend_Locale_Format::getTime($date, array('date_format' => $format, 'format_type' => 'iso', 'locale' => $locale)); + if ($calc != 'set') { + $month = 1; + $day = 1; + $year = 1970; + } + + if (!isset($parsed['second'])) { + $parsed['second'] = 0; + } + + return $this->_assign($calc, $this->mktime($parsed['hour'], $parsed['minute'], $parsed['second'], $month, $day, $year, true), + $this->mktime($hour, $minute, $second, $month, $day, $year, true), false); + } catch (Zend_Locale_Exception $e) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception($e->getMessage(), 0, $e, $date); + } + break; + + case self::DATETIME: + try { + $parsed = Zend_Locale_Format::getDateTime($date, array('locale' => $locale, 'format_type' => 'iso', 'fix_date' => true)); + if (($calc == 'set') || ($calc == 'cmp')) { + --$parsed['month']; + --$month; + --$parsed['day']; + --$day; + $parsed['year'] -= 1970; + $year -= 1970; + } + return $this->_assign($calc, $this->mktime($parsed['hour'], $parsed['minute'], $parsed['second'], 1 + $parsed['month'], 1 + $parsed['day'], 1970 + $parsed['year'], true), + $this->mktime($hour, $minute, $second, 1 + $month, 1 + $day, 1970 + $year, true), $hour); + } catch (Zend_Locale_Exception $e) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception($e->getMessage(), 0, $e, $date); + } + break; + + case self::DATETIME_FULL: + try { + $format = Zend_Locale_Data::getContent($locale, 'datetime', array('gregorian', 'full')); + $parsed = Zend_Locale_Format::getDateTime($date, array('date_format' => $format, 'format_type' => 'iso', 'locale' => $locale)); + + if (($calc == 'set') || ($calc == 'cmp')) { + --$parsed['month']; + --$month; + --$parsed['day']; + --$day; + $parsed['year'] -= 1970; + $year -= 1970; + } + + if (!isset($parsed['second'])) { + $parsed['second'] = 0; + } + + return $this->_assign($calc, $this->mktime($parsed['hour'], $parsed['minute'], $parsed['second'], 1 + $parsed['month'], 1 + $parsed['day'], 1970 + $parsed['year'], true), + $this->mktime($hour, $minute, $second, 1 + $month, 1 + $day, 1970 + $year, true), $hour); + } catch (Zend_Locale_Exception $e) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception($e->getMessage(), 0, $e, $date); + } + break; + + case self::DATETIME_LONG: + try { + $format = Zend_Locale_Data::getContent($locale, 'datetime', array('gregorian', 'long')); + $parsed = Zend_Locale_Format::getDateTime($date, array('date_format' => $format, 'format_type' => 'iso', 'locale' => $locale)); + + if (($calc == 'set') || ($calc == 'cmp')){ + --$parsed['month']; + --$month; + --$parsed['day']; + --$day; + $parsed['year'] -= 1970; + $year -= 1970; + } + return $this->_assign($calc, $this->mktime($parsed['hour'], $parsed['minute'], $parsed['second'], 1 + $parsed['month'], 1 + $parsed['day'], 1970 + $parsed['year'], true), + $this->mktime($hour, $minute, $second, 1 + $month, 1 + $day, 1970 + $year, true), $hour); + } catch (Zend_Locale_Exception $e) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception($e->getMessage(), 0, $e, $date); + } + break; + + case self::DATETIME_MEDIUM: + try { + $format = Zend_Locale_Data::getContent($locale, 'datetime', array('gregorian', 'medium')); + $parsed = Zend_Locale_Format::getDateTime($date, array('date_format' => $format, 'format_type' => 'iso', 'locale' => $locale)); + if (($calc == 'set') || ($calc == 'cmp')) { + --$parsed['month']; + --$month; + --$parsed['day']; + --$day; + $parsed['year'] -= 1970; + $year -= 1970; + } + return $this->_assign($calc, $this->mktime($parsed['hour'], $parsed['minute'], $parsed['second'], 1 + $parsed['month'], 1 + $parsed['day'], 1970 + $parsed['year'], true), + $this->mktime($hour, $minute, $second, 1 + $month, 1 + $day, 1970 + $year, true), $hour); + } catch (Zend_Locale_Exception $e) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception($e->getMessage(), 0, $e, $date); + } + break; + + case self::DATETIME_SHORT: + try { + $format = Zend_Locale_Data::getContent($locale, 'datetime', array('gregorian', 'short')); + $parsed = Zend_Locale_Format::getDateTime($date, array('date_format' => $format, 'format_type' => 'iso', 'locale' => $locale)); + + $parsed['year'] = self::getFullYear($parsed['year']); + + if (($calc == 'set') || ($calc == 'cmp')) { + --$parsed['month']; + --$month; + --$parsed['day']; + --$day; + $parsed['year'] -= 1970; + $year -= 1970; + } + + if (!isset($parsed['second'])) { + $parsed['second'] = 0; + } + + return $this->_assign($calc, $this->mktime($parsed['hour'], $parsed['minute'], $parsed['second'], 1 + $parsed['month'], 1 + $parsed['day'], 1970 + $parsed['year'], true), + $this->mktime($hour, $minute, $second, 1 + $month, 1 + $day, 1970 + $year, true), $hour); + } catch (Zend_Locale_Exception $e) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception($e->getMessage(), 0, $e, $date); + } + break; + + // ATOM and RFC_3339 are identical + case self::ATOM: + case self::RFC_3339: + $result = preg_match('/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})\d{0,4}([+-]{1}\d{2}:\d{2}|Z)$/', $date, $match); + if (!$result) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, ATOM format expected", 0, null, $date); + } + + if (($calc == 'set') || ($calc == 'cmp')) { + --$match[2]; + --$month; + --$match[3]; + --$day; + $match[1] -= 1970; + $year -= 1970; + } + return $this->_assign($calc, $this->mktime($match[4], $match[5], $match[6], 1 + $match[2], 1 + $match[3], 1970 + $match[1], true), + $this->mktime($hour, $minute, $second, 1 + $month, 1 + $day, 1970 + $year, true), false); + break; + + case self::COOKIE: + $result = preg_match("/^\w{6,9},\s(\d{2})-(\w{3})-(\d{2})\s(\d{2}):(\d{2}):(\d{2})\s.{3,20}$/", $date, $match); + if (!$result) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, COOKIE format expected", 0, null, $date); + } + $matchStartPos = iconv_strpos($match[0], ' ', 0, 'UTF-8') + 1; + $match[0] = iconv_substr($match[0], + $matchStartPos, + iconv_strlen($match[0], 'UTF-8') - $matchStartPos, + 'UTF-8'); + + $months = $this->_getDigitFromName($match[2]); + $match[3] = self::getFullYear($match[3]); + + if (($calc == 'set') || ($calc == 'cmp')) { + --$months; + --$month; + --$match[1]; + --$day; + $match[3] -= 1970; + $year -= 1970; + } + return $this->_assign($calc, $this->mktime($match[4], $match[5], $match[6], 1 + $months, 1 + $match[1], 1970 + $match[3], true), + $this->mktime($hour, $minute, $second, 1 + $month, 1 + $day, 1970 + $year, true), false); + break; + + case self::RFC_822: + case self::RFC_1036: + // new RFC 822 format, identical to RFC 1036 standard + $result = preg_match('/^\w{0,3},{0,1}\s{0,1}(\d{1,2})\s(\w{3})\s(\d{2})\s(\d{2}):(\d{2}):{0,1}(\d{0,2})\s([+-]{1}\d{4}|\w{1,20})$/', $date, $match); + if (!$result) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, RFC 822 date format expected", 0, null, $date); + } + + $months = $this->_getDigitFromName($match[2]); + $match[3] = self::getFullYear($match[3]); + + if (($calc == 'set') || ($calc == 'cmp')) { + --$months; + --$month; + --$match[1]; + --$day; + $match[3] -= 1970; + $year -= 1970; + } + return $this->_assign($calc, $this->mktime($match[4], $match[5], $match[6], 1 + $months, 1 + $match[1], 1970 + $match[3], false), + $this->mktime($hour, $minute, $second, 1 + $month, 1 + $day, 1970 + $year, false), false); + break; + + case self::RFC_850: + $result = preg_match('/^\w{6,9},\s(\d{2})-(\w{3})-(\d{2})\s(\d{2}):(\d{2}):(\d{2})\s.{3,21}$/', $date, $match); + if (!$result) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, RFC 850 date format expected", 0, null, $date); + } + + $months = $this->_getDigitFromName($match[2]); + $match[3] = self::getFullYear($match[3]); + + if (($calc == 'set') || ($calc == 'cmp')) { + --$months; + --$month; + --$match[1]; + --$day; + $match[3] -= 1970; + $year -= 1970; + } + return $this->_assign($calc, $this->mktime($match[4], $match[5], $match[6], 1 + $months, 1 + $match[1], 1970 + $match[3], true), + $this->mktime($hour, $minute, $second, 1 + $month, 1 + $day, 1970 + $year, true), false); + break; + + case self::RFC_1123: + $result = preg_match('/^\w{0,3},{0,1}\s{0,1}(\d{1,2})\s(\w{3})\s(\d{2,4})\s(\d{2}):(\d{2}):{0,1}(\d{0,2})\s([+-]{1}\d{4}|\w{1,20})$/', $date, $match); + if (!$result) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, RFC 1123 date format expected", 0, null, $date); + } + + $months = $this->_getDigitFromName($match[2]); + + if (($calc == 'set') || ($calc == 'cmp')) { + --$months; + --$month; + --$match[1]; + --$day; + $match[3] -= 1970; + $year -= 1970; + } + return $this->_assign($calc, $this->mktime($match[4], $match[5], $match[6], 1 + $months, 1 + $match[1], 1970 + $match[3], true), + $this->mktime($hour, $minute, $second, 1 + $month, 1 + $day, 1970 + $year, true), false); + break; + + case self::RSS: + $result = preg_match('/^\w{3},\s(\d{2})\s(\w{3})\s(\d{2,4})\s(\d{1,2}):(\d{2}):(\d{2})\s.{1,21}$/', $date, $match); + if (!$result) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, RSS date format expected", 0, null, $date); + } + + $months = $this->_getDigitFromName($match[2]); + $match[3] = self::getFullYear($match[3]); + + if (($calc == 'set') || ($calc == 'cmp')) { + --$months; + --$month; + --$match[1]; + --$day; + $match[3] -= 1970; + $year -= 1970; + } + return $this->_assign($calc, $this->mktime($match[4], $match[5], $match[6], 1 + $months, 1 + $match[1], 1970 + $match[3], true), + $this->mktime($hour, $minute, $second, 1 + $month, 1 + $day, 1970 + $year, true), false); + break; + + case self::W3C: + $result = preg_match('/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})[+-]{1}\d{2}:\d{2}$/', $date, $match); + if (!$result) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, W3C date format expected", 0, null, $date); + } + + if (($calc == 'set') || ($calc == 'cmp')) { + --$match[2]; + --$month; + --$match[3]; + --$day; + $match[1] -= 1970; + $year -= 1970; + } + return $this->_assign($calc, $this->mktime($match[4], $match[5], $match[6], 1 + $match[2], 1 + $match[3], 1970 + $match[1], true), + $this->mktime($hour, $minute, $second, 1 + $month, 1 + $day, 1970 + $year, true), false); + break; + + default: + if (!is_numeric($date) || !empty($part)) { + try { + if (empty($part)) { + $part = Zend_Locale_Format::getDateFormat($locale) . " "; + $part .= Zend_Locale_Format::getTimeFormat($locale); + } + + $parsed = Zend_Locale_Format::getDate($date, array('date_format' => $part, 'locale' => $locale, 'fix_date' => true, 'format_type' => 'iso')); + if ((strpos(strtoupper($part), 'YY') !== false) and (strpos(strtoupper($part), 'YYYY') === false)) { + $parsed['year'] = self::getFullYear($parsed['year']); + } + + if (($calc == 'set') || ($calc == 'cmp')) { + if (isset($parsed['month'])) { + --$parsed['month']; + } else { + $parsed['month'] = 0; + } + + if (isset($parsed['day'])) { + --$parsed['day']; + } else { + $parsed['day'] = 0; + } + + if (isset($parsed['year'])) { + $parsed['year'] -= 1970; + } else { + $parsed['year'] = 0; + } + } + + return $this->_assign($calc, $this->mktime( + isset($parsed['hour']) ? $parsed['hour'] : 0, + isset($parsed['minute']) ? $parsed['minute'] : 0, + isset($parsed['second']) ? $parsed['second'] : 0, + isset($parsed['month']) ? (1 + $parsed['month']) : 1, + isset($parsed['day']) ? (1 + $parsed['day']) : 1, + isset($parsed['year']) ? (1970 + $parsed['year']) : 1970, + false), $this->getUnixTimestamp(), false); + } catch (Zend_Locale_Exception $e) { + if (!is_numeric($date)) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception($e->getMessage(), 0, $e, $date); + } + } + } + + return $this->_assign($calc, $date, $this->getUnixTimestamp(), false); + break; + } + } + + /** + * Returns true when both date objects or date parts are equal. + * For example: + * 15.May.2000 <-> 15.June.2000 Equals only for Day or Year... all other will return false + * + * @param string|integer|array|Zend_Date $date Date or datepart to equal with + * @param string $part OPTIONAL Part of the date to compare, if null the timestamp is used + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return boolean + * @throws Zend_Date_Exception + */ + public function equals($date, $part = null, $locale = null) + { + $result = $this->compare($date, $part, $locale); + + if ($result == 0) { + return true; + } + + return false; + } + + /** + * Returns if the given date or datepart is earlier + * For example: + * 15.May.2000 <-> 13.June.1999 will return true for day, year and date, but not for month + * + * @param string|integer|array|Zend_Date $date Date or datepart to compare with + * @param string $part OPTIONAL Part of the date to compare, if null the timestamp is used + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return boolean + * @throws Zend_Date_Exception + */ + public function isEarlier($date, $part = null, $locale = null) + { + $result = $this->compare($date, $part, $locale); + + if ($result == -1) { + return true; + } + + return false; + } + + /** + * Returns if the given date or datepart is later + * For example: + * 15.May.2000 <-> 13.June.1999 will return true for month but false for day, year and date + * Returns if the given date is later + * + * @param string|integer|array|Zend_Date $date Date or datepart to compare with + * @param string $part OPTIONAL Part of the date to compare, if null the timestamp is used + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return boolean + * @throws Zend_Date_Exception + */ + public function isLater($date, $part = null, $locale = null) + { + $result = $this->compare($date, $part, $locale); + + if ($result == 1) { + return true; + } + + return false; + } + + /** + * Returns only the time of the date as new Zend_Date object + * For example: + * 15.May.2000 10:11:23 will return a dateobject equal to 01.Jan.1970 10:11:23 + * + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date + */ + public function getTime($locale = null) + { + if (self::$_options['format_type'] == 'php') { + $format = 'H:i:s'; + } else { + $format = self::TIME_MEDIUM; + } + + return $this->copyPart($format, $locale); + } + + /** + * Returns the calculated time + * + * @param string $calc Calculation to make + * @param string|integer|array|Zend_Date $time Time to calculate with, if null the actual time is taken + * @param string $format Timeformat for parsing input + * @param string|Zend_Locale $locale Locale for parsing input + * @return integer|Zend_Date new time + * @throws Zend_Date_Exception + */ + private function _time($calc, $time, $format, $locale) + { + if ($time === null) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception('parameter $time must be set, null is not allowed'); + } + + if ($time instanceof Zend_Date) { + // extract time from object + $time = $time->toString('HH:mm:ss', 'iso'); + } else { + if (is_array($time)) { + if ((isset($time['hour']) === true) or (isset($time['minute']) === true) or + (isset($time['second']) === true)) { + $parsed = $time; + } else { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("no hour, minute or second given in array"); + } + } else { + if (self::$_options['format_type'] == 'php') { + $format = Zend_Locale_Format::convertPhpToIsoFormat($format); + } + try { + if ($locale === null) { + $locale = $this->getLocale(); + } + + $parsed = Zend_Locale_Format::getTime($time, array('date_format' => $format, 'locale' => $locale, 'format_type' => 'iso')); + } catch (Zend_Locale_Exception $e) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception($e->getMessage(), 0, $e); + } + } + + if (!array_key_exists('hour', $parsed)) { + $parsed['hour'] = 0; + } + + if (!array_key_exists('minute', $parsed)) { + $parsed['minute'] = 0; + } + + if (!array_key_exists('second', $parsed)) { + $parsed['second'] = 0; + } + + $time = str_pad($parsed['hour'], 2, '0', STR_PAD_LEFT) . ":"; + $time .= str_pad($parsed['minute'], 2, '0', STR_PAD_LEFT) . ":"; + $time .= str_pad($parsed['second'], 2, '0', STR_PAD_LEFT); + } + + $return = $this->_calcdetail($calc, $time, self::TIMES, 'de'); + if ($calc != 'cmp') { + return $this; + } + + return $return; + } + + + /** + * Sets a new time for the date object. Format defines how to parse the time string. + * Also a complete date can be given, but only the time is used for setting. + * For example: dd.MMMM.yyTHH:mm' and 'ss sec'-> 10.May.07T25:11 and 44 sec => 1h11min44sec + 1 day + * Returned is the new date object and the existing date is left as it was before + * + * @param string|integer|array|Zend_Date $time Time to set + * @param string $format OPTIONAL Timeformat for parsing input + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function setTime($time, $format = null, $locale = null) + { + return $this->_time('set', $time, $format, $locale); + } + + + /** + * Adds a time to the existing date. Format defines how to parse the time string. + * If only parts are given the other parts are set to 0. + * If no format is given, the standardformat of this locale is used. + * For example: HH:mm:ss -> 10 -> +10 hours + * + * @param string|integer|array|Zend_Date $time Time to add + * @param string $format OPTIONAL Timeformat for parsing input + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function addTime($time, $format = null, $locale = null) + { + return $this->_time('add', $time, $format, $locale); + } + + + /** + * Subtracts a time from the existing date. Format defines how to parse the time string. + * If only parts are given the other parts are set to 0. + * If no format is given, the standardformat of this locale is used. + * For example: HH:mm:ss -> 10 -> -10 hours + * + * @param string|integer|array|Zend_Date $time Time to sub + * @param string $format OPTIONAL Timeformat for parsing input + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid inteface + * @throws Zend_Date_Exception + */ + public function subTime($time, $format = null, $locale = null) + { + return $this->_time('sub', $time, $format, $locale); + } + + + /** + * Compares the time from the existing date. Format defines how to parse the time string. + * If only parts are given the other parts are set to default. + * If no format us given, the standardformat of this locale is used. + * For example: HH:mm:ss -> 10 -> 10 hours + * + * @param string|integer|array|Zend_Date $time Time to compare + * @param string $format OPTIONAL Timeformat for parsing input + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return integer 0 = equal, 1 = later, -1 = earlier + * @throws Zend_Date_Exception + */ + public function compareTime($time, $format = null, $locale = null) + { + return $this->_time('cmp', $time, $format, $locale); + } + + /** + * Returns a clone of $this, with the time part set to 00:00:00. + * + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date + */ + public function getDate($locale = null) + { + $orig = self::$_options['format_type']; + if (self::$_options['format_type'] == 'php') { + self::$_options['format_type'] = 'iso'; + } + + $date = $this->copyPart(self::DATE_MEDIUM, $locale); + $date->addTimestamp($this->getGmtOffset()); + self::$_options['format_type'] = $orig; + + return $date; + } + + /** + * Returns the calculated date + * + * @param string $calc Calculation to make + * @param string|integer|array|Zend_Date $date Date to calculate with, if null the actual date is taken + * @param string $format Date format for parsing + * @param string|Zend_Locale $locale Locale for parsing input + * @return integer|Zend_Date new date + * @throws Zend_Date_Exception + */ + private function _date($calc, $date, $format, $locale) + { + if ($date === null) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception('parameter $date must be set, null is not allowed'); + } + + if ($date instanceof Zend_Date) { + // extract date from object + $date = $date->toString('d.M.y', 'iso'); + } else { + if (is_array($date)) { + if ((isset($date['year']) === true) or (isset($date['month']) === true) or + (isset($date['day']) === true)) { + $parsed = $date; + } else { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("no day,month or year given in array"); + } + } else { + if ((self::$_options['format_type'] == 'php') && !defined($format)) { + $format = Zend_Locale_Format::convertPhpToIsoFormat($format); + } + try { + if ($locale === null) { + $locale = $this->getLocale(); + } + + $parsed = Zend_Locale_Format::getDate($date, array('date_format' => $format, 'locale' => $locale, 'format_type' => 'iso')); + if ((strpos(strtoupper($format), 'YY') !== false) and (strpos(strtoupper($format), 'YYYY') === false)) { + $parsed['year'] = self::getFullYear($parsed['year']); + } + } catch (Zend_Locale_Exception $e) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception($e->getMessage(), 0, $e); + } + } + + if (!array_key_exists('day', $parsed)) { + $parsed['day'] = 1; + } + + if (!array_key_exists('month', $parsed)) { + $parsed['month'] = 1; + } + + if (!array_key_exists('year', $parsed)) { + $parsed['year'] = 0; + } + + $date = $parsed['day'] . "." . $parsed['month'] . "." . $parsed['year']; + } + + $return = $this->_calcdetail($calc, $date, self::DATE_MEDIUM, 'de'); + if ($calc != 'cmp') { + return $this; + } + return $return; + } + + + /** + * Sets a new date for the date object. Format defines how to parse the date string. + * Also a complete date with time can be given, but only the date is used for setting. + * For example: MMMM.yy HH:mm-> May.07 22:11 => 01.May.07 00:00 + * Returned is the new date object and the existing time is left as it was before + * + * @param string|integer|array|Zend_Date $date Date to set + * @param string $format OPTIONAL Date format for parsing + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function setDate($date, $format = null, $locale = null) + { + return $this->_date('set', $date, $format, $locale); + } + + + /** + * Adds a date to the existing date object. Format defines how to parse the date string. + * If only parts are given the other parts are set to 0. + * If no format is given, the standardformat of this locale is used. + * For example: MM.dd.YYYY -> 10 -> +10 months + * + * @param string|integer|array|Zend_Date $date Date to add + * @param string $format OPTIONAL Date format for parsing input + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function addDate($date, $format = null, $locale = null) + { + return $this->_date('add', $date, $format, $locale); + } + + + /** + * Subtracts a date from the existing date object. Format defines how to parse the date string. + * If only parts are given the other parts are set to 0. + * If no format is given, the standardformat of this locale is used. + * For example: MM.dd.YYYY -> 10 -> -10 months + * Be aware: Subtracting 2 months is not equal to Adding -2 months !!! + * + * @param string|integer|array|Zend_Date $date Date to sub + * @param string $format OPTIONAL Date format for parsing input + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function subDate($date, $format = null, $locale = null) + { + return $this->_date('sub', $date, $format, $locale); + } + + + /** + * Compares the date from the existing date object, ignoring the time. + * Format defines how to parse the date string. + * If only parts are given the other parts are set to 0. + * If no format is given, the standardformat of this locale is used. + * For example: 10.01.2000 => 10.02.1999 -> false + * + * @param string|integer|array|Zend_Date $date Date to compare + * @param string $format OPTIONAL Date format for parsing input + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return integer 0 = equal, 1 = later, -1 = earlier + * @throws Zend_Date_Exception + */ + public function compareDate($date, $format = null, $locale = null) + { + return $this->_date('cmp', $date, $format, $locale); + } + + + /** + * Returns the full ISO 8601 date from the date object. + * Always the complete ISO 8601 specifiction is used. If an other ISO date is needed + * (ISO 8601 defines several formats) use toString() instead. + * This function does not return the ISO date as object. Use copy() instead. + * + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return string + */ + public function getIso($locale = null) + { + return $this->toString(self::ISO_8601, 'iso', $locale); + } + + + /** + * Sets a new date for the date object. Not given parts are set to default. + * Only supported ISO 8601 formats are accepted. + * For example: 050901 -> 01.Sept.2005 00:00:00, 20050201T10:00:30 -> 01.Feb.2005 10h00m30s + * Returned is the new date object + * + * @param string|integer|Zend_Date $date ISO Date to set + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function setIso($date, $locale = null) + { + return $this->_calcvalue('set', $date, 'iso', self::ISO_8601, $locale); + } + + + /** + * Adds a ISO date to the date object. Not given parts are set to default. + * Only supported ISO 8601 formats are accepted. + * For example: 050901 -> + 01.Sept.2005 00:00:00, 10:00:00 -> +10h + * Returned is the new date object + * + * @param string|integer|Zend_Date $date ISO Date to add + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function addIso($date, $locale = null) + { + return $this->_calcvalue('add', $date, 'iso', self::ISO_8601, $locale); + } + + + /** + * Subtracts a ISO date from the date object. Not given parts are set to default. + * Only supported ISO 8601 formats are accepted. + * For example: 050901 -> - 01.Sept.2005 00:00:00, 10:00:00 -> -10h + * Returned is the new date object + * + * @param string|integer|Zend_Date $date ISO Date to sub + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function subIso($date, $locale = null) + { + return $this->_calcvalue('sub', $date, 'iso', self::ISO_8601, $locale); + } + + + /** + * Compares a ISO date with the date object. Not given parts are set to default. + * Only supported ISO 8601 formats are accepted. + * For example: 050901 -> - 01.Sept.2005 00:00:00, 10:00:00 -> -10h + * Returns if equal, earlier or later + * + * @param string|integer|Zend_Date $date ISO Date to sub + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return integer 0 = equal, 1 = later, -1 = earlier + * @throws Zend_Date_Exception + */ + public function compareIso($date, $locale = null) + { + return $this->_calcvalue('cmp', $date, 'iso', self::ISO_8601, $locale); + } + + + /** + * Returns a RFC 822 compilant datestring from the date object. + * This function does not return the RFC date as object. Use copy() instead. + * + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return string + */ + public function getArpa($locale = null) + { + if (self::$_options['format_type'] == 'php') { + $format = 'D\, d M y H\:i\:s O'; + } else { + $format = self::RFC_822; + } + + return $this->toString($format, 'iso', $locale); + } + + + /** + * Sets a RFC 822 date as new date for the date object. + * Only RFC 822 compilant date strings are accepted. + * For example: Sat, 14 Feb 09 00:31:30 +0100 + * Returned is the new date object + * + * @param string|integer|Zend_Date $date RFC 822 to set + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function setArpa($date, $locale = null) + { + return $this->_calcvalue('set', $date, 'arpa', self::RFC_822, $locale); + } + + + /** + * Adds a RFC 822 date to the date object. + * ARPA messages are used in emails or HTTP Headers. + * Only RFC 822 compilant date strings are accepted. + * For example: Sat, 14 Feb 09 00:31:30 +0100 + * Returned is the new date object + * + * @param string|integer|Zend_Date $date RFC 822 Date to add + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function addArpa($date, $locale = null) + { + return $this->_calcvalue('add', $date, 'arpa', self::RFC_822, $locale); + } + + + /** + * Subtracts a RFC 822 date from the date object. + * ARPA messages are used in emails or HTTP Headers. + * Only RFC 822 compilant date strings are accepted. + * For example: Sat, 14 Feb 09 00:31:30 +0100 + * Returned is the new date object + * + * @param string|integer|Zend_Date $date RFC 822 Date to sub + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function subArpa($date, $locale = null) + { + return $this->_calcvalue('sub', $date, 'arpa', self::RFC_822, $locale); + } + + + /** + * Compares a RFC 822 compilant date with the date object. + * ARPA messages are used in emails or HTTP Headers. + * Only RFC 822 compilant date strings are accepted. + * For example: Sat, 14 Feb 09 00:31:30 +0100 + * Returns if equal, earlier or later + * + * @param string|integer|Zend_Date $date RFC 822 Date to sub + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return integer 0 = equal, 1 = later, -1 = earlier + * @throws Zend_Date_Exception + */ + public function compareArpa($date, $locale = null) + { + return $this->_calcvalue('cmp', $date, 'arpa', self::RFC_822, $locale); + } + + + /** + * Check if location is supported + * + * @param $location array - locations array + * @return $horizon float + */ + private function _checkLocation($location) + { + if (!isset($location['longitude']) or !isset($location['latitude'])) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception('Location must include \'longitude\' and \'latitude\'', 0, null, $location); + } + if (($location['longitude'] > 180) or ($location['longitude'] < -180)) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception('Longitude must be between -180 and 180', 0, null, $location); + } + if (($location['latitude'] > 90) or ($location['latitude'] < -90)) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception('Latitude must be between -90 and 90', 0, null, $location); + } + + if (!isset($location['horizon'])){ + $location['horizon'] = 'effective'; + } + + switch ($location['horizon']) { + case 'civil' : + return -0.104528; + break; + case 'nautic' : + return -0.207912; + break; + case 'astronomic' : + return -0.309017; + break; + default : + return -0.0145439; + break; + } + } + + + /** + * Returns the time of sunrise for this date and a given location as new date object + * For a list of cities and correct locations use the class Zend_Date_Cities + * + * @param $location array - location of sunrise + * ['horizon'] -> civil, nautic, astronomical, effective (default) + * ['longitude'] -> longitude of location + * ['latitude'] -> latitude of location + * @return Zend_Date + * @throws Zend_Date_Exception + */ + public function getSunrise($location) + { + $horizon = $this->_checkLocation($location); + $result = clone $this; + $result->set($this->calcSun($location, $horizon, true), self::TIMESTAMP); + return $result; + } + + + /** + * Returns the time of sunset for this date and a given location as new date object + * For a list of cities and correct locations use the class Zend_Date_Cities + * + * @param $location array - location of sunset + * ['horizon'] -> civil, nautic, astronomical, effective (default) + * ['longitude'] -> longitude of location + * ['latitude'] -> latitude of location + * @return Zend_Date + * @throws Zend_Date_Exception + */ + public function getSunset($location) + { + $horizon = $this->_checkLocation($location); + $result = clone $this; + $result->set($this->calcSun($location, $horizon, false), self::TIMESTAMP); + return $result; + } + + + /** + * Returns an array with the sunset and sunrise dates for all horizon types + * For a list of cities and correct locations use the class Zend_Date_Cities + * + * @param $location array - location of suninfo + * ['horizon'] -> civil, nautic, astronomical, effective (default) + * ['longitude'] -> longitude of location + * ['latitude'] -> latitude of location + * @return array - [sunset|sunrise][effective|civil|nautic|astronomic] + * @throws Zend_Date_Exception + */ + public function getSunInfo($location) + { + $suninfo = array(); + for ($i = 0; $i < 4; ++$i) { + switch ($i) { + case 0 : + $location['horizon'] = 'effective'; + break; + case 1 : + $location['horizon'] = 'civil'; + break; + case 2 : + $location['horizon'] = 'nautic'; + break; + case 3 : + $location['horizon'] = 'astronomic'; + break; + } + $horizon = $this->_checkLocation($location); + $result = clone $this; + $result->set($this->calcSun($location, $horizon, true), self::TIMESTAMP); + $suninfo['sunrise'][$location['horizon']] = $result; + $result = clone $this; + $result->set($this->calcSun($location, $horizon, false), self::TIMESTAMP); + $suninfo['sunset'][$location['horizon']] = $result; + } + return $suninfo; + } + + + /** + * Check a given year for leap year. + * + * @param integer|array|Zend_Date $year Year to check + * @return boolean + */ + public static function checkLeapYear($year) + { + if ($year instanceof Zend_Date) { + $year = (int) $year->toString(self::YEAR, 'iso'); + } + + if (is_array($year)) { + if (isset($year['year']) === true) { + $year = $year['year']; + } else { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("no year given in array"); + } + } + + if (!is_numeric($year)) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("year ($year) has to be integer for checkLeapYear()", 0, null, $year); + } + + return (bool) parent::isYearLeapYear($year); + } + + + /** + * Returns true, if the year is a leap year. + * + * @return boolean + */ + public function isLeapYear() + { + return self::checkLeapYear($this); + } + + + /** + * Returns if the set date is todays date + * + * @return boolean + */ + public function isToday() + { + $today = $this->date('Ymd', $this->_getTime()); + $day = $this->date('Ymd', $this->getUnixTimestamp()); + return ($today == $day); + } + + + /** + * Returns if the set date is yesterdays date + * + * @return boolean + */ + public function isYesterday() + { + list($year, $month, $day) = explode('-', $this->date('Y-m-d', $this->_getTime())); + // adjusts for leap days and DST changes that are timezone specific + $yesterday = $this->date('Ymd', $this->mktime(0, 0, 0, $month, $day -1, $year)); + $day = $this->date('Ymd', $this->getUnixTimestamp()); + return $day == $yesterday; + } + + + /** + * Returns if the set date is tomorrows date + * + * @return boolean + */ + public function isTomorrow() + { + list($year, $month, $day) = explode('-', $this->date('Y-m-d', $this->_getTime())); + // adjusts for leap days and DST changes that are timezone specific + $tomorrow = $this->date('Ymd', $this->mktime(0, 0, 0, $month, $day +1, $year)); + $day = $this->date('Ymd', $this->getUnixTimestamp()); + return $day == $tomorrow; + } + + /** + * Returns the actual date as new date object + * + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date + */ + public static function now($locale = null) + { + return new Zend_Date(time(), self::TIMESTAMP, $locale); + } + + /** + * Calculate date details + * + * @param string $calc Calculation to make + * @param string|integer|array|Zend_Date $date Date or Part to calculate + * @param string $part Datepart for Calculation + * @param string|Zend_Locale $locale Locale for parsing input + * @return integer|string new date + * @throws Zend_Date_Exception + */ + private function _calcdetail($calc, $date, $type, $locale) + { + $old = false; + if (self::$_options['format_type'] == 'php') { + self::$_options['format_type'] = 'iso'; + $old = true; + } + + switch($calc) { + case 'set' : + $return = $this->set($date, $type, $locale); + break; + case 'add' : + $return = $this->add($date, $type, $locale); + break; + case 'sub' : + $return = $this->sub($date, $type, $locale); + break; + default : + $return = $this->compare($date, $type, $locale); + break; + } + + if ($old) { + self::$_options['format_type'] = 'php'; + } + + return $return; + } + + /** + * Internal calculation, returns the requested date type + * + * @param string $calc Calculation to make + * @param string|integer|Zend_Date $value Datevalue to calculate with, if null the actual value is taken + * @param string|Zend_Locale $locale Locale for parsing input + * @return integer|Zend_Date new date + * @throws Zend_Date_Exception + */ + private function _calcvalue($calc, $value, $type, $parameter, $locale) + { + if ($value === null) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("parameter $type must be set, null is not allowed"); + } + + if ($locale === null) { + $locale = $this->getLocale(); + } + + if ($value instanceof Zend_Date) { + // extract value from object + $value = $value->toString($parameter, 'iso', $locale); + } else if (!is_array($value) && !is_numeric($value) && ($type != 'iso') && ($type != 'arpa')) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid $type ($value) operand", 0, null, $value); + } + + $return = $this->_calcdetail($calc, $value, $parameter, $locale); + if ($calc != 'cmp') { + return $this; + } + return $return; + } + + + /** + * Returns only the year from the date object as new object. + * For example: 10.May.2000 10:30:00 -> 01.Jan.2000 00:00:00 + * + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date + */ + public function getYear($locale = null) + { + if (self::$_options['format_type'] == 'php') { + $format = 'Y'; + } else { + $format = self::YEAR; + } + + return $this->copyPart($format, $locale); + } + + + /** + * Sets a new year + * If the year is between 0 and 69, 2000 will be set (2000-2069) + * If the year if between 70 and 99, 1999 will be set (1970-1999) + * 3 or 4 digit years are set as expected. If you need to set year 0-99 + * use set() instead. + * Returned is the new date object + * + * @param string|integer|array|Zend_Date $date Year to set + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function setYear($year, $locale = null) + { + return $this->_calcvalue('set', $year, 'year', self::YEAR, $locale); + } + + + /** + * Adds the year to the existing date object + * If the year is between 0 and 69, 2000 will be added (2000-2069) + * If the year if between 70 and 99, 1999 will be added (1970-1999) + * 3 or 4 digit years are added as expected. If you need to add years from 0-99 + * use add() instead. + * Returned is the new date object + * + * @param string|integer|array|Zend_Date $date Year to add + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function addYear($year, $locale = null) + { + return $this->_calcvalue('add', $year, 'year', self::YEAR, $locale); + } + + + /** + * Subs the year from the existing date object + * If the year is between 0 and 69, 2000 will be subtracted (2000-2069) + * If the year if between 70 and 99, 1999 will be subtracted (1970-1999) + * 3 or 4 digit years are subtracted as expected. If you need to subtract years from 0-99 + * use sub() instead. + * Returned is the new date object + * + * @param string|integer|array|Zend_Date $date Year to sub + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function subYear($year, $locale = null) + { + return $this->_calcvalue('sub', $year, 'year', self::YEAR, $locale); + } + + + /** + * Compares the year with the existing date object, ignoring other date parts. + * For example: 10.03.2000 -> 15.02.2000 -> true + * Returns if equal, earlier or later + * + * @param string|integer|array|Zend_Date $year Year to compare + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return integer 0 = equal, 1 = later, -1 = earlier + * @throws Zend_Date_Exception + */ + public function compareYear($year, $locale = null) + { + return $this->_calcvalue('cmp', $year, 'year', self::YEAR, $locale); + } + + + /** + * Returns only the month from the date object as new object. + * For example: 10.May.2000 10:30:00 -> 01.May.1970 00:00:00 + * + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date + */ + public function getMonth($locale = null) + { + if (self::$_options['format_type'] == 'php') { + $format = 'm'; + } else { + $format = self::MONTH; + } + + return $this->copyPart($format, $locale); + } + + + /** + * Returns the calculated month + * + * @param string $calc Calculation to make + * @param string|integer|array|Zend_Date $month Month to calculate with, if null the actual month is taken + * @param string|Zend_Locale $locale Locale for parsing input + * @return integer|Zend_Date new time + * @throws Zend_Date_Exception + */ + private function _month($calc, $month, $locale) + { + if ($month === null) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception('parameter $month must be set, null is not allowed'); + } + + if ($locale === null) { + $locale = $this->getLocale(); + } + + if ($month instanceof Zend_Date) { + // extract month from object + $found = $month->toString(self::MONTH_SHORT, 'iso', $locale); + } else { + if (is_numeric($month)) { + $found = $month; + } else if (is_array($month)) { + if (isset($month['month']) === true) { + $month = $month['month']; + } else { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("no month given in array"); + } + } else { + $monthlist = Zend_Locale_Data::getList($locale, 'month'); + $monthlist2 = Zend_Locale_Data::getList($locale, 'month', array('gregorian', 'format', 'abbreviated')); + + $monthlist = array_merge($monthlist, $monthlist2); + $found = 0; + $cnt = 0; + foreach ($monthlist as $key => $value) { + if (strtoupper($value) == strtoupper($month)) { + $found = ($key % 12) + 1; + break; + } + ++$cnt; + } + if ($found == 0) { + foreach ($monthlist2 as $key => $value) { + if (strtoupper(iconv_substr($value, 0, 1, 'UTF-8')) == strtoupper($month)) { + $found = $key + 1; + break; + } + ++$cnt; + } + } + if ($found == 0) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("unknown month name ($month)", 0, null, $month); + } + } + } + $return = $this->_calcdetail($calc, $found, self::MONTH_SHORT, $locale); + if ($calc != 'cmp') { + return $this; + } + return $return; + } + + + /** + * Sets a new month + * The month can be a number or a string. Setting months lower then 0 and greater then 12 + * will result in adding or subtracting the relevant year. (12 months equal one year) + * If a localized monthname is given it will be parsed with the default locale or the optional + * set locale. + * Returned is the new date object + * + * @param string|integer|array|Zend_Date $month Month to set + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function setMonth($month, $locale = null) + { + return $this->_month('set', $month, $locale); + } + + + /** + * Adds months to the existing date object. + * The month can be a number or a string. Adding months lower then 0 and greater then 12 + * will result in adding or subtracting the relevant year. (12 months equal one year) + * If a localized monthname is given it will be parsed with the default locale or the optional + * set locale. + * Returned is the new date object + * + * @param string|integer|array|Zend_Date $month Month to add + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function addMonth($month, $locale = null) + { + return $this->_month('add', $month, $locale); + } + + + /** + * Subtracts months from the existing date object. + * The month can be a number or a string. Subtracting months lower then 0 and greater then 12 + * will result in adding or subtracting the relevant year. (12 months equal one year) + * If a localized monthname is given it will be parsed with the default locale or the optional + * set locale. + * Returned is the new date object + * + * @param string|integer|array|Zend_Date $month Month to sub + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function subMonth($month, $locale = null) + { + return $this->_month('sub', $month, $locale); + } + + + /** + * Compares the month with the existing date object, ignoring other date parts. + * For example: 10.03.2000 -> 15.03.1950 -> true + * Returns if equal, earlier or later + * + * @param string|integer|array|Zend_Date $month Month to compare + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return integer 0 = equal, 1 = later, -1 = earlier + * @throws Zend_Date_Exception + */ + public function compareMonth($month, $locale = null) + { + return $this->_month('cmp', $month, $locale); + } + + + /** + * Returns the day as new date object + * Example: 20.May.1986 -> 20.Jan.1970 00:00:00 + * + * @param $locale string|Zend_Locale OPTIONAL Locale for parsing input + * @return Zend_Date + */ + public function getDay($locale = null) + { + return $this->copyPart(self::DAY_SHORT, $locale); + } + + + /** + * Returns the calculated day + * + * @param $calc string Type of calculation to make + * @param $day string|integer|Zend_Date Day to calculate, when null the actual day is calculated + * @param $locale string|Zend_Locale Locale for parsing input + * @return Zend_Date|integer + */ + private function _day($calc, $day, $locale) + { + if ($day === null) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception('parameter $day must be set, null is not allowed'); + } + + if ($locale === null) { + $locale = $this->getLocale(); + } + + if ($day instanceof Zend_Date) { + $day = $day->toString(self::DAY_SHORT, 'iso', $locale); + } + + if (is_numeric($day)) { + $type = self::DAY_SHORT; + } else if (is_array($day)) { + if (isset($day['day']) === true) { + $day = $day['day']; + $type = self::WEEKDAY; + } else { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("no day given in array"); + } + } else { + switch (iconv_strlen($day, 'UTF-8')) { + case 1 : + $type = self::WEEKDAY_NARROW; + break; + case 2: + $type = self::WEEKDAY_NAME; + break; + case 3: + $type = self::WEEKDAY_SHORT; + break; + default: + $type = self::WEEKDAY; + break; + } + } + $return = $this->_calcdetail($calc, $day, $type, $locale); + if ($calc != 'cmp') { + return $this; + } + return $return; + } + + + /** + * Sets a new day + * The day can be a number or a string. Setting days lower then 0 or greater than the number of this months days + * will result in adding or subtracting the relevant month. + * If a localized dayname is given it will be parsed with the default locale or the optional + * set locale. + * Returned is the new date object + * Example: setDay('Montag', 'de_AT'); will set the monday of this week as day. + * + * @param string|integer|array|Zend_Date $month Day to set + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function setDay($day, $locale = null) + { + return $this->_day('set', $day, $locale); + } + + + /** + * Adds days to the existing date object. + * The day can be a number or a string. Adding days lower then 0 or greater than the number of this months days + * will result in adding or subtracting the relevant month. + * If a localized dayname is given it will be parsed with the default locale or the optional + * set locale. + * + * @param string|integer|array|Zend_Date $month Day to add + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function addDay($day, $locale = null) + { + return $this->_day('add', $day, $locale); + } + + + /** + * Subtracts days from the existing date object. + * The day can be a number or a string. Subtracting days lower then 0 or greater than the number of this months days + * will result in adding or subtracting the relevant month. + * If a localized dayname is given it will be parsed with the default locale or the optional + * set locale. + * + * @param string|integer|array|Zend_Date $month Day to sub + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function subDay($day, $locale = null) + { + return $this->_day('sub', $day, $locale); + } + + + /** + * Compares the day with the existing date object, ignoring other date parts. + * For example: 'Monday', 'en' -> 08.Jan.2007 -> 0 + * Returns if equal, earlier or later + * + * @param string|integer|array|Zend_Date $day Day to compare + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return integer 0 = equal, 1 = later, -1 = earlier + * @throws Zend_Date_Exception + */ + public function compareDay($day, $locale = null) + { + return $this->_day('cmp', $day, $locale); + } + + + /** + * Returns the weekday as new date object + * Weekday is always from 1-7 + * Example: 09-Jan-2007 -> 2 = Tuesday -> 02-Jan-1970 (when 02.01.1970 is also Tuesday) + * + * @param $locale string|Zend_Locale OPTIONAL Locale for parsing input + * @return Zend_Date + */ + public function getWeekday($locale = null) + { + if (self::$_options['format_type'] == 'php') { + $format = 'l'; + } else { + $format = self::WEEKDAY; + } + + return $this->copyPart($format, $locale); + } + + + /** + * Returns the calculated weekday + * + * @param $calc string Type of calculation to make + * @param $weekday string|integer|array|Zend_Date Weekday to calculate, when null the actual weekday is calculated + * @param $locale string|Zend_Locale Locale for parsing input + * @return Zend_Date|integer + * @throws Zend_Date_Exception + */ + private function _weekday($calc, $weekday, $locale) + { + if ($weekday === null) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception('parameter $weekday must be set, null is not allowed'); + } + + if ($locale === null) { + $locale = $this->getLocale(); + } + + if ($weekday instanceof Zend_Date) { + $weekday = $weekday->toString(self::WEEKDAY_8601, 'iso', $locale); + } + + if (is_numeric($weekday)) { + $type = self::WEEKDAY_8601; + } else if (is_array($weekday)) { + if (isset($weekday['weekday']) === true) { + $weekday = $weekday['weekday']; + $type = self::WEEKDAY; + } else { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("no weekday given in array"); + } + } else { + switch(iconv_strlen($weekday, 'UTF-8')) { + case 1: + $type = self::WEEKDAY_NARROW; + break; + case 2: + $type = self::WEEKDAY_NAME; + break; + case 3: + $type = self::WEEKDAY_SHORT; + break; + default: + $type = self::WEEKDAY; + break; + } + } + $return = $this->_calcdetail($calc, $weekday, $type, $locale); + if ($calc != 'cmp') { + return $this; + } + return $return; + } + + + /** + * Sets a new weekday + * The weekday can be a number or a string. If a localized weekday name is given, + * then it will be parsed as a date in $locale (defaults to the same locale as $this). + * Returned is the new date object. + * Example: setWeekday(3); will set the wednesday of this week as day. + * + * @param string|integer|array|Zend_Date $month Weekday to set + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function setWeekday($weekday, $locale = null) + { + return $this->_weekday('set', $weekday, $locale); + } + + + /** + * Adds weekdays to the existing date object. + * The weekday can be a number or a string. + * If a localized dayname is given it will be parsed with the default locale or the optional + * set locale. + * Returned is the new date object + * Example: addWeekday(3); will add the difference of days from the begining of the month until + * wednesday. + * + * @param string|integer|array|Zend_Date $month Weekday to add + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function addWeekday($weekday, $locale = null) + { + return $this->_weekday('add', $weekday, $locale); + } + + + /** + * Subtracts weekdays from the existing date object. + * The weekday can be a number or a string. + * If a localized dayname is given it will be parsed with the default locale or the optional + * set locale. + * Returned is the new date object + * Example: subWeekday(3); will subtract the difference of days from the begining of the month until + * wednesday. + * + * @param string|integer|array|Zend_Date $month Weekday to sub + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function subWeekday($weekday, $locale = null) + { + return $this->_weekday('sub', $weekday, $locale); + } + + + /** + * Compares the weekday with the existing date object, ignoring other date parts. + * For example: 'Monday', 'en' -> 08.Jan.2007 -> 0 + * Returns if equal, earlier or later + * + * @param string|integer|array|Zend_Date $weekday Weekday to compare + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return integer 0 = equal, 1 = later, -1 = earlier + * @throws Zend_Date_Exception + */ + public function compareWeekday($weekday, $locale = null) + { + return $this->_weekday('cmp', $weekday, $locale); + } + + + /** + * Returns the day of year as new date object + * Example: 02.Feb.1986 10:00:00 -> 02.Feb.1970 00:00:00 + * + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date + */ + public function getDayOfYear($locale = null) + { + if (self::$_options['format_type'] == 'php') { + $format = 'D'; + } else { + $format = self::DAY_OF_YEAR; + } + + return $this->copyPart($format, $locale); + } + + + /** + * Sets a new day of year + * The day of year is always a number. + * Returned is the new date object + * Example: 04.May.2004 -> setDayOfYear(10) -> 10.Jan.2004 + * + * @param string|integer|array|Zend_Date $day Day of Year to set + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function setDayOfYear($day, $locale = null) + { + return $this->_calcvalue('set', $day, 'day of year', self::DAY_OF_YEAR, $locale); + } + + + /** + * Adds a day of year to the existing date object. + * The day of year is always a number. + * Returned is the new date object + * Example: addDayOfYear(10); will add 10 days to the existing date object. + * + * @param string|integer|array|Zend_Date $day Day of Year to add + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function addDayOfYear($day, $locale = null) + { + return $this->_calcvalue('add', $day, 'day of year', self::DAY_OF_YEAR, $locale); + } + + + /** + * Subtracts a day of year from the existing date object. + * The day of year is always a number. + * Returned is the new date object + * Example: subDayOfYear(10); will subtract 10 days from the existing date object. + * + * @param string|integer|array|Zend_Date $day Day of Year to sub + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function subDayOfYear($day, $locale = null) + { + return $this->_calcvalue('sub', $day, 'day of year', self::DAY_OF_YEAR, $locale); + } + + + /** + * Compares the day of year with the existing date object. + * For example: compareDayOfYear(33) -> 02.Feb.2007 -> 0 + * Returns if equal, earlier or later + * + * @param string|integer|array|Zend_Date $day Day of Year to compare + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return integer 0 = equal, 1 = later, -1 = earlier + * @throws Zend_Date_Exception + */ + public function compareDayOfYear($day, $locale = null) + { + return $this->_calcvalue('cmp', $day, 'day of year', self::DAY_OF_YEAR, $locale); + } + + + /** + * Returns the hour as new date object + * Example: 02.Feb.1986 10:30:25 -> 01.Jan.1970 10:00:00 + * + * @param $locale string|Zend_Locale OPTIONAL Locale for parsing input + * @return Zend_Date + */ + public function getHour($locale = null) + { + return $this->copyPart(self::HOUR, $locale); + } + + + /** + * Sets a new hour + * The hour is always a number. + * Returned is the new date object + * Example: 04.May.1993 13:07:25 -> setHour(7); -> 04.May.1993 07:07:25 + * + * @param string|integer|array|Zend_Date $hour Hour to set + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function setHour($hour, $locale = null) + { + return $this->_calcvalue('set', $hour, 'hour', self::HOUR_SHORT, $locale); + } + + + /** + * Adds hours to the existing date object. + * The hour is always a number. + * Returned is the new date object + * Example: 04.May.1993 13:07:25 -> addHour(12); -> 05.May.1993 01:07:25 + * + * @param string|integer|array|Zend_Date $hour Hour to add + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function addHour($hour, $locale = null) + { + return $this->_calcvalue('add', $hour, 'hour', self::HOUR_SHORT, $locale); + } + + + /** + * Subtracts hours from the existing date object. + * The hour is always a number. + * Returned is the new date object + * Example: 04.May.1993 13:07:25 -> subHour(6); -> 05.May.1993 07:07:25 + * + * @param string|integer|array|Zend_Date $hour Hour to sub + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function subHour($hour, $locale = null) + { + return $this->_calcvalue('sub', $hour, 'hour', self::HOUR_SHORT, $locale); + } + + + /** + * Compares the hour with the existing date object. + * For example: 10:30:25 -> compareHour(10) -> 0 + * Returns if equal, earlier or later + * + * @param string|integer|array|Zend_Date $hour Hour to compare + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return integer 0 = equal, 1 = later, -1 = earlier + * @throws Zend_Date_Exception + */ + public function compareHour($hour, $locale = null) + { + return $this->_calcvalue('cmp', $hour, 'hour', self::HOUR_SHORT, $locale); + } + + + /** + * Returns the minute as new date object + * Example: 02.Feb.1986 10:30:25 -> 01.Jan.1970 00:30:00 + * + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date + */ + public function getMinute($locale = null) + { + if (self::$_options['format_type'] == 'php') { + $format = 'i'; + } else { + $format = self::MINUTE; + } + + return $this->copyPart($format, $locale); + } + + + /** + * Sets a new minute + * The minute is always a number. + * Returned is the new date object + * Example: 04.May.1993 13:07:25 -> setMinute(29); -> 04.May.1993 13:29:25 + * + * @param string|integer|array|Zend_Date $minute Minute to set + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function setMinute($minute, $locale = null) + { + return $this->_calcvalue('set', $minute, 'minute', self::MINUTE_SHORT, $locale); + } + + + /** + * Adds minutes to the existing date object. + * The minute is always a number. + * Returned is the new date object + * Example: 04.May.1993 13:07:25 -> addMinute(65); -> 04.May.1993 13:12:25 + * + * @param string|integer|array|Zend_Date $minute Minute to add + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function addMinute($minute, $locale = null) + { + return $this->_calcvalue('add', $minute, 'minute', self::MINUTE_SHORT, $locale); + } + + + /** + * Subtracts minutes from the existing date object. + * The minute is always a number. + * Returned is the new date object + * Example: 04.May.1993 13:07:25 -> subMinute(9); -> 04.May.1993 12:58:25 + * + * @param string|integer|array|Zend_Date $minute Minute to sub + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function subMinute($minute, $locale = null) + { + return $this->_calcvalue('sub', $minute, 'minute', self::MINUTE_SHORT, $locale); + } + + + /** + * Compares the minute with the existing date object. + * For example: 10:30:25 -> compareMinute(30) -> 0 + * Returns if equal, earlier or later + * + * @param string|integer|array|Zend_Date $minute Hour to compare + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return integer 0 = equal, 1 = later, -1 = earlier + * @throws Zend_Date_Exception + */ + public function compareMinute($minute, $locale = null) + { + return $this->_calcvalue('cmp', $minute, 'minute', self::MINUTE_SHORT, $locale); + } + + + /** + * Returns the second as new date object + * Example: 02.Feb.1986 10:30:25 -> 01.Jan.1970 00:00:25 + * + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date + */ + public function getSecond($locale = null) + { + if (self::$_options['format_type'] == 'php') { + $format = 's'; + } else { + $format = self::SECOND; + } + + return $this->copyPart($format, $locale); + } + + + /** + * Sets new seconds to the existing date object. + * The second is always a number. + * Returned is the new date object + * Example: 04.May.1993 13:07:25 -> setSecond(100); -> 04.May.1993 13:08:40 + * + * @param string|integer|array|Zend_Date $second Second to set + * @param string|Zend_Locale $locale (Optional) Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function setSecond($second, $locale = null) + { + return $this->_calcvalue('set', $second, 'second', self::SECOND_SHORT, $locale); + } + + + /** + * Adds seconds to the existing date object. + * The second is always a number. + * Returned is the new date object + * Example: 04.May.1993 13:07:25 -> addSecond(65); -> 04.May.1993 13:08:30 + * + * @param string|integer|array|Zend_Date $second Second to add + * @param string|Zend_Locale $locale (Optional) Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function addSecond($second, $locale = null) + { + return $this->_calcvalue('add', $second, 'second', self::SECOND_SHORT, $locale); + } + + + /** + * Subtracts seconds from the existing date object. + * The second is always a number. + * Returned is the new date object + * Example: 04.May.1993 13:07:25 -> subSecond(10); -> 04.May.1993 13:07:15 + * + * @param string|integer|array|Zend_Date $second Second to sub + * @param string|Zend_Locale $locale (Optional) Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function subSecond($second, $locale = null) + { + return $this->_calcvalue('sub', $second, 'second', self::SECOND_SHORT, $locale); + } + + + /** + * Compares the second with the existing date object. + * For example: 10:30:25 -> compareSecond(25) -> 0 + * Returns if equal, earlier or later + * + * @param string|integer|array|Zend_Date $second Second to compare + * @param string|Zend_Locale $locale (Optional) Locale for parsing input + * @return integer 0 = equal, 1 = later, -1 = earlier + * @throws Zend_Date_Exception + */ + public function compareSecond($second, $locale = null) + { + return $this->_calcvalue('cmp', $second, 'second', self::SECOND_SHORT, $locale); + } + + + /** + * Returns the precision for fractional seconds + * + * @return integer + */ + public function getFractionalPrecision() + { + return $this->_precision; + } + + + /** + * Sets a new precision for fractional seconds + * + * @param integer $precision Precision for the fractional datepart 3 = milliseconds + * @throws Zend_Date_Exception + * @return Zend_Date Provides fluid interface + */ + public function setFractionalPrecision($precision) + { + if (!intval($precision) or ($precision < 0) or ($precision > 9)) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("precision ($precision) must be a positive integer less than 10", 0, null, $precision); + } + + $this->_precision = (int) $precision; + if ($this->_precision < strlen($this->_fractional)) { + $this->_fractional = substr($this->_fractional, 0, $this->_precision); + } else { + $this->_fractional = str_pad($this->_fractional, $this->_precision, '0', STR_PAD_RIGHT); + } + + return $this; + } + + + /** + * Returns the milliseconds of the date object + * + * @return string + */ + public function getMilliSecond() + { + return $this->_fractional; + } + + + /** + * Sets new milliseconds for the date object + * Example: setMilliSecond(550, 2) -> equals +5 Sec +50 MilliSec + * + * @param integer|Zend_Date $milli (Optional) Millisecond to set, when null the actual millisecond is set + * @param integer $precision (Optional) Fraction precision of the given milliseconds + * @return Zend_Date Provides fluid interface + */ + public function setMilliSecond($milli = null, $precision = null) + { + if ($milli === null) { + list($milli, $time) = explode(" ", microtime()); + $milli = intval($milli); + $precision = 6; + } else if (!is_numeric($milli)) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid milli second ($milli) operand", 0, null, $milli); + } + + if ($precision === null) { + $precision = $this->_precision; + } + + if (!is_int($precision) || $precision < 1 || $precision > 9) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("precision ($precision) must be a positive integer less than 10", 0, null, $precision); + } + + $this->_fractional = 0; + $this->addMilliSecond($milli, $precision); + return $this; + } + + + /** + * Adds milliseconds to the date object + * + * @param integer|Zend_Date $milli (Optional) Millisecond to add, when null the actual millisecond is added + * @param integer $precision (Optional) Fractional precision for the given milliseconds + * @return Zend_Date Provides fluid interface + */ + public function addMilliSecond($milli = null, $precision = null) + { + if ($milli === null) { + list($milli, $time) = explode(" ", microtime()); + $milli = intval($milli); + } else if (!is_numeric($milli)) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid milli second ($milli) operand", 0, null, $milli); + } + + if ($precision === null) { + $precision = strlen($milli); + if ($milli < 0) { + --$precision; + } + } + + if (!is_int($precision) || $precision < 1 || $precision > 9) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("precision ($precision) must be a positive integer less than 10", 0, null, $precision); + } + + $this->_fractional += $milli; + + // Add/sub milliseconds + add/sub seconds + $max = pow(10, $this->_precision); + // Milli includes seconds + if ($this->_fractional >= $max) { + while ($this->_fractional >= $max) { + $this->addSecond(1); + $this->_fractional -= $max; + } + } + + if ($this->_fractional < 0) { + while ($this->_fractional < 0) { + $this->subSecond(1); + $this->_fractional += $max; + } + } + + if ($this->_precision > strlen($this->_fractional)) { + $this->_fractional = str_pad($this->_fractional, $this->_precision, '0', STR_PAD_LEFT); + } + + return $this; + } + + + /** + * Subtracts a millisecond + * + * @param integer|Zend_Date $milli (Optional) Millisecond to sub, when null the actual millisecond is subtracted + * @param integer $precision (Optional) Fractional precision for the given milliseconds + * @return Zend_Date Provides fluid interface + */ + public function subMilliSecond($milli = null, $precision = null) + { + $this->addMilliSecond(0 - $milli, $precision); + return $this; + } + + /** + * Compares only the millisecond part, returning the difference + * + * @param integer|Zend_Date $milli OPTIONAL Millisecond to compare, when null the actual millisecond is compared + * @param integer $precision OPTIONAL Fractional precision for the given milliseconds + * @throws Zend_Date_Exception On invalid input + * @return integer 0 = equal, 1 = later, -1 = earlier + */ + public function compareMilliSecond($milli = null, $precision = null) + { + if ($milli === null) { + list($milli, $time) = explode(" ", microtime()); + $milli = intval($milli); + } else if (is_numeric($milli) === false) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid milli second ($milli) operand", 0, null, $milli); + } + + if ($precision === null) { + $precision = strlen($milli); + } else if (!is_int($precision) || $precision < 1 || $precision > 9) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("precision ($precision) must be a positive integer less than 10", 0, null, $precision); + } + + if ($precision === 0) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception('precision is 0'); + } + + if ($precision != $this->_precision) { + if ($precision > $this->_precision) { + $diff = $precision - $this->_precision; + $milli = (int) ($milli / (10 * $diff)); + } else { + $diff = $this->_precision - $precision; + $milli = (int) ($milli * (10 * $diff)); + } + } + + $comp = $this->_fractional - $milli; + if ($comp < 0) { + return -1; + } else if ($comp > 0) { + return 1; + } + return 0; + } + + /** + * Returns the week as new date object using monday as begining of the week + * Example: 12.Jan.2007 -> 08.Jan.1970 00:00:00 + * + * @param $locale string|Zend_Locale OPTIONAL Locale for parsing input + * @return Zend_Date + */ + public function getWeek($locale = null) + { + if (self::$_options['format_type'] == 'php') { + $format = 'W'; + } else { + $format = self::WEEK; + } + + return $this->copyPart($format, $locale); + } + + /** + * Sets a new week. The week is always a number. The day of week is not changed. + * Returned is the new date object + * Example: 09.Jan.2007 13:07:25 -> setWeek(1); -> 02.Jan.2007 13:07:25 + * + * @param string|integer|array|Zend_Date $week Week to set + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function setWeek($week, $locale = null) + { + return $this->_calcvalue('set', $week, 'week', self::WEEK, $locale); + } + + /** + * Adds a week. The week is always a number. The day of week is not changed. + * Returned is the new date object + * Example: 09.Jan.2007 13:07:25 -> addWeek(1); -> 16.Jan.2007 13:07:25 + * + * @param string|integer|array|Zend_Date $week Week to add + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function addWeek($week, $locale = null) + { + return $this->_calcvalue('add', $week, 'week', self::WEEK, $locale); + } + + /** + * Subtracts a week. The week is always a number. The day of week is not changed. + * Returned is the new date object + * Example: 09.Jan.2007 13:07:25 -> subWeek(1); -> 02.Jan.2007 13:07:25 + * + * @param string|integer|array|Zend_Date $week Week to sub + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function subWeek($week, $locale = null) + { + return $this->_calcvalue('sub', $week, 'week', self::WEEK, $locale); + } + + /** + * Compares only the week part, returning the difference + * Returned is the new date object + * Returns if equal, earlier or later + * Example: 09.Jan.2007 13:07:25 -> compareWeek(2); -> 0 + * + * @param string|integer|array|Zend_Date $week Week to compare + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return integer 0 = equal, 1 = later, -1 = earlier + */ + public function compareWeek($week, $locale = null) + { + return $this->_calcvalue('cmp', $week, 'week', self::WEEK, $locale); + } + + /** + * Sets a new standard locale for the date object. + * This locale will be used for all functions + * Returned is the really set locale. + * Example: 'de_XX' will be set to 'de' because 'de_XX' does not exist + * 'xx_YY' will be set to 'root' because 'xx' does not exist + * + * @param string|Zend_Locale $locale (Optional) Locale for parsing input + * @throws Zend_Date_Exception When the given locale does not exist + * @return Zend_Date Provides fluent interface + */ + public function setLocale($locale = null) + { + try { + $this->_locale = Zend_Locale::findLocale($locale); + } catch (Zend_Locale_Exception $e) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception($e->getMessage(), 0, $e); + } + + return $this; + } + + /** + * Returns the actual set locale + * + * @return string + */ + public function getLocale() + { + return $this->_locale; + } + + /** + * Checks if the given date is a real date or datepart. + * Returns false if a expected datepart is missing or a datepart exceeds its possible border. + * But the check will only be done for the expected dateparts which are given by format. + * If no format is given the standard dateformat for the actual locale is used. + * f.e. 30.February.2007 will return false if format is 'dd.MMMM.YYYY' + * + * @param string|array|Zend_Date $date Date to parse for correctness + * @param string $format (Optional) Format for parsing the date string + * @param string|Zend_Locale $locale (Optional) Locale for parsing date parts + * @return boolean True when all date parts are correct + */ + public static function isDate($date, $format = null, $locale = null) + { + if (!is_string($date) && !is_numeric($date) && !($date instanceof Zend_Date) && + !is_array($date)) { + return false; + } + + if (($format !== null) and (Zend_Locale::isLocale($format, null, false))) { + $locale = $format; + $format = null; + } + + $locale = Zend_Locale::findLocale($locale); + + if ($format === null) { + $format = Zend_Locale_Format::getDateFormat($locale); + } else if ((self::$_options['format_type'] == 'php') && !defined($format)) { + $format = Zend_Locale_Format::convertPhpToIsoFormat($format); + } + + $format = self::_getLocalizedToken($format, $locale); + if (!is_array($date)) { + try { + $parsed = Zend_Locale_Format::getDate($date, array('locale' => $locale, + 'date_format' => $format, 'format_type' => 'iso', + 'fix_date' => false)); + } catch (Zend_Locale_Exception $e) { + // Date can not be parsed + return false; + } + } else { + $parsed = $date; + } + + if (((strpos($format, 'Y') !== false) or (strpos($format, 'y') !== false)) and + (!isset($parsed['year']))) { + // Year expected but not found + return false; + } + + if ((strpos($format, 'M') !== false) and (!isset($parsed['month']))) { + // Month expected but not found + return false; + } + + if ((strpos($format, 'd') !== false) and (!isset($parsed['day']))) { + // Day expected but not found + return false; + } + + if (((strpos($format, 'H') !== false) or (strpos($format, 'h') !== false)) and + (!isset($parsed['hour']))) { + // Hour expected but not found + return false; + } + + if ((strpos($format, 'm') !== false) and (!isset($parsed['minute']))) { + // Minute expected but not found + return false; + } + + if ((strpos($format, 's') !== false) and (!isset($parsed['second']))) { + // Second expected but not found + return false; + } + + // Set not given dateparts + if (isset($parsed['hour']) === false) { + $parsed['hour'] = 12; + } + + if (isset($parsed['minute']) === false) { + $parsed['minute'] = 0; + } + + if (isset($parsed['second']) === false) { + $parsed['second'] = 0; + } + + if (isset($parsed['month']) === false) { + $parsed['month'] = 1; + } + + if (isset($parsed['day']) === false) { + $parsed['day'] = 1; + } + + if (isset($parsed['year']) === false) { + $parsed['year'] = 1970; + } + + if (self::isYearLeapYear($parsed['year'])) { + $parsed['year'] = 1972; + } else { + $parsed['year'] = 1971; + } + + $date = new self($parsed, null, $locale); + $timestamp = $date->mktime($parsed['hour'], $parsed['minute'], $parsed['second'], + $parsed['month'], $parsed['day'], $parsed['year']); + + if ($parsed['year'] != $date->date('Y', $timestamp)) { + // Given year differs from parsed year + return false; + } + + if ($parsed['month'] != $date->date('n', $timestamp)) { + // Given month differs from parsed month + return false; + } + + if ($parsed['day'] != $date->date('j', $timestamp)) { + // Given day differs from parsed day + return false; + } + + if ($parsed['hour'] != $date->date('G', $timestamp)) { + // Given hour differs from parsed hour + return false; + } + + if ($parsed['minute'] != $date->date('i', $timestamp)) { + // Given minute differs from parsed minute + return false; + } + + if ($parsed['second'] != $date->date('s', $timestamp)) { + // Given second differs from parsed second + return false; + } + + return true; + } + + /** + * Returns the ISO Token for all localized constants + * + * @param string $token Token to normalize + * @param string $locale Locale to search + * @return string + */ + protected static function _getLocalizedToken($token, $locale) + { + switch($token) { + case self::ISO_8601 : + return "yyyy-MM-ddThh:mm:ss"; + break; + case self::RFC_2822 : + return "EEE, dd MMM yyyy HH:mm:ss"; + break; + case self::DATES : + return Zend_Locale_Data::getContent($locale, 'date'); + break; + case self::DATE_FULL : + return Zend_Locale_Data::getContent($locale, 'date', array('gregorian', 'full')); + break; + case self::DATE_LONG : + return Zend_Locale_Data::getContent($locale, 'date', array('gregorian', 'long')); + break; + case self::DATE_MEDIUM : + return Zend_Locale_Data::getContent($locale, 'date', array('gregorian', 'medium')); + break; + case self::DATE_SHORT : + return Zend_Locale_Data::getContent($locale, 'date', array('gregorian', 'short')); + break; + case self::TIMES : + return Zend_Locale_Data::getContent($locale, 'date'); + break; + case self::TIME_FULL : + return Zend_Locale_Data::getContent($locale, 'time', array('gregorian', 'full')); + break; + case self::TIME_LONG : + return Zend_Locale_Data::getContent($locale, 'date', array('gregorian', 'long')); + break; + case self::TIME_MEDIUM : + return Zend_Locale_Data::getContent($locale, 'date', array('gregorian', 'medium')); + break; + case self::TIME_SHORT : + return Zend_Locale_Data::getContent($locale, 'date', array('gregorian', 'short')); + break; + case self::DATETIME : + return Zend_Locale_Data::getContent($locale, 'datetime'); + break; + case self::DATETIME_FULL : + return Zend_Locale_Data::getContent($locale, 'datetime', array('gregorian', 'full')); + break; + case self::DATETIME_LONG : + return Zend_Locale_Data::getContent($locale, 'datetime', array('gregorian', 'long')); + break; + case self::DATETIME_MEDIUM : + return Zend_Locale_Data::getContent($locale, 'datetime', array('gregorian', 'medium')); + break; + case self::DATETIME_SHORT : + return Zend_Locale_Data::getContent($locale, 'datetime', array('gregorian', 'short')); + break; + case self::ATOM : + case self::RFC_3339 : + case self::W3C : + return "yyyy-MM-DD HH:mm:ss"; + break; + case self::COOKIE : + case self::RFC_850 : + return "EEEE, dd-MM-yyyy HH:mm:ss"; + break; + case self::RFC_822 : + case self::RFC_1036 : + case self::RFC_1123 : + case self::RSS : + return "EEE, dd MM yyyy HH:mm:ss"; + break; + } + + return $token; + } +} diff --git a/library/Zend/Date/Cities.php b/library/Zend/Date/Cities.php new file mode 100644 index 000000000..47244b629 --- /dev/null +++ b/library/Zend/Date/Cities.php @@ -0,0 +1,322 @@ + array('latitude' => 5.3411111, 'longitude' => -4.0280556), + 'Abu Dhabi' => array('latitude' => 24.4666667, 'longitude' => 54.3666667), + 'Abuja' => array('latitude' => 9.1758333, 'longitude' => 7.1808333), + 'Accra' => array('latitude' => 5.55, 'longitude' => -0.2166667), + 'Adamstown' => array('latitude' => -25.0666667, 'longitude' => -130.0833333), + 'Addis Ababa' => array('latitude' => 9.0333333, 'longitude' => 38.7), + 'Adelaide' => array('latitude' => -34.9333333, 'longitude' => 138.6), + 'Algiers' => array('latitude' => 36.7630556, 'longitude' => 3.0505556), + 'Alofi' => array('latitude' => -19.0166667, 'longitude' => -169.9166667), + 'Amman' => array('latitude' => 31.95, 'longitude' => 35.9333333), + 'Amsterdam' => array('latitude' => 52.35, 'longitude' => 4.9166667), + 'Andorra la Vella' => array('latitude' => 42.5, 'longitude' => 1.5166667), + 'Ankara' => array('latitude' => 39.9272222, 'longitude' => 32.8644444), + 'Antananarivo' => array('latitude' => -18.9166667, 'longitude' => 47.5166667), + 'Apia' => array('latitude' => -13.8333333, 'longitude' => -171.7333333), + 'Ashgabat' => array('latitude' => 37.95, 'longitude' => 58.3833333), + 'Asmara' => array('latitude' => 15.3333333, 'longitude' => 38.9333333), + 'Astana' => array('latitude' => 51.1811111, 'longitude' => 71.4277778), + 'Asunción' => array('latitude' => -25.2666667, 'longitude' => -57.6666667), + 'Athens' => array('latitude' => 37.9833333, 'longitude' => 23.7333333), + 'Auckland' => array('latitude' => -36.8666667, 'longitude' => 174.7666667), + 'Avarua' => array('latitude' => -21.2, 'longitude' => -159.7666667), + 'Baghdad' => array('latitude' => 33.3386111, 'longitude' => 44.3938889), + 'Baku' => array('latitude' => 40.3952778, 'longitude' => 49.8822222), + 'Bamako' => array('latitude' => 12.65, 'longitude' => -8), + 'Bandar Seri Begawan' => array('latitude' => 4.8833333, 'longitude' => 114.9333333), + 'Bankok' => array('latitude' => 13.5833333, 'longitude' => 100.2166667), + 'Bangui' => array('latitude' => 4.3666667, 'longitude' => 18.5833333), + 'Banjul' => array('latitude' => 13.4530556, 'longitude' => -16.5775), + 'Basel' => array('latitude' => 47.5666667, 'longitude' => 7.6), + 'Basseterre' => array('latitude' => 17.3, 'longitude' => -62.7166667), + 'Beijing' => array('latitude' => 39.9288889, 'longitude' => 116.3883333), + 'Beirut' => array('latitude' => 33.8719444, 'longitude' => 35.5097222), + 'Belgrade' => array('latitude' => 44.8186111, 'longitude' => 20.4680556), + 'Belmopan' => array('latitude' => 17.25, 'longitude' => -88.7666667), + 'Berlin' => array('latitude' => 52.5166667, 'longitude' => 13.4), + 'Bern' => array('latitude' => 46.9166667, 'longitude' => 7.4666667), + 'Bishkek' => array('latitude' => 42.8730556, 'longitude' => 74.6002778), + 'Bissau' => array('latitude' => 11.85, 'longitude' => -15.5833333), + 'Bloemfontein' => array('latitude' => -29.1333333, 'longitude' => 26.2), + 'Bogotá' => array('latitude' => 4.6, 'longitude' => -74.0833333), + 'Brasilia' => array('latitude' => -15.7833333, 'longitude' => -47.9166667), + 'Bratislava' => array('latitude' => 48.15, 'longitude' => 17.1166667), + 'Brazzaville' => array('latitude' => -4.2591667, 'longitude' => 15.2847222), + 'Bridgetown' => array('latitude' => 13.1, 'longitude' => -59.6166667), + 'Brisbane' => array('latitude' => -27.5, 'longitude' => 153.0166667), + 'Brussels' => array('latitude' => 50.8333333, 'longitude' => 4.3333333), + 'Bucharest' => array('latitude' => 44.4333333, 'longitude' => 26.1), + 'Budapest' => array('latitude' => 47.5, 'longitude' => 19.0833333), + 'Buenos Aires' => array('latitude' => -34.5875, 'longitude' => -58.6725), + 'Bujumbura' => array('latitude' => -3.3761111, 'longitude' => 29.36), + 'Cairo' => array('latitude' => 30.05, 'longitude' => 31.25), + 'Calgary' => array('latitude' => 51.0833333, 'longitude' => -114.0833333), + 'Canberra' => array('latitude' => -35.2833333, 'longitude' => 149.2166667), + 'Cape Town' => array('latitude' => -33.9166667, 'longitude' => 18.4166667), + 'Caracas' => array('latitude' => 10.5, 'longitude' => -66.9166667), + 'Castries' => array('latitude' => 14, 'longitude' => -61), + 'Charlotte Amalie' => array('latitude' => 18.34389, 'longitude' => -64.93111), + 'Chicago' => array('latitude' => 41.85, 'longitude' => -87.65), + 'Chisinau' => array('latitude' => 47.055556, 'longitude' => 28.8575), + 'Cockburn Town' => array('latitude' => 21.4666667, 'longitude' => -71.1333333), + 'Colombo' => array('latitude' => 6.9319444, 'longitude' => 79.8477778), + 'Conakry' => array('latitude' => 9.5091667, 'longitude' => -13.7122222), + 'Copenhagen' => array('latitude' => 55.6666667, 'longitude' => 12.5833333), + 'Cotonou' => array('latitude' => 6.35, 'longitude' => 2.4333333), + 'Dakar' => array('latitude' => 14.6708333, 'longitude' => -17.4380556), + 'Damascus' => array('latitude' => 33.5, 'longitude' => 36.3), + 'Dar es Salaam' => array('latitude' => -6.8, 'longitude' => 39.2833333), + 'Dhaka' => array('latitude' => 23.7230556, 'longitude' => 90.4086111), + 'Dili' => array('latitude' => -8.5586111, 'longitude' => 125.5736111), + 'Djibouti' => array('latitude' => 11.595, 'longitude' => 43.1480556), + 'Dodoma' => array('latitude' => -6.1833333, 'longitude' => 35.75), + 'Doha' => array('latitude' => 25.2866667, 'longitude' => 51.5333333), + 'Dubai' => array('latitude' => 25.2522222, 'longitude' => 55.28), + 'Dublin' => array('latitude' => 53.3330556, 'longitude' => -6.2488889), + 'Dushanbe' => array('latitude' => 38.56, 'longitude' => 68.7738889 ), + 'Fagatogo' => array('latitude' => -14.2825, 'longitude' => -170.69), + 'Fongafale' => array('latitude' => -8.5166667, 'longitude' => 179.2166667), + 'Freetown' => array('latitude' => 8.49, 'longitude' => -13.2341667), + 'Gaborone' => array('latitude' => -24.6463889, 'longitude' => 25.9119444), + 'Geneva' => array('latitude' => 46.2, 'longitude' => 6.1666667), + 'George Town' => array('latitude' => 19.3, 'longitude' => -81.3833333), + 'Georgetown' => array('latitude' => 6.8, 'longitude' => -58.1666667), + 'Gibraltar' => array('latitude' => 36.1333333, 'longitude' => -5.35), + 'Glasgow' => array('latitude' => 55.8333333, 'longitude' => -4.25), + 'Guatemala la Nueva' => array('latitude' => 14.6211111, 'longitude' => -90.5269444), + 'Hagatna' => array('latitude' => 13.47417, 'longitude' => 144.74778), + 'The Hague' => array('latitude' => 52.0833333, 'longitude' => 4.3), + 'Hamilton' => array('latitude' => 32.2941667, 'longitude' => -64.7838889), + 'Hanoi' => array('latitude' => 21.0333333, 'longitude' => 105.85), + 'Harare' => array('latitude' => -17.8177778, 'longitude' => 31.0447222), + 'Havana' => array('latitude' => 23.1319444, 'longitude' => -82.3641667), + 'Helsinki' => array('latitude' => 60.1755556, 'longitude' => 24.9341667), + 'Honiara' => array('latitude' => -9.4333333, 'longitude' => 159.95), + 'Islamabad' => array('latitude' => 30.8486111, 'longitude' => 72.4944444), + 'Istanbul' => array('latitude' => 41.0186111, 'longitude' => 28.9647222), + 'Jakarta' => array('latitude' => -6.1744444, 'longitude' => 106.8294444), + 'Jamestown' => array('latitude' => -15.9333333, 'longitude' => -5.7166667), + 'Jerusalem' => array('latitude' => 31.7666667, 'longitude' => 35.2333333), + 'Johannesburg' => array('latitude' => -26.2, 'longitude' => 28.0833333), + 'Kabul' => array('latitude' => 34.5166667, 'longitude' => 69.1833333), + 'Kampala' => array('latitude' => 0.3155556, 'longitude' => 32.5655556), + 'Kathmandu' => array('latitude' => 27.7166667, 'longitude' => 85.3166667), + 'Khartoum' => array('latitude' => 15.5880556, 'longitude' => 32.5341667), + 'Kigali' => array('latitude' => -1.9536111, 'longitude' => 30.0605556), + 'Kingston' => array('latitude' => -29.05, 'longitude' => 167.95), + 'Kingstown' => array('latitude' => 13.1333333, 'longitude' => -61.2166667), + 'Kinshasa' => array('latitude' => -4.3, 'longitude' => 15.3), + 'Kolkata' => array('latitude' => 22.5697222, 'longitude' => 88.3697222), + 'Kuala Lumpur' => array('latitude' => 3.1666667, 'longitude' => 101.7), + 'Kuwait City' => array('latitude' => 29.3697222, 'longitude' => 47.9783333), + 'Kiev' => array('latitude' => 50.4333333, 'longitude' => 30.5166667), + 'La Paz' => array('latitude' => -16.5, 'longitude' => -68.15), + 'Libreville' => array('latitude' => 0.3833333, 'longitude' => 9.45), + 'Lilongwe' => array('latitude' => -13.9833333, 'longitude' => 33.7833333), + 'Lima' => array('latitude' => -12.05, 'longitude' => -77.05), + 'Lisbon' => array('latitude' => 38.7166667, 'longitude' => -9.1333333), + 'Ljubljana' => array('latitude' => 46.0552778, 'longitude' => 14.5144444), + 'Lobamba' => array('latitude' => -26.4666667, 'longitude' => 31.2), + 'Lomé' => array('latitude' => 9.7166667, 'longitude' => 38.3), + 'London' => array('latitude' => 51.5, 'longitude' => -0.1166667), + 'Los Angeles' => array('latitude' => 34.05222, 'longitude' => -118.24278), + 'Luanda' => array('latitude' => -8.8383333, 'longitude' => 13.2344444), + 'Lusaka' => array('latitude' => -15.4166667, 'longitude' => 28.2833333), + 'Luxembourg' => array('latitude' => 49.6116667, 'longitude' => 6.13), + 'Madrid' => array('latitude' => 40.4, 'longitude' => -3.6833333), + 'Majuro' => array('latitude' => 7.1, 'longitude' => 171.3833333), + 'Malabo' => array('latitude' => 3.75, 'longitude' => 8.7833333), + 'Managua' => array('latitude' => 12.1508333, 'longitude' => -86.2683333), + 'Manama' => array('latitude' => 26.2361111, 'longitude' => 50.5830556), + 'Manila' => array('latitude' => 14.6041667, 'longitude' => 120.9822222), + 'Maputo' => array('latitude' => -25.9652778, 'longitude' => 32.5891667), + 'Maseru' => array('latitude' => -29.3166667, 'longitude' => 27.4833333), + 'Mbabane' => array('latitude' => -26.3166667, 'longitude' => 31.1333333), + 'Melbourne' => array('latitude' => -37.8166667, 'longitude' => 144.9666667), + 'Melekeok' => array('latitude' => 7.4933333, 'longitude' => 134.6341667), + 'Mexiko City' => array('latitude' => 19.4341667, 'longitude' => -99.1386111), + 'Minsk' => array('latitude' => 53.9, 'longitude' => 27.5666667), + 'Mogadishu' => array('latitude' => 2.0666667, 'longitude' => 45.3666667), + 'Monaco' => array('latitude' => 43.7333333, 'longitude' => 7.4166667), + 'Monrovia' => array('latitude' => 6.3105556, 'longitude' => -10.8047222), + 'Montevideo' => array('latitude' => -34.8580556, 'longitude' => -56.1708333), + 'Montreal' => array('latitude' => 45.5, 'longitude' => -73.5833333), + 'Moroni' => array('latitude' => -11.7041667, 'longitude' => 43.2402778), + 'Moscow' => array('latitude' => 55.7522222, 'longitude' => 37.6155556), + 'Muscat' => array('latitude' => 23.6133333, 'longitude' => 58.5933333), + 'Nairobi' => array('latitude' => -1.3166667, 'longitude' => 36.8333333), + 'Nassau' => array('latitude' => 25.0833333, 'longitude' => -77.35), + 'N´Djamena' => array('latitude' => 12.1130556, 'longitude' => 15.0491667), + 'New Dehli' => array('latitude' => 28.6, 'longitude' => 77.2), + 'New York' => array('latitude' => 40.71417, 'longitude' => -74.00639), + 'Newcastle' => array('latitude' => -32.9166667, 'longitude' => 151.75), + 'Niamey' => array('latitude' => 13.6666667, 'longitude' => 1.7833333), + 'Nicosia' => array('latitude' => 35.1666667, 'longitude' => 33.3666667), + 'Nouakchott' => array('latitude' => 18.0863889, 'longitude' => -15.9752778), + 'Noumea' => array('latitude' => -22.2666667, 'longitude' => 166.45), + 'Nuku´alofa' => array('latitude' => -21.1333333, 'longitude' => -175.2), + 'Nuuk' => array('latitude' => 64.1833333, 'longitude' => -51.75), + 'Oranjestad' => array('latitude' => 12.5166667, 'longitude' => -70.0333333), + 'Oslo' => array('latitude' => 59.9166667, 'longitude' => 10.75), + 'Ouagadougou' => array('latitude' => 12.3702778, 'longitude' => -1.5247222), + 'Palikir' => array('latitude' => 6.9166667, 'longitude' => 158.15), + 'Panama City' => array('latitude' => 8.9666667, 'longitude' => -79.5333333), + 'Papeete' => array('latitude' => -17.5333333, 'longitude' => -149.5666667), + 'Paramaribo' => array('latitude' => 5.8333333, 'longitude' => -55.1666667), + 'Paris' => array('latitude' => 48.8666667, 'longitude' => 2.3333333), + 'Perth' => array('latitude' => -31.9333333, 'longitude' => 115.8333333), + 'Phnom Penh' => array('latitude' => 11.55, 'longitude' => 104.9166667), + 'Podgorica' => array('latitude' => 43.7752778, 'longitude' => 19.6827778), + 'Port Louis' => array('latitude' => -20.1666667, 'longitude' => 57.5), + 'Port Moresby' => array('latitude' => -9.4647222, 'longitude' => 147.1925), + 'Port-au-Prince' => array('latitude' => 18.5391667, 'longitude' => -72.335), + 'Port of Spain' => array('latitude' => 10.6666667, 'longitude' => -61.5), + 'Porto-Novo' => array('latitude' => 6.4833333, 'longitude' => 2.6166667), + 'Prague' => array('latitude' => 50.0833333, 'longitude' => 14.4666667), + 'Praia' => array('latitude' => 14.9166667, 'longitude' => -23.5166667), + 'Pretoria' => array('latitude' => -25.7069444, 'longitude' => 28.2294444), + 'Pyongyang' => array('latitude' => 39.0194444, 'longitude' => 125.7547222), + 'Quito' => array('latitude' => -0.2166667, 'longitude' => -78.5), + 'Rabat' => array('latitude' => 34.0252778, 'longitude' => -6.8361111), + 'Reykjavik' => array('latitude' => 64.15, 'longitude' => -21.95), + 'Riga' => array('latitude' => 56.95, 'longitude' => 24.1), + 'Rio de Janero' => array('latitude' => -22.9, 'longitude' => -43.2333333), + 'Road Town' => array('latitude' => 18.4166667, 'longitude' => -64.6166667), + 'Rome' => array('latitude' => 41.9, 'longitude' => 12.4833333), + 'Roseau' => array('latitude' => 15.3, 'longitude' => -61.4), + 'Rotterdam' => array('latitude' => 51.9166667, 'longitude' => 4.5), + 'Salvador' => array('latitude' => -12.9833333, 'longitude' => -38.5166667), + 'San José' => array('latitude' => 9.9333333, 'longitude' => -84.0833333), + 'San Juan' => array('latitude' => 18.46833, 'longitude' => -66.10611), + 'San Marino' => array('latitude' => 43.5333333, 'longitude' => 12.9666667), + 'San Salvador' => array('latitude' => 13.7086111, 'longitude' => -89.2030556), + 'Sanaá' => array('latitude' => 15.3547222, 'longitude' => 44.2066667), + 'Santa Cruz' => array('latitude' => -17.8, 'longitude' => -63.1666667), + 'Santiago' => array('latitude' => -33.45, 'longitude' => -70.6666667), + 'Santo Domingo' => array('latitude' => 18.4666667, 'longitude' => -69.9), + 'Sao Paulo' => array('latitude' => -23.5333333, 'longitude' => -46.6166667), + 'Sarajevo' => array('latitude' => 43.85, 'longitude' => 18.3833333), + 'Seoul' => array('latitude' => 37.5663889, 'longitude' => 126.9997222), + 'Shanghai' => array('latitude' => 31.2222222, 'longitude' => 121.4580556), + 'Sydney' => array('latitude' => -33.8833333, 'longitude' => 151.2166667), + 'Singapore' => array('latitude' => 1.2930556, 'longitude' => 103.8558333), + 'Skopje' => array('latitude' => 42, 'longitude' => 21.4333333), + 'Sofia' => array('latitude' => 42.6833333, 'longitude' => 23.3166667), + 'St. George´s' => array('latitude' => 12.05, 'longitude' => -61.75), + 'St. John´s' => array('latitude' => 17.1166667, 'longitude' => -61.85), + 'Stanley' => array('latitude' => -51.7, 'longitude' => -57.85), + 'Stockholm' => array('latitude' => 59.3333333, 'longitude' => 18.05), + 'Suva' => array('latitude' => -18.1333333, 'longitude' => 178.4166667), + 'Taipei' => array('latitude' => 25.0166667, 'longitude' => 121.45), + 'Tallinn' => array('latitude' => 59.4338889, 'longitude' => 24.7280556), + 'Tashkent' => array('latitude' => 41.3166667, 'longitude' => 69.25), + 'Tbilisi' => array('latitude' => 41.725, 'longitude' => 44.7908333), + 'Tegucigalpa' => array('latitude' => 14.1, 'longitude' => -87.2166667), + 'Tehran' => array('latitude' => 35.6719444, 'longitude' => 51.4244444), + 'The Hague' => array('latitude' => 52.0833333, 'longitude' => 4.3), + 'Thimphu' => array('latitude' => 27.4833333, 'longitude' => 89.6), + 'Tirana' => array('latitude' => 41.3275, 'longitude' => 19.8188889), + 'Tiraspol' => array('latitude' => 46.8402778, 'longitude' => 29.6433333), + 'Tokyo' => array('latitude' => 35.685, 'longitude' => 139.7513889), + 'Toronto' => array('latitude' => 43.6666667, 'longitude' => -79.4166667), + 'Tórshavn' => array('latitude' => 62.0166667, 'longitude' => -6.7666667), + 'Tripoli' => array('latitude' => 32.8925, 'longitude' => 13.18), + 'Tunis' => array('latitude' => 36.8027778, 'longitude' => 10.1797222), + 'Ulaanbaatar' => array('latitude' => 47.9166667, 'longitude' => 106.9166667), + 'Vaduz' => array('latitude' => 47.1333333, 'longitude' => 9.5166667), + 'Valletta' => array('latitude' => 35.8997222, 'longitude' => 14.5147222), + 'Valparaiso' => array('latitude' => -33.0477778, 'longitude' => -71.6011111), + 'Vancouver' => array('latitude' => 49.25, 'longitude' => -123.1333333), + 'Vatican City' => array('latitude' => 41.9, 'longitude' => 12.4833333), + 'Victoria' => array('latitude' => -4.6166667, 'longitude' => 55.45), + 'Vienna' => array('latitude' => 48.2, 'longitude' => 16.3666667), + 'Vientaine' => array('latitude' => 17.9666667, 'longitude' => 102.6), + 'Vilnius' => array('latitude' => 54.6833333, 'longitude' => 25.3166667), + 'Warsaw' => array('latitude' => 52.25, 'longitude' => 21), + 'Washington dc' => array('latitude' => 38.895, 'longitude' => -77.03667), + 'Wellington' => array('latitude' => -41.3, 'longitude' => 174.7833333), + 'Willemstad' => array('latitude' => 12.1, 'longitude' => -68.9166667), + 'Windhoek' => array('latitude' => -22.57, 'longitude' => 17.0836111), + 'Yamoussoukro' => array('latitude' => 6.8166667, 'longitude' => -5.2833333), + 'Yaoundé' => array('latitude' => 3.8666667, 'longitude' => 11.5166667), + 'Yerevan' => array('latitude' => 40.1811111, 'longitude' => 44.5136111), + 'Zürich' => array('latitude' => 47.3666667, 'longitude' => 8.55), + 'Zagreb' => array('latitude' => 45.8, 'longitude' => 16) + ); + + /** + * Returns the location from the selected city + * + * @param string $city City to get location for + * @param string $horizon Horizon to use : + * default: effective + * others are civil, nautic, astronomic + * @return array + * @throws Zend_Date_Exception When city is unknown + */ + public static function City($city, $horizon = false) + { + foreach (self::$cities as $key => $value) { + if (strtolower($key) === strtolower($city)) { + $return = $value; + $return['horizon'] = $horizon; + return $return; + } + } + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception('unknown city'); + } + + /** + * Return a list with all known cities + * + * @return array + */ + public static function getCityList() + { + return array_keys(self::$cities); + } +} diff --git a/library/Zend/Date/DateObject.php b/library/Zend/Date/DateObject.php new file mode 100644 index 000000000..4d7e5a46c --- /dev/null +++ b/library/Zend/Date/DateObject.php @@ -0,0 +1,1060 @@ + 0, 1960 => -315619200, 1950 => -631152000, + 1940 => -946771200, 1930 => -1262304000, 1920 => -1577923200, + 1910 => -1893456000, 1900 => -2208988800, 1890 => -2524521600, + 1880 => -2840140800, 1870 => -3155673600, 1860 => -3471292800, + 1850 => -3786825600, 1840 => -4102444800, 1830 => -4417977600, + 1820 => -4733596800, 1810 => -5049129600, 1800 => -5364662400, + 1790 => -5680195200, 1780 => -5995814400, 1770 => -6311347200, + 1760 => -6626966400, 1750 => -6942499200, 1740 => -7258118400, + 1730 => -7573651200, 1720 => -7889270400, 1710 => -8204803200, + 1700 => -8520336000, 1690 => -8835868800, 1680 => -9151488000, + 1670 => -9467020800, 1660 => -9782640000, 1650 => -10098172800, + 1640 => -10413792000, 1630 => -10729324800, 1620 => -11044944000, + 1610 => -11360476800, 1600 => -11676096000); + + /** + * Set this object to have a new UNIX timestamp. + * + * @param string|integer $timestamp OPTIONAL timestamp; defaults to local time using time() + * @return string|integer old timestamp + * @throws Zend_Date_Exception + */ + protected function setUnixTimestamp($timestamp = null) + { + $old = $this->_unixTimestamp; + + if (is_numeric($timestamp)) { + $this->_unixTimestamp = $timestamp; + } else if ($timestamp === null) { + $this->_unixTimestamp = time(); + } else { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception('\'' . $timestamp . '\' is not a valid UNIX timestamp', 0, null, $timestamp); + } + + return $old; + } + + /** + * Returns this object's UNIX timestamp + * A timestamp greater then the integer range will be returned as string + * This function does not return the timestamp as object. Use copy() instead. + * + * @return integer|string timestamp + */ + protected function getUnixTimestamp() + { + if ($this->_unixTimestamp === intval($this->_unixTimestamp)) { + return (int) $this->_unixTimestamp; + } else { + return (string) $this->_unixTimestamp; + } + } + + /** + * Internal function. + * Returns time(). This method exists to allow unit tests to work-around methods that might otherwise + * be hard-coded to use time(). For example, this makes it possible to test isYesterday() in Date.php. + * + * @param integer $sync OPTIONAL time syncronisation value + * @return integer timestamp + */ + protected function _getTime($sync = null) + { + if ($sync !== null) { + $this->_syncronised = round($sync); + } + return (time() + $this->_syncronised); + } + + /** + * Internal mktime function used by Zend_Date. + * The timestamp returned by mktime() can exceed the precision of traditional UNIX timestamps, + * by allowing PHP to auto-convert to using a float value. + * + * Returns a timestamp relative to 1970/01/01 00:00:00 GMT/UTC. + * DST (Summer/Winter) is depriciated since php 5.1.0. + * Year has to be 4 digits otherwise it would be recognised as + * year 70 AD instead of 1970 AD as expected !! + * + * @param integer $hour + * @param integer $minute + * @param integer $second + * @param integer $month + * @param integer $day + * @param integer $year + * @param boolean $gmt OPTIONAL true = other arguments are for UTC time, false = arguments are for local time/date + * @return integer|float timestamp (number of seconds elapsed relative to 1970/01/01 00:00:00 GMT/UTC) + */ + protected function mktime($hour, $minute, $second, $month, $day, $year, $gmt = false) + { + + // complete date but in 32bit timestamp - use PHP internal + if ((1901 < $year) and ($year < 2038)) { + + $oldzone = @date_default_timezone_get(); + // Timezone also includes DST settings, therefor substracting the GMT offset is not enough + // We have to set the correct timezone to get the right value + if (($this->_timezone != $oldzone) and ($gmt === false)) { + date_default_timezone_set($this->_timezone); + } + $result = ($gmt) ? @gmmktime($hour, $minute, $second, $month, $day, $year) + : @mktime($hour, $minute, $second, $month, $day, $year); + date_default_timezone_set($oldzone); + + return $result; + } + + if ($gmt !== true) { + $second += $this->_offset; + } + + if (isset(self::$_cache)) { + $id = strtr('Zend_DateObject_mkTime_' . $this->_offset . '_' . $year.$month.$day.'_'.$hour.$minute.$second . '_'.(int)$gmt, '-','_'); + if ($result = self::$_cache->load($id)) { + return unserialize($result); + } + } + + // date to integer + $day = intval($day); + $month = intval($month); + $year = intval($year); + + // correct months > 12 and months < 1 + if ($month > 12) { + $overlap = floor($month / 12); + $year += $overlap; + $month -= $overlap * 12; + } else { + $overlap = ceil((1 - $month) / 12); + $year -= $overlap; + $month += $overlap * 12; + } + + $date = 0; + if ($year >= 1970) { + + // Date is after UNIX epoch + // go through leapyears + // add months from latest given year + for ($count = 1970; $count <= $year; $count++) { + + $leapyear = self::isYearLeapYear($count); + if ($count < $year) { + + $date += 365; + if ($leapyear === true) { + $date++; + } + + } else { + + for ($mcount = 0; $mcount < ($month - 1); $mcount++) { + $date += self::$_monthTable[$mcount]; + if (($leapyear === true) and ($mcount == 1)) { + $date++; + } + + } + } + } + + $date += $day - 1; + $date = (($date * 86400) + ($hour * 3600) + ($minute * 60) + $second); + } else { + + // Date is before UNIX epoch + // go through leapyears + // add months from latest given year + for ($count = 1969; $count >= $year; $count--) { + + $leapyear = self::isYearLeapYear($count); + if ($count > $year) + { + $date += 365; + if ($leapyear === true) + $date++; + } else { + + for ($mcount = 11; $mcount > ($month - 1); $mcount--) { + $date += self::$_monthTable[$mcount]; + if (($leapyear === true) and ($mcount == 1)) { + $date++; + } + + } + } + } + + $date += (self::$_monthTable[$month - 1] - $day); + $date = -(($date * 86400) + (86400 - (($hour * 3600) + ($minute * 60) + $second))); + + // gregorian correction for 5.Oct.1582 + if ($date < -12220185600) { + $date += 864000; + } else if ($date < -12219321600) { + $date = -12219321600; + } + } + + if (isset(self::$_cache)) { + self::$_cache->save( serialize($date), $id); + } + + return $date; + } + + /** + * Returns true, if given $year is a leap year. + * + * @param integer $year + * @return boolean true, if year is leap year + */ + protected static function isYearLeapYear($year) + { + // all leapyears can be divided through 4 + if (($year % 4) != 0) { + return false; + } + + // all leapyears can be divided through 400 + if ($year % 400 == 0) { + return true; + } else if (($year > 1582) and ($year % 100 == 0)) { + return false; + } + + return true; + } + + /** + * Internal mktime function used by Zend_Date for handling 64bit timestamps. + * + * Returns a formatted date for a given timestamp. + * + * @param string $format format for output + * @param mixed $timestamp + * @param boolean $gmt OPTIONAL true = other arguments are for UTC time, false = arguments are for local time/date + * @return string + */ + protected function date($format, $timestamp = null, $gmt = false) + { + $oldzone = @date_default_timezone_get(); + if ($this->_timezone != $oldzone) { + date_default_timezone_set($this->_timezone); + } + + if ($timestamp === null) { + $result = ($gmt) ? @gmdate($format) : @date($format); + date_default_timezone_set($oldzone); + return $result; + } + + if (abs($timestamp) <= 0x7FFFFFFF) { + $result = ($gmt) ? @gmdate($format, $timestamp) : @date($format, $timestamp); + date_default_timezone_set($oldzone); + return $result; + } + + $jump = false; + $origstamp = $timestamp; + if (isset(self::$_cache)) { + $idstamp = strtr('Zend_DateObject_date_' . $this->_offset . '_'. $timestamp . '_'.(int)$gmt, '-','_'); + if ($result2 = self::$_cache->load($idstamp)) { + $timestamp = unserialize($result2); + $jump = true; + } + } + + // check on false or null alone failes + if (empty($gmt) and empty($jump)) { + $tempstamp = $timestamp; + if ($tempstamp > 0) { + while (abs($tempstamp) > 0x7FFFFFFF) { + $tempstamp -= (86400 * 23376); + } + + $dst = date("I", $tempstamp); + if ($dst === 1) { + $timestamp += 3600; + } + + $temp = date('Z', $tempstamp); + $timestamp += $temp; + } + + if (isset(self::$_cache)) { + self::$_cache->save( serialize($timestamp), $idstamp); + } + } + + + if (($timestamp < 0) and ($gmt !== true)) { + $timestamp -= $this->_offset; + } + + date_default_timezone_set($oldzone); + $date = $this->getDateParts($timestamp, true); + $length = strlen($format); + $output = ''; + + for ($i = 0; $i < $length; $i++) { + switch($format[$i]) { + // day formats + case 'd': // day of month, 2 digits, with leading zero, 01 - 31 + $output .= (($date['mday'] < 10) ? '0' . $date['mday'] : $date['mday']); + break; + + case 'D': // day of week, 3 letters, Mon - Sun + $output .= date('D', 86400 * (3 + self::dayOfWeek($date['year'], $date['mon'], $date['mday']))); + break; + + case 'j': // day of month, without leading zero, 1 - 31 + $output .= $date['mday']; + break; + + case 'l': // day of week, full string name, Sunday - Saturday + $output .= date('l', 86400 * (3 + self::dayOfWeek($date['year'], $date['mon'], $date['mday']))); + break; + + case 'N': // ISO 8601 numeric day of week, 1 - 7 + $day = self::dayOfWeek($date['year'], $date['mon'], $date['mday']); + if ($day == 0) { + $day = 7; + } + $output .= $day; + break; + + case 'S': // english suffix for day of month, st nd rd th + if (($date['mday'] % 10) == 1) { + $output .= 'st'; + } else if ((($date['mday'] % 10) == 2) and ($date['mday'] != 12)) { + $output .= 'nd'; + } else if (($date['mday'] % 10) == 3) { + $output .= 'rd'; + } else { + $output .= 'th'; + } + break; + + case 'w': // numeric day of week, 0 - 6 + $output .= self::dayOfWeek($date['year'], $date['mon'], $date['mday']); + break; + + case 'z': // day of year, 0 - 365 + $output .= $date['yday']; + break; + + + // week formats + case 'W': // ISO 8601, week number of year + $output .= $this->weekNumber($date['year'], $date['mon'], $date['mday']); + break; + + + // month formats + case 'F': // string month name, january - december + $output .= date('F', mktime(0, 0, 0, $date['mon'], 2, 1971)); + break; + + case 'm': // number of month, with leading zeros, 01 - 12 + $output .= (($date['mon'] < 10) ? '0' . $date['mon'] : $date['mon']); + break; + + case 'M': // 3 letter month name, Jan - Dec + $output .= date('M',mktime(0, 0, 0, $date['mon'], 2, 1971)); + break; + + case 'n': // number of month, without leading zeros, 1 - 12 + $output .= $date['mon']; + break; + + case 't': // number of day in month + $output .= self::$_monthTable[$date['mon'] - 1]; + break; + + + // year formats + case 'L': // is leap year ? + $output .= (self::isYearLeapYear($date['year'])) ? '1' : '0'; + break; + + case 'o': // ISO 8601 year number + $week = $this->weekNumber($date['year'], $date['mon'], $date['mday']); + if (($week > 50) and ($date['mon'] == 1)) { + $output .= ($date['year'] - 1); + } else { + $output .= $date['year']; + } + break; + + case 'Y': // year number, 4 digits + $output .= $date['year']; + break; + + case 'y': // year number, 2 digits + $output .= substr($date['year'], strlen($date['year']) - 2, 2); + break; + + + // time formats + case 'a': // lower case am/pm + $output .= (($date['hours'] >= 12) ? 'pm' : 'am'); + break; + + case 'A': // upper case am/pm + $output .= (($date['hours'] >= 12) ? 'PM' : 'AM'); + break; + + case 'B': // swatch internet time + $dayseconds = ($date['hours'] * 3600) + ($date['minutes'] * 60) + $date['seconds']; + if ($gmt === true) { + $dayseconds += 3600; + } + $output .= (int) (($dayseconds % 86400) / 86.4); + break; + + case 'g': // hours without leading zeros, 12h format + if ($date['hours'] > 12) { + $hour = $date['hours'] - 12; + } else { + if ($date['hours'] == 0) { + $hour = '12'; + } else { + $hour = $date['hours']; + } + } + $output .= $hour; + break; + + case 'G': // hours without leading zeros, 24h format + $output .= $date['hours']; + break; + + case 'h': // hours with leading zeros, 12h format + if ($date['hours'] > 12) { + $hour = $date['hours'] - 12; + } else { + if ($date['hours'] == 0) { + $hour = '12'; + } else { + $hour = $date['hours']; + } + } + $output .= (($hour < 10) ? '0'.$hour : $hour); + break; + + case 'H': // hours with leading zeros, 24h format + $output .= (($date['hours'] < 10) ? '0' . $date['hours'] : $date['hours']); + break; + + case 'i': // minutes with leading zeros + $output .= (($date['minutes'] < 10) ? '0' . $date['minutes'] : $date['minutes']); + break; + + case 's': // seconds with leading zeros + $output .= (($date['seconds'] < 10) ? '0' . $date['seconds'] : $date['seconds']); + break; + + + // timezone formats + case 'e': // timezone identifier + if ($gmt === true) { + $output .= gmdate('e', mktime($date['hours'], $date['minutes'], $date['seconds'], + $date['mon'], $date['mday'], 2000)); + } else { + $output .= date('e', mktime($date['hours'], $date['minutes'], $date['seconds'], + $date['mon'], $date['mday'], 2000)); + } + break; + + case 'I': // daylight saving time or not + if ($gmt === true) { + $output .= gmdate('I', mktime($date['hours'], $date['minutes'], $date['seconds'], + $date['mon'], $date['mday'], 2000)); + } else { + $output .= date('I', mktime($date['hours'], $date['minutes'], $date['seconds'], + $date['mon'], $date['mday'], 2000)); + } + break; + + case 'O': // difference to GMT in hours + $gmtstr = ($gmt === true) ? 0 : $this->getGmtOffset(); + $output .= sprintf('%s%04d', ($gmtstr <= 0) ? '+' : '-', abs($gmtstr) / 36); + break; + + case 'P': // difference to GMT with colon + $gmtstr = ($gmt === true) ? 0 : $this->getGmtOffset(); + $gmtstr = sprintf('%s%04d', ($gmtstr <= 0) ? '+' : '-', abs($gmtstr) / 36); + $output = $output . substr($gmtstr, 0, 3) . ':' . substr($gmtstr, 3); + break; + + case 'T': // timezone settings + if ($gmt === true) { + $output .= gmdate('T', mktime($date['hours'], $date['minutes'], $date['seconds'], + $date['mon'], $date['mday'], 2000)); + } else { + $output .= date('T', mktime($date['hours'], $date['minutes'], $date['seconds'], + $date['mon'], $date['mday'], 2000)); + } + break; + + case 'Z': // timezone offset in seconds + $output .= ($gmt === true) ? 0 : -$this->getGmtOffset(); + break; + + + // complete time formats + case 'c': // ISO 8601 date format + $difference = $this->getGmtOffset(); + $difference = sprintf('%s%04d', ($difference <= 0) ? '+' : '-', abs($difference) / 36); + $difference = substr($difference, 0, 3) . ':' . substr($difference, 3); + $output .= $date['year'] . '-' + . (($date['mon'] < 10) ? '0' . $date['mon'] : $date['mon']) . '-' + . (($date['mday'] < 10) ? '0' . $date['mday'] : $date['mday']) . 'T' + . (($date['hours'] < 10) ? '0' . $date['hours'] : $date['hours']) . ':' + . (($date['minutes'] < 10) ? '0' . $date['minutes'] : $date['minutes']) . ':' + . (($date['seconds'] < 10) ? '0' . $date['seconds'] : $date['seconds']) + . $difference; + break; + + case 'r': // RFC 2822 date format + $difference = $this->getGmtOffset(); + $difference = sprintf('%s%04d', ($difference <= 0) ? '+' : '-', abs($difference) / 36); + $output .= gmdate('D', 86400 * (3 + self::dayOfWeek($date['year'], $date['mon'], $date['mday']))) . ', ' + . (($date['mday'] < 10) ? '0' . $date['mday'] : $date['mday']) . ' ' + . date('M', mktime(0, 0, 0, $date['mon'], 2, 1971)) . ' ' + . $date['year'] . ' ' + . (($date['hours'] < 10) ? '0' . $date['hours'] : $date['hours']) . ':' + . (($date['minutes'] < 10) ? '0' . $date['minutes'] : $date['minutes']) . ':' + . (($date['seconds'] < 10) ? '0' . $date['seconds'] : $date['seconds']) . ' ' + . $difference; + break; + + case 'U': // Unix timestamp + $output .= $origstamp; + break; + + + // special formats + case "\\": // next letter to print with no format + $i++; + if ($i < $length) { + $output .= $format[$i]; + } + break; + + default: // letter is no format so add it direct + $output .= $format[$i]; + break; + } + } + + return (string) $output; + } + + /** + * Returns the day of week for a Gregorian calendar date. + * 0 = sunday, 6 = saturday + * + * @param integer $year + * @param integer $month + * @param integer $day + * @return integer dayOfWeek + */ + protected static function dayOfWeek($year, $month, $day) + { + if ((1901 < $year) and ($year < 2038)) { + return (int) date('w', mktime(0, 0, 0, $month, $day, $year)); + } + + // gregorian correction + $correction = 0; + if (($year < 1582) or (($year == 1582) and (($month < 10) or (($month == 10) && ($day < 15))))) { + $correction = 3; + } + + if ($month > 2) { + $month -= 2; + } else { + $month += 10; + $year--; + } + + $day = floor((13 * $month - 1) / 5) + $day + ($year % 100) + floor(($year % 100) / 4); + $day += floor(($year / 100) / 4) - 2 * floor($year / 100) + 77 + $correction; + + return (int) ($day - 7 * floor($day / 7)); + } + + /** + * Internal getDateParts function for handling 64bit timestamps, similar to: + * http://www.php.net/getdate + * + * Returns an array of date parts for $timestamp, relative to 1970/01/01 00:00:00 GMT/UTC. + * + * $fast specifies ALL date parts should be returned (slower) + * Default is false, and excludes $dayofweek, weekday, month and timestamp from parts returned. + * + * @param mixed $timestamp + * @param boolean $fast OPTIONAL defaults to fast (false), resulting in fewer date parts + * @return array + */ + protected function getDateParts($timestamp = null, $fast = null) + { + + // actual timestamp + if (!is_numeric($timestamp)) { + return getdate(); + } + + // 32bit timestamp + if (abs($timestamp) <= 0x7FFFFFFF) { + return @getdate((int) $timestamp); + } + + if (isset(self::$_cache)) { + $id = strtr('Zend_DateObject_getDateParts_' . $timestamp.'_'.(int)$fast, '-','_'); + if ($result = self::$_cache->load($id)) { + return unserialize($result); + } + } + + $otimestamp = $timestamp; + $numday = 0; + $month = 0; + // gregorian correction + if ($timestamp < -12219321600) { + $timestamp -= 864000; + } + + // timestamp lower 0 + if ($timestamp < 0) { + $sec = 0; + $act = 1970; + + // iterate through 10 years table, increasing speed + foreach(self::$_yearTable as $year => $seconds) { + if ($timestamp >= $seconds) { + $i = $act; + break; + } + $sec = $seconds; + $act = $year; + } + + $timestamp -= $sec; + if (!isset($i)) { + $i = $act; + } + + // iterate the max last 10 years + do { + --$i; + $day = $timestamp; + + $timestamp += 31536000; + $leapyear = self::isYearLeapYear($i); + if ($leapyear === true) { + $timestamp += 86400; + } + + if ($timestamp >= 0) { + $year = $i; + break; + } + } while ($timestamp < 0); + + $secondsPerYear = 86400 * ($leapyear ? 366 : 365) + $day; + + $timestamp = $day; + // iterate through months + for ($i = 12; --$i >= 0;) { + $day = $timestamp; + + $timestamp += self::$_monthTable[$i] * 86400; + if (($leapyear === true) and ($i == 1)) { + $timestamp += 86400; + } + + if ($timestamp >= 0) { + $month = $i; + $numday = self::$_monthTable[$i]; + if (($leapyear === true) and ($i == 1)) { + ++$numday; + } + break; + } + } + + $timestamp = $day; + $numberdays = $numday + ceil(($timestamp + 1) / 86400); + + $timestamp += ($numday - $numberdays + 1) * 86400; + $hours = floor($timestamp / 3600); + } else { + + // iterate through years + for ($i = 1970;;$i++) { + $day = $timestamp; + + $timestamp -= 31536000; + $leapyear = self::isYearLeapYear($i); + if ($leapyear === true) { + $timestamp -= 86400; + } + + if ($timestamp < 0) { + $year = $i; + break; + } + } + + $secondsPerYear = $day; + + $timestamp = $day; + // iterate through months + for ($i = 0; $i <= 11; $i++) { + $day = $timestamp; + $timestamp -= self::$_monthTable[$i] * 86400; + + if (($leapyear === true) and ($i == 1)) { + $timestamp -= 86400; + } + + if ($timestamp < 0) { + $month = $i; + $numday = self::$_monthTable[$i]; + if (($leapyear === true) and ($i == 1)) { + ++$numday; + } + break; + } + } + + $timestamp = $day; + $numberdays = ceil(($timestamp + 1) / 86400); + $timestamp = $timestamp - ($numberdays - 1) * 86400; + $hours = floor($timestamp / 3600); + } + + $timestamp -= $hours * 3600; + + $month += 1; + $minutes = floor($timestamp / 60); + $seconds = $timestamp - $minutes * 60; + + if ($fast === true) { + $array = array( + 'seconds' => $seconds, + 'minutes' => $minutes, + 'hours' => $hours, + 'mday' => $numberdays, + 'mon' => $month, + 'year' => $year, + 'yday' => floor($secondsPerYear / 86400), + ); + } else { + + $dayofweek = self::dayOfWeek($year, $month, $numberdays); + $array = array( + 'seconds' => $seconds, + 'minutes' => $minutes, + 'hours' => $hours, + 'mday' => $numberdays, + 'wday' => $dayofweek, + 'mon' => $month, + 'year' => $year, + 'yday' => floor($secondsPerYear / 86400), + 'weekday' => gmdate('l', 86400 * (3 + $dayofweek)), + 'month' => gmdate('F', mktime(0, 0, 0, $month, 1, 1971)), + 0 => $otimestamp + ); + } + + if (isset(self::$_cache)) { + self::$_cache->save( serialize($array), $id); + } + + return $array; + } + + /** + * Internal getWeekNumber function for handling 64bit timestamps + * + * Returns the ISO 8601 week number of a given date + * + * @param integer $year + * @param integer $month + * @param integer $day + * @return integer + */ + protected function weekNumber($year, $month, $day) + { + if ((1901 < $year) and ($year < 2038)) { + return (int) date('W', mktime(0, 0, 0, $month, $day, $year)); + } + + $dayofweek = self::dayOfWeek($year, $month, $day); + $firstday = self::dayOfWeek($year, 1, 1); + if (($month == 1) and (($firstday < 1) or ($firstday > 4)) and ($day < 4)) { + $firstday = self::dayOfWeek($year - 1, 1, 1); + $month = 12; + $day = 31; + + } else if (($month == 12) and ((self::dayOfWeek($year + 1, 1, 1) < 5) and + (self::dayOfWeek($year + 1, 1, 1) > 0))) { + return 1; + } + + return intval (((self::dayOfWeek($year, 1, 1) < 5) and (self::dayOfWeek($year, 1, 1) > 0)) + + 4 * ($month - 1) + (2 * ($month - 1) + ($day - 1) + $firstday - $dayofweek + 6) * 36 / 256); + } + + /** + * Internal _range function + * Sets the value $a to be in the range of [0, $b] + * + * @param float $a - value to correct + * @param float $b - maximum range to set + */ + private function _range($a, $b) { + while ($a < 0) { + $a += $b; + } + while ($a >= $b) { + $a -= $b; + } + return $a; + } + + /** + * Calculates the sunrise or sunset based on a location + * + * @param array $location Location for calculation MUST include 'latitude', 'longitude', 'horizon' + * @param bool $horizon true: sunrise; false: sunset + * @return mixed - false: midnight sun, integer: + */ + protected function calcSun($location, $horizon, $rise = false) + { + // timestamp within 32bit + if (abs($this->_unixTimestamp) <= 0x7FFFFFFF) { + if ($rise === false) { + return date_sunset($this->_unixTimestamp, SUNFUNCS_RET_TIMESTAMP, $location['latitude'], + $location['longitude'], 90 + $horizon, $this->getGmtOffset() / 3600); + } + return date_sunrise($this->_unixTimestamp, SUNFUNCS_RET_TIMESTAMP, $location['latitude'], + $location['longitude'], 90 + $horizon, $this->getGmtOffset() / 3600); + } + + // self calculation - timestamp bigger than 32bit + // fix circle values + $quarterCircle = 0.5 * M_PI; + $halfCircle = M_PI; + $threeQuarterCircle = 1.5 * M_PI; + $fullCircle = 2 * M_PI; + + // radiant conversion for coordinates + $radLatitude = $location['latitude'] * $halfCircle / 180; + $radLongitude = $location['longitude'] * $halfCircle / 180; + + // get solar coordinates + $tmpRise = $rise ? $quarterCircle : $threeQuarterCircle; + $radDay = $this->date('z',$this->_unixTimestamp) + ($tmpRise - $radLongitude) / $fullCircle; + + // solar anomoly and longitude + $solAnomoly = $radDay * 0.017202 - 0.0574039; + $solLongitude = $solAnomoly + 0.0334405 * sin($solAnomoly); + $solLongitude += 4.93289 + 3.49066E-4 * sin(2 * $solAnomoly); + + // get quadrant + $solLongitude = $this->_range($solLongitude, $fullCircle); + + if (($solLongitude / $quarterCircle) - intval($solLongitude / $quarterCircle) == 0) { + $solLongitude += 4.84814E-6; + } + + // solar ascension + $solAscension = sin($solLongitude) / cos($solLongitude); + $solAscension = atan2(0.91746 * $solAscension, 1); + + // adjust quadrant + if ($solLongitude > $threeQuarterCircle) { + $solAscension += $fullCircle; + } else if ($solLongitude > $quarterCircle) { + $solAscension += $halfCircle; + } + + // solar declination + $solDeclination = 0.39782 * sin($solLongitude); + $solDeclination /= sqrt(-$solDeclination * $solDeclination + 1); + $solDeclination = atan2($solDeclination, 1); + + $solHorizon = $horizon - sin($solDeclination) * sin($radLatitude); + $solHorizon /= cos($solDeclination) * cos($radLatitude); + + // midnight sun, always night + if (abs($solHorizon) > 1) { + return false; + } + + $solHorizon /= sqrt(-$solHorizon * $solHorizon + 1); + $solHorizon = $quarterCircle - atan2($solHorizon, 1); + + if ($rise) { + $solHorizon = $fullCircle - $solHorizon; + } + + // time calculation + $localTime = $solHorizon + $solAscension - 0.0172028 * $radDay - 1.73364; + $universalTime = $localTime - $radLongitude; + + // determinate quadrant + $universalTime = $this->_range($universalTime, $fullCircle); + + // radiant to hours + $universalTime *= 24 / $fullCircle; + + // convert to time + $hour = intval($universalTime); + $universalTime = ($universalTime - $hour) * 60; + $min = intval($universalTime); + $universalTime = ($universalTime - $min) * 60; + $sec = intval($universalTime); + + return $this->mktime($hour, $min, $sec, $this->date('m', $this->_unixTimestamp), + $this->date('j', $this->_unixTimestamp), $this->date('Y', $this->_unixTimestamp), + -1, true); + } + + /** + * Sets a new timezone for calculation of $this object's gmt offset. + * For a list of supported timezones look here: http://php.net/timezones + * If no timezone can be detected or the given timezone is wrong UTC will be set. + * + * @param string $zone OPTIONAL timezone for date calculation; defaults to date_default_timezone_get() + * @return Zend_Date_DateObject Provides fluent interface + * @throws Zend_Date_Exception + */ + public function setTimezone($zone = null) + { + $oldzone = @date_default_timezone_get(); + if ($zone === null) { + $zone = $oldzone; + } + + // throw an error on false input, but only if the new date extension is available + if (function_exists('timezone_open')) { + if (!@timezone_open($zone)) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("timezone ($zone) is not a known timezone", 0, null, $zone); + } + } + // this can generate an error if the date extension is not available and a false timezone is given + $result = @date_default_timezone_set($zone); + if ($result === true) { + $this->_offset = mktime(0, 0, 0, 1, 2, 1970) - gmmktime(0, 0, 0, 1, 2, 1970); + $this->_timezone = $zone; + } + date_default_timezone_set($oldzone); + + if (($zone == 'UTC') or ($zone == 'GMT')) { + $this->_dst = false; + } else { + $this->_dst = true; + } + + return $this; + } + + /** + * Return the timezone of $this object. + * The timezone is initially set when the object is instantiated. + * + * @return string actual set timezone string + */ + public function getTimezone() + { + return $this->_timezone; + } + + /** + * Return the offset to GMT of $this object's timezone. + * The offset to GMT is initially set when the object is instantiated using the currently, + * in effect, default timezone for PHP functions. + * + * @return integer seconds difference between GMT timezone and timezone when object was instantiated + */ + public function getGmtOffset() + { + $date = $this->getDateParts($this->getUnixTimestamp(), true); + $zone = @date_default_timezone_get(); + $result = @date_default_timezone_set($this->_timezone); + if ($result === true) { + $offset = $this->mktime($date['hours'], $date['minutes'], $date['seconds'], + $date['mon'], $date['mday'], $date['year'], false) + - $this->mktime($date['hours'], $date['minutes'], $date['seconds'], + $date['mon'], $date['mday'], $date['year'], true); + } + date_default_timezone_set($zone); + + return $offset; + } +} diff --git a/library/Zend/Date/Exception.php b/library/Zend/Date/Exception.php new file mode 100644 index 000000000..d0db60786 --- /dev/null +++ b/library/Zend/Date/Exception.php @@ -0,0 +1,49 @@ +operand = $op; + parent::__construct($message, $code, $e); + } + + public function getOperand() + { + return $this->operand; + } +} diff --git a/library/Zend/Db.php b/library/Zend/Db.php new file mode 100644 index 000000000..eb0070ce1 --- /dev/null +++ b/library/Zend/Db.php @@ -0,0 +1,281 @@ +toArray(); + } + + /* + * Convert Zend_Config argument to plain string + * adapter name and separate config object. + */ + if ($adapter instanceof Zend_Config) { + if (isset($adapter->params)) { + $config = $adapter->params->toArray(); + } + if (isset($adapter->adapter)) { + $adapter = (string) $adapter->adapter; + } else { + $adapter = null; + } + } + + /* + * Verify that adapter parameters are in an array. + */ + if (!is_array($config)) { + /** + * @see Zend_Db_Exception + */ + require_once 'Zend/Db/Exception.php'; + throw new Zend_Db_Exception('Adapter parameters must be in an array or a Zend_Config object'); + } + + /* + * Verify that an adapter name has been specified. + */ + if (!is_string($adapter) || empty($adapter)) { + /** + * @see Zend_Db_Exception + */ + require_once 'Zend/Db/Exception.php'; + throw new Zend_Db_Exception('Adapter name must be specified in a string'); + } + + /* + * Form full adapter class name + */ + $adapterNamespace = 'Zend_Db_Adapter'; + if (isset($config['adapterNamespace'])) { + if ($config['adapterNamespace'] != '') { + $adapterNamespace = $config['adapterNamespace']; + } + unset($config['adapterNamespace']); + } + + // Adapter no longer normalized- see http://framework.zend.com/issues/browse/ZF-5606 + $adapterName = $adapterNamespace . '_'; + $adapterName .= str_replace(' ', '_', ucwords(str_replace('_', ' ', strtolower($adapter)))); + + /* + * Load the adapter class. This throws an exception + * if the specified class cannot be loaded. + */ + if (!class_exists($adapterName)) { + require_once 'Zend/Loader.php'; + Zend_Loader::loadClass($adapterName); + } + + /* + * Create an instance of the adapter class. + * Pass the config to the adapter class constructor. + */ + $dbAdapter = new $adapterName($config); + + /* + * Verify that the object created is a descendent of the abstract adapter type. + */ + if (! $dbAdapter instanceof Zend_Db_Adapter_Abstract) { + /** + * @see Zend_Db_Exception + */ + require_once 'Zend/Db/Exception.php'; + throw new Zend_Db_Exception("Adapter class '$adapterName' does not extend Zend_Db_Adapter_Abstract"); + } + + return $dbAdapter; + } + +} diff --git a/library/Zend/Db/Adapter/Abstract.php b/library/Zend/Db/Adapter/Abstract.php new file mode 100644 index 000000000..ac9403ef2 --- /dev/null +++ b/library/Zend/Db/Adapter/Abstract.php @@ -0,0 +1,1252 @@ + Zend_Db::INT_TYPE, + Zend_Db::BIGINT_TYPE => Zend_Db::BIGINT_TYPE, + Zend_Db::FLOAT_TYPE => Zend_Db::FLOAT_TYPE + ); + + /** Weither or not that object can get serialized + * + * @var bool + */ + protected $_allowSerialization = true; + + /** + * Weither or not the database should be reconnected + * to that adapter when waking up + * + * @var bool + */ + protected $_autoReconnectOnUnserialize = false; + + /** + * Constructor. + * + * $config is an array of key/value pairs or an instance of Zend_Config + * containing configuration options. These options are common to most adapters: + * + * dbname => (string) The name of the database to user + * username => (string) Connect to the database as this username. + * password => (string) Password associated with the username. + * host => (string) What host to connect to, defaults to localhost + * + * Some options are used on a case-by-case basis by adapters: + * + * port => (string) The port of the database + * persistent => (boolean) Whether to use a persistent connection or not, defaults to false + * protocol => (string) The network protocol, defaults to TCPIP + * caseFolding => (int) style of case-alteration used for identifiers + * + * @param array|Zend_Config $config An array or instance of Zend_Config having configuration data + * @throws Zend_Db_Adapter_Exception + */ + public function __construct($config) + { + /* + * Verify that adapter parameters are in an array. + */ + if (!is_array($config)) { + /* + * Convert Zend_Config argument to a plain array. + */ + if ($config instanceof Zend_Config) { + $config = $config->toArray(); + } else { + /** + * @see Zend_Db_Adapter_Exception + */ + require_once 'Zend/Db/Adapter/Exception.php'; + throw new Zend_Db_Adapter_Exception('Adapter parameters must be in an array or a Zend_Config object'); + } + } + + $this->_checkRequiredOptions($config); + + $options = array( + Zend_Db::CASE_FOLDING => $this->_caseFolding, + Zend_Db::AUTO_QUOTE_IDENTIFIERS => $this->_autoQuoteIdentifiers + ); + $driverOptions = array(); + + /* + * normalize the config and merge it with the defaults + */ + if (array_key_exists('options', $config)) { + // can't use array_merge() because keys might be integers + foreach ((array) $config['options'] as $key => $value) { + $options[$key] = $value; + } + } + if (array_key_exists('driver_options', $config)) { + if (!empty($config['driver_options'])) { + // can't use array_merge() because keys might be integers + foreach ((array) $config['driver_options'] as $key => $value) { + $driverOptions[$key] = $value; + } + } + } + + if (!isset($config['charset'])) { + $config['charset'] = null; + } + + if (!isset($config['persistent'])) { + $config['persistent'] = false; + } + + $this->_config = array_merge($this->_config, $config); + $this->_config['options'] = $options; + $this->_config['driver_options'] = $driverOptions; + + + // obtain the case setting, if there is one + if (array_key_exists(Zend_Db::CASE_FOLDING, $options)) { + $case = (int) $options[Zend_Db::CASE_FOLDING]; + switch ($case) { + case Zend_Db::CASE_LOWER: + case Zend_Db::CASE_UPPER: + case Zend_Db::CASE_NATURAL: + $this->_caseFolding = $case; + break; + default: + /** @see Zend_Db_Adapter_Exception */ + require_once 'Zend/Db/Adapter/Exception.php'; + throw new Zend_Db_Adapter_Exception('Case must be one of the following constants: ' + . 'Zend_Db::CASE_NATURAL, Zend_Db::CASE_LOWER, Zend_Db::CASE_UPPER'); + } + } + + // obtain quoting property if there is one + if (array_key_exists(Zend_Db::AUTO_QUOTE_IDENTIFIERS, $options)) { + $this->_autoQuoteIdentifiers = (bool) $options[Zend_Db::AUTO_QUOTE_IDENTIFIERS]; + } + + // obtain allow serialization property if there is one + if (array_key_exists(Zend_Db::ALLOW_SERIALIZATION, $options)) { + $this->_allowSerialization = (bool) $options[Zend_Db::ALLOW_SERIALIZATION]; + } + + // obtain auto reconnect on unserialize property if there is one + if (array_key_exists(Zend_Db::AUTO_RECONNECT_ON_UNSERIALIZE, $options)) { + $this->_autoReconnectOnUnserialize = (bool) $options[Zend_Db::AUTO_RECONNECT_ON_UNSERIALIZE]; + } + + // create a profiler object + $profiler = false; + if (array_key_exists(Zend_Db::PROFILER, $this->_config)) { + $profiler = $this->_config[Zend_Db::PROFILER]; + unset($this->_config[Zend_Db::PROFILER]); + } + $this->setProfiler($profiler); + } + + /** + * Check for config options that are mandatory. + * Throw exceptions if any are missing. + * + * @param array $config + * @throws Zend_Db_Adapter_Exception + */ + protected function _checkRequiredOptions(array $config) + { + // we need at least a dbname + if (! array_key_exists('dbname', $config)) { + /** @see Zend_Db_Adapter_Exception */ + require_once 'Zend/Db/Adapter/Exception.php'; + throw new Zend_Db_Adapter_Exception("Configuration array must have a key for 'dbname' that names the database instance"); + } + + if (! array_key_exists('password', $config)) { + /** + * @see Zend_Db_Adapter_Exception + */ + require_once 'Zend/Db/Adapter/Exception.php'; + throw new Zend_Db_Adapter_Exception("Configuration array must have a key for 'password' for login credentials"); + } + + if (! array_key_exists('username', $config)) { + /** + * @see Zend_Db_Adapter_Exception + */ + require_once 'Zend/Db/Adapter/Exception.php'; + throw new Zend_Db_Adapter_Exception("Configuration array must have a key for 'username' for login credentials"); + } + } + + /** + * Returns the underlying database connection object or resource. + * If not presently connected, this initiates the connection. + * + * @return object|resource|null + */ + public function getConnection() + { + $this->_connect(); + return $this->_connection; + } + + /** + * Returns the configuration variables in this adapter. + * + * @return array + */ + public function getConfig() + { + return $this->_config; + } + + /** + * Set the adapter's profiler object. + * + * The argument may be a boolean, an associative array, an instance of + * Zend_Db_Profiler, or an instance of Zend_Config. + * + * A boolean argument sets the profiler to enabled if true, or disabled if + * false. The profiler class is the adapter's default profiler class, + * Zend_Db_Profiler. + * + * An instance of Zend_Db_Profiler sets the adapter's instance to that + * object. The profiler is enabled and disabled separately. + * + * An associative array argument may contain any of the keys 'enabled', + * 'class', and 'instance'. The 'enabled' and 'instance' keys correspond to the + * boolean and object types documented above. The 'class' key is used to name a + * class to use for a custom profiler. The class must be Zend_Db_Profiler or a + * subclass. The class is instantiated with no constructor arguments. The 'class' + * option is ignored when the 'instance' option is supplied. + * + * An object of type Zend_Config may contain the properties 'enabled', 'class', and + * 'instance', just as if an associative array had been passed instead. + * + * @param Zend_Db_Profiler|Zend_Config|array|boolean $profiler + * @return Zend_Db_Adapter_Abstract Provides a fluent interface + * @throws Zend_Db_Profiler_Exception if the object instance or class specified + * is not Zend_Db_Profiler or an extension of that class. + */ + public function setProfiler($profiler) + { + $enabled = null; + $profilerClass = $this->_defaultProfilerClass; + $profilerInstance = null; + + if ($profilerIsObject = is_object($profiler)) { + if ($profiler instanceof Zend_Db_Profiler) { + $profilerInstance = $profiler; + } else if ($profiler instanceof Zend_Config) { + $profiler = $profiler->toArray(); + } else { + /** + * @see Zend_Db_Profiler_Exception + */ + require_once 'Zend/Db/Profiler/Exception.php'; + throw new Zend_Db_Profiler_Exception('Profiler argument must be an instance of either Zend_Db_Profiler' + . ' or Zend_Config when provided as an object'); + } + } + + if (is_array($profiler)) { + if (isset($profiler['enabled'])) { + $enabled = (bool) $profiler['enabled']; + } + if (isset($profiler['class'])) { + $profilerClass = $profiler['class']; + } + if (isset($profiler['instance'])) { + $profilerInstance = $profiler['instance']; + } + } else if (!$profilerIsObject) { + $enabled = (bool) $profiler; + } + + if ($profilerInstance === null) { + if (!class_exists($profilerClass)) { + require_once 'Zend/Loader.php'; + Zend_Loader::loadClass($profilerClass); + } + $profilerInstance = new $profilerClass(); + } + + if (!$profilerInstance instanceof Zend_Db_Profiler) { + /** @see Zend_Db_Profiler_Exception */ + require_once 'Zend/Db/Profiler/Exception.php'; + throw new Zend_Db_Profiler_Exception('Class ' . get_class($profilerInstance) . ' does not extend ' + . 'Zend_Db_Profiler'); + } + + if (null !== $enabled) { + $profilerInstance->setEnabled($enabled); + } + + $this->_profiler = $profilerInstance; + + return $this; + } + + + /** + * Returns the profiler for this adapter. + * + * @return Zend_Db_Profiler + */ + public function getProfiler() + { + return $this->_profiler; + } + + /** + * Get the default statement class. + * + * @return string + */ + public function getStatementClass() + { + return $this->_defaultStmtClass; + } + + /** + * Set the default statement class. + * + * @return Zend_Db_Adapter_Abstract Fluent interface + */ + public function setStatementClass($class) + { + $this->_defaultStmtClass = $class; + return $this; + } + + /** + * Prepares and executes an SQL statement with bound data. + * + * @param mixed $sql The SQL statement with placeholders. + * May be a string or Zend_Db_Select. + * @param mixed $bind An array of data to bind to the placeholders. + * @return Zend_Db_Statement_Interface + */ + public function query($sql, $bind = array()) + { + // connect to the database if needed + $this->_connect(); + + // is the $sql a Zend_Db_Select object? + if ($sql instanceof Zend_Db_Select) { + if (empty($bind)) { + $bind = $sql->getBind(); + } + + $sql = $sql->assemble(); + } + + // make sure $bind to an array; + // don't use (array) typecasting because + // because $bind may be a Zend_Db_Expr object + if (!is_array($bind)) { + $bind = array($bind); + } + + // prepare and execute the statement with profiling + $stmt = $this->prepare($sql); + $stmt->execute($bind); + + // return the results embedded in the prepared statement object + $stmt->setFetchMode($this->_fetchMode); + return $stmt; + } + + /** + * Leave autocommit mode and begin a transaction. + * + * @return Zend_Db_Adapter_Abstract + */ + public function beginTransaction() + { + $this->_connect(); + $q = $this->_profiler->queryStart('begin', Zend_Db_Profiler::TRANSACTION); + $this->_beginTransaction(); + $this->_profiler->queryEnd($q); + return $this; + } + + /** + * Commit a transaction and return to autocommit mode. + * + * @return Zend_Db_Adapter_Abstract + */ + public function commit() + { + $this->_connect(); + $q = $this->_profiler->queryStart('commit', Zend_Db_Profiler::TRANSACTION); + $this->_commit(); + $this->_profiler->queryEnd($q); + return $this; + } + + /** + * Roll back a transaction and return to autocommit mode. + * + * @return Zend_Db_Adapter_Abstract + */ + public function rollBack() + { + $this->_connect(); + $q = $this->_profiler->queryStart('rollback', Zend_Db_Profiler::TRANSACTION); + $this->_rollBack(); + $this->_profiler->queryEnd($q); + return $this; + } + + /** + * Inserts a table row with specified data. + * + * @param mixed $table The table to insert data into. + * @param array $bind Column-value pairs. + * @return int The number of affected rows. + */ + public function insert($table, array $bind) + { + // extract and quote col names from the array keys + $cols = array(); + $vals = array(); + foreach ($bind as $col => $val) { + $cols[] = $this->quoteIdentifier($col, true); + if ($val instanceof Zend_Db_Expr) { + $vals[] = $val->__toString(); + unset($bind[$col]); + } else { + $vals[] = '?'; + } + } + + // build the statement + $sql = "INSERT INTO " + . $this->quoteIdentifier($table, true) + . ' (' . implode(', ', $cols) . ') ' + . 'VALUES (' . implode(', ', $vals) . ')'; + + // execute the statement and return the number of affected rows + $stmt = $this->query($sql, array_values($bind)); + $result = $stmt->rowCount(); + return $result; + } + + /** + * Updates table rows with specified data based on a WHERE clause. + * + * @param mixed $table The table to update. + * @param array $bind Column-value pairs. + * @param mixed $where UPDATE WHERE clause(s). + * @return int The number of affected rows. + */ + public function update($table, array $bind, $where = '') + { + /** + * Build "col = ?" pairs for the statement, + * except for Zend_Db_Expr which is treated literally. + */ + $set = array(); + $i = 0; + foreach ($bind as $col => $val) { + if ($val instanceof Zend_Db_Expr) { + $val = $val->__toString(); + unset($bind[$col]); + } else { + if ($this->supportsParameters('positional')) { + $val = '?'; + } else { + if ($this->supportsParameters('named')) { + unset($bind[$col]); + $bind[':'.$col.$i] = $val; + $val = ':'.$col.$i; + $i++; + } else { + /** @see Zend_Db_Adapter_Exception */ + require_once 'Zend/Db/Adapter/Exception.php'; + throw new Zend_Db_Adapter_Exception(get_class($this) ." doesn't support positional or named binding"); + } + } + } + $set[] = $this->quoteIdentifier($col, true) . ' = ' . $val; + } + + $where = $this->_whereExpr($where); + + /** + * Build the UPDATE statement + */ + $sql = "UPDATE " + . $this->quoteIdentifier($table, true) + . ' SET ' . implode(', ', $set) + . (($where) ? " WHERE $where" : ''); + + /** + * Execute the statement and return the number of affected rows + */ + if ($this->supportsParameters('positional')) { + $stmt = $this->query($sql, array_values($bind)); + } else { + $stmt = $this->query($sql, $bind); + } + $result = $stmt->rowCount(); + return $result; + } + + /** + * Deletes table rows based on a WHERE clause. + * + * @param mixed $table The table to update. + * @param mixed $where DELETE WHERE clause(s). + * @return int The number of affected rows. + */ + public function delete($table, $where = '') + { + $where = $this->_whereExpr($where); + + /** + * Build the DELETE statement + */ + $sql = "DELETE FROM " + . $this->quoteIdentifier($table, true) + . (($where) ? " WHERE $where" : ''); + + /** + * Execute the statement and return the number of affected rows + */ + $stmt = $this->query($sql); + $result = $stmt->rowCount(); + return $result; + } + + /** + * Convert an array, string, or Zend_Db_Expr object + * into a string to put in a WHERE clause. + * + * @param mixed $where + * @return string + */ + protected function _whereExpr($where) + { + if (empty($where)) { + return $where; + } + if (!is_array($where)) { + $where = array($where); + } + foreach ($where as $cond => &$term) { + // is $cond an int? (i.e. Not a condition) + if (is_int($cond)) { + // $term is the full condition + if ($term instanceof Zend_Db_Expr) { + $term = $term->__toString(); + } + } else { + // $cond is the condition with placeholder, + // and $term is quoted into the condition + $term = $this->quoteInto($cond, $term); + } + $term = '(' . $term . ')'; + } + + $where = implode(' AND ', $where); + return $where; + } + + /** + * Creates and returns a new Zend_Db_Select object for this adapter. + * + * @return Zend_Db_Select + */ + public function select() + { + return new Zend_Db_Select($this); + } + + /** + * Get the fetch mode. + * + * @return int + */ + public function getFetchMode() + { + return $this->_fetchMode; + } + + /** + * Fetches all SQL result rows as a sequential array. + * Uses the current fetchMode for the adapter. + * + * @param string|Zend_Db_Select $sql An SQL SELECT statement. + * @param mixed $bind Data to bind into SELECT placeholders. + * @param mixed $fetchMode Override current fetch mode. + * @return array + */ + public function fetchAll($sql, $bind = array(), $fetchMode = null) + { + if ($fetchMode === null) { + $fetchMode = $this->_fetchMode; + } + $stmt = $this->query($sql, $bind); + $result = $stmt->fetchAll($fetchMode); + return $result; + } + + /** + * Fetches the first row of the SQL result. + * Uses the current fetchMode for the adapter. + * + * @param string|Zend_Db_Select $sql An SQL SELECT statement. + * @param mixed $bind Data to bind into SELECT placeholders. + * @param mixed $fetchMode Override current fetch mode. + * @return array + */ + public function fetchRow($sql, $bind = array(), $fetchMode = null) + { + if ($fetchMode === null) { + $fetchMode = $this->_fetchMode; + } + $stmt = $this->query($sql, $bind); + $result = $stmt->fetch($fetchMode); + return $result; + } + + /** + * Fetches all SQL result rows as an associative array. + * + * The first column is the key, the entire row array is the + * value. You should construct the query to be sure that + * the first column contains unique values, or else + * rows with duplicate values in the first column will + * overwrite previous data. + * + * @param string|Zend_Db_Select $sql An SQL SELECT statement. + * @param mixed $bind Data to bind into SELECT placeholders. + * @return array + */ + public function fetchAssoc($sql, $bind = array()) + { + $stmt = $this->query($sql, $bind); + $data = array(); + while ($row = $stmt->fetch(Zend_Db::FETCH_ASSOC)) { + $tmp = array_values(array_slice($row, 0, 1)); + $data[$tmp[0]] = $row; + } + return $data; + } + + /** + * Fetches the first column of all SQL result rows as an array. + * + * The first column in each row is used as the array key. + * + * @param string|Zend_Db_Select $sql An SQL SELECT statement. + * @param mixed $bind Data to bind into SELECT placeholders. + * @return array + */ + public function fetchCol($sql, $bind = array()) + { + $stmt = $this->query($sql, $bind); + $result = $stmt->fetchAll(Zend_Db::FETCH_COLUMN, 0); + return $result; + } + + /** + * Fetches all SQL result rows as an array of key-value pairs. + * + * The first column is the key, the second column is the + * value. + * + * @param string|Zend_Db_Select $sql An SQL SELECT statement. + * @param mixed $bind Data to bind into SELECT placeholders. + * @return array + */ + public function fetchPairs($sql, $bind = array()) + { + $stmt = $this->query($sql, $bind); + $data = array(); + while ($row = $stmt->fetch(Zend_Db::FETCH_NUM)) { + $data[$row[0]] = $row[1]; + } + return $data; + } + + /** + * Fetches the first column of the first row of the SQL result. + * + * @param string|Zend_Db_Select $sql An SQL SELECT statement. + * @param mixed $bind Data to bind into SELECT placeholders. + * @return string + */ + public function fetchOne($sql, $bind = array()) + { + $stmt = $this->query($sql, $bind); + $result = $stmt->fetchColumn(0); + return $result; + } + + /** + * Quote a raw string. + * + * @param string $value Raw string + * @return string Quoted string + */ + protected function _quote($value) + { + if (is_int($value)) { + return $value; + } elseif (is_float($value)) { + return sprintf('%F', $value); + } + return "'" . addcslashes($value, "\000\n\r\\'\"\032") . "'"; + } + + /** + * Safely quotes a value for an SQL statement. + * + * If an array is passed as the value, the array values are quoted + * and then returned as a comma-separated string. + * + * @param mixed $value The value to quote. + * @param mixed $type OPTIONAL the SQL datatype name, or constant, or null. + * @return mixed An SQL-safe quoted value (or string of separated values). + */ + public function quote($value, $type = null) + { + $this->_connect(); + + if ($value instanceof Zend_Db_Select) { + return '(' . $value->assemble() . ')'; + } + + if ($value instanceof Zend_Db_Expr) { + return $value->__toString(); + } + + if (is_array($value)) { + foreach ($value as &$val) { + $val = $this->quote($val, $type); + } + return implode(', ', $value); + } + + if ($type !== null && array_key_exists($type = strtoupper($type), $this->_numericDataTypes)) { + $quotedValue = '0'; + switch ($this->_numericDataTypes[$type]) { + case Zend_Db::INT_TYPE: // 32-bit integer + $quotedValue = (string) intval($value); + break; + case Zend_Db::BIGINT_TYPE: // 64-bit integer + // ANSI SQL-style hex literals (e.g. x'[\dA-F]+') + // are not supported here, because these are string + // literals, not numeric literals. + if (preg_match('/^( + [+-]? # optional sign + (?: + 0[Xx][\da-fA-F]+ # ODBC-style hexadecimal + |\d+ # decimal or octal, or MySQL ZEROFILL decimal + (?:[eE][+-]?\d+)? # optional exponent on decimals or octals + ) + )/x', + (string) $value, $matches)) { + $quotedValue = $matches[1]; + } + break; + case Zend_Db::FLOAT_TYPE: // float or decimal + $quotedValue = sprintf('%F', $value); + } + return $quotedValue; + } + + return $this->_quote($value); + } + + /** + * Quotes a value and places into a piece of text at a placeholder. + * + * The placeholder is a question-mark; all placeholders will be replaced + * with the quoted value. For example: + * + * + * $text = "WHERE date < ?"; + * $date = "2005-01-02"; + * $safe = $sql->quoteInto($text, $date); + * // $safe = "WHERE date < '2005-01-02'" + * + * + * @param string $text The text with a placeholder. + * @param mixed $value The value to quote. + * @param string $type OPTIONAL SQL datatype + * @param integer $count OPTIONAL count of placeholders to replace + * @return string An SQL-safe quoted value placed into the original text. + */ + public function quoteInto($text, $value, $type = null, $count = null) + { + if ($count === null) { + return str_replace('?', $this->quote($value, $type), $text); + } else { + while ($count > 0) { + if (strpos($text, '?') !== false) { + $text = substr_replace($text, $this->quote($value, $type), strpos($text, '?'), 1); + } + --$count; + } + return $text; + } + } + + /** + * Quotes an identifier. + * + * Accepts a string representing a qualified indentifier. For Example: + * + * $adapter->quoteIdentifier('myschema.mytable') + * + * Returns: "myschema"."mytable" + * + * Or, an array of one or more identifiers that may form a qualified identifier: + * + * $adapter->quoteIdentifier(array('myschema','my.table')) + * + * Returns: "myschema"."my.table" + * + * The actual quote character surrounding the identifiers may vary depending on + * the adapter. + * + * @param string|array|Zend_Db_Expr $ident The identifier. + * @param boolean $auto If true, heed the AUTO_QUOTE_IDENTIFIERS config option. + * @return string The quoted identifier. + */ + public function quoteIdentifier($ident, $auto=false) + { + return $this->_quoteIdentifierAs($ident, null, $auto); + } + + /** + * Quote a column identifier and alias. + * + * @param string|array|Zend_Db_Expr $ident The identifier or expression. + * @param string $alias An alias for the column. + * @param boolean $auto If true, heed the AUTO_QUOTE_IDENTIFIERS config option. + * @return string The quoted identifier and alias. + */ + public function quoteColumnAs($ident, $alias, $auto=false) + { + return $this->_quoteIdentifierAs($ident, $alias, $auto); + } + + /** + * Quote a table identifier and alias. + * + * @param string|array|Zend_Db_Expr $ident The identifier or expression. + * @param string $alias An alias for the table. + * @param boolean $auto If true, heed the AUTO_QUOTE_IDENTIFIERS config option. + * @return string The quoted identifier and alias. + */ + public function quoteTableAs($ident, $alias = null, $auto = false) + { + return $this->_quoteIdentifierAs($ident, $alias, $auto); + } + + /** + * Quote an identifier and an optional alias. + * + * @param string|array|Zend_Db_Expr $ident The identifier or expression. + * @param string $alias An optional alias. + * @param boolean $auto If true, heed the AUTO_QUOTE_IDENTIFIERS config option. + * @param string $as The string to add between the identifier/expression and the alias. + * @return string The quoted identifier and alias. + */ + protected function _quoteIdentifierAs($ident, $alias = null, $auto = false, $as = ' AS ') + { + if ($ident instanceof Zend_Db_Expr) { + $quoted = $ident->__toString(); + } elseif ($ident instanceof Zend_Db_Select) { + $quoted = '(' . $ident->assemble() . ')'; + } else { + if (is_string($ident)) { + $ident = explode('.', $ident); + } + if (is_array($ident)) { + $segments = array(); + foreach ($ident as $segment) { + if ($segment instanceof Zend_Db_Expr) { + $segments[] = $segment->__toString(); + } else { + $segments[] = $this->_quoteIdentifier($segment, $auto); + } + } + if ($alias !== null && end($ident) == $alias) { + $alias = null; + } + $quoted = implode('.', $segments); + } else { + $quoted = $this->_quoteIdentifier($ident, $auto); + } + } + if ($alias !== null) { + $quoted .= $as . $this->_quoteIdentifier($alias, $auto); + } + return $quoted; + } + + /** + * Quote an identifier. + * + * @param string $value The identifier or expression. + * @param boolean $auto If true, heed the AUTO_QUOTE_IDENTIFIERS config option. + * @return string The quoted identifier and alias. + */ + protected function _quoteIdentifier($value, $auto=false) + { + if ($auto === false || $this->_autoQuoteIdentifiers === true) { + $q = $this->getQuoteIdentifierSymbol(); + return ($q . str_replace("$q", "$q$q", $value) . $q); + } + return $value; + } + + /** + * Returns the symbol the adapter uses for delimited identifiers. + * + * @return string + */ + public function getQuoteIdentifierSymbol() + { + return '"'; + } + + /** + * Return the most recent value from the specified sequence in the database. + * This is supported only on RDBMS brands that support sequences + * (e.g. Oracle, PostgreSQL, DB2). Other RDBMS brands return null. + * + * @param string $sequenceName + * @return string + */ + public function lastSequenceId($sequenceName) + { + return null; + } + + /** + * Generate a new value from the specified sequence in the database, and return it. + * This is supported only on RDBMS brands that support sequences + * (e.g. Oracle, PostgreSQL, DB2). Other RDBMS brands return null. + * + * @param string $sequenceName + * @return string + */ + public function nextSequenceId($sequenceName) + { + return null; + } + + /** + * Helper method to change the case of the strings used + * when returning result sets in FETCH_ASSOC and FETCH_BOTH + * modes. + * + * This is not intended to be used by application code, + * but the method must be public so the Statement class + * can invoke it. + * + * @param string $key + * @return string + */ + public function foldCase($key) + { + switch ($this->_caseFolding) { + case Zend_Db::CASE_LOWER: + $value = strtolower((string) $key); + break; + case Zend_Db::CASE_UPPER: + $value = strtoupper((string) $key); + break; + case Zend_Db::CASE_NATURAL: + default: + $value = (string) $key; + } + return $value; + } + + /** + * called when object is getting serialized + * This disconnects the DB object that cant be serialized + * + * @throws Zend_Db_Adapter_Exception + * @return array + */ + public function __sleep() + { + if ($this->_allowSerialization == false) { + /** @see Zend_Db_Adapter_Exception */ + require_once 'Zend/Db/Adapter/Exception.php'; + throw new Zend_Db_Adapter_Exception(get_class($this) ." is not allowed to be serialized"); + } + $this->_connection = false; + return array_keys(array_diff_key(get_object_vars($this), array('_connection'=>false))); + } + + /** + * called when object is getting unserialized + * + * @return void + */ + public function __wakeup() + { + if ($this->_autoReconnectOnUnserialize == true) { + $this->getConnection(); + } + } + + /** + * Abstract Methods + */ + + /** + * Returns a list of the tables in the database. + * + * @return array + */ + abstract public function listTables(); + + /** + * Returns the column descriptions for a table. + * + * The return value is an associative array keyed by the column name, + * as returned by the RDBMS. + * + * The value of each array element is an associative array + * with the following keys: + * + * SCHEMA_NAME => string; name of database or schema + * TABLE_NAME => string; + * COLUMN_NAME => string; column name + * COLUMN_POSITION => number; ordinal position of column in table + * DATA_TYPE => string; SQL datatype name of column + * DEFAULT => string; default expression of column, null if none + * NULLABLE => boolean; true if column can have nulls + * LENGTH => number; length of CHAR/VARCHAR + * SCALE => number; scale of NUMERIC/DECIMAL + * PRECISION => number; precision of NUMERIC/DECIMAL + * UNSIGNED => boolean; unsigned property of an integer type + * PRIMARY => boolean; true if column is part of the primary key + * PRIMARY_POSITION => integer; position of column in primary key + * + * @param string $tableName + * @param string $schemaName OPTIONAL + * @return array + */ + abstract public function describeTable($tableName, $schemaName = null); + + /** + * Creates a connection to the database. + * + * @return void + */ + abstract protected function _connect(); + + /** + * Test if a connection is active + * + * @return boolean + */ + abstract public function isConnected(); + + /** + * Force the connection to close. + * + * @return void + */ + abstract public function closeConnection(); + + /** + * Prepare a statement and return a PDOStatement-like object. + * + * @param string|Zend_Db_Select $sql SQL query + * @return Zend_Db_Statement|PDOStatement + */ + abstract public function prepare($sql); + + /** + * Gets the last ID generated automatically by an IDENTITY/AUTOINCREMENT column. + * + * As a convention, on RDBMS brands that support sequences + * (e.g. Oracle, PostgreSQL, DB2), this method forms the name of a sequence + * from the arguments and returns the last id generated by that sequence. + * On RDBMS brands that support IDENTITY/AUTOINCREMENT columns, this method + * returns the last value generated for such a column, and the table name + * argument is disregarded. + * + * @param string $tableName OPTIONAL Name of table. + * @param string $primaryKey OPTIONAL Name of primary key column. + * @return string + */ + abstract public function lastInsertId($tableName = null, $primaryKey = null); + + /** + * Begin a transaction. + */ + abstract protected function _beginTransaction(); + + /** + * Commit a transaction. + */ + abstract protected function _commit(); + + /** + * Roll-back a transaction. + */ + abstract protected function _rollBack(); + + /** + * Set the fetch mode. + * + * @param integer $mode + * @return void + * @throws Zend_Db_Adapter_Exception + */ + abstract public function setFetchMode($mode); + + /** + * Adds an adapter-specific LIMIT clause to the SELECT statement. + * + * @param mixed $sql + * @param integer $count + * @param integer $offset + * @return string + */ + abstract public function limit($sql, $count, $offset = 0); + + /** + * Check if the adapter supports real SQL parameters. + * + * @param string $type 'positional' or 'named' + * @return bool + */ + abstract public function supportsParameters($type); + + /** + * Retrieve server version in PHP style + * + * @return string + */ + abstract public function getServerVersion(); +} diff --git a/library/Zend/Db/Adapter/Db2.php b/library/Zend/Db/Adapter/Db2.php new file mode 100644 index 000000000..7181a45fa --- /dev/null +++ b/library/Zend/Db/Adapter/Db2.php @@ -0,0 +1,832 @@ + (string) Connect to the database as this username. + * password => (string) Password associated with the username. + * host => (string) What host to connect to (default 127.0.0.1) + * dbname => (string) The name of the database to user + * protocol => (string) Protocol to use, defaults to "TCPIP" + * port => (integer) Port number to use for TCP/IP if protocol is "TCPIP" + * persistent => (boolean) Set TRUE to use a persistent connection (db2_pconnect) + * os => (string) This should be set to 'i5' if the db is on an os400/i5 + * schema => (string) The default schema the connection should use + * + * @var array + */ + protected $_config = array( + 'dbname' => null, + 'username' => null, + 'password' => null, + 'host' => 'localhost', + 'port' => '50000', + 'protocol' => 'TCPIP', + 'persistent' => false, + 'os' => null, + 'schema' => null + ); + + /** + * Execution mode + * + * @var int execution flag (DB2_AUTOCOMMIT_ON or DB2_AUTOCOMMIT_OFF) + */ + protected $_execute_mode = DB2_AUTOCOMMIT_ON; + + /** + * Default class name for a DB statement. + * + * @var string + */ + protected $_defaultStmtClass = 'Zend_Db_Statement_Db2'; + protected $_isI5 = false; + + /** + * Keys are UPPERCASE SQL datatypes or the constants + * Zend_Db::INT_TYPE, Zend_Db::BIGINT_TYPE, or Zend_Db::FLOAT_TYPE. + * + * Values are: + * 0 = 32-bit integer + * 1 = 64-bit integer + * 2 = float or decimal + * + * @var array Associative array of datatypes to values 0, 1, or 2. + */ + protected $_numericDataTypes = array( + Zend_Db::INT_TYPE => Zend_Db::INT_TYPE, + Zend_Db::BIGINT_TYPE => Zend_Db::BIGINT_TYPE, + Zend_Db::FLOAT_TYPE => Zend_Db::FLOAT_TYPE, + 'INTEGER' => Zend_Db::INT_TYPE, + 'SMALLINT' => Zend_Db::INT_TYPE, + 'BIGINT' => Zend_Db::BIGINT_TYPE, + 'DECIMAL' => Zend_Db::FLOAT_TYPE, + 'NUMERIC' => Zend_Db::FLOAT_TYPE + ); + + /** + * Creates a connection resource. + * + * @return void + */ + protected function _connect() + { + if (is_resource($this->_connection)) { + // connection already exists + return; + } + + if (!extension_loaded('ibm_db2')) { + /** + * @see Zend_Db_Adapter_Db2_Exception + */ + require_once 'Zend/Db/Adapter/Db2/Exception.php'; + throw new Zend_Db_Adapter_Db2_Exception('The IBM DB2 extension is required for this adapter but the extension is not loaded'); + } + + $this->_determineI5(); + if ($this->_config['persistent']) { + // use persistent connection + $conn_func_name = 'db2_pconnect'; + } else { + // use "normal" connection + $conn_func_name = 'db2_connect'; + } + + if (!isset($this->_config['driver_options']['autocommit'])) { + // set execution mode + $this->_config['driver_options']['autocommit'] = &$this->_execute_mode; + } + + if (isset($this->_config['options'][Zend_Db::CASE_FOLDING])) { + $caseAttrMap = array( + Zend_Db::CASE_NATURAL => DB2_CASE_NATURAL, + Zend_Db::CASE_UPPER => DB2_CASE_UPPER, + Zend_Db::CASE_LOWER => DB2_CASE_LOWER + ); + $this->_config['driver_options']['DB2_ATTR_CASE'] = $caseAttrMap[$this->_config['options'][Zend_Db::CASE_FOLDING]]; + } + + if ($this->_config['host'] !== 'localhost' && !$this->_isI5) { + // if the host isn't localhost, use extended connection params + $dbname = 'DRIVER={IBM DB2 ODBC DRIVER}' . + ';DATABASE=' . $this->_config['dbname'] . + ';HOSTNAME=' . $this->_config['host'] . + ';PORT=' . $this->_config['port'] . + ';PROTOCOL=' . $this->_config['protocol'] . + ';UID=' . $this->_config['username'] . + ';PWD=' . $this->_config['password'] .';'; + $this->_connection = $conn_func_name( + $dbname, + null, + null, + $this->_config['driver_options'] + ); + } else { + // host is localhost, so use standard connection params + $this->_connection = $conn_func_name( + $this->_config['dbname'], + $this->_config['username'], + $this->_config['password'], + $this->_config['driver_options'] + ); + } + + // check the connection + if (!$this->_connection) { + /** + * @see Zend_Db_Adapter_Db2_Exception + */ + require_once 'Zend/Db/Adapter/Db2/Exception.php'; + throw new Zend_Db_Adapter_Db2_Exception(db2_conn_errormsg(), db2_conn_error()); + } + } + + /** + * Test if a connection is active + * + * @return boolean + */ + public function isConnected() + { + return ((bool) (is_resource($this->_connection) + && get_resource_type($this->_connection) == 'DB2 Connection')); + } + + /** + * Force the connection to close. + * + * @return void + */ + public function closeConnection() + { + if ($this->isConnected()) { + db2_close($this->_connection); + } + $this->_connection = null; + } + + /** + * Returns an SQL statement for preparation. + * + * @param string $sql The SQL statement with placeholders. + * @return Zend_Db_Statement_Db2 + */ + public function prepare($sql) + { + $this->_connect(); + $stmtClass = $this->_defaultStmtClass; + if (!class_exists($stmtClass)) { + require_once 'Zend/Loader.php'; + Zend_Loader::loadClass($stmtClass); + } + $stmt = new $stmtClass($this, $sql); + $stmt->setFetchMode($this->_fetchMode); + return $stmt; + } + + /** + * Gets the execution mode + * + * @return int the execution mode (DB2_AUTOCOMMIT_ON or DB2_AUTOCOMMIT_OFF) + */ + public function _getExecuteMode() + { + return $this->_execute_mode; + } + + /** + * @param integer $mode + * @return void + */ + public function _setExecuteMode($mode) + { + switch ($mode) { + case DB2_AUTOCOMMIT_OFF: + case DB2_AUTOCOMMIT_ON: + $this->_execute_mode = $mode; + db2_autocommit($this->_connection, $mode); + break; + default: + /** + * @see Zend_Db_Adapter_Db2_Exception + */ + require_once 'Zend/Db/Adapter/Db2/Exception.php'; + throw new Zend_Db_Adapter_Db2_Exception("execution mode not supported"); + break; + } + } + + /** + * Quote a raw string. + * + * @param string $value Raw string + * @return string Quoted string + */ + protected function _quote($value) + { + if (is_int($value) || is_float($value)) { + return $value; + } + /** + * Use db2_escape_string() if it is present in the IBM DB2 extension. + * But some supported versions of PHP do not include this function, + * so fall back to default quoting in the parent class. + */ + if (function_exists('db2_escape_string')) { + return "'" . db2_escape_string($value) . "'"; + } + return parent::_quote($value); + } + + /** + * @return string + */ + public function getQuoteIdentifierSymbol() + { + $this->_connect(); + $info = db2_server_info($this->_connection); + if ($info) { + $identQuote = $info->IDENTIFIER_QUOTE_CHAR; + } else { + // db2_server_info() does not return result on some i5 OS version + if ($this->_isI5) { + $identQuote ="'"; + } + } + return $identQuote; + } + + /** + * Returns a list of the tables in the database. + * @param string $schema OPTIONAL + * @return array + */ + public function listTables($schema = null) + { + $this->_connect(); + + if ($schema === null && $this->_config['schema'] != null) { + $schema = $this->_config['schema']; + } + + $tables = array(); + + if (!$this->_isI5) { + if ($schema) { + $stmt = db2_tables($this->_connection, null, $schema); + } else { + $stmt = db2_tables($this->_connection); + } + while ($row = db2_fetch_assoc($stmt)) { + $tables[] = $row['TABLE_NAME']; + } + } else { + $tables = $this->_i5listTables($schema); + } + + return $tables; + } + + + /** + * Returns the column descriptions for a table. + * + * The return value is an associative array keyed by the column name, + * as returned by the RDBMS. + * + * The value of each array element is an associative array + * with the following keys: + * + * SCHEMA_NAME => string; name of database or schema + * TABLE_NAME => string; + * COLUMN_NAME => string; column name + * COLUMN_POSITION => number; ordinal position of column in table + * DATA_TYPE => string; SQL datatype name of column + * DEFAULT => string; default expression of column, null if none + * NULLABLE => boolean; true if column can have nulls + * LENGTH => number; length of CHAR/VARCHAR + * SCALE => number; scale of NUMERIC/DECIMAL + * PRECISION => number; precision of NUMERIC/DECIMAL + * UNSIGNED => boolean; unsigned property of an integer type + * DB2 not supports UNSIGNED integer. + * PRIMARY => boolean; true if column is part of the primary key + * PRIMARY_POSITION => integer; position of column in primary key + * IDENTITY => integer; true if column is auto-generated with unique values + * + * @param string $tableName + * @param string $schemaName OPTIONAL + * @return array + */ + public function describeTable($tableName, $schemaName = null) + { + // Ensure the connection is made so that _isI5 is set + $this->_connect(); + + if ($schemaName === null && $this->_config['schema'] != null) { + $schemaName = $this->_config['schema']; + } + + if (!$this->_isI5) { + + $sql = "SELECT DISTINCT c.tabschema, c.tabname, c.colname, c.colno, + c.typename, c.default, c.nulls, c.length, c.scale, + c.identity, tc.type AS tabconsttype, k.colseq + FROM syscat.columns c + LEFT JOIN (syscat.keycoluse k JOIN syscat.tabconst tc + ON (k.tabschema = tc.tabschema + AND k.tabname = tc.tabname + AND tc.type = 'P')) + ON (c.tabschema = k.tabschema + AND c.tabname = k.tabname + AND c.colname = k.colname) + WHERE " + . $this->quoteInto('UPPER(c.tabname) = UPPER(?)', $tableName); + + if ($schemaName) { + $sql .= $this->quoteInto(' AND UPPER(c.tabschema) = UPPER(?)', $schemaName); + } + + $sql .= " ORDER BY c.colno"; + + } else { + + // DB2 On I5 specific query + $sql = "SELECT DISTINCT C.TABLE_SCHEMA, C.TABLE_NAME, C.COLUMN_NAME, C.ORDINAL_POSITION, + C.DATA_TYPE, C.COLUMN_DEFAULT, C.NULLS ,C.LENGTH, C.SCALE, LEFT(C.IDENTITY,1), + LEFT(tc.TYPE, 1) AS tabconsttype, k.COLSEQ + FROM QSYS2.SYSCOLUMNS C + LEFT JOIN (QSYS2.syskeycst k JOIN QSYS2.SYSCST tc + ON (k.TABLE_SCHEMA = tc.TABLE_SCHEMA + AND k.TABLE_NAME = tc.TABLE_NAME + AND LEFT(tc.type,1) = 'P')) + ON (C.TABLE_SCHEMA = k.TABLE_SCHEMA + AND C.TABLE_NAME = k.TABLE_NAME + AND C.COLUMN_NAME = k.COLUMN_NAME) + WHERE " + . $this->quoteInto('UPPER(C.TABLE_NAME) = UPPER(?)', $tableName); + + if ($schemaName) { + $sql .= $this->quoteInto(' AND UPPER(C.TABLE_SCHEMA) = UPPER(?)', $schemaName); + } + + $sql .= " ORDER BY C.ORDINAL_POSITION FOR FETCH ONLY"; + } + + $desc = array(); + $stmt = $this->query($sql); + + /** + * To avoid case issues, fetch using FETCH_NUM + */ + $result = $stmt->fetchAll(Zend_Db::FETCH_NUM); + + /** + * The ordering of columns is defined by the query so we can map + * to variables to improve readability + */ + $tabschema = 0; + $tabname = 1; + $colname = 2; + $colno = 3; + $typename = 4; + $default = 5; + $nulls = 6; + $length = 7; + $scale = 8; + $identityCol = 9; + $tabconstType = 10; + $colseq = 11; + + foreach ($result as $key => $row) { + list ($primary, $primaryPosition, $identity) = array(false, null, false); + if ($row[$tabconstType] == 'P') { + $primary = true; + $primaryPosition = $row[$colseq]; + } + /** + * In IBM DB2, an column can be IDENTITY + * even if it is not part of the PRIMARY KEY. + */ + if ($row[$identityCol] == 'Y') { + $identity = true; + } + + // only colname needs to be case adjusted + $desc[$this->foldCase($row[$colname])] = array( + 'SCHEMA_NAME' => $this->foldCase($row[$tabschema]), + 'TABLE_NAME' => $this->foldCase($row[$tabname]), + 'COLUMN_NAME' => $this->foldCase($row[$colname]), + 'COLUMN_POSITION' => (!$this->_isI5) ? $row[$colno]+1 : $row[$colno], + 'DATA_TYPE' => $row[$typename], + 'DEFAULT' => $row[$default], + 'NULLABLE' => (bool) ($row[$nulls] == 'Y'), + 'LENGTH' => $row[$length], + 'SCALE' => $row[$scale], + 'PRECISION' => ($row[$typename] == 'DECIMAL' ? $row[$length] : 0), + 'UNSIGNED' => false, + 'PRIMARY' => $primary, + 'PRIMARY_POSITION' => $primaryPosition, + 'IDENTITY' => $identity + ); + } + + return $desc; + } + + /** + * Return the most recent value from the specified sequence in the database. + * This is supported only on RDBMS brands that support sequences + * (e.g. Oracle, PostgreSQL, DB2). Other RDBMS brands return null. + * + * @param string $sequenceName + * @return string + */ + public function lastSequenceId($sequenceName) + { + $this->_connect(); + + if (!$this->_isI5) { + $quotedSequenceName = $this->quoteIdentifier($sequenceName, true); + $sql = 'SELECT PREVVAL FOR ' . $quotedSequenceName . ' AS VAL FROM SYSIBM.SYSDUMMY1'; + } else { + $quotedSequenceName = $sequenceName; + $sql = 'SELECT PREVVAL FOR ' . $this->quoteIdentifier($sequenceName, true) . ' AS VAL FROM QSYS2.QSQPTABL'; + } + + $value = $this->fetchOne($sql); + return (string) $value; + } + + /** + * Generate a new value from the specified sequence in the database, and return it. + * This is supported only on RDBMS brands that support sequences + * (e.g. Oracle, PostgreSQL, DB2). Other RDBMS brands return null. + * + * @param string $sequenceName + * @return string + */ + public function nextSequenceId($sequenceName) + { + $this->_connect(); + $sql = 'SELECT NEXTVAL FOR '.$this->quoteIdentifier($sequenceName, true).' AS VAL FROM SYSIBM.SYSDUMMY1'; + $value = $this->fetchOne($sql); + return (string) $value; + } + + /** + * Gets the last ID generated automatically by an IDENTITY/AUTOINCREMENT column. + * + * As a convention, on RDBMS brands that support sequences + * (e.g. Oracle, PostgreSQL, DB2), this method forms the name of a sequence + * from the arguments and returns the last id generated by that sequence. + * On RDBMS brands that support IDENTITY/AUTOINCREMENT columns, this method + * returns the last value generated for such a column, and the table name + * argument is disregarded. + * + * The IDENTITY_VAL_LOCAL() function gives the last generated identity value + * in the current process, even if it was for a GENERATED column. + * + * @param string $tableName OPTIONAL + * @param string $primaryKey OPTIONAL + * @param string $idType OPTIONAL used for i5 platform to define sequence/idenity unique value + * @return string + */ + + public function lastInsertId($tableName = null, $primaryKey = null, $idType = null) + { + $this->_connect(); + + if ($this->_isI5) { + return (string) $this->_i5LastInsertId($tableName, $idType); + } + + if ($tableName !== null) { + $sequenceName = $tableName; + if ($primaryKey) { + $sequenceName .= "_$primaryKey"; + } + $sequenceName .= '_seq'; + return $this->lastSequenceId($sequenceName); + } + + $sql = 'SELECT IDENTITY_VAL_LOCAL() AS VAL FROM SYSIBM.SYSDUMMY1'; + $value = $this->fetchOne($sql); + return (string) $value; + } + + /** + * Begin a transaction. + * + * @return void + */ + protected function _beginTransaction() + { + $this->_setExecuteMode(DB2_AUTOCOMMIT_OFF); + } + + /** + * Commit a transaction. + * + * @return void + */ + protected function _commit() + { + if (!db2_commit($this->_connection)) { + /** + * @see Zend_Db_Adapter_Db2_Exception + */ + require_once 'Zend/Db/Adapter/Db2/Exception.php'; + throw new Zend_Db_Adapter_Db2_Exception( + db2_conn_errormsg($this->_connection), + db2_conn_error($this->_connection)); + } + + $this->_setExecuteMode(DB2_AUTOCOMMIT_ON); + } + + /** + * Rollback a transaction. + * + * @return void + */ + protected function _rollBack() + { + if (!db2_rollback($this->_connection)) { + /** + * @see Zend_Db_Adapter_Db2_Exception + */ + require_once 'Zend/Db/Adapter/Db2/Exception.php'; + throw new Zend_Db_Adapter_Db2_Exception( + db2_conn_errormsg($this->_connection), + db2_conn_error($this->_connection)); + } + $this->_setExecuteMode(DB2_AUTOCOMMIT_ON); + } + + /** + * Set the fetch mode. + * + * @param integer $mode + * @return void + * @throws Zend_Db_Adapter_Db2_Exception + */ + public function setFetchMode($mode) + { + switch ($mode) { + case Zend_Db::FETCH_NUM: // seq array + case Zend_Db::FETCH_ASSOC: // assoc array + case Zend_Db::FETCH_BOTH: // seq+assoc array + case Zend_Db::FETCH_OBJ: // object + $this->_fetchMode = $mode; + break; + case Zend_Db::FETCH_BOUND: // bound to PHP variable + /** + * @see Zend_Db_Adapter_Db2_Exception + */ + require_once 'Zend/Db/Adapter/Db2/Exception.php'; + throw new Zend_Db_Adapter_Db2_Exception('FETCH_BOUND is not supported yet'); + break; + default: + /** + * @see Zend_Db_Adapter_Db2_Exception + */ + require_once 'Zend/Db/Adapter/Db2/Exception.php'; + throw new Zend_Db_Adapter_Db2_Exception("Invalid fetch mode '$mode' specified"); + break; + } + } + + /** + * Adds an adapter-specific LIMIT clause to the SELECT statement. + * + * @param string $sql + * @param integer $count + * @param integer $offset OPTIONAL + * @return string + */ + public function limit($sql, $count, $offset = 0) + { + $count = intval($count); + if ($count <= 0) { + /** + * @see Zend_Db_Adapter_Db2_Exception + */ + require_once 'Zend/Db/Adapter/Db2/Exception.php'; + throw new Zend_Db_Adapter_Db2_Exception("LIMIT argument count=$count is not valid"); + } + + $offset = intval($offset); + if ($offset < 0) { + /** + * @see Zend_Db_Adapter_Db2_Exception + */ + require_once 'Zend/Db/Adapter/Db2/Exception.php'; + throw new Zend_Db_Adapter_Db2_Exception("LIMIT argument offset=$offset is not valid"); + } + + if ($offset == 0) { + $limit_sql = $sql . " FETCH FIRST $count ROWS ONLY"; + return $limit_sql; + } + + /** + * DB2 does not implement the LIMIT clause as some RDBMS do. + * We have to simulate it with subqueries and ROWNUM. + * Unfortunately because we use the column wildcard "*", + * this puts an extra column into the query result set. + */ + $limit_sql = "SELECT z2.* + FROM ( + SELECT ROW_NUMBER() OVER() AS \"ZEND_DB_ROWNUM\", z1.* + FROM ( + " . $sql . " + ) z1 + ) z2 + WHERE z2.zend_db_rownum BETWEEN " . ($offset+1) . " AND " . ($offset+$count); + return $limit_sql; + } + + /** + * Check if the adapter supports real SQL parameters. + * + * @param string $type 'positional' or 'named' + * @return bool + */ + public function supportsParameters($type) + { + if ($type == 'positional') { + return true; + } + + // if its 'named' or anything else + return false; + } + + /** + * Retrieve server version in PHP style + * + * @return string + */ + public function getServerVersion() + { + $this->_connect(); + $server_info = db2_server_info($this->_connection); + if ($server_info !== false) { + $version = $server_info->DBMS_VER; + if ($this->_isI5) { + $version = (int) substr($version, 0, 2) . '.' . (int) substr($version, 2, 2) . '.' . (int) substr($version, 4); + } + return $version; + } else { + return null; + } + } + + /** + * Return whether or not this is running on i5 + * + * @return bool + */ + public function isI5() + { + if ($this->_isI5 === null) { + $this->_determineI5(); + } + + return (bool) $this->_isI5; + } + + /** + * Check the connection parameters according to verify + * type of used OS + * + * @return void + */ + protected function _determineI5() + { + // first us the compiled flag. + $this->_isI5 = (php_uname('s') == 'OS400') ? true : false; + + // if this is set, then us it + if (isset($this->_config['os'])){ + if (strtolower($this->_config['os']) === 'i5') { + $this->_isI5 = true; + } else { + // any other value passed in, its null + $this->_isI5 = false; + } + } + + } + + /** + * Db2 On I5 specific method + * + * Returns a list of the tables in the database . + * Used only for DB2/400. + * + * @return array + */ + protected function _i5listTables($schema = null) + { + //list of i5 libraries. + $tables = array(); + if ($schema) { + $tablesStatement = db2_tables($this->_connection, null, $schema); + while ($rowTables = db2_fetch_assoc($tablesStatement) ) { + if ($rowTables['TABLE_NAME'] !== null) { + $tables[] = $rowTables['TABLE_NAME']; + } + } + } else { + $schemaStatement = db2_tables($this->_connection); + while ($schema = db2_fetch_assoc($schemaStatement)) { + if ($schema['TABLE_SCHEM'] !== null) { + // list of the tables which belongs to the selected library + $tablesStatement = db2_tables($this->_connection, NULL, $schema['TABLE_SCHEM']); + if (is_resource($tablesStatement)) { + while ($rowTables = db2_fetch_assoc($tablesStatement) ) { + if ($rowTables['TABLE_NAME'] !== null) { + $tables[] = $rowTables['TABLE_NAME']; + } + } + } + } + } + } + + return $tables; + } + + protected function _i5LastInsertId($objectName = null, $idType = null) + { + + if ($objectName === null) { + $sql = 'SELECT IDENTITY_VAL_LOCAL() AS VAL FROM QSYS2.QSQPTABL'; + $value = $this->fetchOne($sql); + return $value; + } + + if (strtoupper($idType) === 'S'){ + //check i5_lib option + $sequenceName = $objectName; + return $this->lastSequenceId($sequenceName); + } + + //returns last identity value for the specified table + //if (strtoupper($idType) === 'I') { + $tableName = $objectName; + return $this->fetchOne('SELECT IDENTITY_VAL_LOCAL() from ' . $this->quoteIdentifier($tableName)); + } + +} + + diff --git a/library/Zend/Db/Adapter/Db2/Exception.php b/library/Zend/Db/Adapter/Db2/Exception.php new file mode 100644 index 000000000..2ee356d40 --- /dev/null +++ b/library/Zend/Db/Adapter/Db2/Exception.php @@ -0,0 +1,45 @@ +getCode(); + } + parent::__construct($message, $code, $e); + } + + public function hasChainedException() + { + return ($this->_previous !== null); + } + + public function getChainedException() + { + return $this->getPrevious(); + } + +} diff --git a/library/Zend/Db/Adapter/Mysqli.php b/library/Zend/Db/Adapter/Mysqli.php new file mode 100644 index 000000000..a92a00fef --- /dev/null +++ b/library/Zend/Db/Adapter/Mysqli.php @@ -0,0 +1,549 @@ + Zend_Db::INT_TYPE, + Zend_Db::BIGINT_TYPE => Zend_Db::BIGINT_TYPE, + Zend_Db::FLOAT_TYPE => Zend_Db::FLOAT_TYPE, + 'INT' => Zend_Db::INT_TYPE, + 'INTEGER' => Zend_Db::INT_TYPE, + 'MEDIUMINT' => Zend_Db::INT_TYPE, + 'SMALLINT' => Zend_Db::INT_TYPE, + 'TINYINT' => Zend_Db::INT_TYPE, + 'BIGINT' => Zend_Db::BIGINT_TYPE, + 'SERIAL' => Zend_Db::BIGINT_TYPE, + 'DEC' => Zend_Db::FLOAT_TYPE, + 'DECIMAL' => Zend_Db::FLOAT_TYPE, + 'DOUBLE' => Zend_Db::FLOAT_TYPE, + 'DOUBLE PRECISION' => Zend_Db::FLOAT_TYPE, + 'FIXED' => Zend_Db::FLOAT_TYPE, + 'FLOAT' => Zend_Db::FLOAT_TYPE + ); + + /** + * @var Zend_Db_Statement_Mysqli + */ + protected $_stmt = null; + + /** + * Default class name for a DB statement. + * + * @var string + */ + protected $_defaultStmtClass = 'Zend_Db_Statement_Mysqli'; + + /** + * Quote a raw string. + * + * @param mixed $value Raw string + * + * @return string Quoted string + */ + protected function _quote($value) + { + if (is_int($value) || is_float($value)) { + return $value; + } + $this->_connect(); + return "'" . $this->_connection->real_escape_string($value) . "'"; + } + + /** + * Returns the symbol the adapter uses for delimiting identifiers. + * + * @return string + */ + public function getQuoteIdentifierSymbol() + { + return "`"; + } + + /** + * Returns a list of the tables in the database. + * + * @return array + */ + public function listTables() + { + $result = array(); + // Use mysqli extension API, because SHOW doesn't work + // well as a prepared statement on MySQL 4.1. + $sql = 'SHOW TABLES'; + if ($queryResult = $this->getConnection()->query($sql)) { + while ($row = $queryResult->fetch_row()) { + $result[] = $row[0]; + } + $queryResult->close(); + } else { + /** + * @see Zend_Db_Adapter_Mysqli_Exception + */ + require_once 'Zend/Db/Adapter/Mysqli/Exception.php'; + throw new Zend_Db_Adapter_Mysqli_Exception($this->getConnection()->error); + } + return $result; + } + + /** + * Returns the column descriptions for a table. + * + * The return value is an associative array keyed by the column name, + * as returned by the RDBMS. + * + * The value of each array element is an associative array + * with the following keys: + * + * SCHEMA_NAME => string; name of database or schema + * TABLE_NAME => string; + * COLUMN_NAME => string; column name + * COLUMN_POSITION => number; ordinal position of column in table + * DATA_TYPE => string; SQL datatype name of column + * DEFAULT => string; default expression of column, null if none + * NULLABLE => boolean; true if column can have nulls + * LENGTH => number; length of CHAR/VARCHAR + * SCALE => number; scale of NUMERIC/DECIMAL + * PRECISION => number; precision of NUMERIC/DECIMAL + * UNSIGNED => boolean; unsigned property of an integer type + * PRIMARY => boolean; true if column is part of the primary key + * PRIMARY_POSITION => integer; position of column in primary key + * IDENTITY => integer; true if column is auto-generated with unique values + * + * @param string $tableName + * @param string $schemaName OPTIONAL + * @return array + */ + public function describeTable($tableName, $schemaName = null) + { + /** + * @todo use INFORMATION_SCHEMA someday when + * MySQL's implementation isn't too slow. + */ + + if ($schemaName) { + $sql = 'DESCRIBE ' . $this->quoteIdentifier("$schemaName.$tableName", true); + } else { + $sql = 'DESCRIBE ' . $this->quoteIdentifier($tableName, true); + } + + /** + * Use mysqli extension API, because DESCRIBE doesn't work + * well as a prepared statement on MySQL 4.1. + */ + if ($queryResult = $this->getConnection()->query($sql)) { + while ($row = $queryResult->fetch_assoc()) { + $result[] = $row; + } + $queryResult->close(); + } else { + /** + * @see Zend_Db_Adapter_Mysqli_Exception + */ + require_once 'Zend/Db/Adapter/Mysqli/Exception.php'; + throw new Zend_Db_Adapter_Mysqli_Exception($this->getConnection()->error); + } + + $desc = array(); + + $row_defaults = array( + 'Length' => null, + 'Scale' => null, + 'Precision' => null, + 'Unsigned' => null, + 'Primary' => false, + 'PrimaryPosition' => null, + 'Identity' => false + ); + $i = 1; + $p = 1; + foreach ($result as $key => $row) { + $row = array_merge($row_defaults, $row); + if (preg_match('/unsigned/', $row['Type'])) { + $row['Unsigned'] = true; + } + if (preg_match('/^((?:var)?char)\((\d+)\)/', $row['Type'], $matches)) { + $row['Type'] = $matches[1]; + $row['Length'] = $matches[2]; + } else if (preg_match('/^decimal\((\d+),(\d+)\)/', $row['Type'], $matches)) { + $row['Type'] = 'decimal'; + $row['Precision'] = $matches[1]; + $row['Scale'] = $matches[2]; + } else if (preg_match('/^float\((\d+),(\d+)\)/', $row['Type'], $matches)) { + $row['Type'] = 'float'; + $row['Precision'] = $matches[1]; + $row['Scale'] = $matches[2]; + } else if (preg_match('/^((?:big|medium|small|tiny)?int)\((\d+)\)/', $row['Type'], $matches)) { + $row['Type'] = $matches[1]; + /** + * The optional argument of a MySQL int type is not precision + * or length; it is only a hint for display width. + */ + } + if (strtoupper($row['Key']) == 'PRI') { + $row['Primary'] = true; + $row['PrimaryPosition'] = $p; + if ($row['Extra'] == 'auto_increment') { + $row['Identity'] = true; + } else { + $row['Identity'] = false; + } + ++$p; + } + $desc[$this->foldCase($row['Field'])] = array( + 'SCHEMA_NAME' => null, // @todo + 'TABLE_NAME' => $this->foldCase($tableName), + 'COLUMN_NAME' => $this->foldCase($row['Field']), + 'COLUMN_POSITION' => $i, + 'DATA_TYPE' => $row['Type'], + 'DEFAULT' => $row['Default'], + 'NULLABLE' => (bool) ($row['Null'] == 'YES'), + 'LENGTH' => $row['Length'], + 'SCALE' => $row['Scale'], + 'PRECISION' => $row['Precision'], + 'UNSIGNED' => $row['Unsigned'], + 'PRIMARY' => $row['Primary'], + 'PRIMARY_POSITION' => $row['PrimaryPosition'], + 'IDENTITY' => $row['Identity'] + ); + ++$i; + } + return $desc; + } + + /** + * Creates a connection to the database. + * + * @return void + * @throws Zend_Db_Adapter_Mysqli_Exception + */ + protected function _connect() + { + if ($this->_connection) { + return; + } + + if (!extension_loaded('mysqli')) { + /** + * @see Zend_Db_Adapter_Mysqli_Exception + */ + require_once 'Zend/Db/Adapter/Mysqli/Exception.php'; + throw new Zend_Db_Adapter_Mysqli_Exception('The Mysqli extension is required for this adapter but the extension is not loaded'); + } + + if (isset($this->_config['port'])) { + $port = (integer) $this->_config['port']; + } else { + $port = null; + } + + $this->_connection = mysqli_init(); + + if(!empty($this->_config['driver_options'])) { + foreach($this->_config['driver_options'] as $option=>$value) { + if(is_string($option)) { + // Suppress warnings here + // Ignore it if it's not a valid constant + $option = @constant(strtoupper($option)); + if($option === null) + continue; + } + mysqli_options($this->_connection, $option, $value); + } + } + + // Suppress connection warnings here. + // Throw an exception instead. + $_isConnected = @mysqli_real_connect( + $this->_connection, + $this->_config['host'], + $this->_config['username'], + $this->_config['password'], + $this->_config['dbname'], + $port + ); + + if ($_isConnected === false || mysqli_connect_errno()) { + + $this->closeConnection(); + /** + * @see Zend_Db_Adapter_Mysqli_Exception + */ + require_once 'Zend/Db/Adapter/Mysqli/Exception.php'; + throw new Zend_Db_Adapter_Mysqli_Exception(mysqli_connect_error()); + } + + if (!empty($this->_config['charset'])) { + mysqli_set_charset($this->_connection, $this->_config['charset']); + } + } + + /** + * Test if a connection is active + * + * @return boolean + */ + public function isConnected() + { + return ((bool) ($this->_connection instanceof mysqli)); + } + + /** + * Force the connection to close. + * + * @return void + */ + public function closeConnection() + { + if ($this->isConnected()) { + $this->_connection->close(); + } + $this->_connection = null; + } + + /** + * Prepare a statement and return a PDOStatement-like object. + * + * @param string $sql SQL query + * @return Zend_Db_Statement_Mysqli + */ + public function prepare($sql) + { + $this->_connect(); + if ($this->_stmt) { + $this->_stmt->close(); + } + $stmtClass = $this->_defaultStmtClass; + if (!class_exists($stmtClass)) { + require_once 'Zend/Loader.php'; + Zend_Loader::loadClass($stmtClass); + } + $stmt = new $stmtClass($this, $sql); + if ($stmt === false) { + return false; + } + $stmt->setFetchMode($this->_fetchMode); + $this->_stmt = $stmt; + return $stmt; + } + + /** + * Gets the last ID generated automatically by an IDENTITY/AUTOINCREMENT column. + * + * As a convention, on RDBMS brands that support sequences + * (e.g. Oracle, PostgreSQL, DB2), this method forms the name of a sequence + * from the arguments and returns the last id generated by that sequence. + * On RDBMS brands that support IDENTITY/AUTOINCREMENT columns, this method + * returns the last value generated for such a column, and the table name + * argument is disregarded. + * + * MySQL does not support sequences, so $tableName and $primaryKey are ignored. + * + * @param string $tableName OPTIONAL Name of table. + * @param string $primaryKey OPTIONAL Name of primary key column. + * @return string + * @todo Return value should be int? + */ + public function lastInsertId($tableName = null, $primaryKey = null) + { + $mysqli = $this->_connection; + return (string) $mysqli->insert_id; + } + + /** + * Begin a transaction. + * + * @return void + */ + protected function _beginTransaction() + { + $this->_connect(); + $this->_connection->autocommit(false); + } + + /** + * Commit a transaction. + * + * @return void + */ + protected function _commit() + { + $this->_connect(); + $this->_connection->commit(); + $this->_connection->autocommit(true); + } + + /** + * Roll-back a transaction. + * + * @return void + */ + protected function _rollBack() + { + $this->_connect(); + $this->_connection->rollback(); + $this->_connection->autocommit(true); + } + + /** + * Set the fetch mode. + * + * @param int $mode + * @return void + * @throws Zend_Db_Adapter_Mysqli_Exception + */ + public function setFetchMode($mode) + { + switch ($mode) { + case Zend_Db::FETCH_LAZY: + case Zend_Db::FETCH_ASSOC: + case Zend_Db::FETCH_NUM: + case Zend_Db::FETCH_BOTH: + case Zend_Db::FETCH_NAMED: + case Zend_Db::FETCH_OBJ: + $this->_fetchMode = $mode; + break; + case Zend_Db::FETCH_BOUND: // bound to PHP variable + /** + * @see Zend_Db_Adapter_Mysqli_Exception + */ + require_once 'Zend/Db/Adapter/Mysqli/Exception.php'; + throw new Zend_Db_Adapter_Mysqli_Exception('FETCH_BOUND is not supported yet'); + break; + default: + /** + * @see Zend_Db_Adapter_Mysqli_Exception + */ + require_once 'Zend/Db/Adapter/Mysqli/Exception.php'; + throw new Zend_Db_Adapter_Mysqli_Exception("Invalid fetch mode '$mode' specified"); + } + } + + /** + * Adds an adapter-specific LIMIT clause to the SELECT statement. + * + * @param string $sql + * @param int $count + * @param int $offset OPTIONAL + * @return string + */ + public function limit($sql, $count, $offset = 0) + { + $count = intval($count); + if ($count <= 0) { + /** + * @see Zend_Db_Adapter_Mysqli_Exception + */ + require_once 'Zend/Db/Adapter/Mysqli/Exception.php'; + throw new Zend_Db_Adapter_Mysqli_Exception("LIMIT argument count=$count is not valid"); + } + + $offset = intval($offset); + if ($offset < 0) { + /** + * @see Zend_Db_Adapter_Mysqli_Exception + */ + require_once 'Zend/Db/Adapter/Mysqli/Exception.php'; + throw new Zend_Db_Adapter_Mysqli_Exception("LIMIT argument offset=$offset is not valid"); + } + + $sql .= " LIMIT $count"; + if ($offset > 0) { + $sql .= " OFFSET $offset"; + } + + return $sql; + } + + /** + * Check if the adapter supports real SQL parameters. + * + * @param string $type 'positional' or 'named' + * @return bool + */ + public function supportsParameters($type) + { + switch ($type) { + case 'positional': + return true; + case 'named': + default: + return false; + } + } + + /** + * Retrieve server version in PHP style + * + *@return string + */ + public function getServerVersion() + { + $this->_connect(); + $version = $this->_connection->server_version; + $major = (int) ($version / 10000); + $minor = (int) ($version % 10000 / 100); + $revision = (int) ($version % 100); + return $major . '.' . $minor . '.' . $revision; + } +} diff --git a/library/Zend/Db/Adapter/Mysqli/Exception.php b/library/Zend/Db/Adapter/Mysqli/Exception.php new file mode 100644 index 000000000..2c3eb93ef --- /dev/null +++ b/library/Zend/Db/Adapter/Mysqli/Exception.php @@ -0,0 +1,40 @@ + (string) Connect to the database as this username. + * password => (string) Password associated with the username. + * dbname => Either the name of the local Oracle instance, or the + * name of the entry in tnsnames.ora to which you want to connect. + * persistent => (boolean) Set TRUE to use a persistent connection + * @var array + */ + protected $_config = array( + 'dbname' => null, + 'username' => null, + 'password' => null, + 'persistent' => false + ); + + /** + * Keys are UPPERCASE SQL datatypes or the constants + * Zend_Db::INT_TYPE, Zend_Db::BIGINT_TYPE, or Zend_Db::FLOAT_TYPE. + * + * Values are: + * 0 = 32-bit integer + * 1 = 64-bit integer + * 2 = float or decimal + * + * @var array Associative array of datatypes to values 0, 1, or 2. + */ + protected $_numericDataTypes = array( + Zend_Db::INT_TYPE => Zend_Db::INT_TYPE, + Zend_Db::BIGINT_TYPE => Zend_Db::BIGINT_TYPE, + Zend_Db::FLOAT_TYPE => Zend_Db::FLOAT_TYPE, + 'BINARY_DOUBLE' => Zend_Db::FLOAT_TYPE, + 'BINARY_FLOAT' => Zend_Db::FLOAT_TYPE, + 'NUMBER' => Zend_Db::FLOAT_TYPE, + ); + + /** + * @var integer + */ + protected $_execute_mode = null; + + /** + * Default class name for a DB statement. + * + * @var string + */ + protected $_defaultStmtClass = 'Zend_Db_Statement_Oracle'; + + /** + * Check if LOB field are returned as string + * instead of OCI-Lob object + * + * @var boolean + */ + protected $_lobAsString = null; + + /** + * Creates a connection resource. + * + * @return void + * @throws Zend_Db_Adapter_Oracle_Exception + */ + protected function _connect() + { + if (is_resource($this->_connection)) { + // connection already exists + return; + } + + if (!extension_loaded('oci8')) { + /** + * @see Zend_Db_Adapter_Oracle_Exception + */ + require_once 'Zend/Db/Adapter/Oracle/Exception.php'; + throw new Zend_Db_Adapter_Oracle_Exception('The OCI8 extension is required for this adapter but the extension is not loaded'); + } + + $this->_setExecuteMode(OCI_COMMIT_ON_SUCCESS); + + $connectionFuncName = ($this->_config['persistent'] == true) ? 'oci_pconnect' : 'oci_connect'; + + $this->_connection = @$connectionFuncName( + $this->_config['username'], + $this->_config['password'], + $this->_config['dbname'], + $this->_config['charset']); + + // check the connection + if (!$this->_connection) { + /** + * @see Zend_Db_Adapter_Oracle_Exception + */ + require_once 'Zend/Db/Adapter/Oracle/Exception.php'; + throw new Zend_Db_Adapter_Oracle_Exception(oci_error()); + } + } + + /** + * Test if a connection is active + * + * @return boolean + */ + public function isConnected() + { + return ((bool) (is_resource($this->_connection) + && get_resource_type($this->_connection) == 'oci8 connection')); + } + + /** + * Force the connection to close. + * + * @return void + */ + public function closeConnection() + { + if ($this->isConnected()) { + oci_close($this->_connection); + } + $this->_connection = null; + } + + /** + * Activate/deactivate return of LOB as string + * + * @param string $lob_as_string + * @return Zend_Db_Adapter_Oracle + */ + public function setLobAsString($lobAsString) + { + $this->_lobAsString = (bool) $lobAsString; + return $this; + } + + /** + * Return whether or not LOB are returned as string + * + * @return boolean + */ + public function getLobAsString() + { + if ($this->_lobAsString === null) { + // if never set by user, we use driver option if it exists otherwise false + if (isset($this->_config['driver_options']) && + isset($this->_config['driver_options']['lob_as_string'])) { + $this->_lobAsString = (bool) $this->_config['driver_options']['lob_as_string']; + } else { + $this->_lobAsString = false; + } + } + return $this->_lobAsString; + } + + /** + * Returns an SQL statement for preparation. + * + * @param string $sql The SQL statement with placeholders. + * @return Zend_Db_Statement_Oracle + */ + public function prepare($sql) + { + $this->_connect(); + $stmtClass = $this->_defaultStmtClass; + if (!class_exists($stmtClass)) { + require_once 'Zend/Loader.php'; + Zend_Loader::loadClass($stmtClass); + } + $stmt = new $stmtClass($this, $sql); + if ($stmt instanceof Zend_Db_Statement_Oracle) { + $stmt->setLobAsString($this->getLobAsString()); + } + $stmt->setFetchMode($this->_fetchMode); + return $stmt; + } + + /** + * Quote a raw string. + * + * @param string $value Raw string + * @return string Quoted string + */ + protected function _quote($value) + { + if (is_int($value) || is_float($value)) { + return $value; + } + $value = str_replace("'", "''", $value); + return "'" . addcslashes($value, "\000\n\r\\\032") . "'"; + } + + /** + * Quote a table identifier and alias. + * + * @param string|array|Zend_Db_Expr $ident The identifier or expression. + * @param string $alias An alias for the table. + * @param boolean $auto If true, heed the AUTO_QUOTE_IDENTIFIERS config option. + * @return string The quoted identifier and alias. + */ + public function quoteTableAs($ident, $alias = null, $auto = false) + { + // Oracle doesn't allow the 'AS' keyword between the table identifier/expression and alias. + return $this->_quoteIdentifierAs($ident, $alias, $auto, ' '); + } + + /** + * Return the most recent value from the specified sequence in the database. + * This is supported only on RDBMS brands that support sequences + * (e.g. Oracle, PostgreSQL, DB2). Other RDBMS brands return null. + * + * @param string $sequenceName + * @return string + */ + public function lastSequenceId($sequenceName) + { + $this->_connect(); + $sql = 'SELECT '.$this->quoteIdentifier($sequenceName, true).'.CURRVAL FROM dual'; + $value = $this->fetchOne($sql); + return $value; + } + + /** + * Generate a new value from the specified sequence in the database, and return it. + * This is supported only on RDBMS brands that support sequences + * (e.g. Oracle, PostgreSQL, DB2). Other RDBMS brands return null. + * + * @param string $sequenceName + * @return string + */ + public function nextSequenceId($sequenceName) + { + $this->_connect(); + $sql = 'SELECT '.$this->quoteIdentifier($sequenceName, true).'.NEXTVAL FROM dual'; + $value = $this->fetchOne($sql); + return $value; + } + + /** + * Gets the last ID generated automatically by an IDENTITY/AUTOINCREMENT column. + * + * As a convention, on RDBMS brands that support sequences + * (e.g. Oracle, PostgreSQL, DB2), this method forms the name of a sequence + * from the arguments and returns the last id generated by that sequence. + * On RDBMS brands that support IDENTITY/AUTOINCREMENT columns, this method + * returns the last value generated for such a column, and the table name + * argument is disregarded. + * + * Oracle does not support IDENTITY columns, so if the sequence is not + * specified, this method returns null. + * + * @param string $tableName OPTIONAL Name of table. + * @param string $primaryKey OPTIONAL Name of primary key column. + * @return string + */ + public function lastInsertId($tableName = null, $primaryKey = null) + { + if ($tableName !== null) { + $sequenceName = $tableName; + if ($primaryKey) { + $sequenceName .= "_$primaryKey"; + } + $sequenceName .= '_seq'; + return $this->lastSequenceId($sequenceName); + } + + // No support for IDENTITY columns; return null + return null; + } + + /** + * Returns a list of the tables in the database. + * + * @return array + */ + public function listTables() + { + $this->_connect(); + $data = $this->fetchCol('SELECT table_name FROM all_tables'); + return $data; + } + + /** + * Returns the column descriptions for a table. + * + * The return value is an associative array keyed by the column name, + * as returned by the RDBMS. + * + * The value of each array element is an associative array + * with the following keys: + * + * SCHEMA_NAME => string; name of schema + * TABLE_NAME => string; + * COLUMN_NAME => string; column name + * COLUMN_POSITION => number; ordinal position of column in table + * DATA_TYPE => string; SQL datatype name of column + * DEFAULT => string; default expression of column, null if none + * NULLABLE => boolean; true if column can have nulls + * LENGTH => number; length of CHAR/VARCHAR + * SCALE => number; scale of NUMERIC/DECIMAL + * PRECISION => number; precision of NUMERIC/DECIMAL + * UNSIGNED => boolean; unsigned property of an integer type + * PRIMARY => boolean; true if column is part of the primary key + * PRIMARY_POSITION => integer; position of column in primary key + * IDENTITY => integer; true if column is auto-generated with unique values + * + * @todo Discover integer unsigned property. + * + * @param string $tableName + * @param string $schemaName OPTIONAL + * @return array + */ + public function describeTable($tableName, $schemaName = null) + { + $version = $this->getServerVersion(); + if (($version === null) || version_compare($version, '9.0.0', '>=')) { + $sql = "SELECT TC.TABLE_NAME, TC.OWNER, TC.COLUMN_NAME, TC.DATA_TYPE, + TC.DATA_DEFAULT, TC.NULLABLE, TC.COLUMN_ID, TC.DATA_LENGTH, + TC.DATA_SCALE, TC.DATA_PRECISION, C.CONSTRAINT_TYPE, CC.POSITION + FROM ALL_TAB_COLUMNS TC + LEFT JOIN (ALL_CONS_COLUMNS CC JOIN ALL_CONSTRAINTS C + ON (CC.CONSTRAINT_NAME = C.CONSTRAINT_NAME AND CC.TABLE_NAME = C.TABLE_NAME AND CC.OWNER = C.OWNER AND C.CONSTRAINT_TYPE = 'P')) + ON TC.TABLE_NAME = CC.TABLE_NAME AND TC.COLUMN_NAME = CC.COLUMN_NAME + WHERE UPPER(TC.TABLE_NAME) = UPPER(:TBNAME)"; + $bind[':TBNAME'] = $tableName; + if ($schemaName) { + $sql .= ' AND UPPER(TC.OWNER) = UPPER(:SCNAME)'; + $bind[':SCNAME'] = $schemaName; + } + $sql .= ' ORDER BY TC.COLUMN_ID'; + } else { + $subSql="SELECT AC.OWNER, AC.TABLE_NAME, ACC.COLUMN_NAME, AC.CONSTRAINT_TYPE, ACC.POSITION + from ALL_CONSTRAINTS AC, ALL_CONS_COLUMNS ACC + WHERE ACC.CONSTRAINT_NAME = AC.CONSTRAINT_NAME + AND ACC.TABLE_NAME = AC.TABLE_NAME + AND ACC.OWNER = AC.OWNER + AND AC.CONSTRAINT_TYPE = 'P' + AND UPPER(AC.TABLE_NAME) = UPPER(:TBNAME)"; + $bind[':TBNAME'] = $tableName; + if ($schemaName) { + $subSql .= ' AND UPPER(ACC.OWNER) = UPPER(:SCNAME)'; + $bind[':SCNAME'] = $schemaName; + } + $sql="SELECT TC.TABLE_NAME, TC.OWNER, TC.COLUMN_NAME, TC.DATA_TYPE, + TC.DATA_DEFAULT, TC.NULLABLE, TC.COLUMN_ID, TC.DATA_LENGTH, + TC.DATA_SCALE, TC.DATA_PRECISION, CC.CONSTRAINT_TYPE, CC.POSITION + FROM ALL_TAB_COLUMNS TC, ($subSql) CC + WHERE UPPER(TC.TABLE_NAME) = UPPER(:TBNAME) + AND TC.OWNER = CC.OWNER(+) AND TC.TABLE_NAME = CC.TABLE_NAME(+) AND TC.COLUMN_NAME = CC.COLUMN_NAME(+)"; + if ($schemaName) { + $sql .= ' AND UPPER(TC.OWNER) = UPPER(:SCNAME)'; + } + $sql .= ' ORDER BY TC.COLUMN_ID'; + } + + $stmt = $this->query($sql, $bind); + + /** + * Use FETCH_NUM so we are not dependent on the CASE attribute of the PDO connection + */ + $result = $stmt->fetchAll(Zend_Db::FETCH_NUM); + + $table_name = 0; + $owner = 1; + $column_name = 2; + $data_type = 3; + $data_default = 4; + $nullable = 5; + $column_id = 6; + $data_length = 7; + $data_scale = 8; + $data_precision = 9; + $constraint_type = 10; + $position = 11; + + $desc = array(); + foreach ($result as $key => $row) { + list ($primary, $primaryPosition, $identity) = array(false, null, false); + if ($row[$constraint_type] == 'P') { + $primary = true; + $primaryPosition = $row[$position]; + /** + * Oracle does not support auto-increment keys. + */ + $identity = false; + } + $desc[$this->foldCase($row[$column_name])] = array( + 'SCHEMA_NAME' => $this->foldCase($row[$owner]), + 'TABLE_NAME' => $this->foldCase($row[$table_name]), + 'COLUMN_NAME' => $this->foldCase($row[$column_name]), + 'COLUMN_POSITION' => $row[$column_id], + 'DATA_TYPE' => $row[$data_type], + 'DEFAULT' => $row[$data_default], + 'NULLABLE' => (bool) ($row[$nullable] == 'Y'), + 'LENGTH' => $row[$data_length], + 'SCALE' => $row[$data_scale], + 'PRECISION' => $row[$data_precision], + 'UNSIGNED' => null, // @todo + 'PRIMARY' => $primary, + 'PRIMARY_POSITION' => $primaryPosition, + 'IDENTITY' => $identity + ); + } + return $desc; + } + + /** + * Leave autocommit mode and begin a transaction. + * + * @return void + */ + protected function _beginTransaction() + { + $this->_setExecuteMode(OCI_DEFAULT); + } + + /** + * Commit a transaction and return to autocommit mode. + * + * @return void + * @throws Zend_Db_Adapter_Oracle_Exception + */ + protected function _commit() + { + if (!oci_commit($this->_connection)) { + /** + * @see Zend_Db_Adapter_Oracle_Exception + */ + require_once 'Zend/Db/Adapter/Oracle/Exception.php'; + throw new Zend_Db_Adapter_Oracle_Exception(oci_error($this->_connection)); + } + $this->_setExecuteMode(OCI_COMMIT_ON_SUCCESS); + } + + /** + * Roll back a transaction and return to autocommit mode. + * + * @return void + * @throws Zend_Db_Adapter_Oracle_Exception + */ + protected function _rollBack() + { + if (!oci_rollback($this->_connection)) { + /** + * @see Zend_Db_Adapter_Oracle_Exception + */ + require_once 'Zend/Db/Adapter/Oracle/Exception.php'; + throw new Zend_Db_Adapter_Oracle_Exception(oci_error($this->_connection)); + } + $this->_setExecuteMode(OCI_COMMIT_ON_SUCCESS); + } + + /** + * Set the fetch mode. + * + * @todo Support FETCH_CLASS and FETCH_INTO. + * + * @param integer $mode A fetch mode. + * @return void + * @throws Zend_Db_Adapter_Oracle_Exception + */ + public function setFetchMode($mode) + { + switch ($mode) { + case Zend_Db::FETCH_NUM: // seq array + case Zend_Db::FETCH_ASSOC: // assoc array + case Zend_Db::FETCH_BOTH: // seq+assoc array + case Zend_Db::FETCH_OBJ: // object + $this->_fetchMode = $mode; + break; + case Zend_Db::FETCH_BOUND: // bound to PHP variable + /** + * @see Zend_Db_Adapter_Oracle_Exception + */ + require_once 'Zend/Db/Adapter/Oracle/Exception.php'; + throw new Zend_Db_Adapter_Oracle_Exception('FETCH_BOUND is not supported yet'); + break; + default: + /** + * @see Zend_Db_Adapter_Oracle_Exception + */ + require_once 'Zend/Db/Adapter/Oracle/Exception.php'; + throw new Zend_Db_Adapter_Oracle_Exception("Invalid fetch mode '$mode' specified"); + break; + } + } + + /** + * Adds an adapter-specific LIMIT clause to the SELECT statement. + * + * @param string $sql + * @param integer $count + * @param integer $offset OPTIONAL + * @return string + * @throws Zend_Db_Adapter_Oracle_Exception + */ + public function limit($sql, $count, $offset = 0) + { + $count = intval($count); + if ($count <= 0) { + /** + * @see Zend_Db_Adapter_Oracle_Exception + */ + require_once 'Zend/Db/Adapter/Oracle/Exception.php'; + throw new Zend_Db_Adapter_Oracle_Exception("LIMIT argument count=$count is not valid"); + } + + $offset = intval($offset); + if ($offset < 0) { + /** + * @see Zend_Db_Adapter_Oracle_Exception + */ + require_once 'Zend/Db/Adapter/Oracle/Exception.php'; + throw new Zend_Db_Adapter_Oracle_Exception("LIMIT argument offset=$offset is not valid"); + } + + /** + * Oracle does not implement the LIMIT clause as some RDBMS do. + * We have to simulate it with subqueries and ROWNUM. + * Unfortunately because we use the column wildcard "*", + * this puts an extra column into the query result set. + */ + $limit_sql = "SELECT z2.* + FROM ( + SELECT z1.*, ROWNUM AS \"zend_db_rownum\" + FROM ( + " . $sql . " + ) z1 + ) z2 + WHERE z2.\"zend_db_rownum\" BETWEEN " . ($offset+1) . " AND " . ($offset+$count); + return $limit_sql; + } + + /** + * @param integer $mode + * @throws Zend_Db_Adapter_Oracle_Exception + */ + private function _setExecuteMode($mode) + { + switch($mode) { + case OCI_COMMIT_ON_SUCCESS: + case OCI_DEFAULT: + case OCI_DESCRIBE_ONLY: + $this->_execute_mode = $mode; + break; + default: + /** + * @see Zend_Db_Adapter_Oracle_Exception + */ + require_once 'Zend/Db/Adapter/Oracle/Exception.php'; + throw new Zend_Db_Adapter_Oracle_Exception("Invalid execution mode '$mode' specified"); + break; + } + } + + /** + * @return int + */ + public function _getExecuteMode() + { + return $this->_execute_mode; + } + + /** + * Inserts a table row with specified data. + * + * Oracle does not support anonymous ('?') binds. + * + * @param mixed $table The table to insert data into. + * @param array $bind Column-value pairs. + * @return int The number of affected rows. + */ + public function insert($table, array $bind) + { + $i = 0; + // extract and quote col names from the array keys + $cols = array(); + $vals = array(); + foreach ($bind as $col => $val) { + $cols[] = $this->quoteIdentifier($col, true); + if ($val instanceof Zend_Db_Expr) { + $vals[] = $val->__toString(); + unset($bind[$col]); + } else { + $vals[] = ':'.$col.$i; + unset($bind[$col]); + $bind[':'.$col.$i] = $val; + } + $i++; + } + + // build the statement + $sql = "INSERT INTO " + . $this->quoteIdentifier($table, true) + . ' (' . implode(', ', $cols) . ') ' + . 'VALUES (' . implode(', ', $vals) . ')'; + + // execute the statement and return the number of affected rows + $stmt = $this->query($sql, $bind); + $result = $stmt->rowCount(); + return $result; + } + + /** + * Check if the adapter supports real SQL parameters. + * + * @param string $type 'positional' or 'named' + * @return bool + */ + public function supportsParameters($type) + { + switch ($type) { + case 'named': + return true; + case 'positional': + default: + return false; + } + } + + /** + * Retrieve server version in PHP style + * + * @return string + */ + public function getServerVersion() + { + $this->_connect(); + $version = oci_server_version($this->_connection); + if ($version !== false) { + $matches = null; + if (preg_match('/((?:[0-9]{1,2}\.){1,3}[0-9]{1,2})/', $version, $matches)) { + return $matches[1]; + } else { + return null; + } + } else { + return null; + } + } +} diff --git a/library/Zend/Db/Adapter/Oracle/Exception.php b/library/Zend/Db/Adapter/Oracle/Exception.php new file mode 100644 index 000000000..61f4a3dc8 --- /dev/null +++ b/library/Zend/Db/Adapter/Oracle/Exception.php @@ -0,0 +1,60 @@ +message = $error['code'] .' '. $error['message']; + } else { + $this->message = $error['code'] .' '. $error['message']." " + . substr($error['sqltext'], 0, $error['offset']) + . "*" + . substr($error['sqltext'], $error['offset']); + } + $this->code = $error['code']; + } else if (is_string($error)) { + $this->message = $error; + } + if (!$this->code && $code) { + $this->code = $code; + } + } +} diff --git a/library/Zend/Db/Adapter/Pdo/Abstract.php b/library/Zend/Db/Adapter/Pdo/Abstract.php new file mode 100644 index 000000000..97dd2fdf8 --- /dev/null +++ b/library/Zend/Db/Adapter/Pdo/Abstract.php @@ -0,0 +1,401 @@ +_config settings. + * + * @return string + */ + protected function _dsn() + { + // baseline of DSN parts + $dsn = $this->_config; + + // don't pass the username, password, charset, persistent and driver_options in the DSN + unset($dsn['username']); + unset($dsn['password']); + unset($dsn['options']); + unset($dsn['charset']); + unset($dsn['persistent']); + unset($dsn['driver_options']); + + // use all remaining parts in the DSN + foreach ($dsn as $key => $val) { + $dsn[$key] = "$key=$val"; + } + + return $this->_pdoType . ':' . implode(';', $dsn); + } + + /** + * Creates a PDO object and connects to the database. + * + * @return void + * @throws Zend_Db_Adapter_Exception + */ + protected function _connect() + { + // if we already have a PDO object, no need to re-connect. + if ($this->_connection) { + return; + } + + // get the dsn first, because some adapters alter the $_pdoType + $dsn = $this->_dsn(); + + // check for PDO extension + if (!extension_loaded('pdo')) { + /** + * @see Zend_Db_Adapter_Exception + */ + require_once 'Zend/Db/Adapter/Exception.php'; + throw new Zend_Db_Adapter_Exception('The PDO extension is required for this adapter but the extension is not loaded'); + } + + // check the PDO driver is available + if (!in_array($this->_pdoType, PDO::getAvailableDrivers())) { + /** + * @see Zend_Db_Adapter_Exception + */ + require_once 'Zend/Db/Adapter/Exception.php'; + throw new Zend_Db_Adapter_Exception('The ' . $this->_pdoType . ' driver is not currently installed'); + } + + // create PDO connection + $q = $this->_profiler->queryStart('connect', Zend_Db_Profiler::CONNECT); + + // add the persistence flag if we find it in our config array + if (isset($this->_config['persistent']) && ($this->_config['persistent'] == true)) { + $this->_config['driver_options'][PDO::ATTR_PERSISTENT] = true; + } + + try { + $this->_connection = new PDO( + $dsn, + $this->_config['username'], + $this->_config['password'], + $this->_config['driver_options'] + ); + + $this->_profiler->queryEnd($q); + + // set the PDO connection to perform case-folding on array keys, or not + $this->_connection->setAttribute(PDO::ATTR_CASE, $this->_caseFolding); + + // always use exceptions. + $this->_connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + + } catch (PDOException $e) { + /** + * @see Zend_Db_Adapter_Exception + */ + require_once 'Zend/Db/Adapter/Exception.php'; + throw new Zend_Db_Adapter_Exception($e->getMessage(), $e->getCode(), $e); + } + + } + + /** + * Test if a connection is active + * + * @return boolean + */ + public function isConnected() + { + return ((bool) ($this->_connection instanceof PDO)); + } + + /** + * Force the connection to close. + * + * @return void + */ + public function closeConnection() + { + $this->_connection = null; + } + + /** + * Prepares an SQL statement. + * + * @param string $sql The SQL statement with placeholders. + * @param array $bind An array of data to bind to the placeholders. + * @return PDOStatement + */ + public function prepare($sql) + { + $this->_connect(); + $stmtClass = $this->_defaultStmtClass; + if (!class_exists($stmtClass)) { + require_once 'Zend/Loader.php'; + Zend_Loader::loadClass($stmtClass); + } + $stmt = new $stmtClass($this, $sql); + $stmt->setFetchMode($this->_fetchMode); + return $stmt; + } + + /** + * Gets the last ID generated automatically by an IDENTITY/AUTOINCREMENT column. + * + * As a convention, on RDBMS brands that support sequences + * (e.g. Oracle, PostgreSQL, DB2), this method forms the name of a sequence + * from the arguments and returns the last id generated by that sequence. + * On RDBMS brands that support IDENTITY/AUTOINCREMENT columns, this method + * returns the last value generated for such a column, and the table name + * argument is disregarded. + * + * On RDBMS brands that don't support sequences, $tableName and $primaryKey + * are ignored. + * + * @param string $tableName OPTIONAL Name of table. + * @param string $primaryKey OPTIONAL Name of primary key column. + * @return string + */ + public function lastInsertId($tableName = null, $primaryKey = null) + { + $this->_connect(); + return $this->_connection->lastInsertId(); + } + + /** + * Special handling for PDO query(). + * All bind parameter names must begin with ':' + * + * @param string|Zend_Db_Select $sql The SQL statement with placeholders. + * @param array $bind An array of data to bind to the placeholders. + * @return Zend_Db_Statement_Pdo + * @throws Zend_Db_Adapter_Exception To re-throw PDOException. + */ + public function query($sql, $bind = array()) + { + if (empty($bind) && $sql instanceof Zend_Db_Select) { + $bind = $sql->getBind(); + } + + if (is_array($bind)) { + foreach ($bind as $name => $value) { + if (!is_int($name) && !preg_match('/^:/', $name)) { + $newName = ":$name"; + unset($bind[$name]); + $bind[$newName] = $value; + } + } + } + + try { + return parent::query($sql, $bind); + } catch (PDOException $e) { + /** + * @see Zend_Db_Statement_Exception + */ + require_once 'Zend/Db/Statement/Exception.php'; + throw new Zend_Db_Statement_Exception($e->getMessage(), $e->getCode(), $e); + } + } + + /** + * Executes an SQL statement and return the number of affected rows + * + * @param mixed $sql The SQL statement with placeholders. + * May be a string or Zend_Db_Select. + * @return integer Number of rows that were modified + * or deleted by the SQL statement + */ + public function exec($sql) + { + if ($sql instanceof Zend_Db_Select) { + $sql = $sql->assemble(); + } + + try { + $affected = $this->getConnection()->exec($sql); + + if ($affected === false) { + $errorInfo = $this->getConnection()->errorInfo(); + /** + * @see Zend_Db_Adapter_Exception + */ + require_once 'Zend/Db/Adapter/Exception.php'; + throw new Zend_Db_Adapter_Exception($errorInfo[2]); + } + + return $affected; + } catch (PDOException $e) { + /** + * @see Zend_Db_Adapter_Exception + */ + require_once 'Zend/Db/Adapter/Exception.php'; + throw new Zend_Db_Adapter_Exception($e->getMessage(), $e->getCode(), $e); + } + } + + /** + * Quote a raw string. + * + * @param string $value Raw string + * @return string Quoted string + */ + protected function _quote($value) + { + if (is_int($value) || is_float($value)) { + return $value; + } + $this->_connect(); + return $this->_connection->quote($value); + } + + /** + * Begin a transaction. + */ + protected function _beginTransaction() + { + $this->_connect(); + $this->_connection->beginTransaction(); + } + + /** + * Commit a transaction. + */ + protected function _commit() + { + $this->_connect(); + $this->_connection->commit(); + } + + /** + * Roll-back a transaction. + */ + protected function _rollBack() { + $this->_connect(); + $this->_connection->rollBack(); + } + + /** + * Set the PDO fetch mode. + * + * @todo Support FETCH_CLASS and FETCH_INTO. + * + * @param int $mode A PDO fetch mode. + * @return void + * @throws Zend_Db_Adapter_Exception + */ + public function setFetchMode($mode) + { + //check for PDO extension + if (!extension_loaded('pdo')) { + /** + * @see Zend_Db_Adapter_Exception + */ + require_once 'Zend/Db/Adapter/Exception.php'; + throw new Zend_Db_Adapter_Exception('The PDO extension is required for this adapter but the extension is not loaded'); + } + switch ($mode) { + case PDO::FETCH_LAZY: + case PDO::FETCH_ASSOC: + case PDO::FETCH_NUM: + case PDO::FETCH_BOTH: + case PDO::FETCH_NAMED: + case PDO::FETCH_OBJ: + $this->_fetchMode = $mode; + break; + default: + /** + * @see Zend_Db_Adapter_Exception + */ + require_once 'Zend/Db/Adapter/Exception.php'; + throw new Zend_Db_Adapter_Exception("Invalid fetch mode '$mode' specified"); + break; + } + } + + /** + * Check if the adapter supports real SQL parameters. + * + * @param string $type 'positional' or 'named' + * @return bool + */ + public function supportsParameters($type) + { + switch ($type) { + case 'positional': + case 'named': + default: + return true; + } + } + + /** + * Retrieve server version in PHP style + * + * @return string + */ + public function getServerVersion() + { + $this->_connect(); + try { + $version = $this->_connection->getAttribute(PDO::ATTR_SERVER_VERSION); + } catch (PDOException $e) { + // In case of the driver doesn't support getting attributes + return null; + } + $matches = null; + if (preg_match('/((?:[0-9]{1,2}\.){1,3}[0-9]{1,2})/', $version, $matches)) { + return $matches[1]; + } else { + return null; + } + } +} + diff --git a/library/Zend/Db/Adapter/Pdo/Ibm.php b/library/Zend/Db/Adapter/Pdo/Ibm.php new file mode 100644 index 000000000..92f740d4d --- /dev/null +++ b/library/Zend/Db/Adapter/Pdo/Ibm.php @@ -0,0 +1,360 @@ + Zend_Db::INT_TYPE, + Zend_Db::BIGINT_TYPE => Zend_Db::BIGINT_TYPE, + Zend_Db::FLOAT_TYPE => Zend_Db::FLOAT_TYPE, + 'INTEGER' => Zend_Db::INT_TYPE, + 'SMALLINT' => Zend_Db::INT_TYPE, + 'BIGINT' => Zend_Db::BIGINT_TYPE, + 'DECIMAL' => Zend_Db::FLOAT_TYPE, + 'DEC' => Zend_Db::FLOAT_TYPE, + 'REAL' => Zend_Db::FLOAT_TYPE, + 'NUMERIC' => Zend_Db::FLOAT_TYPE, + 'DOUBLE PRECISION' => Zend_Db::FLOAT_TYPE, + 'FLOAT' => Zend_Db::FLOAT_TYPE + ); + + /** + * Creates a PDO object and connects to the database. + * + * The IBM data server is set. + * Current options are DB2 or IDS + * @todo also differentiate between z/OS and i/5 + * + * @return void + * @throws Zend_Db_Adapter_Exception + */ + public function _connect() + { + if ($this->_connection) { + return; + } + parent::_connect(); + + $this->getConnection()->setAttribute(Zend_Db::ATTR_STRINGIFY_FETCHES, true); + + try { + if ($this->_serverType === null) { + $server = substr($this->getConnection()->getAttribute(PDO::ATTR_SERVER_INFO), 0, 3); + + switch ($server) { + case 'DB2': + $this->_serverType = new Zend_Db_Adapter_Pdo_Ibm_Db2($this); + + // Add DB2-specific numeric types + $this->_numericDataTypes['DECFLOAT'] = Zend_Db::FLOAT_TYPE; + $this->_numericDataTypes['DOUBLE'] = Zend_Db::FLOAT_TYPE; + $this->_numericDataTypes['NUM'] = Zend_Db::FLOAT_TYPE; + + break; + case 'IDS': + $this->_serverType = new Zend_Db_Adapter_Pdo_Ibm_Ids($this); + + // Add IDS-specific numeric types + $this->_numericDataTypes['SERIAL'] = Zend_Db::INT_TYPE; + $this->_numericDataTypes['SERIAL8'] = Zend_Db::BIGINT_TYPE; + $this->_numericDataTypes['INT8'] = Zend_Db::BIGINT_TYPE; + $this->_numericDataTypes['SMALLFLOAT'] = Zend_Db::FLOAT_TYPE; + $this->_numericDataTypes['MONEY'] = Zend_Db::FLOAT_TYPE; + + break; + } + } + } catch (PDOException $e) { + /** @see Zend_Db_Adapter_Exception */ + require_once 'Zend/Db/Adapter/Exception.php'; + $error = strpos($e->getMessage(), 'driver does not support that attribute'); + if ($error) { + throw new Zend_Db_Adapter_Exception("PDO_IBM driver extension is downlevel. Please use driver release version 1.2.1 or later", 0, $e); + } else { + throw new Zend_Db_Adapter_Exception($e->getMessage(), $e->getCode(), $e); + } + } + } + + /** + * Creates a PDO DSN for the adapter from $this->_config settings. + * + * @return string + */ + protected function _dsn() + { + $this->_checkRequiredOptions($this->_config); + + // check if using full connection string + if (array_key_exists('host', $this->_config)) { + $dsn = ';DATABASE=' . $this->_config['dbname'] + . ';HOSTNAME=' . $this->_config['host'] + . ';PORT=' . $this->_config['port'] + // PDO_IBM supports only DB2 TCPIP protocol + . ';PROTOCOL=' . 'TCPIP;'; + } else { + // catalogued connection + $dsn = $this->_config['dbname']; + } + return $this->_pdoType . ': ' . $dsn; + } + + /** + * Checks required options + * + * @param array $config + * @throws Zend_Db_Adapter_Exception + * @return void + */ + protected function _checkRequiredOptions(array $config) + { + parent::_checkRequiredOptions($config); + + if (array_key_exists('host', $this->_config) && + !array_key_exists('port', $config)) { + /** @see Zend_Db_Adapter_Exception */ + require_once 'Zend/Db/Adapter/Exception.php'; + throw new Zend_Db_Adapter_Exception("Configuration must have a key for 'port' when 'host' is specified"); + } + } + + /** + * Prepares an SQL statement. + * + * @param string $sql The SQL statement with placeholders. + * @param array $bind An array of data to bind to the placeholders. + * @return PDOStatement + */ + public function prepare($sql) + { + $this->_connect(); + $stmtClass = $this->_defaultStmtClass; + $stmt = new $stmtClass($this, $sql); + $stmt->setFetchMode($this->_fetchMode); + return $stmt; + } + + /** + * Returns a list of the tables in the database. + * + * @return array + */ + public function listTables() + { + $this->_connect(); + return $this->_serverType->listTables(); + } + + /** + * Returns the column descriptions for a table. + * + * The return value is an associative array keyed by the column name, + * as returned by the RDBMS. + * + * The value of each array element is an associative array + * with the following keys: + * + * SCHEMA_NAME => string; name of database or schema + * TABLE_NAME => string; + * COLUMN_NAME => string; column name + * COLUMN_POSITION => number; ordinal position of column in table + * DATA_TYPE => string; SQL datatype name of column + * DEFAULT => string; default expression of column, null if none + * NULLABLE => boolean; true if column can have nulls + * LENGTH => number; length of CHAR/VARCHAR + * SCALE => number; scale of NUMERIC/DECIMAL + * PRECISION => number; precision of NUMERIC/DECIMAL + * UNSIGNED => boolean; unsigned property of an integer type + * PRIMARY => boolean; true if column is part of the primary key + * PRIMARY_POSITION => integer; position of column in primary key + * + * @todo Discover integer unsigned property. + * + * @param string $tableName + * @param string $schemaName OPTIONAL + * @return array + */ + public function describeTable($tableName, $schemaName = null) + { + $this->_connect(); + return $this->_serverType->describeTable($tableName, $schemaName); + } + + /** + * Inserts a table row with specified data. + * Special handling for PDO_IBM + * remove empty slots + * + * @param mixed $table The table to insert data into. + * @param array $bind Column-value pairs. + * @return int The number of affected rows. + */ + public function insert($table, array $bind) + { + $this->_connect(); + $newbind = array(); + if (is_array($bind)) { + foreach ($bind as $name => $value) { + if($value !== null) { + $newbind[$name] = $value; + } + } + } + + return parent::insert($table, $newbind); + } + + /** + * Adds an adapter-specific LIMIT clause to the SELECT statement. + * + * @param string $sql + * @param integer $count + * @param integer $offset OPTIONAL + * @return string + */ + public function limit($sql, $count, $offset = 0) + { + $this->_connect(); + return $this->_serverType->limit($sql, $count, $offset); + } + + /** + * Gets the last ID generated automatically by an IDENTITY/AUTOINCREMENT + * column. + * + * @param string $tableName OPTIONAL + * @param string $primaryKey OPTIONAL + * @return integer + */ + public function lastInsertId($tableName = null, $primaryKey = null) + { + $this->_connect(); + + if ($tableName !== null) { + $sequenceName = $tableName; + if ($primaryKey) { + $sequenceName .= "_$primaryKey"; + } + $sequenceName .= '_seq'; + return $this->lastSequenceId($sequenceName); + } + + $id = $this->getConnection()->lastInsertId(); + + return $id; + } + + /** + * Return the most recent value from the specified sequence in the database. + * + * @param string $sequenceName + * @return integer + */ + public function lastSequenceId($sequenceName) + { + $this->_connect(); + return $this->_serverType->lastSequenceId($sequenceName); + } + + /** + * Generate a new value from the specified sequence in the database, + * and return it. + * + * @param string $sequenceName + * @return integer + */ + public function nextSequenceId($sequenceName) + { + $this->_connect(); + return $this->_serverType->nextSequenceId($sequenceName); + } + + /** + * Retrieve server version in PHP style + * Pdo_Idm doesn't support getAttribute(PDO::ATTR_SERVER_VERSION) + * @return string + */ + public function getServerVersion() + { + try { + $stmt = $this->query('SELECT service_level, fixpack_num FROM TABLE (sysproc.env_get_inst_info()) as INSTANCEINFO'); + $result = $stmt->fetchAll(Zend_Db::FETCH_NUM); + if (count($result)) { + $matches = null; + if (preg_match('/((?:[0-9]{1,2}\.){1,3}[0-9]{1,2})/', $result[0][0], $matches)) { + return $matches[1]; + } else { + return null; + } + } + return null; + } catch (PDOException $e) { + return null; + } + } +} diff --git a/library/Zend/Db/Adapter/Pdo/Ibm/Db2.php b/library/Zend/Db/Adapter/Pdo/Ibm/Db2.php new file mode 100644 index 000000000..214b71626 --- /dev/null +++ b/library/Zend/Db/Adapter/Pdo/Ibm/Db2.php @@ -0,0 +1,228 @@ +_adapter = $adapter; + } + + /** + * Returns a list of the tables in the database. + * + * @return array + */ + public function listTables() + { + $sql = "SELECT tabname " + . "FROM SYSCAT.TABLES "; + return $this->_adapter->fetchCol($sql); + } + + /** + * DB2 catalog lookup for describe table + * + * @param string $tableName + * @param string $schemaName OPTIONAL + * @return array + */ + public function describeTable($tableName, $schemaName = null) + { + $sql = "SELECT DISTINCT c.tabschema, c.tabname, c.colname, c.colno, + c.typename, c.default, c.nulls, c.length, c.scale, + c.identity, tc.type AS tabconsttype, k.colseq + FROM syscat.columns c + LEFT JOIN (syscat.keycoluse k JOIN syscat.tabconst tc + ON (k.tabschema = tc.tabschema + AND k.tabname = tc.tabname + AND tc.type = 'P')) + ON (c.tabschema = k.tabschema + AND c.tabname = k.tabname + AND c.colname = k.colname) + WHERE " + . $this->_adapter->quoteInto('UPPER(c.tabname) = UPPER(?)', $tableName); + if ($schemaName) { + $sql .= $this->_adapter->quoteInto(' AND UPPER(c.tabschema) = UPPER(?)', $schemaName); + } + $sql .= " ORDER BY c.colno"; + + $desc = array(); + $stmt = $this->_adapter->query($sql); + + /** + * To avoid case issues, fetch using FETCH_NUM + */ + $result = $stmt->fetchAll(Zend_Db::FETCH_NUM); + + /** + * The ordering of columns is defined by the query so we can map + * to variables to improve readability + */ + $tabschema = 0; + $tabname = 1; + $colname = 2; + $colno = 3; + $typename = 4; + $default = 5; + $nulls = 6; + $length = 7; + $scale = 8; + $identityCol = 9; + $tabconstype = 10; + $colseq = 11; + + foreach ($result as $key => $row) { + list ($primary, $primaryPosition, $identity) = array(false, null, false); + if ($row[$tabconstype] == 'P') { + $primary = true; + $primaryPosition = $row[$colseq]; + } + /** + * In IBM DB2, an column can be IDENTITY + * even if it is not part of the PRIMARY KEY. + */ + if ($row[$identityCol] == 'Y') { + $identity = true; + } + + $desc[$this->_adapter->foldCase($row[$colname])] = array( + 'SCHEMA_NAME' => $this->_adapter->foldCase($row[$tabschema]), + 'TABLE_NAME' => $this->_adapter->foldCase($row[$tabname]), + 'COLUMN_NAME' => $this->_adapter->foldCase($row[$colname]), + 'COLUMN_POSITION' => $row[$colno]+1, + 'DATA_TYPE' => $row[$typename], + 'DEFAULT' => $row[$default], + 'NULLABLE' => (bool) ($row[$nulls] == 'Y'), + 'LENGTH' => $row[$length], + 'SCALE' => $row[$scale], + 'PRECISION' => ($row[$typename] == 'DECIMAL' ? $row[$length] : 0), + 'UNSIGNED' => false, + 'PRIMARY' => $primary, + 'PRIMARY_POSITION' => $primaryPosition, + 'IDENTITY' => $identity + ); + } + + return $desc; + } + + /** + * Adds a DB2-specific LIMIT clause to the SELECT statement. + * + * @param string $sql + * @param integer $count + * @param integer $offset OPTIONAL + * @throws Zend_Db_Adapter_Exception + * @return string + */ + public function limit($sql, $count, $offset = 0) + { + $count = intval($count); + if ($count < 0) { + /** @see Zend_Db_Adapter_Exception */ + require_once 'Zend/Db/Adapter/Exception.php'; + throw new Zend_Db_Adapter_Exception("LIMIT argument count=$count is not valid"); + } else { + $offset = intval($offset); + if ($offset < 0) { + /** @see Zend_Db_Adapter_Exception */ + require_once 'Zend/Db/Adapter/Exception.php'; + throw new Zend_Db_Adapter_Exception("LIMIT argument offset=$offset is not valid"); + } + + if ($offset == 0 && $count > 0) { + $limit_sql = $sql . " FETCH FIRST $count ROWS ONLY"; + return $limit_sql; + } + /** + * DB2 does not implement the LIMIT clause as some RDBMS do. + * We have to simulate it with subqueries and ROWNUM. + * Unfortunately because we use the column wildcard "*", + * this puts an extra column into the query result set. + */ + $limit_sql = "SELECT z2.* + FROM ( + SELECT ROW_NUMBER() OVER() AS \"ZEND_DB_ROWNUM\", z1.* + FROM ( + " . $sql . " + ) z1 + ) z2 + WHERE z2.zend_db_rownum BETWEEN " . ($offset+1) . " AND " . ($offset+$count); + } + return $limit_sql; + } + + /** + * DB2-specific last sequence id + * + * @param string $sequenceName + * @return integer + */ + public function lastSequenceId($sequenceName) + { + $sql = 'SELECT PREVVAL FOR '.$this->_adapter->quoteIdentifier($sequenceName).' AS VAL FROM SYSIBM.SYSDUMMY1'; + $value = $this->_adapter->fetchOne($sql); + return $value; + } + + /** + * DB2-specific sequence id value + * + * @param string $sequenceName + * @return integer + */ + public function nextSequenceId($sequenceName) + { + $sql = 'SELECT NEXTVAL FOR '.$this->_adapter->quoteIdentifier($sequenceName).' AS VAL FROM SYSIBM.SYSDUMMY1'; + $value = $this->_adapter->fetchOne($sql); + return $value; + } +} diff --git a/library/Zend/Db/Adapter/Pdo/Ibm/Ids.php b/library/Zend/Db/Adapter/Pdo/Ibm/Ids.php new file mode 100644 index 000000000..7c710b45d --- /dev/null +++ b/library/Zend/Db/Adapter/Pdo/Ibm/Ids.php @@ -0,0 +1,301 @@ +_adapter = $adapter; + } + + /** + * Returns a list of the tables in the database. + * + * @return array + */ + public function listTables() + { + $sql = "SELECT tabname " + . "FROM systables "; + + return $this->_adapter->fetchCol($sql); + } + + /** + * IDS catalog lookup for describe table + * + * @param string $tableName + * @param string $schemaName OPTIONAL + * @return array + */ + public function describeTable($tableName, $schemaName = null) + { + // this is still a work in progress + + $sql= "SELECT DISTINCT t.owner, t.tabname, c.colname, c.colno, c.coltype, + d.default, c.collength, t.tabid + FROM syscolumns c + JOIN systables t ON c.tabid = t.tabid + LEFT JOIN sysdefaults d ON c.tabid = d.tabid AND c.colno = d.colno + WHERE " + . $this->_adapter->quoteInto('UPPER(t.tabname) = UPPER(?)', $tableName); + if ($schemaName) { + $sql .= $this->_adapter->quoteInto(' AND UPPER(t.owner) = UPPER(?)', $schemaName); + } + $sql .= " ORDER BY c.colno"; + + $desc = array(); + $stmt = $this->_adapter->query($sql); + + $result = $stmt->fetchAll(Zend_Db::FETCH_NUM); + + /** + * The ordering of columns is defined by the query so we can map + * to variables to improve readability + */ + $tabschema = 0; + $tabname = 1; + $colname = 2; + $colno = 3; + $typename = 4; + $default = 5; + $length = 6; + $tabid = 7; + + $primaryCols = null; + + foreach ($result as $key => $row) { + $primary = false; + $primaryPosition = null; + + if (!$primaryCols) { + $primaryCols = $this->_getPrimaryInfo($row[$tabid]); + } + + if (array_key_exists($row[$colno], $primaryCols)) { + $primary = true; + $primaryPosition = $primaryCols[$row[$colno]]; + } + + $identity = false; + if ($row[$typename] == 6 + 256 || + $row[$typename] == 18 + 256) { + $identity = true; + } + + $desc[$this->_adapter->foldCase($row[$colname])] = array ( + 'SCHEMA_NAME' => $this->_adapter->foldCase($row[$tabschema]), + 'TABLE_NAME' => $this->_adapter->foldCase($row[$tabname]), + 'COLUMN_NAME' => $this->_adapter->foldCase($row[$colname]), + 'COLUMN_POSITION' => $row[$colno], + 'DATA_TYPE' => $this->_getDataType($row[$typename]), + 'DEFAULT' => $row[$default], + 'NULLABLE' => (bool) !($row[$typename] - 256 >= 0), + 'LENGTH' => $row[$length], + 'SCALE' => ($row[$typename] == 5 ? $row[$length]&255 : 0), + 'PRECISION' => ($row[$typename] == 5 ? (int)($row[$length]/256) : 0), + 'UNSIGNED' => false, + 'PRIMARY' => $primary, + 'PRIMARY_POSITION' => $primaryPosition, + 'IDENTITY' => $identity + ); + } + + return $desc; + } + + /** + * Map number representation of a data type + * to a string + * + * @param int $typeNo + * @return string + */ + protected function _getDataType($typeNo) + { + $typemap = array( + 0 => "CHAR", + 1 => "SMALLINT", + 2 => "INTEGER", + 3 => "FLOAT", + 4 => "SMALLFLOAT", + 5 => "DECIMAL", + 6 => "SERIAL", + 7 => "DATE", + 8 => "MONEY", + 9 => "NULL", + 10 => "DATETIME", + 11 => "BYTE", + 12 => "TEXT", + 13 => "VARCHAR", + 14 => "INTERVAL", + 15 => "NCHAR", + 16 => "NVARCHAR", + 17 => "INT8", + 18 => "SERIAL8", + 19 => "SET", + 20 => "MULTISET", + 21 => "LIST", + 22 => "Unnamed ROW", + 40 => "Variable-length opaque type", + 4118 => "Named ROW" + ); + + if ($typeNo - 256 >= 0) { + $typeNo = $typeNo - 256; + } + + return $typemap[$typeNo]; + } + + /** + * Helper method to retrieve primary key column + * and column location + * + * @param int $tabid + * @return array + */ + protected function _getPrimaryInfo($tabid) + { + $sql = "SELECT i.part1, i.part2, i.part3, i.part4, i.part5, i.part6, + i.part7, i.part8, i.part9, i.part10, i.part11, i.part12, + i.part13, i.part14, i.part15, i.part16 + FROM sysindexes i + JOIN sysconstraints c ON c.idxname = i.idxname + WHERE i.tabid = " . $tabid . " AND c.constrtype = 'P'"; + + $stmt = $this->_adapter->query($sql); + $results = $stmt->fetchAll(); + + $cols = array(); + + // this should return only 1 row + // unless there is no primary key, + // in which case, the empty array is returned + if ($results) { + $row = $results[0]; + } else { + return $cols; + } + + $position = 0; + foreach ($row as $key => $colno) { + $position++; + if ($colno == 0) { + return $cols; + } else { + $cols[$colno] = $position; + } + } + } + + /** + * Adds an IDS-specific LIMIT clause to the SELECT statement. + * + * @param string $sql + * @param integer $count + * @param integer $offset OPTIONAL + * @throws Zend_Db_Adapter_Exception + * @return string + */ + public function limit($sql, $count, $offset = 0) + { + $count = intval($count); + if ($count < 0) { + /** @see Zend_Db_Adapter_Exception */ + require_once 'Zend/Db/Adapter/Exception.php'; + throw new Zend_Db_Adapter_Exception("LIMIT argument count=$count is not valid"); + } else if ($count == 0) { + $limit_sql = str_ireplace("SELECT", "SELECT * FROM (SELECT", $sql); + $limit_sql .= ") WHERE 0 = 1"; + } else { + $offset = intval($offset); + if ($offset < 0) { + /** @see Zend_Db_Adapter_Exception */ + require_once 'Zend/Db/Adapter/Exception.php'; + throw new Zend_Db_Adapter_Exception("LIMIT argument offset=$offset is not valid"); + } + if ($offset == 0) { + $limit_sql = str_ireplace("SELECT", "SELECT FIRST $count", $sql); + } else { + $limit_sql = str_ireplace("SELECT", "SELECT SKIP $offset LIMIT $count", $sql); + } + } + return $limit_sql; + } + + /** + * IDS-specific last sequence id + * + * @param string $sequenceName + * @return integer + */ + public function lastSequenceId($sequenceName) + { + $sql = 'SELECT '.$this->_adapter->quoteIdentifier($sequenceName).'.CURRVAL FROM ' + .'systables WHERE tabid = 1'; + $value = $this->_adapter->fetchOne($sql); + return $value; + } + + /** + * IDS-specific sequence id value + * + * @param string $sequenceName + * @return integer + */ + public function nextSequenceId($sequenceName) + { + $sql = 'SELECT '.$this->_adapter->quoteIdentifier($sequenceName).'.NEXTVAL FROM ' + .'systables WHERE tabid = 1'; + $value = $this->_adapter->fetchOne($sql); + return $value; + } +} diff --git a/library/Zend/Db/Adapter/Pdo/Mssql.php b/library/Zend/Db/Adapter/Pdo/Mssql.php new file mode 100644 index 000000000..3c3fb7710 --- /dev/null +++ b/library/Zend/Db/Adapter/Pdo/Mssql.php @@ -0,0 +1,423 @@ + Zend_Db::INT_TYPE, + Zend_Db::BIGINT_TYPE => Zend_Db::BIGINT_TYPE, + Zend_Db::FLOAT_TYPE => Zend_Db::FLOAT_TYPE, + 'INT' => Zend_Db::INT_TYPE, + 'SMALLINT' => Zend_Db::INT_TYPE, + 'TINYINT' => Zend_Db::INT_TYPE, + 'BIGINT' => Zend_Db::BIGINT_TYPE, + 'DECIMAL' => Zend_Db::FLOAT_TYPE, + 'FLOAT' => Zend_Db::FLOAT_TYPE, + 'MONEY' => Zend_Db::FLOAT_TYPE, + 'NUMERIC' => Zend_Db::FLOAT_TYPE, + 'REAL' => Zend_Db::FLOAT_TYPE, + 'SMALLMONEY' => Zend_Db::FLOAT_TYPE + ); + + /** + * Creates a PDO DSN for the adapter from $this->_config settings. + * + * @return string + */ + protected function _dsn() + { + // baseline of DSN parts + $dsn = $this->_config; + + // don't pass the username and password in the DSN + unset($dsn['username']); + unset($dsn['password']); + unset($dsn['options']); + unset($dsn['persistent']); + unset($dsn['driver_options']); + + if (isset($dsn['port'])) { + $seperator = ':'; + if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { + $seperator = ','; + } + $dsn['host'] .= $seperator . $dsn['port']; + unset($dsn['port']); + } + + // this driver supports multiple DSN prefixes + // @see http://www.php.net/manual/en/ref.pdo-dblib.connection.php + if (isset($dsn['pdoType'])) { + switch (strtolower($dsn['pdoType'])) { + case 'freetds': + case 'sybase': + $this->_pdoType = 'sybase'; + break; + case 'mssql': + $this->_pdoType = 'mssql'; + break; + case 'dblib': + default: + $this->_pdoType = 'dblib'; + break; + } + unset($dsn['pdoType']); + } + + // use all remaining parts in the DSN + foreach ($dsn as $key => $val) { + $dsn[$key] = "$key=$val"; + } + + $dsn = $this->_pdoType . ':' . implode(';', $dsn); + return $dsn; + } + + /** + * @return void + */ + protected function _connect() + { + if ($this->_connection) { + return; + } + parent::_connect(); + $this->_connection->exec('SET QUOTED_IDENTIFIER ON'); + } + + /** + * Begin a transaction. + * + * It is necessary to override the abstract PDO transaction functions here, as + * the PDO driver for MSSQL does not support transactions. + */ + protected function _beginTransaction() + { + $this->_connect(); + $this->_connection->exec('BEGIN TRANSACTION'); + return true; + } + + /** + * Commit a transaction. + * + * It is necessary to override the abstract PDO transaction functions here, as + * the PDO driver for MSSQL does not support transactions. + */ + protected function _commit() + { + $this->_connect(); + $this->_connection->exec('COMMIT TRANSACTION'); + return true; + } + + /** + * Roll-back a transaction. + * + * It is necessary to override the abstract PDO transaction functions here, as + * the PDO driver for MSSQL does not support transactions. + */ + protected function _rollBack() { + $this->_connect(); + $this->_connection->exec('ROLLBACK TRANSACTION'); + return true; + } + + /** + * Returns a list of the tables in the database. + * + * @return array + */ + public function listTables() + { + $sql = "SELECT name FROM sysobjects WHERE type = 'U' ORDER BY name"; + return $this->fetchCol($sql); + } + + /** + * Returns the column descriptions for a table. + * + * The return value is an associative array keyed by the column name, + * as returned by the RDBMS. + * + * The value of each array element is an associative array + * with the following keys: + * + * SCHEMA_NAME => string; name of database or schema + * TABLE_NAME => string; + * COLUMN_NAME => string; column name + * COLUMN_POSITION => number; ordinal position of column in table + * DATA_TYPE => string; SQL datatype name of column + * DEFAULT => string; default expression of column, null if none + * NULLABLE => boolean; true if column can have nulls + * LENGTH => number; length of CHAR/VARCHAR + * SCALE => number; scale of NUMERIC/DECIMAL + * PRECISION => number; precision of NUMERIC/DECIMAL + * UNSIGNED => boolean; unsigned property of an integer type + * PRIMARY => boolean; true if column is part of the primary key + * PRIMARY_POSITION => integer; position of column in primary key + * PRIMARY_AUTO => integer; position of auto-generated column in primary key + * + * @todo Discover column primary key position. + * @todo Discover integer unsigned property. + * + * @param string $tableName + * @param string $schemaName OPTIONAL + * @return array + */ + public function describeTable($tableName, $schemaName = null) + { + if ($schemaName != null) { + if (strpos($schemaName, '.') !== false) { + $result = explode('.', $schemaName); + $schemaName = $result[1]; + } + } + /** + * Discover metadata information about this table. + */ + $sql = "exec sp_columns @table_name = " . $this->quoteIdentifier($tableName, true); + if ($schemaName != null) { + $sql .= ", @table_owner = " . $this->quoteIdentifier($schemaName, true); + } + + $stmt = $this->query($sql); + $result = $stmt->fetchAll(Zend_Db::FETCH_NUM); + + $table_name = 2; + $column_name = 3; + $type_name = 5; + $precision = 6; + $length = 7; + $scale = 8; + $nullable = 10; + $column_def = 12; + $column_position = 16; + + /** + * Discover primary key column(s) for this table. + */ + $sql = "exec sp_pkeys @table_name = " . $this->quoteIdentifier($tableName, true); + if ($schemaName != null) { + $sql .= ", @table_owner = " . $this->quoteIdentifier($schemaName, true); + } + + $stmt = $this->query($sql); + $primaryKeysResult = $stmt->fetchAll(Zend_Db::FETCH_NUM); + $primaryKeyColumn = array(); + $pkey_column_name = 3; + $pkey_key_seq = 4; + foreach ($primaryKeysResult as $pkeysRow) { + $primaryKeyColumn[$pkeysRow[$pkey_column_name]] = $pkeysRow[$pkey_key_seq]; + } + + $desc = array(); + $p = 1; + foreach ($result as $key => $row) { + $identity = false; + $words = explode(' ', $row[$type_name], 2); + if (isset($words[0])) { + $type = $words[0]; + if (isset($words[1])) { + $identity = (bool) preg_match('/identity/', $words[1]); + } + } + + $isPrimary = array_key_exists($row[$column_name], $primaryKeyColumn); + if ($isPrimary) { + $primaryPosition = $primaryKeyColumn[$row[$column_name]]; + } else { + $primaryPosition = null; + } + + $desc[$this->foldCase($row[$column_name])] = array( + 'SCHEMA_NAME' => null, // @todo + 'TABLE_NAME' => $this->foldCase($row[$table_name]), + 'COLUMN_NAME' => $this->foldCase($row[$column_name]), + 'COLUMN_POSITION' => (int) $row[$column_position], + 'DATA_TYPE' => $type, + 'DEFAULT' => $row[$column_def], + 'NULLABLE' => (bool) $row[$nullable], + 'LENGTH' => $row[$length], + 'SCALE' => $row[$scale], + 'PRECISION' => $row[$precision], + 'UNSIGNED' => null, // @todo + 'PRIMARY' => $isPrimary, + 'PRIMARY_POSITION' => $primaryPosition, + 'IDENTITY' => $identity + ); + } + return $desc; + } + + /** + * Adds an adapter-specific LIMIT clause to the SELECT statement. + * + * @link http://lists.bestpractical.com/pipermail/rt-devel/2005-June/007339.html + * + * @param string $sql + * @param integer $count + * @param integer $offset OPTIONAL + * @throws Zend_Db_Adapter_Exception + * @return string + */ + public function limit($sql, $count, $offset = 0) + { + $count = intval($count); + if ($count <= 0) { + /** @see Zend_Db_Adapter_Exception */ + require_once 'Zend/Db/Adapter/Exception.php'; + throw new Zend_Db_Adapter_Exception("LIMIT argument count=$count is not valid"); + } + + $offset = intval($offset); + if ($offset < 0) { + /** @see Zend_Db_Adapter_Exception */ + require_once 'Zend/Db/Adapter/Exception.php'; + throw new Zend_Db_Adapter_Exception("LIMIT argument offset=$offset is not valid"); + } + + $sql = preg_replace( + '/^SELECT\s+(DISTINCT\s)?/i', + 'SELECT $1TOP ' . ($count+$offset) . ' ', + $sql + ); + + if ($offset > 0) { + $orderby = stristr($sql, 'ORDER BY'); + + if ($orderby !== false) { + $orderParts = explode(',', substr($orderby, 8)); + $pregReplaceCount = null; + $orderbyInverseParts = array(); + foreach ($orderParts as $orderPart) { + $orderPart = rtrim($orderPart); + $inv = preg_replace('/\s+desc$/i', ' ASC', $orderPart, 1, $pregReplaceCount); + if ($pregReplaceCount) { + $orderbyInverseParts[] = $inv; + continue; + } + $inv = preg_replace('/\s+asc$/i', ' DESC', $orderPart, 1, $pregReplaceCount); + if ($pregReplaceCount) { + $orderbyInverseParts[] = $inv; + continue; + } else { + $orderbyInverseParts[] = $orderPart . ' DESC'; + } + } + + $orderbyInverse = 'ORDER BY ' . implode(', ', $orderbyInverseParts); + } + + + + + $sql = 'SELECT * FROM (SELECT TOP ' . $count . ' * FROM (' . $sql . ') AS inner_tbl'; + if ($orderby !== false) { + $sql .= ' ' . $orderbyInverse . ' '; + } + $sql .= ') AS outer_tbl'; + if ($orderby !== false) { + $sql .= ' ' . $orderby; + } + } + + return $sql; + } + + /** + * Gets the last ID generated automatically by an IDENTITY/AUTOINCREMENT column. + * + * As a convention, on RDBMS brands that support sequences + * (e.g. Oracle, PostgreSQL, DB2), this method forms the name of a sequence + * from the arguments and returns the last id generated by that sequence. + * On RDBMS brands that support IDENTITY/AUTOINCREMENT columns, this method + * returns the last value generated for such a column, and the table name + * argument is disregarded. + * + * Microsoft SQL Server does not support sequences, so the arguments to + * this method are ignored. + * + * @param string $tableName OPTIONAL Name of table. + * @param string $primaryKey OPTIONAL Name of primary key column. + * @return string + * @throws Zend_Db_Adapter_Exception + */ + public function lastInsertId($tableName = null, $primaryKey = null) + { + $sql = 'SELECT SCOPE_IDENTITY()'; + return (int)$this->fetchOne($sql); + } + + /** + * Retrieve server version in PHP style + * Pdo_Mssql doesn't support getAttribute(PDO::ATTR_SERVER_VERSION) + * @return string + */ + public function getServerVersion() + { + try { + $stmt = $this->query("SELECT SERVERPROPERTY('productversion')"); + $result = $stmt->fetchAll(Zend_Db::FETCH_NUM); + if (count($result)) { + return $result[0][0]; + } + return null; + } catch (PDOException $e) { + return null; + } + } +} \ No newline at end of file diff --git a/library/Zend/Db/Adapter/Pdo/Mysql.php b/library/Zend/Db/Adapter/Pdo/Mysql.php new file mode 100644 index 000000000..768b88711 --- /dev/null +++ b/library/Zend/Db/Adapter/Pdo/Mysql.php @@ -0,0 +1,257 @@ + Zend_Db::INT_TYPE, + Zend_Db::BIGINT_TYPE => Zend_Db::BIGINT_TYPE, + Zend_Db::FLOAT_TYPE => Zend_Db::FLOAT_TYPE, + 'INT' => Zend_Db::INT_TYPE, + 'INTEGER' => Zend_Db::INT_TYPE, + 'MEDIUMINT' => Zend_Db::INT_TYPE, + 'SMALLINT' => Zend_Db::INT_TYPE, + 'TINYINT' => Zend_Db::INT_TYPE, + 'BIGINT' => Zend_Db::BIGINT_TYPE, + 'SERIAL' => Zend_Db::BIGINT_TYPE, + 'DEC' => Zend_Db::FLOAT_TYPE, + 'DECIMAL' => Zend_Db::FLOAT_TYPE, + 'DOUBLE' => Zend_Db::FLOAT_TYPE, + 'DOUBLE PRECISION' => Zend_Db::FLOAT_TYPE, + 'FIXED' => Zend_Db::FLOAT_TYPE, + 'FLOAT' => Zend_Db::FLOAT_TYPE + ); + + /** + * Creates a PDO object and connects to the database. + * + * @return void + * @throws Zend_Db_Adapter_Exception + */ + protected function _connect() + { + if ($this->_connection) { + return; + } + + if (!empty($this->_config['charset'])) { + $initCommand = "SET NAMES '" . $this->_config['charset'] . "'"; + $this->_config['driver_options'][1002] = $initCommand; // 1002 = PDO::MYSQL_ATTR_INIT_COMMAND + } + + parent::_connect(); + } + + /** + * @return string + */ + public function getQuoteIdentifierSymbol() + { + return "`"; + } + + /** + * Returns a list of the tables in the database. + * + * @return array + */ + public function listTables() + { + return $this->fetchCol('SHOW TABLES'); + } + + /** + * Returns the column descriptions for a table. + * + * The return value is an associative array keyed by the column name, + * as returned by the RDBMS. + * + * The value of each array element is an associative array + * with the following keys: + * + * SCHEMA_NAME => string; name of database or schema + * TABLE_NAME => string; + * COLUMN_NAME => string; column name + * COLUMN_POSITION => number; ordinal position of column in table + * DATA_TYPE => string; SQL datatype name of column + * DEFAULT => string; default expression of column, null if none + * NULLABLE => boolean; true if column can have nulls + * LENGTH => number; length of CHAR/VARCHAR + * SCALE => number; scale of NUMERIC/DECIMAL + * PRECISION => number; precision of NUMERIC/DECIMAL + * UNSIGNED => boolean; unsigned property of an integer type + * PRIMARY => boolean; true if column is part of the primary key + * PRIMARY_POSITION => integer; position of column in primary key + * IDENTITY => integer; true if column is auto-generated with unique values + * + * @param string $tableName + * @param string $schemaName OPTIONAL + * @return array + */ + public function describeTable($tableName, $schemaName = null) + { + // @todo use INFORMATION_SCHEMA someday when MySQL's + // implementation has reasonably good performance and + // the version with this improvement is in wide use. + + if ($schemaName) { + $sql = 'DESCRIBE ' . $this->quoteIdentifier("$schemaName.$tableName", true); + } else { + $sql = 'DESCRIBE ' . $this->quoteIdentifier($tableName, true); + } + $stmt = $this->query($sql); + + // Use FETCH_NUM so we are not dependent on the CASE attribute of the PDO connection + $result = $stmt->fetchAll(Zend_Db::FETCH_NUM); + + $field = 0; + $type = 1; + $null = 2; + $key = 3; + $default = 4; + $extra = 5; + + $desc = array(); + $i = 1; + $p = 1; + foreach ($result as $row) { + list($length, $scale, $precision, $unsigned, $primary, $primaryPosition, $identity) + = array(null, null, null, null, false, null, false); + if (preg_match('/unsigned/', $row[$type])) { + $unsigned = true; + } + if (preg_match('/^((?:var)?char)\((\d+)\)/', $row[$type], $matches)) { + $row[$type] = $matches[1]; + $length = $matches[2]; + } else if (preg_match('/^decimal\((\d+),(\d+)\)/', $row[$type], $matches)) { + $row[$type] = 'decimal'; + $precision = $matches[1]; + $scale = $matches[2]; + } else if (preg_match('/^float\((\d+),(\d+)\)/', $row[$type], $matches)) { + $row[$type] = 'float'; + $precision = $matches[1]; + $scale = $matches[2]; + } else if (preg_match('/^((?:big|medium|small|tiny)?int)\((\d+)\)/', $row[$type], $matches)) { + $row[$type] = $matches[1]; + // The optional argument of a MySQL int type is not precision + // or length; it is only a hint for display width. + } + if (strtoupper($row[$key]) == 'PRI') { + $primary = true; + $primaryPosition = $p; + if ($row[$extra] == 'auto_increment') { + $identity = true; + } else { + $identity = false; + } + ++$p; + } + $desc[$this->foldCase($row[$field])] = array( + 'SCHEMA_NAME' => null, // @todo + 'TABLE_NAME' => $this->foldCase($tableName), + 'COLUMN_NAME' => $this->foldCase($row[$field]), + 'COLUMN_POSITION' => $i, + 'DATA_TYPE' => $row[$type], + 'DEFAULT' => $row[$default], + 'NULLABLE' => (bool) ($row[$null] == 'YES'), + 'LENGTH' => $length, + 'SCALE' => $scale, + 'PRECISION' => $precision, + 'UNSIGNED' => $unsigned, + 'PRIMARY' => $primary, + 'PRIMARY_POSITION' => $primaryPosition, + 'IDENTITY' => $identity + ); + ++$i; + } + return $desc; + } + + /** + * Adds an adapter-specific LIMIT clause to the SELECT statement. + * + * @param string $sql + * @param integer $count + * @param integer $offset OPTIONAL + * @throws Zend_Db_Adapter_Exception + * @return string + */ + public function limit($sql, $count, $offset = 0) + { + $count = intval($count); + if ($count <= 0) { + /** @see Zend_Db_Adapter_Exception */ + require_once 'Zend/Db/Adapter/Exception.php'; + throw new Zend_Db_Adapter_Exception("LIMIT argument count=$count is not valid"); + } + + $offset = intval($offset); + if ($offset < 0) { + /** @see Zend_Db_Adapter_Exception */ + require_once 'Zend/Db/Adapter/Exception.php'; + throw new Zend_Db_Adapter_Exception("LIMIT argument offset=$offset is not valid"); + } + + $sql .= " LIMIT $count"; + if ($offset > 0) { + $sql .= " OFFSET $offset"; + } + + return $sql; + } + +} diff --git a/library/Zend/Db/Adapter/Pdo/Oci.php b/library/Zend/Db/Adapter/Pdo/Oci.php new file mode 100644 index 000000000..195cdd337 --- /dev/null +++ b/library/Zend/Db/Adapter/Pdo/Oci.php @@ -0,0 +1,378 @@ + Zend_Db::INT_TYPE, + Zend_Db::BIGINT_TYPE => Zend_Db::BIGINT_TYPE, + Zend_Db::FLOAT_TYPE => Zend_Db::FLOAT_TYPE, + 'BINARY_DOUBLE' => Zend_Db::FLOAT_TYPE, + 'BINARY_FLOAT' => Zend_Db::FLOAT_TYPE, + 'NUMBER' => Zend_Db::FLOAT_TYPE + ); + + /** + * Creates a PDO DSN for the adapter from $this->_config settings. + * + * @return string + */ + protected function _dsn() + { + // baseline of DSN parts + $dsn = $this->_config; + + if (isset($dsn['host'])) { + $tns = 'dbname=(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)' . + '(HOST=' . $dsn['host'] . ')'; + + if (isset($dsn['port'])) { + $tns .= '(PORT=' . $dsn['port'] . ')'; + } else { + $tns .= '(PORT=1521)'; + } + + $tns .= '))(CONNECT_DATA=(SID=' . $dsn['dbname'] . ')))'; + } else { + $tns = 'dbname=' . $dsn['dbname']; + } + + if (isset($dsn['charset'])) { + $tns .= ';charset=' . $dsn['charset']; + } + + return $this->_pdoType . ':' . $tns; + } + + /** + * Quote a raw string. + * Most PDO drivers have an implementation for the quote() method, + * but the Oracle OCI driver must use the same implementation as the + * Zend_Db_Adapter_Abstract class. + * + * @param string $value Raw string + * @return string Quoted string + */ + protected function _quote($value) + { + if (is_int($value) || is_float($value)) { + return $value; + } + $value = str_replace("'", "''", $value); + return "'" . addcslashes($value, "\000\n\r\\\032") . "'"; + } + + /** + * Quote a table identifier and alias. + * + * @param string|array|Zend_Db_Expr $ident The identifier or expression. + * @param string $alias An alias for the table. + * @return string The quoted identifier and alias. + */ + public function quoteTableAs($ident, $alias = null, $auto = false) + { + // Oracle doesn't allow the 'AS' keyword between the table identifier/expression and alias. + return $this->_quoteIdentifierAs($ident, $alias, $auto, ' '); + } + + /** + * Returns a list of the tables in the database. + * + * @return array + */ + public function listTables() + { + $data = $this->fetchCol('SELECT table_name FROM all_tables'); + return $data; + } + + /** + * Returns the column descriptions for a table. + * + * The return value is an associative array keyed by the column name, + * as returned by the RDBMS. + * + * The value of each array element is an associative array + * with the following keys: + * + * SCHEMA_NAME => string; name of schema + * TABLE_NAME => string; + * COLUMN_NAME => string; column name + * COLUMN_POSITION => number; ordinal position of column in table + * DATA_TYPE => string; SQL datatype name of column + * DEFAULT => string; default expression of column, null if none + * NULLABLE => boolean; true if column can have nulls + * LENGTH => number; length of CHAR/VARCHAR + * SCALE => number; scale of NUMERIC/DECIMAL + * PRECISION => number; precision of NUMERIC/DECIMAL + * UNSIGNED => boolean; unsigned property of an integer type + * PRIMARY => boolean; true if column is part of the primary key + * PRIMARY_POSITION => integer; position of column in primary key + * IDENTITY => integer; true if column is auto-generated with unique values + * + * @todo Discover integer unsigned property. + * + * @param string $tableName + * @param string $schemaName OPTIONAL + * @return array + */ + public function describeTable($tableName, $schemaName = null) + { + $version = $this->getServerVersion(); + if (($version === null) || version_compare($version, '9.0.0', '>=')) { + $sql = "SELECT TC.TABLE_NAME, TC.OWNER, TC.COLUMN_NAME, TC.DATA_TYPE, + TC.DATA_DEFAULT, TC.NULLABLE, TC.COLUMN_ID, TC.DATA_LENGTH, + TC.DATA_SCALE, TC.DATA_PRECISION, C.CONSTRAINT_TYPE, CC.POSITION + FROM ALL_TAB_COLUMNS TC + LEFT JOIN (ALL_CONS_COLUMNS CC JOIN ALL_CONSTRAINTS C + ON (CC.CONSTRAINT_NAME = C.CONSTRAINT_NAME AND CC.TABLE_NAME = C.TABLE_NAME AND CC.OWNER = C.OWNER AND C.CONSTRAINT_TYPE = 'P')) + ON TC.TABLE_NAME = CC.TABLE_NAME AND TC.COLUMN_NAME = CC.COLUMN_NAME + WHERE UPPER(TC.TABLE_NAME) = UPPER(:TBNAME)"; + $bind[':TBNAME'] = $tableName; + if ($schemaName) { + $sql .= ' AND UPPER(TC.OWNER) = UPPER(:SCNAME)'; + $bind[':SCNAME'] = $schemaName; + } + $sql .= ' ORDER BY TC.COLUMN_ID'; + } else { + $subSql="SELECT AC.OWNER, AC.TABLE_NAME, ACC.COLUMN_NAME, AC.CONSTRAINT_TYPE, ACC.POSITION + from ALL_CONSTRAINTS AC, ALL_CONS_COLUMNS ACC + WHERE ACC.CONSTRAINT_NAME = AC.CONSTRAINT_NAME + AND ACC.TABLE_NAME = AC.TABLE_NAME + AND ACC.OWNER = AC.OWNER + AND AC.CONSTRAINT_TYPE = 'P' + AND UPPER(AC.TABLE_NAME) = UPPER(:TBNAME)"; + $bind[':TBNAME'] = $tableName; + if ($schemaName) { + $subSql .= ' AND UPPER(ACC.OWNER) = UPPER(:SCNAME)'; + $bind[':SCNAME'] = $schemaName; + } + $sql="SELECT TC.TABLE_NAME, TC.OWNER, TC.COLUMN_NAME, TC.DATA_TYPE, + TC.DATA_DEFAULT, TC.NULLABLE, TC.COLUMN_ID, TC.DATA_LENGTH, + TC.DATA_SCALE, TC.DATA_PRECISION, CC.CONSTRAINT_TYPE, CC.POSITION + FROM ALL_TAB_COLUMNS TC, ($subSql) CC + WHERE UPPER(TC.TABLE_NAME) = UPPER(:TBNAME) + AND TC.OWNER = CC.OWNER(+) AND TC.TABLE_NAME = CC.TABLE_NAME(+) AND TC.COLUMN_NAME = CC.COLUMN_NAME(+)"; + if ($schemaName) { + $sql .= ' AND UPPER(TC.OWNER) = UPPER(:SCNAME)'; + } + $sql .= ' ORDER BY TC.COLUMN_ID'; + } + + $stmt = $this->query($sql, $bind); + + /** + * Use FETCH_NUM so we are not dependent on the CASE attribute of the PDO connection + */ + $result = $stmt->fetchAll(Zend_Db::FETCH_NUM); + + $table_name = 0; + $owner = 1; + $column_name = 2; + $data_type = 3; + $data_default = 4; + $nullable = 5; + $column_id = 6; + $data_length = 7; + $data_scale = 8; + $data_precision = 9; + $constraint_type = 10; + $position = 11; + + $desc = array(); + foreach ($result as $key => $row) { + list ($primary, $primaryPosition, $identity) = array(false, null, false); + if ($row[$constraint_type] == 'P') { + $primary = true; + $primaryPosition = $row[$position]; + /** + * Oracle does not support auto-increment keys. + */ + $identity = false; + } + $desc[$this->foldCase($row[$column_name])] = array( + 'SCHEMA_NAME' => $this->foldCase($row[$owner]), + 'TABLE_NAME' => $this->foldCase($row[$table_name]), + 'COLUMN_NAME' => $this->foldCase($row[$column_name]), + 'COLUMN_POSITION' => $row[$column_id], + 'DATA_TYPE' => $row[$data_type], + 'DEFAULT' => $row[$data_default], + 'NULLABLE' => (bool) ($row[$nullable] == 'Y'), + 'LENGTH' => $row[$data_length], + 'SCALE' => $row[$data_scale], + 'PRECISION' => $row[$data_precision], + 'UNSIGNED' => null, // @todo + 'PRIMARY' => $primary, + 'PRIMARY_POSITION' => $primaryPosition, + 'IDENTITY' => $identity + ); + } + return $desc; + } + + /** + * Return the most recent value from the specified sequence in the database. + * This is supported only on RDBMS brands that support sequences + * (e.g. Oracle, PostgreSQL, DB2). Other RDBMS brands return null. + * + * @param string $sequenceName + * @return integer + */ + public function lastSequenceId($sequenceName) + { + $this->_connect(); + $value = $this->fetchOne('SELECT '.$this->quoteIdentifier($sequenceName, true).'.CURRVAL FROM dual'); + return $value; + } + + /** + * Generate a new value from the specified sequence in the database, and return it. + * This is supported only on RDBMS brands that support sequences + * (e.g. Oracle, PostgreSQL, DB2). Other RDBMS brands return null. + * + * @param string $sequenceName + * @return integer + */ + public function nextSequenceId($sequenceName) + { + $this->_connect(); + $value = $this->fetchOne('SELECT '.$this->quoteIdentifier($sequenceName, true).'.NEXTVAL FROM dual'); + return $value; + } + + /** + * Gets the last ID generated automatically by an IDENTITY/AUTOINCREMENT column. + * + * As a convention, on RDBMS brands that support sequences + * (e.g. Oracle, PostgreSQL, DB2), this method forms the name of a sequence + * from the arguments and returns the last id generated by that sequence. + * On RDBMS brands that support IDENTITY/AUTOINCREMENT columns, this method + * returns the last value generated for such a column, and the table name + * argument is disregarded. + * + * Oracle does not support IDENTITY columns, so if the sequence is not + * specified, this method returns null. + * + * @param string $tableName OPTIONAL Name of table. + * @param string $primaryKey OPTIONAL Name of primary key column. + * @return string + * @throws Zend_Db_Adapter_Oracle_Exception + */ + public function lastInsertId($tableName = null, $primaryKey = null) + { + if ($tableName !== null) { + $sequenceName = $tableName; + if ($primaryKey) { + $sequenceName .= $this->foldCase("_$primaryKey"); + } + $sequenceName .= $this->foldCase('_seq'); + return $this->lastSequenceId($sequenceName); + } + // No support for IDENTITY columns; return null + return null; + } + + /** + * Adds an adapter-specific LIMIT clause to the SELECT statement. + * + * @param string $sql + * @param integer $count + * @param integer $offset + * @throws Zend_Db_Adapter_Exception + * @return string + */ + public function limit($sql, $count, $offset = 0) + { + $count = intval($count); + if ($count <= 0) { + /** @see Zend_Db_Adapter_Exception */ + require_once 'Zend/Db/Adapter/Exception.php'; + throw new Zend_Db_Adapter_Exception("LIMIT argument count=$count is not valid"); + } + + $offset = intval($offset); + if ($offset < 0) { + /** @see Zend_Db_Adapter_Exception */ + require_once 'Zend/Db/Adapter/Exception.php'; + throw new Zend_Db_Adapter_Exception("LIMIT argument offset=$offset is not valid"); + } + + /** + * Oracle does not implement the LIMIT clause as some RDBMS do. + * We have to simulate it with subqueries and ROWNUM. + * Unfortunately because we use the column wildcard "*", + * this puts an extra column into the query result set. + */ + $limit_sql = "SELECT z2.* + FROM ( + SELECT z1.*, ROWNUM AS \"zend_db_rownum\" + FROM ( + " . $sql . " + ) z1 + ) z2 + WHERE z2.\"zend_db_rownum\" BETWEEN " . ($offset+1) . " AND " . ($offset+$count); + return $limit_sql; + } + +} diff --git a/library/Zend/Db/Adapter/Pdo/Pgsql.php b/library/Zend/Db/Adapter/Pdo/Pgsql.php new file mode 100644 index 000000000..d4fb17451 --- /dev/null +++ b/library/Zend/Db/Adapter/Pdo/Pgsql.php @@ -0,0 +1,330 @@ + Zend_Db::INT_TYPE, + Zend_Db::BIGINT_TYPE => Zend_Db::BIGINT_TYPE, + Zend_Db::FLOAT_TYPE => Zend_Db::FLOAT_TYPE, + 'INTEGER' => Zend_Db::INT_TYPE, + 'SERIAL' => Zend_Db::INT_TYPE, + 'SMALLINT' => Zend_Db::INT_TYPE, + 'BIGINT' => Zend_Db::BIGINT_TYPE, + 'BIGSERIAL' => Zend_Db::BIGINT_TYPE, + 'DECIMAL' => Zend_Db::FLOAT_TYPE, + 'DOUBLE PRECISION' => Zend_Db::FLOAT_TYPE, + 'NUMERIC' => Zend_Db::FLOAT_TYPE, + 'REAL' => Zend_Db::FLOAT_TYPE + ); + + /** + * Creates a PDO object and connects to the database. + * + * @return void + * @throws Zend_Db_Adapter_Exception + */ + protected function _connect() + { + if ($this->_connection) { + return; + } + + parent::_connect(); + + if (!empty($this->_config['charset'])) { + $sql = "SET NAMES '" . $this->_config['charset'] . "'"; + $this->_connection->exec($sql); + } + } + + /** + * Returns a list of the tables in the database. + * + * @return array + */ + public function listTables() + { + // @todo use a better query with joins instead of subqueries + $sql = "SELECT c.relname AS table_name " + . "FROM pg_class c, pg_user u " + . "WHERE c.relowner = u.usesysid AND c.relkind = 'r' " + . "AND NOT EXISTS (SELECT 1 FROM pg_views WHERE viewname = c.relname) " + . "AND c.relname !~ '^(pg_|sql_)' " + . "UNION " + . "SELECT c.relname AS table_name " + . "FROM pg_class c " + . "WHERE c.relkind = 'r' " + . "AND NOT EXISTS (SELECT 1 FROM pg_views WHERE viewname = c.relname) " + . "AND NOT EXISTS (SELECT 1 FROM pg_user WHERE usesysid = c.relowner) " + . "AND c.relname !~ '^pg_'"; + + return $this->fetchCol($sql); + } + + /** + * Returns the column descriptions for a table. + * + * The return value is an associative array keyed by the column name, + * as returned by the RDBMS. + * + * The value of each array element is an associative array + * with the following keys: + * + * SCHEMA_NAME => string; name of database or schema + * TABLE_NAME => string; + * COLUMN_NAME => string; column name + * COLUMN_POSITION => number; ordinal position of column in table + * DATA_TYPE => string; SQL datatype name of column + * DEFAULT => string; default expression of column, null if none + * NULLABLE => boolean; true if column can have nulls + * LENGTH => number; length of CHAR/VARCHAR + * SCALE => number; scale of NUMERIC/DECIMAL + * PRECISION => number; precision of NUMERIC/DECIMAL + * UNSIGNED => boolean; unsigned property of an integer type + * PRIMARY => boolean; true if column is part of the primary key + * PRIMARY_POSITION => integer; position of column in primary key + * IDENTITY => integer; true if column is auto-generated with unique values + * + * @todo Discover integer unsigned property. + * + * @param string $tableName + * @param string $schemaName OPTIONAL + * @return array + */ + public function describeTable($tableName, $schemaName = null) + { + $sql = "SELECT + a.attnum, + n.nspname, + c.relname, + a.attname AS colname, + t.typname AS type, + a.atttypmod, + FORMAT_TYPE(a.atttypid, a.atttypmod) AS complete_type, + d.adsrc AS default_value, + a.attnotnull AS notnull, + a.attlen AS length, + co.contype, + ARRAY_TO_STRING(co.conkey, ',') AS conkey + FROM pg_attribute AS a + JOIN pg_class AS c ON a.attrelid = c.oid + JOIN pg_namespace AS n ON c.relnamespace = n.oid + JOIN pg_type AS t ON a.atttypid = t.oid + LEFT OUTER JOIN pg_constraint AS co ON (co.conrelid = c.oid + AND a.attnum = ANY(co.conkey) AND co.contype = 'p') + LEFT OUTER JOIN pg_attrdef AS d ON d.adrelid = c.oid AND d.adnum = a.attnum + WHERE a.attnum > 0 AND c.relname = ".$this->quote($tableName); + if ($schemaName) { + $sql .= " AND n.nspname = ".$this->quote($schemaName); + } + $sql .= ' ORDER BY a.attnum'; + + $stmt = $this->query($sql); + + // Use FETCH_NUM so we are not dependent on the CASE attribute of the PDO connection + $result = $stmt->fetchAll(Zend_Db::FETCH_NUM); + + $attnum = 0; + $nspname = 1; + $relname = 2; + $colname = 3; + $type = 4; + $atttypemod = 5; + $complete_type = 6; + $default_value = 7; + $notnull = 8; + $length = 9; + $contype = 10; + $conkey = 11; + + $desc = array(); + foreach ($result as $key => $row) { + $defaultValue = $row[$default_value]; + if ($row[$type] == 'varchar' || $row[$type] == 'bpchar' ) { + if (preg_match('/character(?: varying)?(?:\((\d+)\))?/', $row[$complete_type], $matches)) { + if (isset($matches[1])) { + $row[$length] = $matches[1]; + } else { + $row[$length] = null; // unlimited + } + } + if (preg_match("/^'(.*?)'::(?:character varying|bpchar)$/", $defaultValue, $matches)) { + $defaultValue = $matches[1]; + } + } + list($primary, $primaryPosition, $identity) = array(false, null, false); + if ($row[$contype] == 'p') { + $primary = true; + $primaryPosition = array_search($row[$attnum], explode(',', $row[$conkey])) + 1; + $identity = (bool) (preg_match('/^nextval/', $row[$default_value])); + } + $desc[$this->foldCase($row[$colname])] = array( + 'SCHEMA_NAME' => $this->foldCase($row[$nspname]), + 'TABLE_NAME' => $this->foldCase($row[$relname]), + 'COLUMN_NAME' => $this->foldCase($row[$colname]), + 'COLUMN_POSITION' => $row[$attnum], + 'DATA_TYPE' => $row[$type], + 'DEFAULT' => $defaultValue, + 'NULLABLE' => (bool) ($row[$notnull] != 't'), + 'LENGTH' => $row[$length], + 'SCALE' => null, // @todo + 'PRECISION' => null, // @todo + 'UNSIGNED' => null, // @todo + 'PRIMARY' => $primary, + 'PRIMARY_POSITION' => $primaryPosition, + 'IDENTITY' => $identity + ); + } + return $desc; + } + + + /** + * Adds an adapter-specific LIMIT clause to the SELECT statement. + * + * @param string $sql + * @param integer $count + * @param integer $offset OPTIONAL + * @return string + */ + public function limit($sql, $count, $offset = 0) + { + $count = intval($count); + if ($count <= 0) { + /** + * @see Zend_Db_Adapter_Exception + */ + require_once 'Zend/Db/Adapter/Exception.php'; + throw new Zend_Db_Adapter_Exception("LIMIT argument count=$count is not valid"); + } + + $offset = intval($offset); + if ($offset < 0) { + /** + * @see Zend_Db_Adapter_Exception + */ + require_once 'Zend/Db/Adapter/Exception.php'; + throw new Zend_Db_Adapter_Exception("LIMIT argument offset=$offset is not valid"); + } + + $sql .= " LIMIT $count"; + if ($offset > 0) { + $sql .= " OFFSET $offset"; + } + + return $sql; + } + + /** + * Return the most recent value from the specified sequence in the database. + * This is supported only on RDBMS brands that support sequences + * (e.g. Oracle, PostgreSQL, DB2). Other RDBMS brands return null. + * + * @param string $sequenceName + * @return string + */ + public function lastSequenceId($sequenceName) + { + $this->_connect(); + $value = $this->fetchOne("SELECT CURRVAL(".$this->quote($sequenceName).")"); + return $value; + } + + /** + * Generate a new value from the specified sequence in the database, and return it. + * This is supported only on RDBMS brands that support sequences + * (e.g. Oracle, PostgreSQL, DB2). Other RDBMS brands return null. + * + * @param string $sequenceName + * @return string + */ + public function nextSequenceId($sequenceName) + { + $this->_connect(); + $value = $this->fetchOne("SELECT NEXTVAL(".$this->quote($sequenceName).")"); + return $value; + } + + /** + * Gets the last ID generated automatically by an IDENTITY/AUTOINCREMENT column. + * + * As a convention, on RDBMS brands that support sequences + * (e.g. Oracle, PostgreSQL, DB2), this method forms the name of a sequence + * from the arguments and returns the last id generated by that sequence. + * On RDBMS brands that support IDENTITY/AUTOINCREMENT columns, this method + * returns the last value generated for such a column, and the table name + * argument is disregarded. + * + * @param string $tableName OPTIONAL Name of table. + * @param string $primaryKey OPTIONAL Name of primary key column. + * @return string + */ + public function lastInsertId($tableName = null, $primaryKey = null) + { + if ($tableName !== null) { + $sequenceName = $tableName; + if ($primaryKey) { + $sequenceName .= "_$primaryKey"; + } + $sequenceName .= '_seq'; + return $this->lastSequenceId($sequenceName); + } + return $this->_connection->lastInsertId($tableName); + } + +} diff --git a/library/Zend/Db/Adapter/Pdo/Sqlite.php b/library/Zend/Db/Adapter/Pdo/Sqlite.php new file mode 100644 index 000000000..afc26c53e --- /dev/null +++ b/library/Zend/Db/Adapter/Pdo/Sqlite.php @@ -0,0 +1,297 @@ + Zend_Db::INT_TYPE, + Zend_Db::BIGINT_TYPE => Zend_Db::BIGINT_TYPE, + Zend_Db::FLOAT_TYPE => Zend_Db::FLOAT_TYPE, + 'INTEGER' => Zend_Db::BIGINT_TYPE, + 'REAL' => Zend_Db::FLOAT_TYPE + ); + + /** + * Constructor. + * + * $config is an array of key/value pairs containing configuration + * options. Note that the SQLite options are different than most of + * the other PDO adapters in that no username or password are needed. + * Also, an extra config key "sqlite2" specifies compatibility mode. + * + * dbname => (string) The name of the database to user (required, + * use :memory: for memory-based database) + * + * sqlite2 => (boolean) PDO_SQLITE defaults to SQLite 3. For compatibility + * with an older SQLite 2 database, set this to TRUE. + * + * @param array $config An array of configuration keys. + */ + public function __construct(array $config = array()) + { + if (isset($config['sqlite2']) && $config['sqlite2']) { + $this->_pdoType = 'sqlite2'; + } + + // SQLite uses no username/password. Stub to satisfy parent::_connect() + $this->_config['username'] = null; + $this->_config['password'] = null; + + return parent::__construct($config); + } + + /** + * Check for config options that are mandatory. + * Throw exceptions if any are missing. + * + * @param array $config + * @throws Zend_Db_Adapter_Exception + */ + protected function _checkRequiredOptions(array $config) + { + // we need at least a dbname + if (! array_key_exists('dbname', $config)) { + /** @see Zend_Db_Adapter_Exception */ + require_once 'Zend/Db/Adapter/Exception.php'; + throw new Zend_Db_Adapter_Exception("Configuration array must have a key for 'dbname' that names the database instance"); + } + } + + /** + * DSN builder + */ + protected function _dsn() + { + return $this->_pdoType .':'. $this->_config['dbname']; + } + + /** + * Special configuration for SQLite behavior: make sure that result sets + * contain keys like 'column' instead of 'table.column'. + * + * @throws Zend_Db_Adapter_Exception + */ + protected function _connect() + { + /** + * if we already have a PDO object, no need to re-connect. + */ + if ($this->_connection) { + return; + } + + parent::_connect(); + + $retval = $this->_connection->exec('PRAGMA full_column_names=0'); + if ($retval === false) { + $error = $this->_connection->errorInfo(); + /** @see Zend_Db_Adapter_Exception */ + require_once 'Zend/Db/Adapter/Exception.php'; + throw new Zend_Db_Adapter_Exception($error[2]); + } + + $retval = $this->_connection->exec('PRAGMA short_column_names=1'); + if ($retval === false) { + $error = $this->_connection->errorInfo(); + /** @see Zend_Db_Adapter_Exception */ + require_once 'Zend/Db/Adapter/Exception.php'; + throw new Zend_Db_Adapter_Exception($error[2]); + } + } + + /** + * Returns a list of the tables in the database. + * + * @return array + */ + public function listTables() + { + $sql = "SELECT name FROM sqlite_master WHERE type='table' " + . "UNION ALL SELECT name FROM sqlite_temp_master " + . "WHERE type='table' ORDER BY name"; + + return $this->fetchCol($sql); + } + + /** + * Returns the column descriptions for a table. + * + * The return value is an associative array keyed by the column name, + * as returned by the RDBMS. + * + * The value of each array element is an associative array + * with the following keys: + * + * SCHEMA_NAME => string; name of database or schema + * TABLE_NAME => string; + * COLUMN_NAME => string; column name + * COLUMN_POSITION => number; ordinal position of column in table + * DATA_TYPE => string; SQL datatype name of column + * DEFAULT => string; default expression of column, null if none + * NULLABLE => boolean; true if column can have nulls + * LENGTH => number; length of CHAR/VARCHAR + * SCALE => number; scale of NUMERIC/DECIMAL + * PRECISION => number; precision of NUMERIC/DECIMAL + * UNSIGNED => boolean; unsigned property of an integer type + * PRIMARY => boolean; true if column is part of the primary key + * PRIMARY_POSITION => integer; position of column in primary key + * IDENTITY => integer; true if column is auto-generated with unique values + * + * @param string $tableName + * @param string $schemaName OPTIONAL + * @return array + */ + public function describeTable($tableName, $schemaName = null) + { + $sql = 'PRAGMA '; + + if ($schemaName) { + $sql .= $this->quoteIdentifier($schemaName) . '.'; + } + + $sql .= 'table_info('.$this->quoteIdentifier($tableName).')'; + + $stmt = $this->query($sql); + + /** + * Use FETCH_NUM so we are not dependent on the CASE attribute of the PDO connection + */ + $result = $stmt->fetchAll(Zend_Db::FETCH_NUM); + + $cid = 0; + $name = 1; + $type = 2; + $notnull = 3; + $dflt_value = 4; + $pk = 5; + + $desc = array(); + + $p = 1; + foreach ($result as $key => $row) { + list($length, $scale, $precision, $primary, $primaryPosition, $identity) = + array(null, null, null, false, null, false); + if (preg_match('/^((?:var)?char)\((\d+)\)/i', $row[$type], $matches)) { + $row[$type] = $matches[1]; + $length = $matches[2]; + } else if (preg_match('/^decimal\((\d+),(\d+)\)/i', $row[$type], $matches)) { + $row[$type] = 'DECIMAL'; + $precision = $matches[1]; + $scale = $matches[2]; + } + if ((bool) $row[$pk]) { + $primary = true; + $primaryPosition = $p; + /** + * SQLite INTEGER primary key is always auto-increment. + */ + $identity = (bool) ($row[$type] == 'INTEGER'); + ++$p; + } + $desc[$this->foldCase($row[$name])] = array( + 'SCHEMA_NAME' => $this->foldCase($schemaName), + 'TABLE_NAME' => $this->foldCase($tableName), + 'COLUMN_NAME' => $this->foldCase($row[$name]), + 'COLUMN_POSITION' => $row[$cid]+1, + 'DATA_TYPE' => $row[$type], + 'DEFAULT' => $row[$dflt_value], + 'NULLABLE' => ! (bool) $row[$notnull], + 'LENGTH' => $length, + 'SCALE' => $scale, + 'PRECISION' => $precision, + 'UNSIGNED' => null, // Sqlite3 does not support unsigned data + 'PRIMARY' => $primary, + 'PRIMARY_POSITION' => $primaryPosition, + 'IDENTITY' => $identity + ); + } + return $desc; + } + + /** + * Adds an adapter-specific LIMIT clause to the SELECT statement. + * + * @param string $sql + * @param integer $count + * @param integer $offset OPTIONAL + * @return string + */ + public function limit($sql, $count, $offset = 0) + { + $count = intval($count); + if ($count <= 0) { + /** @see Zend_Db_Adapter_Exception */ + require_once 'Zend/Db/Adapter/Exception.php'; + throw new Zend_Db_Adapter_Exception("LIMIT argument count=$count is not valid"); + } + + $offset = intval($offset); + if ($offset < 0) { + /** @see Zend_Db_Adapter_Exception */ + require_once 'Zend/Db/Adapter/Exception.php'; + throw new Zend_Db_Adapter_Exception("LIMIT argument offset=$offset is not valid"); + } + + $sql .= " LIMIT $count"; + if ($offset > 0) { + $sql .= " OFFSET $offset"; + } + + return $sql; + } + +} diff --git a/library/Zend/Db/Adapter/Sqlsrv.php b/library/Zend/Db/Adapter/Sqlsrv.php new file mode 100644 index 000000000..6cccc9f5e --- /dev/null +++ b/library/Zend/Db/Adapter/Sqlsrv.php @@ -0,0 +1,668 @@ + (string) Connect to the database as this username. + * password => (string) Password associated with the username. + * dbname => The name of the local SQL Server instance + * + * @var array + */ + protected $_config = array( + 'dbname' => null, + 'username' => null, + 'password' => null, + ); + + /** + * Last insert id from INSERT query + * + * @var int + */ + protected $_lastInsertId; + + /** + * Query used to fetch last insert id + * + * @var string + */ + protected $_lastInsertSQL = 'SELECT SCOPE_IDENTITY() as Current_Identity'; + + /** + * Keys are UPPERCASE SQL datatypes or the constants + * Zend_Db::INT_TYPE, Zend_Db::BIGINT_TYPE, or Zend_Db::FLOAT_TYPE. + * + * Values are: + * 0 = 32-bit integer + * 1 = 64-bit integer + * 2 = float or decimal + * + * @var array Associative array of datatypes to values 0, 1, or 2. + */ + protected $_numericDataTypes = array( + Zend_Db::INT_TYPE => Zend_Db::INT_TYPE, + Zend_Db::BIGINT_TYPE => Zend_Db::BIGINT_TYPE, + Zend_Db::FLOAT_TYPE => Zend_Db::FLOAT_TYPE, + 'INT' => Zend_Db::INT_TYPE, + 'SMALLINT' => Zend_Db::INT_TYPE, + 'TINYINT' => Zend_Db::INT_TYPE, + 'BIGINT' => Zend_Db::BIGINT_TYPE, + 'DECIMAL' => Zend_Db::FLOAT_TYPE, + 'FLOAT' => Zend_Db::FLOAT_TYPE, + 'MONEY' => Zend_Db::FLOAT_TYPE, + 'NUMERIC' => Zend_Db::FLOAT_TYPE, + 'REAL' => Zend_Db::FLOAT_TYPE, + 'SMALLMONEY' => Zend_Db::FLOAT_TYPE, + ); + + /** + * Default class name for a DB statement. + * + * @var string + */ + protected $_defaultStmtClass = 'Zend_Db_Statement_Sqlsrv'; + + /** + * Creates a connection resource. + * + * @return void + * @throws Zend_Db_Adapter_Sqlsrv_Exception + */ + protected function _connect() + { + if (is_resource($this->_connection)) { + // connection already exists + return; + } + + if (!extension_loaded('sqlsrv')) { + /** + * @see Zend_Db_Adapter_Sqlsrv_Exception + */ + require_once 'Zend/Db/Adapter/Sqlsrv/Exception.php'; + throw new Zend_Db_Adapter_Sqlsrv_Exception('The Sqlsrv extension is required for this adapter but the extension is not loaded'); + } + + $serverName = $this->_config['host']; + if (isset($this->_config['port'])) { + $port = (integer) $this->_config['port']; + $serverName .= ', ' . $port; + } + + $connectionInfo = array( + 'Database' => $this->_config['dbname'], + ); + + if (isset($this->_config['username']) && isset($this->_config['password'])) + { + $connectionInfo += array( + 'UID' => $this->_config['username'], + 'PWD' => $this->_config['password'], + ); + } + // else - windows authentication + + if (!empty($this->_config['driver_options'])) { + foreach ($this->_config['driver_options'] as $option => $value) { + // A value may be a constant. + if (is_string($value)) { + $constantName = strtoupper($value); + if (defined($constantName)) { + $connectionInfo[$option] = constant($constantName); + } else { + $connectionInfo[$option] = $value; + } + } + } + } + + $this->_connection = sqlsrv_connect($serverName, $connectionInfo); + + if (!$this->_connection) { + /** + * @see Zend_Db_Adapter_Sqlsrv_Exception + */ + require_once 'Zend/Db/Adapter/Sqlsrv/Exception.php'; + throw new Zend_Db_Adapter_Sqlsrv_Exception(sqlsrv_errors()); + } + } + + /** + * Check for config options that are mandatory. + * Throw exceptions if any are missing. + * + * @param array $config + * @throws Zend_Db_Adapter_Exception + */ + protected function _checkRequiredOptions(array $config) + { + // we need at least a dbname + if (! array_key_exists('dbname', $config)) { + /** @see Zend_Db_Adapter_Exception */ + require_once 'Zend/Db/Adapter/Exception.php'; + throw new Zend_Db_Adapter_Exception("Configuration array must have a key for 'dbname' that names the database instance"); + } + + if (! array_key_exists('password', $config) && array_key_exists('username', $config)) { + /** + * @see Zend_Db_Adapter_Exception + */ + require_once 'Zend/Db/Adapter/Exception.php'; + throw new Zend_Db_Adapter_Exception("Configuration array must have a key for 'password' for login credentials. + If Windows Authentication is desired, both keys 'username' and 'password' should be ommited from config."); + } + + if (array_key_exists('password', $config) && !array_key_exists('username', $config)) { + /** + * @see Zend_Db_Adapter_Exception + */ + require_once 'Zend/Db/Adapter/Exception.php'; + throw new Zend_Db_Adapter_Exception("Configuration array must have a key for 'username' for login credentials. + If Windows Authentication is desired, both keys 'username' and 'password' should be ommited from config."); + } + } + + /** + * Set the transaction isoltion level. + * + * @param integer|null $level A fetch mode from SQLSRV_TXN_*. + * @return true + * @throws Zend_Db_Adapter_Sqlsrv_Exception + */ + public function setTransactionIsolationLevel($level = null) + { + $this->_connect(); + $sql = null; + + // Default transaction level in sql server + if ($level === null) + { + $level = SQLSRV_TXN_READ_COMMITTED; + } + + switch ($level) { + case SQLSRV_TXN_READ_UNCOMMITTED: + $sql = "READ UNCOMMITTED"; + break; + case SQLSRV_TXN_READ_COMMITTED: + $sql = "READ COMMITTED"; + break; + case SQLSRV_TXN_REPEATABLE_READ: + $sql = "REPEATABLE READ"; + break; + case SQLSRV_TXN_SNAPSHOT: + $sql = "SNAPSHOT"; + break; + case SQLSRV_TXN_SERIALIZABLE: + $sql = "SERIALIZABLE"; + break; + default: + require_once 'Zend/Db/Adapter/Sqlsrv/Exception.php'; + throw new Zend_Db_Adapter_Sqlsrv_Exception("Invalid transaction isolation level mode '$level' specified"); + } + + if (!sqlsrv_query($this->_connection, "SET TRANSACTION ISOLATION LEVEL $sql;")) { + require_once 'Zend/Db/Adapter/Sqlsrv/Exception.php'; + throw new Zend_Db_Adapter_Sqlsrv_Exception("Transaction cannot be changed to '$level'"); + } + + return true; + } + + /** + * Test if a connection is active + * + * @return boolean + */ + public function isConnected() + { + return (is_resource($this->_connection) + && (get_resource_type($this->_connection) == 'SQL Server Connection') + ); + } + + /** + * Force the connection to close. + * + * @return void + */ + public function closeConnection() + { + if ($this->isConnected()) { + sqlsrv_close($this->_connection); + } + $this->_connection = null; + } + + /** + * Returns an SQL statement for preparation. + * + * @param string $sql The SQL statement with placeholders. + * @return Zend_Db_Statement_Sqlsrv + */ + public function prepare($sql) + { + $this->_connect(); + $stmtClass = $this->_defaultStmtClass; + + if (!class_exists($stmtClass)) { + /** + * @see Zend_Loader + */ + require_once 'Zend/Loader.php'; + Zend_Loader::loadClass($stmtClass); + } + + $stmt = new $stmtClass($this, $sql); + $stmt->setFetchMode($this->_fetchMode); + return $stmt; + } + + /** + * Quote a raw string. + * + * @param string $value Raw string + * @return string Quoted string + */ + protected function _quote($value) + { + if (is_int($value)) { + return $value; + } elseif (is_float($value)) { + return sprintf('%F', $value); + } + + return "'" . str_replace("'", "''", $value) . "'"; + } + + /** + * Gets the last ID generated automatically by an IDENTITY/AUTOINCREMENT column. + * + * As a convention, on RDBMS brands that support sequences + * (e.g. Oracle, PostgreSQL, DB2), this method forms the name of a sequence + * from the arguments and returns the last id generated by that sequence. + * On RDBMS brands that support IDENTITY/AUTOINCREMENT columns, this method + * returns the last value generated for such a column, and the table name + * argument is disregarded. + * + * @param string $tableName OPTIONAL Name of table. + * @param string $primaryKey OPTIONAL Name of primary key column. + * @return string + */ + public function lastInsertId($tableName = null, $primaryKey = null) + { + if ($tableName) { + $tableName = $this->quote($tableName); + $sql = 'SELECT IDENT_CURRENT (' . $tableName . ') as Current_Identity'; + return (string) $this->fetchOne($sql); + } + + if ($this->_lastInsertId > 0) { + return (string) $this->_lastInsertId; + } + + $sql = $this->_lastInsertSQL; + return (string) $this->fetchOne($sql); + } + + /** + * Inserts a table row with specified data. + * + * @param mixed $table The table to insert data into. + * @param array $bind Column-value pairs. + * @return int The number of affected rows. + */ + public function insert($table, array $bind) + { + // extract and quote col names from the array keys + $cols = array(); + $vals = array(); + foreach ($bind as $col => $val) { + $cols[] = $this->quoteIdentifier($col, true); + if ($val instanceof Zend_Db_Expr) { + $vals[] = $val->__toString(); + unset($bind[$col]); + } else { + $vals[] = '?'; + } + } + + // build the statement + $sql = "INSERT INTO " + . $this->quoteIdentifier($table, true) + . ' (' . implode(', ', $cols) . ') ' + . 'VALUES (' . implode(', ', $vals) . ')' + . ' ' . $this->_lastInsertSQL; + + // execute the statement and return the number of affected rows + $stmt = $this->query($sql, array_values($bind)); + $result = $stmt->rowCount(); + + $stmt->nextRowset(); + + $this->_lastInsertId = $stmt->fetchColumn(); + + return $result; + } + + /** + * Returns a list of the tables in the database. + * + * @return array + */ + public function listTables() + { + $this->_connect(); + $sql = "SELECT name FROM sysobjects WHERE type = 'U' ORDER BY name"; + return $this->fetchCol($sql); + } + + /** + * Returns the column descriptions for a table. + * + * The return value is an associative array keyed by the column name, + * as returned by the RDBMS. + * + * The value of each array element is an associative array + * with the following keys: + * + * SCHEMA_NAME => string; name of schema + * TABLE_NAME => string; + * COLUMN_NAME => string; column name + * COLUMN_POSITION => number; ordinal position of column in table + * DATA_TYPE => string; SQL datatype name of column + * DEFAULT => string; default expression of column, null if none + * NULLABLE => boolean; true if column can have nulls + * LENGTH => number; length of CHAR/VARCHAR + * SCALE => number; scale of NUMERIC/DECIMAL + * PRECISION => number; precision of NUMERIC/DECIMAL + * UNSIGNED => boolean; unsigned property of an integer type + * PRIMARY => boolean; true if column is part of the primary key + * PRIMARY_POSITION => integer; position of column in primary key + * IDENTITY => integer; true if column is auto-generated with unique values + * + * @todo Discover integer unsigned property. + * + * @param string $tableName + * @param string $schemaName OPTIONAL + * @return array + */ + public function describeTable($tableName, $schemaName = null) + { + /** + * Discover metadata information about this table. + */ + $sql = "exec sp_columns @table_name = " . $this->quoteIdentifier($tableName, true); + $stmt = $this->query($sql); + $result = $stmt->fetchAll(Zend_Db::FETCH_NUM); + + $owner = 1; + $table_name = 2; + $column_name = 3; + $type_name = 5; + $precision = 6; + $length = 7; + $scale = 8; + $nullable = 10; + $column_def = 12; + $column_position = 16; + + /** + * Discover primary key column(s) for this table. + */ + $tableOwner = $result[0][$owner]; + $sql = "exec sp_pkeys @table_owner = " . $tableOwner + . ", @table_name = " . $this->quoteIdentifier($tableName, true); + $stmt = $this->query($sql); + + $primaryKeysResult = $stmt->fetchAll(Zend_Db::FETCH_NUM); + $primaryKeyColumn = array(); + + // Per http://msdn.microsoft.com/en-us/library/ms189813.aspx, + // results from sp_keys stored procedure are: + // 0=TABLE_QUALIFIER 1=TABLE_OWNER 2=TABLE_NAME 3=COLUMN_NAME 4=KEY_SEQ 5=PK_NAME + + $pkey_column_name = 3; + $pkey_key_seq = 4; + foreach ($primaryKeysResult as $pkeysRow) { + $primaryKeyColumn[$pkeysRow[$pkey_column_name]] = $pkeysRow[$pkey_key_seq]; + } + + $desc = array(); + $p = 1; + foreach ($result as $key => $row) { + $identity = false; + $words = explode(' ', $row[$type_name], 2); + if (isset($words[0])) { + $type = $words[0]; + if (isset($words[1])) { + $identity = (bool) preg_match('/identity/', $words[1]); + } + } + + $isPrimary = array_key_exists($row[$column_name], $primaryKeyColumn); + if ($isPrimary) { + $primaryPosition = $primaryKeyColumn[$row[$column_name]]; + } else { + $primaryPosition = null; + } + + $desc[$this->foldCase($row[$column_name])] = array( + 'SCHEMA_NAME' => null, // @todo + 'TABLE_NAME' => $this->foldCase($row[$table_name]), + 'COLUMN_NAME' => $this->foldCase($row[$column_name]), + 'COLUMN_POSITION' => (int) $row[$column_position], + 'DATA_TYPE' => $type, + 'DEFAULT' => $row[$column_def], + 'NULLABLE' => (bool) $row[$nullable], + 'LENGTH' => $row[$length], + 'SCALE' => $row[$scale], + 'PRECISION' => $row[$precision], + 'UNSIGNED' => null, // @todo + 'PRIMARY' => $isPrimary, + 'PRIMARY_POSITION' => $primaryPosition, + 'IDENTITY' => $identity, + ); + } + + return $desc; + } + + /** + * Leave autocommit mode and begin a transaction. + * + * @return void + * @throws Zend_Db_Adapter_Sqlsrv_Exception + */ + protected function _beginTransaction() + { + if (!sqlsrv_begin_transaction($this->_connection)) { + require_once 'Zend/Db/Adapter/Sqlsrv/Exception.php'; + throw new Zend_Db_Adapter_Sqlsrv_Exception(sqlsrv_errors()); + } + } + + /** + * Commit a transaction and return to autocommit mode. + * + * @return void + * @throws Zend_Db_Adapter_Sqlsrv_Exception + */ + protected function _commit() + { + if (!sqlsrv_commit($this->_connection)) { + require_once 'Zend/Db/Adapter/Sqlsrv/Exception.php'; + throw new Zend_Db_Adapter_Sqlsrv_Exception(sqlsrv_errors()); + } + } + + /** + * Roll back a transaction and return to autocommit mode. + * + * @return void + * @throws Zend_Db_Adapter_Sqlsrv_Exception + */ + protected function _rollBack() + { + if (!sqlsrv_rollback($this->_connection)) { + require_once 'Zend/Db/Adapter/Sqlsrv/Exception.php'; + throw new Zend_Db_Adapter_Sqlsrv_Exception(sqlsrv_errors()); + } + } + + /** + * Set the fetch mode. + * + * @todo Support FETCH_CLASS and FETCH_INTO. + * + * @param integer $mode A fetch mode. + * @return void + * @throws Zend_Db_Adapter_Sqlsrv_Exception + */ + public function setFetchMode($mode) + { + switch ($mode) { + case Zend_Db::FETCH_NUM: // seq array + case Zend_Db::FETCH_ASSOC: // assoc array + case Zend_Db::FETCH_BOTH: // seq+assoc array + case Zend_Db::FETCH_OBJ: // object + $this->_fetchMode = $mode; + break; + case Zend_Db::FETCH_BOUND: // bound to PHP variable + require_once 'Zend/Db/Adapter/Sqlsrv/Exception.php'; + throw new Zend_Db_Adapter_Sqlsrv_Exception('FETCH_BOUND is not supported yet'); + break; + default: + require_once 'Zend/Db/Adapter/Sqlsrv/Exception.php'; + throw new Zend_Db_Adapter_Sqlsrv_Exception("Invalid fetch mode '$mode' specified"); + break; + } + } + + /** + * Adds an adapter-specific LIMIT clause to the SELECT statement. + * + * @param string $sql + * @param integer $count + * @param integer $offset OPTIONAL + * @return string + * @throws Zend_Db_Adapter_Sqlsrv_Exception + */ + public function limit($sql, $count, $offset = 0) + { + $count = intval($count); + if ($count <= 0) { + require_once 'Zend/Db/Adapter/Exception.php'; + throw new Zend_Db_Adapter_Exception("LIMIT argument count=$count is not valid"); + } + + $offset = intval($offset); + if ($offset < 0) { + /** @see Zend_Db_Adapter_Exception */ + require_once 'Zend/Db/Adapter/Exception.php'; + throw new Zend_Db_Adapter_Exception("LIMIT argument offset=$offset is not valid"); + } + + if ($offset == 0) { + $sql = preg_replace('/^SELECT\s/i', 'SELECT TOP ' . $count . ' ', $sql); + } else { + $orderby = stristr($sql, 'ORDER BY'); + if ($orderby !== false) { + $sort = (stripos($orderby, ' desc') !== false) ? 'desc' : 'asc'; + $order = str_ireplace('ORDER BY', '', $orderby); + $order = trim(preg_replace('/\bASC\b|\bDESC\b/i', '', $order)); + } + + $sql = preg_replace('/^SELECT\s/i', 'SELECT TOP ' . ($count+$offset) . ' ', $sql); + + $sql = 'SELECT * FROM (SELECT TOP ' . $count . ' * FROM (' . $sql . ') AS inner_tbl'; + if ($orderby !== false) { + $innerOrder = preg_replace('/\".*\".\"(.*)\"/i', '"inner_tbl"."$1"', $order); + $sql .= ' ORDER BY ' . $innerOrder . ' '; + $sql .= (stripos($sort, 'asc') !== false) ? 'DESC' : 'ASC'; + } + $sql .= ') AS outer_tbl'; + if ($orderby !== false) { + $outerOrder = preg_replace('/\".*\".\"(.*)\"/i', '"outer_tbl"."$1"', $order); + $sql .= ' ORDER BY ' . $outerOrder . ' ' . $sort; + } + } + + return $sql; + } + + /** + * Check if the adapter supports real SQL parameters. + * + * @param string $type 'positional' or 'named' + * @return bool + */ + public function supportsParameters($type) + { + if ($type == 'positional') { + return true; + } + + // if its 'named' or anything else + return false; + } + + /** + * Retrieve server version in PHP style + * + * @return string + */ + public function getServerVersion() + { + $this->_connect(); + $version = sqlsrv_client_info($this->_connection); + + if ($version !== false) { + return $version['DriverVer']; + } + + return null; + } +} diff --git a/library/Zend/Db/Adapter/Sqlsrv/Exception.php b/library/Zend/Db/Adapter/Sqlsrv/Exception.php new file mode 100644 index 000000000..75fd14267 --- /dev/null +++ b/library/Zend/Db/Adapter/Sqlsrv/Exception.php @@ -0,0 +1,63 @@ +_expression = (string) $expression; + } + + /** + * @return string The string of the SQL expression stored in this object. + */ + public function __toString() + { + return $this->_expression; + } + +} diff --git a/library/Zend/Db/Profiler.php b/library/Zend/Db/Profiler.php new file mode 100644 index 000000000..007fa7888 --- /dev/null +++ b/library/Zend/Db/Profiler.php @@ -0,0 +1,471 @@ +setEnabled($enabled); + } + + /** + * Enable or disable the profiler. If $enable is false, the profiler + * is disabled and will not log any queries sent to it. + * + * @param boolean $enable + * @return Zend_Db_Profiler Provides a fluent interface + */ + public function setEnabled($enable) + { + $this->_enabled = (boolean) $enable; + + return $this; + } + + /** + * Get the current state of enable. If True is returned, + * the profiler is enabled. + * + * @return boolean + */ + public function getEnabled() + { + return $this->_enabled; + } + + /** + * Sets a minimum number of seconds for saving query profiles. If this + * is set, only those queries whose elapsed time is equal or greater than + * $minimumSeconds will be saved. To save all queries regardless of + * elapsed time, set $minimumSeconds to null. + * + * @param integer $minimumSeconds OPTIONAL + * @return Zend_Db_Profiler Provides a fluent interface + */ + public function setFilterElapsedSecs($minimumSeconds = null) + { + if (null === $minimumSeconds) { + $this->_filterElapsedSecs = null; + } else { + $this->_filterElapsedSecs = (integer) $minimumSeconds; + } + + return $this; + } + + /** + * Returns the minimum number of seconds for saving query profiles, or null if + * query profiles are saved regardless of elapsed time. + * + * @return integer|null + */ + public function getFilterElapsedSecs() + { + return $this->_filterElapsedSecs; + } + + /** + * Sets the types of query profiles to save. Set $queryType to one of + * the Zend_Db_Profiler::* constants to only save profiles for that type of + * query. To save more than one type, logical OR them together. To + * save all queries regardless of type, set $queryType to null. + * + * @param integer $queryTypes OPTIONAL + * @return Zend_Db_Profiler Provides a fluent interface + */ + public function setFilterQueryType($queryTypes = null) + { + $this->_filterTypes = $queryTypes; + + return $this; + } + + /** + * Returns the types of query profiles saved, or null if queries are saved regardless + * of their types. + * + * @return integer|null + * @see Zend_Db_Profiler::setFilterQueryType() + */ + public function getFilterQueryType() + { + return $this->_filterTypes; + } + + /** + * Clears the history of any past query profiles. This is relentless + * and will even clear queries that were started and may not have + * been marked as ended. + * + * @return Zend_Db_Profiler Provides a fluent interface + */ + public function clear() + { + $this->_queryProfiles = array(); + + return $this; + } + + /** + * @param integer $queryId + * @return integer or null + */ + public function queryClone(Zend_Db_Profiler_Query $query) + { + $this->_queryProfiles[] = clone $query; + + end($this->_queryProfiles); + + return key($this->_queryProfiles); + } + + /** + * Starts a query. Creates a new query profile object (Zend_Db_Profiler_Query) + * and returns the "query profiler handle". Run the query, then call + * queryEnd() and pass it this handle to make the query as ended and + * record the time. If the profiler is not enabled, this takes no + * action and immediately returns null. + * + * @param string $queryText SQL statement + * @param integer $queryType OPTIONAL Type of query, one of the Zend_Db_Profiler::* constants + * @return integer|null + */ + public function queryStart($queryText, $queryType = null) + { + if (!$this->_enabled) { + return null; + } + + // make sure we have a query type + if (null === $queryType) { + switch (strtolower(substr(ltrim($queryText), 0, 6))) { + case 'insert': + $queryType = self::INSERT; + break; + case 'update': + $queryType = self::UPDATE; + break; + case 'delete': + $queryType = self::DELETE; + break; + case 'select': + $queryType = self::SELECT; + break; + default: + $queryType = self::QUERY; + break; + } + } + + /** + * @see Zend_Db_Profiler_Query + */ + require_once 'Zend/Db/Profiler/Query.php'; + $this->_queryProfiles[] = new Zend_Db_Profiler_Query($queryText, $queryType); + + end($this->_queryProfiles); + + return key($this->_queryProfiles); + } + + /** + * Ends a query. Pass it the handle that was returned by queryStart(). + * This will mark the query as ended and save the time. + * + * @param integer $queryId + * @throws Zend_Db_Profiler_Exception + * @return void + */ + public function queryEnd($queryId) + { + // Don't do anything if the Zend_Db_Profiler is not enabled. + if (!$this->_enabled) { + return self::IGNORED; + } + + // Check for a valid query handle. + if (!isset($this->_queryProfiles[$queryId])) { + /** + * @see Zend_Db_Profiler_Exception + */ + require_once 'Zend/Db/Profiler/Exception.php'; + throw new Zend_Db_Profiler_Exception("Profiler has no query with handle '$queryId'."); + } + + $qp = $this->_queryProfiles[$queryId]; + + // Ensure that the query profile has not already ended + if ($qp->hasEnded()) { + /** + * @see Zend_Db_Profiler_Exception + */ + require_once 'Zend/Db/Profiler/Exception.php'; + throw new Zend_Db_Profiler_Exception("Query with profiler handle '$queryId' has already ended."); + } + + // End the query profile so that the elapsed time can be calculated. + $qp->end(); + + /** + * If filtering by elapsed time is enabled, only keep the profile if + * it ran for the minimum time. + */ + if (null !== $this->_filterElapsedSecs && $qp->getElapsedSecs() < $this->_filterElapsedSecs) { + unset($this->_queryProfiles[$queryId]); + return self::IGNORED; + } + + /** + * If filtering by query type is enabled, only keep the query if + * it was one of the allowed types. + */ + if (null !== $this->_filterTypes && !($qp->getQueryType() & $this->_filterTypes)) { + unset($this->_queryProfiles[$queryId]); + return self::IGNORED; + } + + return self::STORED; + } + + /** + * Get a profile for a query. Pass it the same handle that was returned + * by queryStart() and it will return a Zend_Db_Profiler_Query object. + * + * @param integer $queryId + * @throws Zend_Db_Profiler_Exception + * @return Zend_Db_Profiler_Query + */ + public function getQueryProfile($queryId) + { + if (!array_key_exists($queryId, $this->_queryProfiles)) { + /** + * @see Zend_Db_Profiler_Exception + */ + require_once 'Zend/Db/Profiler/Exception.php'; + throw new Zend_Db_Profiler_Exception("Query handle '$queryId' not found in profiler log."); + } + + return $this->_queryProfiles[$queryId]; + } + + /** + * Get an array of query profiles (Zend_Db_Profiler_Query objects). If $queryType + * is set to one of the Zend_Db_Profiler::* constants then only queries of that + * type will be returned. Normally, queries that have not yet ended will + * not be returned unless $showUnfinished is set to True. If no + * queries were found, False is returned. The returned array is indexed by the query + * profile handles. + * + * @param integer $queryType + * @param boolean $showUnfinished + * @return array|false + */ + public function getQueryProfiles($queryType = null, $showUnfinished = false) + { + $queryProfiles = array(); + foreach ($this->_queryProfiles as $key => $qp) { + if ($queryType === null) { + $condition = true; + } else { + $condition = ($qp->getQueryType() & $queryType); + } + + if (($qp->hasEnded() || $showUnfinished) && $condition) { + $queryProfiles[$key] = $qp; + } + } + + if (empty($queryProfiles)) { + $queryProfiles = false; + } + + return $queryProfiles; + } + + /** + * Get the total elapsed time (in seconds) of all of the profiled queries. + * Only queries that have ended will be counted. If $queryType is set to + * one or more of the Zend_Db_Profiler::* constants, the elapsed time will be calculated + * only for queries of the given type(s). + * + * @param integer $queryType OPTIONAL + * @return float + */ + public function getTotalElapsedSecs($queryType = null) + { + $elapsedSecs = 0; + foreach ($this->_queryProfiles as $key => $qp) { + if (null === $queryType) { + $condition = true; + } else { + $condition = ($qp->getQueryType() & $queryType); + } + if (($qp->hasEnded()) && $condition) { + $elapsedSecs += $qp->getElapsedSecs(); + } + } + return $elapsedSecs; + } + + /** + * Get the total number of queries that have been profiled. Only queries that have ended will + * be counted. If $queryType is set to one of the Zend_Db_Profiler::* constants, only queries of + * that type will be counted. + * + * @param integer $queryType OPTIONAL + * @return integer + */ + public function getTotalNumQueries($queryType = null) + { + if (null === $queryType) { + return count($this->_queryProfiles); + } + + $numQueries = 0; + foreach ($this->_queryProfiles as $qp) { + if ($qp->hasEnded() && ($qp->getQueryType() & $queryType)) { + $numQueries++; + } + } + + return $numQueries; + } + + /** + * Get the Zend_Db_Profiler_Query object for the last query that was run, regardless if it has + * ended or not. If the query has not ended, its end time will be null. If no queries have + * been profiled, false is returned. + * + * @return Zend_Db_Profiler_Query|false + */ + public function getLastQueryProfile() + { + if (empty($this->_queryProfiles)) { + return false; + } + + end($this->_queryProfiles); + + return current($this->_queryProfiles); + } + +} + diff --git a/library/Zend/Db/Profiler/Exception.php b/library/Zend/Db/Profiler/Exception.php new file mode 100644 index 000000000..9af123246 --- /dev/null +++ b/library/Zend/Db/Profiler/Exception.php @@ -0,0 +1,40 @@ +_label = $label; + if(!$this->_label) { + $this->_label = 'Zend_Db_Profiler_Firebug'; + } + } + + /** + * Enable or disable the profiler. If $enable is false, the profiler + * is disabled and will not log any queries sent to it. + * + * @param boolean $enable + * @return Zend_Db_Profiler Provides a fluent interface + */ + public function setEnabled($enable) + { + parent::setEnabled($enable); + + if ($this->getEnabled()) { + + if (!$this->_message) { + $this->_message = new Zend_Wildfire_Plugin_FirePhp_TableMessage($this->_label); + $this->_message->setBuffered(true); + $this->_message->setHeader(array('Time','Event','Parameters')); + $this->_message->setDestroy(true); + $this->_message->setOption('includeLineNumbers', false); + Zend_Wildfire_Plugin_FirePhp::getInstance()->send($this->_message); + } + + } else { + + if ($this->_message) { + $this->_message->setDestroy(true); + $this->_message = null; + } + + } + + return $this; + } + + /** + * Intercept the query end and log the profiling data. + * + * @param integer $queryId + * @throws Zend_Db_Profiler_Exception + * @return void + */ + public function queryEnd($queryId) + { + $state = parent::queryEnd($queryId); + + if (!$this->getEnabled() || $state == self::IGNORED) { + return; + } + + $this->_message->setDestroy(false); + + $profile = $this->getQueryProfile($queryId); + + $this->_totalElapsedTime += $profile->getElapsedSecs(); + + $this->_message->addRow(array((string)round($profile->getElapsedSecs(),5), + $profile->getQuery(), + ($params=$profile->getQueryParams())?$params:null)); + + $this->updateMessageLabel(); + } + + /** + * Update the label of the message holding the profile info. + * + * @return void + */ + protected function updateMessageLabel() + { + if (!$this->_message) { + return; + } + $this->_message->setLabel(str_replace(array('%label%', + '%totalCount%', + '%totalDuration%'), + array($this->_label, + $this->getTotalNumQueries(), + (string)round($this->_totalElapsedTime,5)), + $this->_label_template)); + } +} diff --git a/library/Zend/Db/Profiler/Query.php b/library/Zend/Db/Profiler/Query.php new file mode 100644 index 000000000..aacc16fbb --- /dev/null +++ b/library/Zend/Db/Profiler/Query.php @@ -0,0 +1,199 @@ +_query = $query; + $this->_queryType = $queryType; + // by default, and for backward-compatibility, start the click ticking + $this->start(); + } + + /** + * Clone handler for the query object. + * @return void + */ + public function __clone() + { + $this->_boundParams = array(); + $this->_endedMicrotime = null; + $this->start(); + } + + /** + * Starts the elapsed time click ticking. + * This can be called subsequent to object creation, + * to restart the clock. For instance, this is useful + * right before executing a prepared query. + * + * @return void + */ + public function start() + { + $this->_startedMicrotime = microtime(true); + } + + /** + * Ends the query and records the time so that the elapsed time can be determined later. + * + * @return void + */ + public function end() + { + $this->_endedMicrotime = microtime(true); + } + + /** + * Returns true if and only if the query has ended. + * + * @return boolean + */ + public function hasEnded() + { + return $this->_endedMicrotime !== null; + } + + /** + * Get the original SQL text of the query. + * + * @return string + */ + public function getQuery() + { + return $this->_query; + } + + /** + * Get the type of this query (one of the Zend_Db_Profiler::* constants) + * + * @return integer + */ + public function getQueryType() + { + return $this->_queryType; + } + + /** + * @param string $param + * @param mixed $variable + * @return void + */ + public function bindParam($param, $variable) + { + $this->_boundParams[$param] = $variable; + } + + /** + * @param array $param + * @return void + */ + public function bindParams(array $params) + { + if (array_key_exists(0, $params)) { + array_unshift($params, null); + unset($params[0]); + } + foreach ($params as $param => $value) { + $this->bindParam($param, $value); + } + } + + /** + * @return array + */ + public function getQueryParams() + { + return $this->_boundParams; + } + + /** + * Get the elapsed time (in seconds) that the query ran. + * If the query has not yet ended, false is returned. + * + * @return float|false + */ + public function getElapsedSecs() + { + if (null === $this->_endedMicrotime) { + return false; + } + + return $this->_endedMicrotime - $this->_startedMicrotime; + } +} + diff --git a/library/Zend/Db/Select.php b/library/Zend/Db/Select.php new file mode 100644 index 000000000..4ef6536ba --- /dev/null +++ b/library/Zend/Db/Select.php @@ -0,0 +1,1351 @@ + false, + self::COLUMNS => array(), + self::UNION => array(), + self::FROM => array(), + self::WHERE => array(), + self::GROUP => array(), + self::HAVING => array(), + self::ORDER => array(), + self::LIMIT_COUNT => null, + self::LIMIT_OFFSET => null, + self::FOR_UPDATE => false + ); + + /** + * Specify legal join types. + * + * @var array + */ + protected static $_joinTypes = array( + self::INNER_JOIN, + self::LEFT_JOIN, + self::RIGHT_JOIN, + self::FULL_JOIN, + self::CROSS_JOIN, + self::NATURAL_JOIN, + ); + + /** + * Specify legal union types. + * + * @var array + */ + protected static $_unionTypes = array( + self::SQL_UNION, + self::SQL_UNION_ALL + ); + + /** + * The component parts of a SELECT statement. + * Initialized to the $_partsInit array in the constructor. + * + * @var array + */ + protected $_parts = array(); + + /** + * Tracks which columns are being select from each table and join. + * + * @var array + */ + protected $_tableCols = array(); + + /** + * Class constructor + * + * @param Zend_Db_Adapter_Abstract $adapter + */ + public function __construct(Zend_Db_Adapter_Abstract $adapter) + { + $this->_adapter = $adapter; + $this->_parts = self::$_partsInit; + } + + /** + * Get bind variables + * + * @return array + */ + public function getBind() + { + return $this->_bind; + } + + /** + * Set bind variables + * + * @param mixed $bind + * @return Zend_Db_Select + */ + public function bind($bind) + { + $this->_bind = $bind; + + return $this; + } + + /** + * Makes the query SELECT DISTINCT. + * + * @param bool $flag Whether or not the SELECT is DISTINCT (default true). + * @return Zend_Db_Select This Zend_Db_Select object. + */ + public function distinct($flag = true) + { + $this->_parts[self::DISTINCT] = (bool) $flag; + return $this; + } + + /** + * Adds a FROM table and optional columns to the query. + * + * The first parameter $name can be a simple string, in which case the + * correlation name is generated automatically. If you want to specify + * the correlation name, the first parameter must be an associative + * array in which the key is the physical table name, and the value is + * the correlation name. For example, array('table' => 'alias'). + * The correlation name is prepended to all columns fetched for this + * table. + * + * The second parameter can be a single string or Zend_Db_Expr object, + * or else an array of strings or Zend_Db_Expr objects. + * + * The first parameter can be null or an empty string, in which case + * no correlation name is generated or prepended to the columns named + * in the second parameter. + * + * @param array|string|Zend_Db_Expr $name The table name or an associative array relating table name to + * correlation name. + * @param array|string|Zend_Db_Expr $cols The columns to select from this table. + * @param string $schema The schema name to specify, if any. + * @return Zend_Db_Select This Zend_Db_Select object. + */ + public function from($name, $cols = '*', $schema = null) + { + return $this->_join(self::FROM, $name, null, $cols, $schema); + } + + /** + * Specifies the columns used in the FROM clause. + * + * The parameter can be a single string or Zend_Db_Expr object, + * or else an array of strings or Zend_Db_Expr objects. + * + * @param array|string|Zend_Db_Expr $cols The columns to select from this table. + * @param string $correlationName Correlation name of target table. OPTIONAL + * @return Zend_Db_Select This Zend_Db_Select object. + */ + public function columns($cols = '*', $correlationName = null) + { + if ($correlationName === null && count($this->_parts[self::FROM])) { + $correlationNameKeys = array_keys($this->_parts[self::FROM]); + $correlationName = current($correlationNameKeys); + } + + if (!array_key_exists($correlationName, $this->_parts[self::FROM])) { + /** + * @see Zend_Db_Select_Exception + */ + require_once 'Zend/Db/Select/Exception.php'; + throw new Zend_Db_Select_Exception("No table has been specified for the FROM clause"); + } + + $this->_tableCols($correlationName, $cols); + + return $this; + } + + /** + * Adds a UNION clause to the query. + * + * The first parameter has to be an array of Zend_Db_Select or + * sql query strings. + * + * + * $sql1 = $db->select(); + * $sql2 = "SELECT ..."; + * $select = $db->select() + * ->union(array($sql1, $sql2)) + * ->order("id"); + * + * + * @param array $select Array of select clauses for the union. + * @return Zend_Db_Select This Zend_Db_Select object. + */ + public function union($select = array(), $type = self::SQL_UNION) + { + if (!is_array($select)) { + require_once 'Zend/Db/Select/Exception.php'; + throw new Zend_Db_Select_Exception( + "union() only accepts an array of Zend_Db_Select instances of sql query strings." + ); + } + + if (!in_array($type, self::$_unionTypes)) { + require_once 'Zend/Db/Select/Exception.php'; + throw new Zend_Db_Select_Exception("Invalid union type '{$type}'"); + } + + foreach ($select as $target) { + $this->_parts[self::UNION][] = array($target, $type); + } + + return $this; + } + + /** + * Adds a JOIN table and columns to the query. + * + * The $name and $cols parameters follow the same logic + * as described in the from() method. + * + * @param array|string|Zend_Db_Expr $name The table name. + * @param string $cond Join on this condition. + * @param array|string $cols The columns to select from the joined table. + * @param string $schema The database name to specify, if any. + * @return Zend_Db_Select This Zend_Db_Select object. + */ + public function join($name, $cond, $cols = self::SQL_WILDCARD, $schema = null) + { + return $this->joinInner($name, $cond, $cols, $schema); + } + + /** + * Add an INNER JOIN table and colums to the query + * Rows in both tables are matched according to the expression + * in the $cond argument. The result set is comprised + * of all cases where rows from the left table match + * rows from the right table. + * + * The $name and $cols parameters follow the same logic + * as described in the from() method. + * + * @param array|string|Zend_Db_Expr $name The table name. + * @param string $cond Join on this condition. + * @param array|string $cols The columns to select from the joined table. + * @param string $schema The database name to specify, if any. + * @return Zend_Db_Select This Zend_Db_Select object. + */ + public function joinInner($name, $cond, $cols = self::SQL_WILDCARD, $schema = null) + { + return $this->_join(self::INNER_JOIN, $name, $cond, $cols, $schema); + } + + /** + * Add a LEFT OUTER JOIN table and colums to the query + * All rows from the left operand table are included, + * matching rows from the right operand table included, + * and the columns from the right operand table are filled + * with NULLs if no row exists matching the left table. + * + * The $name and $cols parameters follow the same logic + * as described in the from() method. + * + * @param array|string|Zend_Db_Expr $name The table name. + * @param string $cond Join on this condition. + * @param array|string $cols The columns to select from the joined table. + * @param string $schema The database name to specify, if any. + * @return Zend_Db_Select This Zend_Db_Select object. + */ + public function joinLeft($name, $cond, $cols = self::SQL_WILDCARD, $schema = null) + { + return $this->_join(self::LEFT_JOIN, $name, $cond, $cols, $schema); + } + + /** + * Add a RIGHT OUTER JOIN table and colums to the query. + * Right outer join is the complement of left outer join. + * All rows from the right operand table are included, + * matching rows from the left operand table included, + * and the columns from the left operand table are filled + * with NULLs if no row exists matching the right table. + * + * The $name and $cols parameters follow the same logic + * as described in the from() method. + * + * @param array|string|Zend_Db_Expr $name The table name. + * @param string $cond Join on this condition. + * @param array|string $cols The columns to select from the joined table. + * @param string $schema The database name to specify, if any. + * @return Zend_Db_Select This Zend_Db_Select object. + */ + public function joinRight($name, $cond, $cols = self::SQL_WILDCARD, $schema = null) + { + return $this->_join(self::RIGHT_JOIN, $name, $cond, $cols, $schema); + } + + /** + * Add a FULL OUTER JOIN table and colums to the query. + * A full outer join is like combining a left outer join + * and a right outer join. All rows from both tables are + * included, paired with each other on the same row of the + * result set if they satisfy the join condition, and otherwise + * paired with NULLs in place of columns from the other table. + * + * The $name and $cols parameters follow the same logic + * as described in the from() method. + * + * @param array|string|Zend_Db_Expr $name The table name. + * @param string $cond Join on this condition. + * @param array|string $cols The columns to select from the joined table. + * @param string $schema The database name to specify, if any. + * @return Zend_Db_Select This Zend_Db_Select object. + */ + public function joinFull($name, $cond, $cols = self::SQL_WILDCARD, $schema = null) + { + return $this->_join(self::FULL_JOIN, $name, $cond, $cols, $schema); + } + + /** + * Add a CROSS JOIN table and colums to the query. + * A cross join is a cartesian product; there is no join condition. + * + * The $name and $cols parameters follow the same logic + * as described in the from() method. + * + * @param array|string|Zend_Db_Expr $name The table name. + * @param array|string $cols The columns to select from the joined table. + * @param string $schema The database name to specify, if any. + * @return Zend_Db_Select This Zend_Db_Select object. + */ + public function joinCross($name, $cols = self::SQL_WILDCARD, $schema = null) + { + return $this->_join(self::CROSS_JOIN, $name, null, $cols, $schema); + } + + /** + * Add a NATURAL JOIN table and colums to the query. + * A natural join assumes an equi-join across any column(s) + * that appear with the same name in both tables. + * Only natural inner joins are supported by this API, + * even though SQL permits natural outer joins as well. + * + * The $name and $cols parameters follow the same logic + * as described in the from() method. + * + * @param array|string|Zend_Db_Expr $name The table name. + * @param array|string $cols The columns to select from the joined table. + * @param string $schema The database name to specify, if any. + * @return Zend_Db_Select This Zend_Db_Select object. + */ + public function joinNatural($name, $cols = self::SQL_WILDCARD, $schema = null) + { + return $this->_join(self::NATURAL_JOIN, $name, null, $cols, $schema); + } + + /** + * Adds a WHERE condition to the query by AND. + * + * If a value is passed as the second param, it will be quoted + * and replaced into the condition wherever a question-mark + * appears. Array values are quoted and comma-separated. + * + * + * // simplest but non-secure + * $select->where("id = $id"); + * + * // secure (ID is quoted but matched anyway) + * $select->where('id = ?', $id); + * + * // alternatively, with named binding + * $select->where('id = :id'); + * + * + * Note that it is more correct to use named bindings in your + * queries for values other than strings. When you use named + * bindings, don't forget to pass the values when actually + * making a query: + * + * + * $db->fetchAll($select, array('id' => 5)); + * + * + * @param string $cond The WHERE condition. + * @param mixed $value OPTIONAL The value to quote into the condition. + * @param constant $type OPTIONAL The type of the given value + * @return Zend_Db_Select This Zend_Db_Select object. + */ + public function where($cond, $value = null, $type = null) + { + $this->_parts[self::WHERE][] = $this->_where($cond, $value, $type, true); + + return $this; + } + + /** + * Adds a WHERE condition to the query by OR. + * + * Otherwise identical to where(). + * + * @param string $cond The WHERE condition. + * @param mixed $value OPTIONAL The value to quote into the condition. + * @param constant $type OPTIONAL The type of the given value + * @return Zend_Db_Select This Zend_Db_Select object. + * + * @see where() + */ + public function orWhere($cond, $value = null, $type = null) + { + $this->_parts[self::WHERE][] = $this->_where($cond, $value, $type, false); + + return $this; + } + + /** + * Adds grouping to the query. + * + * @param array|string $spec The column(s) to group by. + * @return Zend_Db_Select This Zend_Db_Select object. + */ + public function group($spec) + { + if (!is_array($spec)) { + $spec = array($spec); + } + + foreach ($spec as $val) { + if (preg_match('/\(.*\)/', (string) $val)) { + $val = new Zend_Db_Expr($val); + } + $this->_parts[self::GROUP][] = $val; + } + + return $this; + } + + /** + * Adds a HAVING condition to the query by AND. + * + * If a value is passed as the second param, it will be quoted + * and replaced into the condition wherever a question-mark + * appears. See {@link where()} for an example + * + * @param string $cond The HAVING condition. + * @param string|Zend_Db_Expr $val The value to quote into the condition. + * @return Zend_Db_Select This Zend_Db_Select object. + */ + public function having($cond) + { + if (func_num_args() > 1) { + $val = func_get_arg(1); + $cond = $this->_adapter->quoteInto($cond, $val); + } + + if ($this->_parts[self::HAVING]) { + $this->_parts[self::HAVING][] = self::SQL_AND . " ($cond)"; + } else { + $this->_parts[self::HAVING][] = "($cond)"; + } + + return $this; + } + + /** + * Adds a HAVING condition to the query by OR. + * + * Otherwise identical to orHaving(). + * + * @param string $cond The HAVING condition. + * @param mixed $val The value to quote into the condition. + * @return Zend_Db_Select This Zend_Db_Select object. + * + * @see having() + */ + public function orHaving($cond) + { + if (func_num_args() > 1) { + $val = func_get_arg(1); + $cond = $this->_adapter->quoteInto($cond, $val); + } + + if ($this->_parts[self::HAVING]) { + $this->_parts[self::HAVING][] = self::SQL_OR . " ($cond)"; + } else { + $this->_parts[self::HAVING][] = "($cond)"; + } + + return $this; + } + + /** + * Adds a row order to the query. + * + * @param mixed $spec The column(s) and direction to order by. + * @return Zend_Db_Select This Zend_Db_Select object. + */ + public function order($spec) + { + if (!is_array($spec)) { + $spec = array($spec); + } + + // force 'ASC' or 'DESC' on each order spec, default is ASC. + foreach ($spec as $val) { + if ($val instanceof Zend_Db_Expr) { + $expr = $val->__toString(); + if (empty($expr)) { + continue; + } + $this->_parts[self::ORDER][] = $val; + } else { + if (empty($val)) { + continue; + } + $direction = self::SQL_ASC; + if (preg_match('/(.*\W)(' . self::SQL_ASC . '|' . self::SQL_DESC . ')\b/si', $val, $matches)) { + $val = trim($matches[1]); + $direction = $matches[2]; + } + if (preg_match('/\(.*\)/', $val)) { + $val = new Zend_Db_Expr($val); + } + $this->_parts[self::ORDER][] = array($val, $direction); + } + } + + return $this; + } + + /** + * Sets a limit count and offset to the query. + * + * @param int $count OPTIONAL The number of rows to return. + * @param int $offset OPTIONAL Start returning after this many rows. + * @return Zend_Db_Select This Zend_Db_Select object. + */ + public function limit($count = null, $offset = null) + { + $this->_parts[self::LIMIT_COUNT] = (int) $count; + $this->_parts[self::LIMIT_OFFSET] = (int) $offset; + return $this; + } + + /** + * Sets the limit and count by page number. + * + * @param int $page Limit results to this page number. + * @param int $rowCount Use this many rows per page. + * @return Zend_Db_Select This Zend_Db_Select object. + */ + public function limitPage($page, $rowCount) + { + $page = ($page > 0) ? $page : 1; + $rowCount = ($rowCount > 0) ? $rowCount : 1; + $this->_parts[self::LIMIT_COUNT] = (int) $rowCount; + $this->_parts[self::LIMIT_OFFSET] = (int) $rowCount * ($page - 1); + return $this; + } + + /** + * Makes the query SELECT FOR UPDATE. + * + * @param bool $flag Whether or not the SELECT is FOR UPDATE (default true). + * @return Zend_Db_Select This Zend_Db_Select object. + */ + public function forUpdate($flag = true) + { + $this->_parts[self::FOR_UPDATE] = (bool) $flag; + return $this; + } + + /** + * Get part of the structured information for the currect query. + * + * @param string $part + * @return mixed + * @throws Zend_Db_Select_Exception + */ + public function getPart($part) + { + $part = strtolower($part); + if (!array_key_exists($part, $this->_parts)) { + require_once 'Zend/Db/Select/Exception.php'; + throw new Zend_Db_Select_Exception("Invalid Select part '$part'"); + } + return $this->_parts[$part]; + } + + /** + * Executes the current select object and returns the result + * + * @param integer $fetchMode OPTIONAL + * @param mixed $bind An array of data to bind to the placeholders. + * @return PDO_Statement|Zend_Db_Statement + */ + public function query($fetchMode = null, $bind = array()) + { + if (!empty($bind)) { + $this->bind($bind); + } + + $stmt = $this->_adapter->query($this); + if ($fetchMode == null) { + $fetchMode = $this->_adapter->getFetchMode(); + } + $stmt->setFetchMode($fetchMode); + return $stmt; + } + + /** + * Converts this object to an SQL SELECT string. + * + * @return string|null This object as a SELECT string. (or null if a string cannot be produced.) + */ + public function assemble() + { + $sql = self::SQL_SELECT; + foreach (array_keys(self::$_partsInit) as $part) { + $method = '_render' . ucfirst($part); + if (method_exists($this, $method)) { + $sql = $this->$method($sql); + } + } + return $sql; + } + + /** + * Clear parts of the Select object, or an individual part. + * + * @param string $part OPTIONAL + * @return Zend_Db_Select + */ + public function reset($part = null) + { + if ($part == null) { + $this->_parts = self::$_partsInit; + } else if (array_key_exists($part, self::$_partsInit)) { + $this->_parts[$part] = self::$_partsInit[$part]; + } + return $this; + } + + /** + * Gets the Zend_Db_Adapter_Abstract for this + * particular Zend_Db_Select object. + * + * @return Zend_Db_Adapter_Abstract + */ + public function getAdapter() + { + return $this->_adapter; + } + + /** + * Populate the {@link $_parts} 'join' key + * + * Does the dirty work of populating the join key. + * + * The $name and $cols parameters follow the same logic + * as described in the from() method. + * + * @param null|string $type Type of join; inner, left, and null are currently supported + * @param array|string|Zend_Db_Expr $name Table name + * @param string $cond Join on this condition + * @param array|string $cols The columns to select from the joined table + * @param string $schema The database name to specify, if any. + * @return Zend_Db_Select This Zend_Db_Select object + * @throws Zend_Db_Select_Exception + */ + protected function _join($type, $name, $cond, $cols, $schema = null) + { + if (!in_array($type, self::$_joinTypes) && $type != self::FROM) { + /** + * @see Zend_Db_Select_Exception + */ + require_once 'Zend/Db/Select/Exception.php'; + throw new Zend_Db_Select_Exception("Invalid join type '$type'"); + } + + if (count($this->_parts[self::UNION])) { + require_once 'Zend/Db/Select/Exception.php'; + throw new Zend_Db_Select_Exception("Invalid use of table with " . self::SQL_UNION); + } + + if (empty($name)) { + $correlationName = $tableName = ''; + } else if (is_array($name)) { + // Must be array($correlationName => $tableName) or array($ident, ...) + foreach ($name as $_correlationName => $_tableName) { + if (is_string($_correlationName)) { + // We assume the key is the correlation name and value is the table name + $tableName = $_tableName; + $correlationName = $_correlationName; + } else { + // We assume just an array of identifiers, with no correlation name + $tableName = $_tableName; + $correlationName = $this->_uniqueCorrelation($tableName); + } + break; + } + } else if ($name instanceof Zend_Db_Expr|| $name instanceof Zend_Db_Select) { + $tableName = $name; + $correlationName = $this->_uniqueCorrelation('t'); + } else if (preg_match('/^(.+)\s+AS\s+(.+)$/i', $name, $m)) { + $tableName = $m[1]; + $correlationName = $m[2]; + } else { + $tableName = $name; + $correlationName = $this->_uniqueCorrelation($tableName); + } + + // Schema from table name overrides schema argument + if (!is_object($tableName) && false !== strpos($tableName, '.')) { + list($schema, $tableName) = explode('.', $tableName); + } + + $lastFromCorrelationName = null; + if (!empty($correlationName)) { + if (array_key_exists($correlationName, $this->_parts[self::FROM])) { + /** + * @see Zend_Db_Select_Exception + */ + require_once 'Zend/Db/Select/Exception.php'; + throw new Zend_Db_Select_Exception("You cannot define a correlation name '$correlationName' more than once"); + } + + if ($type == self::FROM) { + // append this from after the last from joinType + $tmpFromParts = $this->_parts[self::FROM]; + $this->_parts[self::FROM] = array(); + // move all the froms onto the stack + while ($tmpFromParts) { + $currentCorrelationName = key($tmpFromParts); + if ($tmpFromParts[$currentCorrelationName]['joinType'] != self::FROM) { + break; + } + $lastFromCorrelationName = $currentCorrelationName; + $this->_parts[self::FROM][$currentCorrelationName] = array_shift($tmpFromParts); + } + } else { + $tmpFromParts = array(); + } + $this->_parts[self::FROM][$correlationName] = array( + 'joinType' => $type, + 'schema' => $schema, + 'tableName' => $tableName, + 'joinCondition' => $cond + ); + while ($tmpFromParts) { + $currentCorrelationName = key($tmpFromParts); + $this->_parts[self::FROM][$currentCorrelationName] = array_shift($tmpFromParts); + } + } + + // add to the columns from this joined table + if ($type == self::FROM && $lastFromCorrelationName == null) { + $lastFromCorrelationName = true; + } + $this->_tableCols($correlationName, $cols, $lastFromCorrelationName); + + return $this; + } + + /** + * Handle JOIN... USING... syntax + * + * This is functionality identical to the existing JOIN methods, however + * the join condition can be passed as a single column name. This method + * then completes the ON condition by using the same field for the FROM + * table and the JOIN table. + * + * + * $select = $db->select()->from('table1') + * ->joinUsing('table2', 'column1'); + * + * // SELECT * FROM table1 JOIN table2 ON table1.column1 = table2.column2 + * + * + * These joins are called by the developer simply by adding 'Using' to the + * method name. E.g. + * * joinUsing + * * joinInnerUsing + * * joinFullUsing + * * joinRightUsing + * * joinLeftUsing + * + * @return Zend_Db_Select This Zend_Db_Select object. + */ + public function _joinUsing($type, $name, $cond, $cols = '*', $schema = null) + { + if (empty($this->_parts[self::FROM])) { + require_once 'Zend/Db/Select/Exception.php'; + throw new Zend_Db_Select_Exception("You can only perform a joinUsing after specifying a FROM table"); + } + + $join = $this->_adapter->quoteIdentifier(key($this->_parts[self::FROM]), true); + $from = $this->_adapter->quoteIdentifier($this->_uniqueCorrelation($name), true); + + $cond1 = $from . '.' . $cond; + $cond2 = $join . '.' . $cond; + $cond = $cond1 . ' = ' . $cond2; + + return $this->_join($type, $name, $cond, $cols, $schema); + } + + /** + * Generate a unique correlation name + * + * @param string|array $name A qualified identifier. + * @return string A unique correlation name. + */ + private function _uniqueCorrelation($name) + { + if (is_array($name)) { + $c = end($name); + } else { + // Extract just the last name of a qualified table name + $dot = strrpos($name,'.'); + $c = ($dot === false) ? $name : substr($name, $dot+1); + } + for ($i = 2; array_key_exists($c, $this->_parts[self::FROM]); ++$i) { + $c = $name . '_' . (string) $i; + } + return $c; + } + + /** + * Adds to the internal table-to-column mapping array. + * + * @param string $tbl The table/join the columns come from. + * @param array|string $cols The list of columns; preferably as + * an array, but possibly as a string containing one column. + * @param bool|string True if it should be prepended, a correlation name if it should be inserted + * @return void + */ + protected function _tableCols($correlationName, $cols, $afterCorrelationName = null) + { + if (!is_array($cols)) { + $cols = array($cols); + } + + if ($correlationName == null) { + $correlationName = ''; + } + + $columnValues = array(); + + foreach (array_filter($cols) as $alias => $col) { + $currentCorrelationName = $correlationName; + if (is_string($col)) { + // Check for a column matching " AS " and extract the alias name + if (preg_match('/^(.+)\s+' . self::SQL_AS . '\s+(.+)$/i', $col, $m)) { + $col = $m[1]; + $alias = $m[2]; + } + // Check for columns that look like functions and convert to Zend_Db_Expr + if (preg_match('/\(.*\)/', $col)) { + $col = new Zend_Db_Expr($col); + } elseif (preg_match('/(.+)\.(.+)/', $col, $m)) { + $currentCorrelationName = $m[1]; + $col = $m[2]; + } + } + $columnValues[] = array($currentCorrelationName, $col, is_string($alias) ? $alias : null); + } + + if ($columnValues) { + + // should we attempt to prepend or insert these values? + if ($afterCorrelationName === true || is_string($afterCorrelationName)) { + $tmpColumns = $this->_parts[self::COLUMNS]; + $this->_parts[self::COLUMNS] = array(); + } else { + $tmpColumns = array(); + } + + // find the correlation name to insert after + if (is_string($afterCorrelationName)) { + while ($tmpColumns) { + $this->_parts[self::COLUMNS][] = $currentColumn = array_shift($tmpColumns); + if ($currentColumn[0] == $afterCorrelationName) { + break; + } + } + } + + // apply current values to current stack + foreach ($columnValues as $columnValue) { + array_push($this->_parts[self::COLUMNS], $columnValue); + } + + // finish ensuring that all previous values are applied (if they exist) + while ($tmpColumns) { + array_push($this->_parts[self::COLUMNS], array_shift($tmpColumns)); + } + } + } + + /** + * Internal function for creating the where clause + * + * @param string $condition + * @param mixed $value optional + * @param string $type optional + * @param boolean $bool true = AND, false = OR + * @return string clause + */ + protected function _where($condition, $value = null, $type = null, $bool = true) + { + if (count($this->_parts[self::UNION])) { + require_once 'Zend/Db/Select/Exception.php'; + throw new Zend_Db_Select_Exception("Invalid use of where clause with " . self::SQL_UNION); + } + + if ($value !== null) { + $condition = $this->_adapter->quoteInto($condition, $value, $type); + } + + $cond = ""; + if ($this->_parts[self::WHERE]) { + if ($bool === true) { + $cond = self::SQL_AND . ' '; + } else { + $cond = self::SQL_OR . ' '; + } + } + + return $cond . "($condition)"; + } + + /** + * @return array + */ + protected function _getDummyTable() + { + return array(); + } + + /** + * Return a quoted schema name + * + * @param string $schema The schema name OPTIONAL + * @return string|null + */ + protected function _getQuotedSchema($schema = null) + { + if ($schema === null) { + return null; + } + return $this->_adapter->quoteIdentifier($schema, true) . '.'; + } + + /** + * Return a quoted table name + * + * @param string $tableName The table name + * @param string $correlationName The correlation name OPTIONAL + * @return string + */ + protected function _getQuotedTable($tableName, $correlationName = null) + { + return $this->_adapter->quoteTableAs($tableName, $correlationName, true); + } + + /** + * Render DISTINCT clause + * + * @param string $sql SQL query + * @return string + */ + protected function _renderDistinct($sql) + { + if ($this->_parts[self::DISTINCT]) { + $sql .= ' ' . self::SQL_DISTINCT; + } + + return $sql; + } + + /** + * Render DISTINCT clause + * + * @param string $sql SQL query + * @return string|null + */ + protected function _renderColumns($sql) + { + if (!count($this->_parts[self::COLUMNS])) { + return null; + } + + $columns = array(); + foreach ($this->_parts[self::COLUMNS] as $columnEntry) { + list($correlationName, $column, $alias) = $columnEntry; + if ($column instanceof Zend_Db_Expr) { + $columns[] = $this->_adapter->quoteColumnAs($column, $alias, true); + } else { + if ($column == self::SQL_WILDCARD) { + $column = new Zend_Db_Expr(self::SQL_WILDCARD); + $alias = null; + } + if (empty($correlationName)) { + $columns[] = $this->_adapter->quoteColumnAs($column, $alias, true); + } else { + $columns[] = $this->_adapter->quoteColumnAs(array($correlationName, $column), $alias, true); + } + } + } + + return $sql .= ' ' . implode(', ', $columns); + } + + /** + * Render FROM clause + * + * @param string $sql SQL query + * @return string + */ + protected function _renderFrom($sql) + { + /* + * If no table specified, use RDBMS-dependent solution + * for table-less query. e.g. DUAL in Oracle. + */ + if (empty($this->_parts[self::FROM])) { + $this->_parts[self::FROM] = $this->_getDummyTable(); + } + + $from = array(); + + foreach ($this->_parts[self::FROM] as $correlationName => $table) { + $tmp = ''; + + $joinType = ($table['joinType'] == self::FROM) ? self::INNER_JOIN : $table['joinType']; + + // Add join clause (if applicable) + if (! empty($from)) { + $tmp .= ' ' . strtoupper($joinType) . ' '; + } + + $tmp .= $this->_getQuotedSchema($table['schema']); + $tmp .= $this->_getQuotedTable($table['tableName'], $correlationName); + + // Add join conditions (if applicable) + if (!empty($from) && ! empty($table['joinCondition'])) { + $tmp .= ' ' . self::SQL_ON . ' ' . $table['joinCondition']; + } + + // Add the table name and condition add to the list + $from[] = $tmp; + } + + // Add the list of all joins + if (!empty($from)) { + $sql .= ' ' . self::SQL_FROM . ' ' . implode("\n", $from); + } + + return $sql; + } + + /** + * Render UNION query + * + * @param string $sql SQL query + * @return string + */ + protected function _renderUnion($sql) + { + if ($this->_parts[self::UNION]) { + $parts = count($this->_parts[self::UNION]); + foreach ($this->_parts[self::UNION] as $cnt => $union) { + list($target, $type) = $union; + if ($target instanceof Zend_Db_Select) { + $target = $target->assemble(); + } + $sql .= $target; + if ($cnt < $parts - 1) { + $sql .= ' ' . $type . ' '; + } + } + } + + return $sql; + } + + /** + * Render WHERE clause + * + * @param string $sql SQL query + * @return string + */ + protected function _renderWhere($sql) + { + if ($this->_parts[self::FROM] && $this->_parts[self::WHERE]) { + $sql .= ' ' . self::SQL_WHERE . ' ' . implode(' ', $this->_parts[self::WHERE]); + } + + return $sql; + } + + /** + * Render GROUP clause + * + * @param string $sql SQL query + * @return string + */ + protected function _renderGroup($sql) + { + if ($this->_parts[self::FROM] && $this->_parts[self::GROUP]) { + $group = array(); + foreach ($this->_parts[self::GROUP] as $term) { + $group[] = $this->_adapter->quoteIdentifier($term, true); + } + $sql .= ' ' . self::SQL_GROUP_BY . ' ' . implode(",\n\t", $group); + } + + return $sql; + } + + /** + * Render HAVING clause + * + * @param string $sql SQL query + * @return string + */ + protected function _renderHaving($sql) + { + if ($this->_parts[self::FROM] && $this->_parts[self::HAVING]) { + $sql .= ' ' . self::SQL_HAVING . ' ' . implode(' ', $this->_parts[self::HAVING]); + } + + return $sql; + } + + /** + * Render ORDER clause + * + * @param string $sql SQL query + * @return string + */ + protected function _renderOrder($sql) + { + if ($this->_parts[self::ORDER]) { + $order = array(); + foreach ($this->_parts[self::ORDER] as $term) { + if (is_array($term)) { + if(is_numeric($term[0]) && strval(intval($term[0])) == $term[0]) { + $order[] = (int)trim($term[0]) . ' ' . $term[1]; + } else { + $order[] = $this->_adapter->quoteIdentifier($term[0], true) . ' ' . $term[1]; + } + } else if (is_numeric($term) && strval(intval($term)) == $term) { + $order[] = (int)trim($term); + } else { + $order[] = $this->_adapter->quoteIdentifier($term, true); + } + } + $sql .= ' ' . self::SQL_ORDER_BY . ' ' . implode(', ', $order); + } + + return $sql; + } + + /** + * Render LIMIT OFFSET clause + * + * @param string $sql SQL query + * @return string + */ + protected function _renderLimitoffset($sql) + { + $count = 0; + $offset = 0; + + if (!empty($this->_parts[self::LIMIT_OFFSET])) { + $offset = (int) $this->_parts[self::LIMIT_OFFSET]; + $count = PHP_INT_MAX; + } + + if (!empty($this->_parts[self::LIMIT_COUNT])) { + $count = (int) $this->_parts[self::LIMIT_COUNT]; + } + + /* + * Add limits clause + */ + if ($count > 0) { + $sql = trim($this->_adapter->limit($sql, $count, $offset)); + } + + return $sql; + } + + /** + * Render FOR UPDATE clause + * + * @param string $sql SQL query + * @return string + */ + protected function _renderForupdate($sql) + { + if ($this->_parts[self::FOR_UPDATE]) { + $sql .= ' ' . self::SQL_FOR_UPDATE; + } + + return $sql; + } + + /** + * Turn magic function calls into non-magic function calls + * for joinUsing syntax + * + * @param string $method + * @param array $args OPTIONAL Zend_Db_Table_Select query modifier + * @return Zend_Db_Select + * @throws Zend_Db_Select_Exception If an invalid method is called. + */ + public function __call($method, array $args) + { + $matches = array(); + + /** + * Recognize methods for Has-Many cases: + * findParent() + * findParentBy() + * Use the non-greedy pattern repeat modifier e.g. \w+? + */ + if (preg_match('/^join([a-zA-Z]*?)Using$/', $method, $matches)) { + $type = strtolower($matches[1]); + if ($type) { + $type .= ' join'; + if (!in_array($type, self::$_joinTypes)) { + require_once 'Zend/Db/Select/Exception.php'; + throw new Zend_Db_Select_Exception("Unrecognized method '$method()'"); + } + if (in_array($type, array(self::CROSS_JOIN, self::NATURAL_JOIN))) { + require_once 'Zend/Db/Select/Exception.php'; + throw new Zend_Db_Select_Exception("Cannot perform a joinUsing with method '$method()'"); + } + } else { + $type = self::INNER_JOIN; + } + array_unshift($args, $type); + return call_user_func_array(array($this, '_joinUsing'), $args); + } + + require_once 'Zend/Db/Select/Exception.php'; + throw new Zend_Db_Select_Exception("Unrecognized method '$method()'"); + } + + /** + * Implements magic method. + * + * @return string This object as a SELECT string. + */ + public function __toString() + { + try { + $sql = $this->assemble(); + } catch (Exception $e) { + trigger_error($e->getMessage(), E_USER_WARNING); + $sql = ''; + } + return (string)$sql; + } + +} diff --git a/library/Zend/Db/Select/Exception.php b/library/Zend/Db/Select/Exception.php new file mode 100644 index 000000000..d95fdb20d --- /dev/null +++ b/library/Zend/Db/Select/Exception.php @@ -0,0 +1,39 @@ +_adapter = $adapter; + if ($sql instanceof Zend_Db_Select) { + $sql = $sql->assemble(); + } + $this->_parseParameters($sql); + $this->_prepare($sql); + + $this->_queryId = $this->_adapter->getProfiler()->queryStart($sql); + } + + /** + * Internal method called by abstract statment constructor to setup + * the driver level statement + * + * @return void + */ + protected function _prepare($sql) + { + return; + } + + /** + * @param string $sql + * @return void + */ + protected function _parseParameters($sql) + { + $sql = $this->_stripQuoted($sql); + + // split into text and params + $this->_sqlSplit = preg_split('/(\?|\:[a-zA-Z0-9_]+)/', + $sql, -1, PREG_SPLIT_DELIM_CAPTURE|PREG_SPLIT_NO_EMPTY); + + // map params + $this->_sqlParam = array(); + foreach ($this->_sqlSplit as $key => $val) { + if ($val == '?') { + if ($this->_adapter->supportsParameters('positional') === false) { + /** + * @see Zend_Db_Statement_Exception + */ + require_once 'Zend/Db/Statement/Exception.php'; + throw new Zend_Db_Statement_Exception("Invalid bind-variable position '$val'"); + } + } else if ($val[0] == ':') { + if ($this->_adapter->supportsParameters('named') === false) { + /** + * @see Zend_Db_Statement_Exception + */ + require_once 'Zend/Db/Statement/Exception.php'; + throw new Zend_Db_Statement_Exception("Invalid bind-variable name '$val'"); + } + } + $this->_sqlParam[] = $val; + } + + // set up for binding + $this->_bindParam = array(); + } + + /** + * Remove parts of a SQL string that contain quoted strings + * of values or identifiers. + * + * @param string $sql + * @return string + */ + protected function _stripQuoted($sql) + { + // get the character for delimited id quotes, + // this is usually " but in MySQL is ` + $d = $this->_adapter->quoteIdentifier('a'); + $d = $d[0]; + + // get the value used as an escaped delimited id quote, + // e.g. \" or "" or \` + $de = $this->_adapter->quoteIdentifier($d); + $de = substr($de, 1, 2); + $de = str_replace('\\', '\\\\', $de); + + // get the character for value quoting + // this should be ' + $q = $this->_adapter->quote('a'); + $q = $q[0]; + + // get the value used as an escaped quote, + // e.g. \' or '' + $qe = $this->_adapter->quote($q); + $qe = substr($qe, 1, 2); + $qe = str_replace('\\', '\\\\', $qe); + + // get a version of the SQL statement with all quoted + // values and delimited identifiers stripped out + // remove "foo\"bar" + $sql = preg_replace("/$q($qe|\\\\{2}|[^$q])*$q/", '', $sql); + // remove 'foo\'bar' + if (!empty($q)) { + $sql = preg_replace("/$q($qe|[^$q])*$q/", '', $sql); + } + + return $sql; + } + + /** + * Bind a column of the statement result set to a PHP variable. + * + * @param string $column Name the column in the result set, either by + * position or by name. + * @param mixed $param Reference to the PHP variable containing the value. + * @param mixed $type OPTIONAL + * @return bool + */ + public function bindColumn($column, &$param, $type = null) + { + $this->_bindColumn[$column] =& $param; + return true; + } + + /** + * Binds a parameter to the specified variable name. + * + * @param mixed $parameter Name the parameter, either integer or string. + * @param mixed $variable Reference to PHP variable containing the value. + * @param mixed $type OPTIONAL Datatype of SQL parameter. + * @param mixed $length OPTIONAL Length of SQL parameter. + * @param mixed $options OPTIONAL Other options. + * @return bool + */ + public function bindParam($parameter, &$variable, $type = null, $length = null, $options = null) + { + if (!is_int($parameter) && !is_string($parameter)) { + /** + * @see Zend_Db_Statement_Exception + */ + require_once 'Zend/Db/Statement/Exception.php'; + throw new Zend_Db_Statement_Exception('Invalid bind-variable position'); + } + + $position = null; + if (($intval = (int) $parameter) > 0 && $this->_adapter->supportsParameters('positional')) { + if ($intval >= 1 || $intval <= count($this->_sqlParam)) { + $position = $intval; + } + } else if ($this->_adapter->supportsParameters('named')) { + if ($parameter[0] != ':') { + $parameter = ':' . $parameter; + } + if (in_array($parameter, $this->_sqlParam) !== false) { + $position = $parameter; + } + } + + if ($position === null) { + /** + * @see Zend_Db_Statement_Exception + */ + require_once 'Zend/Db/Statement/Exception.php'; + throw new Zend_Db_Statement_Exception("Invalid bind-variable position '$parameter'"); + } + + // Finally we are assured that $position is valid + $this->_bindParam[$position] =& $variable; + return $this->_bindParam($position, $variable, $type, $length, $options); + } + + /** + * Binds a value to a parameter. + * + * @param mixed $parameter Name the parameter, either integer or string. + * @param mixed $value Scalar value to bind to the parameter. + * @param mixed $type OPTIONAL Datatype of the parameter. + * @return bool + */ + public function bindValue($parameter, $value, $type = null) + { + return $this->bindParam($parameter, $value, $type); + } + + /** + * Executes a prepared statement. + * + * @param array $params OPTIONAL Values to bind to parameter placeholders. + * @return bool + */ + public function execute(array $params = null) + { + /* + * Simple case - no query profiler to manage. + */ + if ($this->_queryId === null) { + return $this->_execute($params); + } + + /* + * Do the same thing, but with query profiler + * management before and after the execute. + */ + $prof = $this->_adapter->getProfiler(); + $qp = $prof->getQueryProfile($this->_queryId); + if ($qp->hasEnded()) { + $this->_queryId = $prof->queryClone($qp); + $qp = $prof->getQueryProfile($this->_queryId); + } + if ($params !== null) { + $qp->bindParams($params); + } else { + $qp->bindParams($this->_bindParam); + } + $qp->start($this->_queryId); + + $retval = $this->_execute($params); + + $prof->queryEnd($this->_queryId); + + return $retval; + } + + /** + * Returns an array containing all of the result set rows. + * + * @param int $style OPTIONAL Fetch mode. + * @param int $col OPTIONAL Column number, if fetch mode is by column. + * @return array Collection of rows, each in a format by the fetch mode. + */ + public function fetchAll($style = null, $col = null) + { + $data = array(); + if ($style === Zend_Db::FETCH_COLUMN && $col === null) { + $col = 0; + } + if ($col === null) { + while ($row = $this->fetch($style)) { + $data[] = $row; + } + } else { + while (false !== ($val = $this->fetchColumn($col))) { + $data[] = $val; + } + } + return $data; + } + + /** + * Returns a single column from the next row of a result set. + * + * @param int $col OPTIONAL Position of the column to fetch. + * @return string One value from the next row of result set, or false. + */ + public function fetchColumn($col = 0) + { + $data = array(); + $col = (int) $col; + $row = $this->fetch(Zend_Db::FETCH_NUM); + if (!is_array($row)) { + return false; + } + return $row[$col]; + } + + /** + * Fetches the next row and returns it as an object. + * + * @param string $class OPTIONAL Name of the class to create. + * @param array $config OPTIONAL Constructor arguments for the class. + * @return mixed One object instance of the specified class, or false. + */ + public function fetchObject($class = 'stdClass', array $config = array()) + { + $obj = new $class($config); + $row = $this->fetch(Zend_Db::FETCH_ASSOC); + if (!is_array($row)) { + return false; + } + foreach ($row as $key => $val) { + $obj->$key = $val; + } + return $obj; + } + + /** + * Retrieve a statement attribute. + * + * @param string $key Attribute name. + * @return mixed Attribute value. + */ + public function getAttribute($key) + { + if (array_key_exists($key, $this->_attribute)) { + return $this->_attribute[$key]; + } + } + + /** + * Set a statement attribute. + * + * @param string $key Attribute name. + * @param mixed $val Attribute value. + * @return bool + */ + public function setAttribute($key, $val) + { + $this->_attribute[$key] = $val; + } + + /** + * Set the default fetch mode for this statement. + * + * @param int $mode The fetch mode. + * @return bool + * @throws Zend_Db_Statement_Exception + */ + public function setFetchMode($mode) + { + switch ($mode) { + case Zend_Db::FETCH_NUM: + case Zend_Db::FETCH_ASSOC: + case Zend_Db::FETCH_BOTH: + case Zend_Db::FETCH_OBJ: + $this->_fetchMode = $mode; + break; + case Zend_Db::FETCH_BOUND: + default: + $this->closeCursor(); + /** + * @see Zend_Db_Statement_Exception + */ + require_once 'Zend/Db/Statement/Exception.php'; + throw new Zend_Db_Statement_Exception('invalid fetch mode'); + break; + } + } + + /** + * Helper function to map retrieved row + * to bound column variables + * + * @param array $row + * @return bool True + */ + public function _fetchBound($row) + { + foreach ($row as $key => $value) { + // bindColumn() takes 1-based integer positions + // but fetch() returns 0-based integer indexes + if (is_int($key)) { + $key++; + } + // set results only to variables that were bound previously + if (isset($this->_bindColumn[$key])) { + $this->_bindColumn[$key] = $value; + } + } + return true; + } + + /** + * Gets the Zend_Db_Adapter_Abstract for this + * particular Zend_Db_Statement object. + * + * @return Zend_Db_Adapter_Abstract + */ + public function getAdapter() + { + return $this->_adapter; + } + + /** + * Gets the resource or object setup by the + * _parse + * @return unknown_type + */ + public function getDriverStatement() + { + return $this->_stmt; + } +} diff --git a/library/Zend/Db/Statement/Db2.php b/library/Zend/Db/Statement/Db2.php new file mode 100644 index 000000000..eec657530 --- /dev/null +++ b/library/Zend/Db/Statement/Db2.php @@ -0,0 +1,360 @@ +_adapter->getConnection(); + + // db2_prepare on i5 emits errors, these need to be + // suppressed so that proper exceptions can be thrown + $this->_stmt = @db2_prepare($connection, $sql); + + if (!$this->_stmt) { + /** + * @see Zend_Db_Statement_Db2_Exception + */ + require_once 'Zend/Db/Statement/Db2/Exception.php'; + throw new Zend_Db_Statement_Db2_Exception( + db2_stmt_errormsg(), + db2_stmt_error() + ); + } + } + + /** + * Binds a parameter to the specified variable name. + * + * @param mixed $parameter Name the parameter, either integer or string. + * @param mixed $variable Reference to PHP variable containing the value. + * @param mixed $type OPTIONAL Datatype of SQL parameter. + * @param mixed $length OPTIONAL Length of SQL parameter. + * @param mixed $options OPTIONAL Other options. + * @return bool + * @throws Zend_Db_Statement_Db2_Exception + */ + public function _bindParam($parameter, &$variable, $type = null, $length = null, $options = null) + { + if ($type === null) { + $type = DB2_PARAM_IN; + } + + if (isset($options['data-type'])) { + $datatype = $options['data-type']; + } else { + $datatype = DB2_CHAR; + } + + if (!db2_bind_param($this->_stmt, $position, "variable", $type, $datatype)) { + /** + * @see Zend_Db_Statement_Db2_Exception + */ + require_once 'Zend/Db/Statement/Db2/Exception.php'; + throw new Zend_Db_Statement_Db2_Exception( + db2_stmt_errormsg(), + db2_stmt_error() + ); + } + + return true; + } + + /** + * Closes the cursor, allowing the statement to be executed again. + * + * @return bool + */ + public function closeCursor() + { + if (!$this->_stmt) { + return false; + } + db2_free_stmt($this->_stmt); + $this->_stmt = false; + return true; + } + + + /** + * Returns the number of columns in the result set. + * Returns null if the statement has no result set metadata. + * + * @return int The number of columns. + */ + public function columnCount() + { + if (!$this->_stmt) { + return false; + } + return db2_num_fields($this->_stmt); + } + + /** + * Retrieves the error code, if any, associated with the last operation on + * the statement handle. + * + * @return string error code. + */ + public function errorCode() + { + if (!$this->_stmt) { + return false; + } + + $error = db2_stmt_error(); + if ($error === '') { + return false; + } + + return $error; + } + + /** + * Retrieves an array of error information, if any, associated with the + * last operation on the statement handle. + * + * @return array + */ + public function errorInfo() + { + $error = $this->errorCode(); + if ($error === false){ + return false; + } + + /* + * Return three-valued array like PDO. But DB2 does not distinguish + * between SQLCODE and native RDBMS error code, so repeat the SQLCODE. + */ + return array( + $error, + $error, + db2_stmt_errormsg() + ); + } + + /** + * Executes a prepared statement. + * + * @param array $params OPTIONAL Values to bind to parameter placeholders. + * @return bool + * @throws Zend_Db_Statement_Db2_Exception + */ + public function _execute(array $params = null) + { + if (!$this->_stmt) { + return false; + } + + $retval = true; + if ($params !== null) { + $retval = @db2_execute($this->_stmt, $params); + } else { + $retval = @db2_execute($this->_stmt); + } + + if ($retval === false) { + /** + * @see Zend_Db_Statement_Db2_Exception + */ + require_once 'Zend/Db/Statement/Db2/Exception.php'; + throw new Zend_Db_Statement_Db2_Exception( + db2_stmt_errormsg(), + db2_stmt_error()); + } + + $this->_keys = array(); + if ($field_num = $this->columnCount()) { + for ($i = 0; $i < $field_num; $i++) { + $name = db2_field_name($this->_stmt, $i); + $this->_keys[] = $name; + } + } + + $this->_values = array(); + if ($this->_keys) { + $this->_values = array_fill(0, count($this->_keys), null); + } + + return $retval; + } + + /** + * Fetches a row from the result set. + * + * @param int $style OPTIONAL Fetch mode for this fetch operation. + * @param int $cursor OPTIONAL Absolute, relative, or other. + * @param int $offset OPTIONAL Number for absolute or relative cursors. + * @return mixed Array, object, or scalar depending on fetch mode. + * @throws Zend_Db_Statement_Db2_Exception + */ + public function fetch($style = null, $cursor = null, $offset = null) + { + if (!$this->_stmt) { + return false; + } + + if ($style === null) { + $style = $this->_fetchMode; + } + + switch ($style) { + case Zend_Db::FETCH_NUM : + $row = db2_fetch_array($this->_stmt); + break; + case Zend_Db::FETCH_ASSOC : + $row = db2_fetch_assoc($this->_stmt); + break; + case Zend_Db::FETCH_BOTH : + $row = db2_fetch_both($this->_stmt); + break; + case Zend_Db::FETCH_OBJ : + $row = db2_fetch_object($this->_stmt); + break; + case Zend_Db::FETCH_BOUND: + $row = db2_fetch_both($this->_stmt); + if ($row !== false) { + return $this->_fetchBound($row); + } + break; + default: + /** + * @see Zend_Db_Statement_Db2_Exception + */ + require_once 'Zend/Db/Statement/Db2/Exception.php'; + throw new Zend_Db_Statement_Db2_Exception("Invalid fetch mode '$style' specified"); + break; + } + + return $row; + } + + /** + * Fetches the next row and returns it as an object. + * + * @param string $class OPTIONAL Name of the class to create. + * @param array $config OPTIONAL Constructor arguments for the class. + * @return mixed One object instance of the specified class. + */ + public function fetchObject($class = 'stdClass', array $config = array()) + { + $obj = $this->fetch(Zend_Db::FETCH_OBJ); + return $obj; + } + + /** + * Retrieves the next rowset (result set) for a SQL statement that has + * multiple result sets. An example is a stored procedure that returns + * the results of multiple queries. + * + * @return bool + * @throws Zend_Db_Statement_Db2_Exception + */ + public function nextRowset() + { + /** + * @see Zend_Db_Statement_Db2_Exception + */ + require_once 'Zend/Db/Statement/Db2/Exception.php'; + throw new Zend_Db_Statement_Db2_Exception(__FUNCTION__ . '() is not implemented'); + } + + /** + * Returns the number of rows affected by the execution of the + * last INSERT, DELETE, or UPDATE statement executed by this + * statement object. + * + * @return int The number of rows affected. + */ + public function rowCount() + { + if (!$this->_stmt) { + return false; + } + + $num = @db2_num_rows($this->_stmt); + + if ($num === false) { + return 0; + } + + return $num; + } + + /** + * Returns an array containing all of the result set rows. + * + * @param int $style OPTIONAL Fetch mode. + * @param int $col OPTIONAL Column number, if fetch mode is by column. + * @return array Collection of rows, each in a format by the fetch mode. + * + * Behaves like parent, but if limit() + * is used, the final result removes the extra column + * 'zend_db_rownum' + */ + public function fetchAll($style = null, $col = null) + { + $data = parent::fetchAll($style, $col); + $results = array(); + $remove = $this->_adapter->foldCase('ZEND_DB_ROWNUM'); + + foreach ($data as $row) { + if (is_array($row) && array_key_exists($remove, $row)) { + unset($row[$remove]); + } + $results[] = $row; + } + return $results; + } +} diff --git a/library/Zend/Db/Statement/Db2/Exception.php b/library/Zend/Db/Statement/Db2/Exception.php new file mode 100644 index 000000000..d758854d8 --- /dev/null +++ b/library/Zend/Db/Statement/Db2/Exception.php @@ -0,0 +1,58 @@ +message = $msg; + $this->code = $state; + } + +} + diff --git a/library/Zend/Db/Statement/Exception.php b/library/Zend/Db/Statement/Exception.php new file mode 100644 index 000000000..f628ea059 --- /dev/null +++ b/library/Zend/Db/Statement/Exception.php @@ -0,0 +1,56 @@ +getPrevious() !== null); + } + + /** + * @return Exception|null + */ + public function getChainedException() + { + return $this->getPrevious(); + } +} diff --git a/library/Zend/Db/Statement/Interface.php b/library/Zend/Db/Statement/Interface.php new file mode 100644 index 000000000..701c64010 --- /dev/null +++ b/library/Zend/Db/Statement/Interface.php @@ -0,0 +1,203 @@ +_adapter->getConnection(); + + $this->_stmt = $mysqli->prepare($sql); + + if ($this->_stmt === false || $mysqli->errno) { + /** + * @see Zend_Db_Statement_Mysqli_Exception + */ + require_once 'Zend/Db/Statement/Mysqli/Exception.php'; + throw new Zend_Db_Statement_Mysqli_Exception("Mysqli prepare error: " . $mysqli->error, $mysqli->errno); + } + } + + /** + * Binds a parameter to the specified variable name. + * + * @param mixed $parameter Name the parameter, either integer or string. + * @param mixed $variable Reference to PHP variable containing the value. + * @param mixed $type OPTIONAL Datatype of SQL parameter. + * @param mixed $length OPTIONAL Length of SQL parameter. + * @param mixed $options OPTIONAL Other options. + * @return bool + * @throws Zend_Db_Statement_Mysqli_Exception + */ + protected function _bindParam($parameter, &$variable, $type = null, $length = null, $options = null) + { + return true; + } + + /** + * Closes the cursor and the statement. + * + * @return bool + */ + public function close() + { + if ($this->_stmt) { + $r = $this->_stmt->close(); + $this->_stmt = null; + return $r; + } + return false; + } + + /** + * Closes the cursor, allowing the statement to be executed again. + * + * @return bool + */ + public function closeCursor() + { + if ($stmt = $this->_stmt) { + $mysqli = $this->_adapter->getConnection(); + while ($mysqli->more_results()) { + $mysqli->next_result(); + } + $this->_stmt->free_result(); + return $this->_stmt->reset(); + } + return false; + } + + /** + * Returns the number of columns in the result set. + * Returns null if the statement has no result set metadata. + * + * @return int The number of columns. + */ + public function columnCount() + { + if (isset($this->_meta) && $this->_meta) { + return $this->_meta->field_count; + } + return 0; + } + + /** + * Retrieves the error code, if any, associated with the last operation on + * the statement handle. + * + * @return string error code. + */ + public function errorCode() + { + if (!$this->_stmt) { + return false; + } + return substr($this->_stmt->sqlstate, 0, 5); + } + + /** + * Retrieves an array of error information, if any, associated with the + * last operation on the statement handle. + * + * @return array + */ + public function errorInfo() + { + if (!$this->_stmt) { + return false; + } + return array( + substr($this->_stmt->sqlstate, 0, 5), + $this->_stmt->errno, + $this->_stmt->error, + ); + } + + /** + * Executes a prepared statement. + * + * @param array $params OPTIONAL Values to bind to parameter placeholders. + * @return bool + * @throws Zend_Db_Statement_Mysqli_Exception + */ + public function _execute(array $params = null) + { + if (!$this->_stmt) { + return false; + } + + // if no params were given as an argument to execute(), + // then default to the _bindParam array + if ($params === null) { + $params = $this->_bindParam; + } + // send $params as input parameters to the statement + if ($params) { + array_unshift($params, str_repeat('s', count($params))); + $stmtParams = array(); + foreach ($params as $k => &$value) { + $stmtParams[$k] = &$value; + } + call_user_func_array( + array($this->_stmt, 'bind_param'), + $stmtParams + ); + } + + // execute the statement + $retval = $this->_stmt->execute(); + if ($retval === false) { + /** + * @see Zend_Db_Statement_Mysqli_Exception + */ + require_once 'Zend/Db/Statement/Mysqli/Exception.php'; + throw new Zend_Db_Statement_Mysqli_Exception("Mysqli statement execute error : " . $this->_stmt->error, $this->_stmt->errno); + } + + + // retain metadata + if ($this->_meta === null) { + $this->_meta = $this->_stmt->result_metadata(); + if ($this->_stmt->errno) { + /** + * @see Zend_Db_Statement_Mysqli_Exception + */ + require_once 'Zend/Db/Statement/Mysqli/Exception.php'; + throw new Zend_Db_Statement_Mysqli_Exception("Mysqli statement metadata error: " . $this->_stmt->error, $this->_stmt->errno); + } + } + + // statements that have no result set do not return metadata + if ($this->_meta !== false) { + + // get the column names that will result + $this->_keys = array(); + foreach ($this->_meta->fetch_fields() as $col) { + $this->_keys[] = $this->_adapter->foldCase($col->name); + } + + // set up a binding space for result variables + $this->_values = array_fill(0, count($this->_keys), null); + + // set up references to the result binding space. + // just passing $this->_values in the call_user_func_array() + // below won't work, you need references. + $refs = array(); + foreach ($this->_values as $i => &$f) { + $refs[$i] = &$f; + } + + $this->_stmt->store_result(); + // bind to the result variables + call_user_func_array( + array($this->_stmt, 'bind_result'), + $this->_values + ); + } + return $retval; + } + + + /** + * Fetches a row from the result set. + * + * @param int $style OPTIONAL Fetch mode for this fetch operation. + * @param int $cursor OPTIONAL Absolute, relative, or other. + * @param int $offset OPTIONAL Number for absolute or relative cursors. + * @return mixed Array, object, or scalar depending on fetch mode. + * @throws Zend_Db_Statement_Mysqli_Exception + */ + public function fetch($style = null, $cursor = null, $offset = null) + { + if (!$this->_stmt) { + return false; + } + // fetch the next result + $retval = $this->_stmt->fetch(); + switch ($retval) { + case null: // end of data + case false: // error occurred + $this->_stmt->reset(); + return false; + default: + // fallthrough + } + + // make sure we have a fetch mode + if ($style === null) { + $style = $this->_fetchMode; + } + + // dereference the result values, otherwise things like fetchAll() + // return the same values for every entry (because of the reference). + $values = array(); + foreach ($this->_values as $key => $val) { + $values[] = $val; + } + + $row = false; + switch ($style) { + case Zend_Db::FETCH_NUM: + $row = $values; + break; + case Zend_Db::FETCH_ASSOC: + $row = array_combine($this->_keys, $values); + break; + case Zend_Db::FETCH_BOTH: + $assoc = array_combine($this->_keys, $values); + $row = array_merge($values, $assoc); + break; + case Zend_Db::FETCH_OBJ: + $row = (object) array_combine($this->_keys, $values); + break; + case Zend_Db::FETCH_BOUND: + $assoc = array_combine($this->_keys, $values); + $row = array_merge($values, $assoc); + return $this->_fetchBound($row); + break; + default: + /** + * @see Zend_Db_Statement_Mysqli_Exception + */ + require_once 'Zend/Db/Statement/Mysqli/Exception.php'; + throw new Zend_Db_Statement_Mysqli_Exception("Invalid fetch mode '$style' specified"); + break; + } + return $row; + } + + /** + * Retrieves the next rowset (result set) for a SQL statement that has + * multiple result sets. An example is a stored procedure that returns + * the results of multiple queries. + * + * @return bool + * @throws Zend_Db_Statement_Mysqli_Exception + */ + public function nextRowset() + { + /** + * @see Zend_Db_Statement_Mysqli_Exception + */ + require_once 'Zend/Db/Statement/Mysqli/Exception.php'; + throw new Zend_Db_Statement_Mysqli_Exception(__FUNCTION__.'() is not implemented'); + } + + /** + * Returns the number of rows affected by the execution of the + * last INSERT, DELETE, or UPDATE statement executed by this + * statement object. + * + * @return int The number of rows affected. + */ + public function rowCount() + { + if (!$this->_adapter) { + return false; + } + $mysqli = $this->_adapter->getConnection(); + return $mysqli->affected_rows; + } + +} \ No newline at end of file diff --git a/library/Zend/Db/Statement/Mysqli/Exception.php b/library/Zend/Db/Statement/Mysqli/Exception.php new file mode 100644 index 000000000..a1c5ff8e7 --- /dev/null +++ b/library/Zend/Db/Statement/Mysqli/Exception.php @@ -0,0 +1,38 @@ +_lobAsString = (bool) $lob_as_string; + return $this; + } + + /** + * Return whether or not LOB are returned as string + * + * @return boolean + */ + public function getLobAsString() + { + return $this->_lobAsString; + } + + /** + * Prepares statement handle + * + * @param string $sql + * @return void + * @throws Zend_Db_Statement_Oracle_Exception + */ + protected function _prepare($sql) + { + $connection = $this->_adapter->getConnection(); + $this->_stmt = oci_parse($connection, $sql); + if (!$this->_stmt) { + /** + * @see Zend_Db_Statement_Oracle_Exception + */ + require_once 'Zend/Db/Statement/Oracle/Exception.php'; + throw new Zend_Db_Statement_Oracle_Exception(oci_error($connection)); + } + } + + /** + * Binds a parameter to the specified variable name. + * + * @param mixed $parameter Name the parameter, either integer or string. + * @param mixed $variable Reference to PHP variable containing the value. + * @param mixed $type OPTIONAL Datatype of SQL parameter. + * @param mixed $length OPTIONAL Length of SQL parameter. + * @param mixed $options OPTIONAL Other options. + * @return bool + * @throws Zend_Db_Statement_Exception + */ + protected function _bindParam($parameter, &$variable, $type = null, $length = null, $options = null) + { + // default value + if ($type === NULL) { + $type = SQLT_CHR; + } + + // default value + if ($length === NULL) { + $length = -1; + } + + $retval = @oci_bind_by_name($this->_stmt, $parameter, $variable, $length, $type); + if ($retval === false) { + /** + * @see Zend_Db_Adapter_Oracle_Exception + */ + require_once 'Zend/Db/Statement/Oracle/Exception.php'; + throw new Zend_Db_Statement_Oracle_Exception(oci_error($this->_stmt)); + } + + return true; + } + + /** + * Closes the cursor, allowing the statement to be executed again. + * + * @return bool + */ + public function closeCursor() + { + if (!$this->_stmt) { + return false; + } + + oci_free_statement($this->_stmt); + $this->_stmt = false; + return true; + } + + /** + * Returns the number of columns in the result set. + * Returns null if the statement has no result set metadata. + * + * @return int The number of columns. + */ + public function columnCount() + { + if (!$this->_stmt) { + return false; + } + + return oci_num_fields($this->_stmt); + } + + + /** + * Retrieves the error code, if any, associated with the last operation on + * the statement handle. + * + * @return string error code. + */ + public function errorCode() + { + if (!$this->_stmt) { + return false; + } + + $error = oci_error($this->_stmt); + + if (!$error) { + return false; + } + + return $error['code']; + } + + + /** + * Retrieves an array of error information, if any, associated with the + * last operation on the statement handle. + * + * @return array + */ + public function errorInfo() + { + if (!$this->_stmt) { + return false; + } + + $error = oci_error($this->_stmt); + if (!$error) { + return false; + } + + if (isset($error['sqltext'])) { + return array( + $error['code'], + $error['message'], + $error['offset'], + $error['sqltext'], + ); + } else { + return array( + $error['code'], + $error['message'], + ); + } + } + + + /** + * Executes a prepared statement. + * + * @param array $params OPTIONAL Values to bind to parameter placeholders. + * @return bool + * @throws Zend_Db_Statement_Exception + */ + public function _execute(array $params = null) + { + $connection = $this->_adapter->getConnection(); + + if (!$this->_stmt) { + return false; + } + + if ($params !== null) { + if (!is_array($params)) { + $params = array($params); + } + $error = false; + foreach (array_keys($params) as $name) { + if (!@oci_bind_by_name($this->_stmt, $name, $params[$name], -1)) { + $error = true; + break; + } + } + if ($error) { + /** + * @see Zend_Db_Adapter_Oracle_Exception + */ + require_once 'Zend/Db/Statement/Oracle/Exception.php'; + throw new Zend_Db_Statement_Oracle_Exception(oci_error($this->_stmt)); + } + } + + $retval = @oci_execute($this->_stmt, $this->_adapter->_getExecuteMode()); + if ($retval === false) { + /** + * @see Zend_Db_Adapter_Oracle_Exception + */ + require_once 'Zend/Db/Statement/Oracle/Exception.php'; + throw new Zend_Db_Statement_Oracle_Exception(oci_error($this->_stmt)); + } + + $this->_keys = Array(); + if ($field_num = oci_num_fields($this->_stmt)) { + for ($i = 1; $i <= $field_num; $i++) { + $name = oci_field_name($this->_stmt, $i); + $this->_keys[] = $name; + } + } + + $this->_values = Array(); + if ($this->_keys) { + $this->_values = array_fill(0, count($this->_keys), null); + } + + return $retval; + } + + /** + * Fetches a row from the result set. + * + * @param int $style OPTIONAL Fetch mode for this fetch operation. + * @param int $cursor OPTIONAL Absolute, relative, or other. + * @param int $offset OPTIONAL Number for absolute or relative cursors. + * @return mixed Array, object, or scalar depending on fetch mode. + * @throws Zend_Db_Statement_Exception + */ + public function fetch($style = null, $cursor = null, $offset = null) + { + if (!$this->_stmt) { + return false; + } + + if ($style === null) { + $style = $this->_fetchMode; + } + + $lob_as_string = $this->getLobAsString() ? OCI_RETURN_LOBS : 0; + + switch ($style) { + case Zend_Db::FETCH_NUM: + $row = oci_fetch_array($this->_stmt, OCI_NUM | OCI_RETURN_NULLS | $lob_as_string); + break; + case Zend_Db::FETCH_ASSOC: + $row = oci_fetch_array($this->_stmt, OCI_ASSOC | OCI_RETURN_NULLS | $lob_as_string); + break; + case Zend_Db::FETCH_BOTH: + $row = oci_fetch_array($this->_stmt, OCI_BOTH | OCI_RETURN_NULLS | $lob_as_string); + break; + case Zend_Db::FETCH_OBJ: + $row = oci_fetch_object($this->_stmt); + break; + case Zend_Db::FETCH_BOUND: + $row = oci_fetch_array($this->_stmt, OCI_BOTH | OCI_RETURN_NULLS | $lob_as_string); + if ($row !== false) { + return $this->_fetchBound($row); + } + break; + default: + /** + * @see Zend_Db_Adapter_Oracle_Exception + */ + require_once 'Zend/Db/Statement/Oracle/Exception.php'; + throw new Zend_Db_Statement_Oracle_Exception( + array( + 'code' => 'HYC00', + 'message' => "Invalid fetch mode '$style' specified" + ) + ); + break; + } + + if (! $row && $error = oci_error($this->_stmt)) { + /** + * @see Zend_Db_Adapter_Oracle_Exception + */ + require_once 'Zend/Db/Statement/Oracle/Exception.php'; + throw new Zend_Db_Statement_Oracle_Exception($error); + } + + if (is_array($row) && array_key_exists('zend_db_rownum', $row)) { + unset($row['zend_db_rownum']); + } + + return $row; + } + + /** + * Returns an array containing all of the result set rows. + * + * @param int $style OPTIONAL Fetch mode. + * @param int $col OPTIONAL Column number, if fetch mode is by column. + * @return array Collection of rows, each in a format by the fetch mode. + * @throws Zend_Db_Statement_Exception + */ + public function fetchAll($style = null, $col = 0) + { + if (!$this->_stmt) { + return false; + } + + // make sure we have a fetch mode + if ($style === null) { + $style = $this->_fetchMode; + } + + $flags = OCI_FETCHSTATEMENT_BY_ROW; + + switch ($style) { + case Zend_Db::FETCH_BOTH: + /** + * @see Zend_Db_Adapter_Oracle_Exception + */ + require_once 'Zend/Db/Statement/Oracle/Exception.php'; + throw new Zend_Db_Statement_Oracle_Exception( + array( + 'code' => 'HYC00', + 'message' => "OCI8 driver does not support fetchAll(FETCH_BOTH), use fetch() in a loop instead" + ) + ); + // notreached + $flags |= OCI_NUM; + $flags |= OCI_ASSOC; + break; + case Zend_Db::FETCH_NUM: + $flags |= OCI_NUM; + break; + case Zend_Db::FETCH_ASSOC: + $flags |= OCI_ASSOC; + break; + case Zend_Db::FETCH_OBJ: + break; + case Zend_Db::FETCH_COLUMN: + $flags = $flags &~ OCI_FETCHSTATEMENT_BY_ROW; + $flags |= OCI_FETCHSTATEMENT_BY_COLUMN; + $flags |= OCI_NUM; + break; + default: + /** + * @see Zend_Db_Adapter_Oracle_Exception + */ + require_once 'Zend/Db/Statement/Oracle/Exception.php'; + throw new Zend_Db_Statement_Oracle_Exception( + array( + 'code' => 'HYC00', + 'message' => "Invalid fetch mode '$style' specified" + ) + ); + break; + } + + $result = Array(); + if ($flags != OCI_FETCHSTATEMENT_BY_ROW) { /* not Zend_Db::FETCH_OBJ */ + if (! ($rows = oci_fetch_all($this->_stmt, $result, 0, -1, $flags) )) { + if ($error = oci_error($this->_stmt)) { + /** + * @see Zend_Db_Adapter_Oracle_Exception + */ + require_once 'Zend/Db/Statement/Oracle/Exception.php'; + throw new Zend_Db_Statement_Oracle_Exception($error); + } + if (!$rows) { + return array(); + } + } + if ($style == Zend_Db::FETCH_COLUMN) { + $result = $result[$col]; + } + foreach ($result as &$row) { + if (is_array($row) && array_key_exists('zend_db_rownum', $row)) { + unset($row['zend_db_rownum']); + } + } + } else { + while (($row = oci_fetch_object($this->_stmt)) !== false) { + $result [] = $row; + } + if ($error = oci_error($this->_stmt)) { + /** + * @see Zend_Db_Adapter_Oracle_Exception + */ + require_once 'Zend/Db/Statement/Oracle/Exception.php'; + throw new Zend_Db_Statement_Oracle_Exception($error); + } + } + + return $result; + } + + + /** + * Returns a single column from the next row of a result set. + * + * @param int $col OPTIONAL Position of the column to fetch. + * @return string + * @throws Zend_Db_Statement_Exception + */ + public function fetchColumn($col = 0) + { + if (!$this->_stmt) { + return false; + } + + if (!oci_fetch($this->_stmt)) { + // if no error, there is simply no record + if (!$error = oci_error($this->_stmt)) { + return false; + } + /** + * @see Zend_Db_Adapter_Oracle_Exception + */ + require_once 'Zend/Db/Statement/Oracle/Exception.php'; + throw new Zend_Db_Statement_Oracle_Exception($error); + } + + $data = oci_result($this->_stmt, $col+1); //1-based + if ($data === false) { + /** + * @see Zend_Db_Adapter_Oracle_Exception + */ + require_once 'Zend/Db/Statement/Oracle/Exception.php'; + throw new Zend_Db_Statement_Oracle_Exception(oci_error($this->_stmt)); + } + + if ($this->getLobAsString()) { + // instanceof doesn't allow '-', we must use a temporary string + $type = 'OCI-Lob'; + if ($data instanceof $type) { + $data = $data->read($data->size()); + } + } + + return $data; + } + + /** + * Fetches the next row and returns it as an object. + * + * @param string $class OPTIONAL Name of the class to create. + * @param array $config OPTIONAL Constructor arguments for the class. + * @return mixed One object instance of the specified class. + * @throws Zend_Db_Statement_Exception + */ + public function fetchObject($class = 'stdClass', array $config = array()) + { + if (!$this->_stmt) { + return false; + } + + $obj = oci_fetch_object($this->_stmt); + + if ($error = oci_error($this->_stmt)) { + /** + * @see Zend_Db_Adapter_Oracle_Exception + */ + require_once 'Zend/Db/Statement/Oracle/Exception.php'; + throw new Zend_Db_Statement_Oracle_Exception($error); + } + + /* @todo XXX handle parameters */ + + return $obj; + } + + /** + * Retrieves the next rowset (result set) for a SQL statement that has + * multiple result sets. An example is a stored procedure that returns + * the results of multiple queries. + * + * @return bool + * @throws Zend_Db_Statement_Exception + */ + public function nextRowset() + { + /** + * @see Zend_Db_Statement_Oracle_Exception + */ + require_once 'Zend/Db/Statement/Oracle/Exception.php'; + throw new Zend_Db_Statement_Oracle_Exception( + array( + 'code' => 'HYC00', + 'message' => 'Optional feature not implemented' + ) + ); + } + + /** + * Returns the number of rows affected by the execution of the + * last INSERT, DELETE, or UPDATE statement executed by this + * statement object. + * + * @return int The number of rows affected. + * @throws Zend_Db_Statement_Exception + */ + public function rowCount() + { + if (!$this->_stmt) { + return false; + } + + $num_rows = oci_num_rows($this->_stmt); + + if ($num_rows === false) { + /** + * @see Zend_Db_Adapter_Oracle_Exception + */ + require_once 'Zend/Db/Statement/Oracle/Exception.php'; + throw new Zend_Db_Statement_Oracle_Exception(oci_error($this->_stmt)); + } + + return $num_rows; + } + +} diff --git a/library/Zend/Db/Statement/Oracle/Exception.php b/library/Zend/Db/Statement/Oracle/Exception.php new file mode 100644 index 000000000..215477bbc --- /dev/null +++ b/library/Zend/Db/Statement/Oracle/Exception.php @@ -0,0 +1,59 @@ +message = $error['code']." ".$error['message']; + } else { + $this->message = $error['code']." ".$error['message']." "; + $this->message .= substr($error['sqltext'], 0, $error['offset']); + $this->message .= "*"; + $this->message .= substr($error['sqltext'], $error['offset']); + } + $this->code = $error['code']; + } + if (!$this->code && $code) { + $this->code = $code; + } + } +} + diff --git a/library/Zend/Db/Statement/Pdo.php b/library/Zend/Db/Statement/Pdo.php new file mode 100644 index 000000000..775e40103 --- /dev/null +++ b/library/Zend/Db/Statement/Pdo.php @@ -0,0 +1,439 @@ +_stmt = $this->_adapter->getConnection()->prepare($sql); + } catch (PDOException $e) { + require_once 'Zend/Db/Statement/Exception.php'; + throw new Zend_Db_Statement_Exception($e->getMessage(), $e->getCode(), $e); + } + } + + /** + * Bind a column of the statement result set to a PHP variable. + * + * @param string $column Name the column in the result set, either by + * position or by name. + * @param mixed $param Reference to the PHP variable containing the value. + * @param mixed $type OPTIONAL + * @return bool + * @throws Zend_Db_Statement_Exception + */ + public function bindColumn($column, &$param, $type = null) + { + try { + if ($type === null) { + return $this->_stmt->bindColumn($column, $param); + } else { + return $this->_stmt->bindColumn($column, $param, $type); + } + } catch (PDOException $e) { + require_once 'Zend/Db/Statement/Exception.php'; + throw new Zend_Db_Statement_Exception($e->getMessage(), $e->getCode(), $e); + } + } + + /** + * Binds a parameter to the specified variable name. + * + * @param mixed $parameter Name the parameter, either integer or string. + * @param mixed $variable Reference to PHP variable containing the value. + * @param mixed $type OPTIONAL Datatype of SQL parameter. + * @param mixed $length OPTIONAL Length of SQL parameter. + * @param mixed $options OPTIONAL Other options. + * @return bool + * @throws Zend_Db_Statement_Exception + */ + protected function _bindParam($parameter, &$variable, $type = null, $length = null, $options = null) + { + try { + if ($type === null) { + if (is_bool($variable)) { + $type = PDO::PARAM_BOOL; + } elseif ($variable === null) { + $type = PDO::PARAM_NULL; + } elseif (is_integer($variable)) { + $type = PDO::PARAM_INT; + } else { + $type = PDO::PARAM_STR; + } + } + return $this->_stmt->bindParam($parameter, $variable, $type, $length, $options); + } catch (PDOException $e) { + require_once 'Zend/Db/Statement/Exception.php'; + throw new Zend_Db_Statement_Exception($e->getMessage(), $e->getCode(), $e); + } + } + + /** + * Binds a value to a parameter. + * + * @param mixed $parameter Name the parameter, either integer or string. + * @param mixed $value Scalar value to bind to the parameter. + * @param mixed $type OPTIONAL Datatype of the parameter. + * @return bool + * @throws Zend_Db_Statement_Exception + */ + public function bindValue($parameter, $value, $type = null) + { + if (is_string($parameter) && $parameter[0] != ':') { + $parameter = ":$parameter"; + } + + $this->_bindParam[$parameter] = $value; + + try { + if ($type === null) { + return $this->_stmt->bindValue($parameter, $value); + } else { + return $this->_stmt->bindValue($parameter, $value, $type); + } + } catch (PDOException $e) { + require_once 'Zend/Db/Statement/Exception.php'; + throw new Zend_Db_Statement_Exception($e->getMessage(), $e->getCode(), $e); + } + } + + /** + * Closes the cursor, allowing the statement to be executed again. + * + * @return bool + * @throws Zend_Db_Statement_Exception + */ + public function closeCursor() + { + try { + return $this->_stmt->closeCursor(); + } catch (PDOException $e) { + require_once 'Zend/Db/Statement/Exception.php'; + throw new Zend_Db_Statement_Exception($e->getMessage(), $e->getCode(), $e); + } + } + + /** + * Returns the number of columns in the result set. + * Returns null if the statement has no result set metadata. + * + * @return int The number of columns. + * @throws Zend_Db_Statement_Exception + */ + public function columnCount() + { + try { + return $this->_stmt->columnCount(); + } catch (PDOException $e) { + require_once 'Zend/Db/Statement/Exception.php'; + throw new Zend_Db_Statement_Exception($e->getMessage(), $e->getCode(), $e); + } + } + + /** + * Retrieves the error code, if any, associated with the last operation on + * the statement handle. + * + * @return string error code. + * @throws Zend_Db_Statement_Exception + */ + public function errorCode() + { + try { + return $this->_stmt->errorCode(); + } catch (PDOException $e) { + require_once 'Zend/Db/Statement/Exception.php'; + throw new Zend_Db_Statement_Exception($e->getMessage(), $e->getCode(), $e); + } + } + + /** + * Retrieves an array of error information, if any, associated with the + * last operation on the statement handle. + * + * @return array + * @throws Zend_Db_Statement_Exception + */ + public function errorInfo() + { + try { + return $this->_stmt->errorInfo(); + } catch (PDOException $e) { + require_once 'Zend/Db/Statement/Exception.php'; + throw new Zend_Db_Statement_Exception($e->getMessage(), $e->getCode(), $e); + } + } + + /** + * Executes a prepared statement. + * + * @param array $params OPTIONAL Values to bind to parameter placeholders. + * @return bool + * @throws Zend_Db_Statement_Exception + */ + public function _execute(array $params = null) + { + try { + if ($params !== null) { + return $this->_stmt->execute($params); + } else { + return $this->_stmt->execute(); + } + } catch (PDOException $e) { + require_once 'Zend/Db/Statement/Exception.php'; + throw new Zend_Db_Statement_Exception($e->getMessage(), (int) $e->getCode(), $e); + } + } + + /** + * Fetches a row from the result set. + * + * @param int $style OPTIONAL Fetch mode for this fetch operation. + * @param int $cursor OPTIONAL Absolute, relative, or other. + * @param int $offset OPTIONAL Number for absolute or relative cursors. + * @return mixed Array, object, or scalar depending on fetch mode. + * @throws Zend_Db_Statement_Exception + */ + public function fetch($style = null, $cursor = null, $offset = null) + { + if ($style === null) { + $style = $this->_fetchMode; + } + try { + return $this->_stmt->fetch($style, $cursor, $offset); + } catch (PDOException $e) { + require_once 'Zend/Db/Statement/Exception.php'; + throw new Zend_Db_Statement_Exception($e->getMessage(), $e->getCode(), $e); + } + } + + /** + * Required by IteratorAggregate interface + * + * @return IteratorIterator + */ + public function getIterator() + { + return new IteratorIterator($this->_stmt); + } + + /** + * Returns an array containing all of the result set rows. + * + * @param int $style OPTIONAL Fetch mode. + * @param int $col OPTIONAL Column number, if fetch mode is by column. + * @return array Collection of rows, each in a format by the fetch mode. + * @throws Zend_Db_Statement_Exception + */ + public function fetchAll($style = null, $col = null) + { + if ($style === null) { + $style = $this->_fetchMode; + } + try { + if ($style == PDO::FETCH_COLUMN) { + if ($col === null) { + $col = 0; + } + return $this->_stmt->fetchAll($style, $col); + } else { + return $this->_stmt->fetchAll($style); + } + } catch (PDOException $e) { + require_once 'Zend/Db/Statement/Exception.php'; + throw new Zend_Db_Statement_Exception($e->getMessage(), $e->getCode(), $e); + } + } + + /** + * Returns a single column from the next row of a result set. + * + * @param int $col OPTIONAL Position of the column to fetch. + * @return string + * @throws Zend_Db_Statement_Exception + */ + public function fetchColumn($col = 0) + { + try { + return $this->_stmt->fetchColumn($col); + } catch (PDOException $e) { + require_once 'Zend/Db/Statement/Exception.php'; + throw new Zend_Db_Statement_Exception($e->getMessage(), $e->getCode(), $e); + } + } + + /** + * Fetches the next row and returns it as an object. + * + * @param string $class OPTIONAL Name of the class to create. + * @param array $config OPTIONAL Constructor arguments for the class. + * @return mixed One object instance of the specified class. + * @throws Zend_Db_Statement_Exception + */ + public function fetchObject($class = 'stdClass', array $config = array()) + { + try { + return $this->_stmt->fetchObject($class, $config); + } catch (PDOException $e) { + require_once 'Zend/Db/Statement/Exception.php'; + throw new Zend_Db_Statement_Exception($e->getMessage(), $e->getCode(), $e); + } + } + + /** + * Retrieve a statement attribute. + * + * @param integer $key Attribute name. + * @return mixed Attribute value. + * @throws Zend_Db_Statement_Exception + */ + public function getAttribute($key) + { + try { + return $this->_stmt->getAttribute($key); + } catch (PDOException $e) { + require_once 'Zend/Db/Statement/Exception.php'; + throw new Zend_Db_Statement_Exception($e->getMessage(), $e->getCode(), $e); + } + } + + /** + * Returns metadata for a column in a result set. + * + * @param int $column + * @return mixed + * @throws Zend_Db_Statement_Exception + */ + public function getColumnMeta($column) + { + try { + return $this->_stmt->getColumnMeta($column); + } catch (PDOException $e) { + require_once 'Zend/Db/Statement/Exception.php'; + throw new Zend_Db_Statement_Exception($e->getMessage(), $e->getCode(), $e); + } + } + + /** + * Retrieves the next rowset (result set) for a SQL statement that has + * multiple result sets. An example is a stored procedure that returns + * the results of multiple queries. + * + * @return bool + * @throws Zend_Db_Statement_Exception + */ + public function nextRowset() + { + try { + return $this->_stmt->nextRowset(); + } catch (PDOException $e) { + require_once 'Zend/Db/Statement/Exception.php'; + throw new Zend_Db_Statement_Exception($e->getMessage(), $e->getCode(), $e); + } + } + + /** + * Returns the number of rows affected by the execution of the + * last INSERT, DELETE, or UPDATE statement executed by this + * statement object. + * + * @return int The number of rows affected. + * @throws Zend_Db_Statement_Exception + */ + public function rowCount() + { + try { + return $this->_stmt->rowCount(); + } catch (PDOException $e) { + require_once 'Zend/Db/Statement/Exception.php'; + throw new Zend_Db_Statement_Exception($e->getMessage(), $e->getCode(), $e); + } + } + + /** + * Set a statement attribute. + * + * @param string $key Attribute name. + * @param mixed $val Attribute value. + * @return bool + * @throws Zend_Db_Statement_Exception + */ + public function setAttribute($key, $val) + { + try { + return $this->_stmt->setAttribute($key, $val); + } catch (PDOException $e) { + require_once 'Zend/Db/Statement/Exception.php'; + throw new Zend_Db_Statement_Exception($e->getMessage(), $e->getCode(), $e); + } + } + + /** + * Set the default fetch mode for this statement. + * + * @param int $mode The fetch mode. + * @return bool + * @throws Zend_Db_Statement_Exception + */ + public function setFetchMode($mode) + { + $this->_fetchMode = $mode; + try { + return $this->_stmt->setFetchMode($mode); + } catch (PDOException $e) { + require_once 'Zend/Db/Statement/Exception.php'; + throw new Zend_Db_Statement_Exception($e->getMessage(), $e->getCode(), $e); + } + } + +} diff --git a/library/Zend/Db/Statement/Pdo/Ibm.php b/library/Zend/Db/Statement/Pdo/Ibm.php new file mode 100644 index 000000000..52eb23b88 --- /dev/null +++ b/library/Zend/Db/Statement/Pdo/Ibm.php @@ -0,0 +1,94 @@ +_adapter->foldCase('ZEND_DB_ROWNUM'); + + foreach ($data as $row) { + if (is_array($row) && array_key_exists($remove, $row)) { + unset($row[$remove]); + } + $results[] = $row; + } + return $results; + } + + /** + * Binds a parameter to the specified variable name. + * + * @param mixed $parameter Name the parameter, either integer or string. + * @param mixed $variable Reference to PHP variable containing the value. + * @param mixed $type OPTIONAL Datatype of SQL parameter. + * @param mixed $length OPTIONAL Length of SQL parameter. + * @param mixed $options OPTIONAL Other options. + * @return bool + * @throws Zend_Db_Statement_Exception + */ + public function _bindParam($parameter, &$variable, $type = null, $length = null, $options = null) + { + try { + if (($type === null) && ($length === null) && ($options === null)) { + return $this->_stmt->bindParam($parameter, $variable); + } else { + return $this->_stmt->bindParam($parameter, $variable, $type, $length, $options); + } + } catch (PDOException $e) { + require_once 'Zend/Db/Statement/Exception.php'; + throw new Zend_Db_Statement_Exception($e->getMessage(), $e->getCode(), $e); + } + } + +} \ No newline at end of file diff --git a/library/Zend/Db/Statement/Pdo/Oci.php b/library/Zend/Db/Statement/Pdo/Oci.php new file mode 100644 index 000000000..49d82c287 --- /dev/null +++ b/library/Zend/Db/Statement/Pdo/Oci.php @@ -0,0 +1,91 @@ +_adapter->foldCase('zend_db_rownum'); + + foreach ($data as $row) { + if (is_array($row) && array_key_exists($remove, $row)) { + unset($row[$remove]); + } + $results[] = $row; + } + return $results; + } + + + /** + * Fetches a row from the result set. + * + * @param int $style OPTIONAL Fetch mode for this fetch operation. + * @param int $cursor OPTIONAL Absolute, relative, or other. + * @param int $offset OPTIONAL Number for absolute or relative cursors. + * @return mixed Array, object, or scalar depending on fetch mode. + * @throws Zend_Db_Statement_Exception + */ + public function fetch($style = null, $cursor = null, $offset = null) + { + $row = parent::fetch($style, $cursor, $offset); + + $remove = $this->_adapter->foldCase('zend_db_rownum'); + if (is_array($row) && array_key_exists($remove, $row)) { + unset($row[$remove]); + } + + return $row; + } +} \ No newline at end of file diff --git a/library/Zend/Db/Statement/Sqlsrv.php b/library/Zend/Db/Statement/Sqlsrv.php new file mode 100644 index 000000000..0460991b8 --- /dev/null +++ b/library/Zend/Db/Statement/Sqlsrv.php @@ -0,0 +1,411 @@ +_adapter->getConnection(); + + $this->_stmt = sqlsrv_prepare($connection, $sql); + + if (!$this->_stmt) { + require_once 'Zend/Db/Statement/Sqlsrv/Exception.php'; + throw new Zend_Db_Statement_Sqlsrv_Exception(sqlsrv_errors()); + } + + $this->_originalSQL = $sql; + } + + /** + * Binds a parameter to the specified variable name. + * + * @param mixed $parameter Name the parameter, either integer or string. + * @param mixed $variable Reference to PHP variable containing the value. + * @param mixed $type OPTIONAL Datatype of SQL parameter. + * @param mixed $length OPTIONAL Length of SQL parameter. + * @param mixed $options OPTIONAL Other options. + * @return bool + * @throws Zend_Db_Statement_Exception + */ + protected function _bindParam($parameter, &$variable, $type = null, $length = null, $options = null) + { + //Sql server doesn't support bind by name + return true; + } + + /** + * Closes the cursor, allowing the statement to be executed again. + * + * @return bool + */ + public function closeCursor() + { + if (!$this->_stmt) { + return false; + } + + sqlsrv_free_stmt($this->_stmt); + $this->_stmt = false; + return true; + } + + /** + * Returns the number of columns in the result set. + * Returns null if the statement has no result set metadata. + * + * @return int The number of columns. + */ + public function columnCount() + { + if ($this->_stmt && $this->_executed) { + return sqlsrv_num_fields($this->_stmt); + } + + return 0; + } + + + /** + * Retrieves the error code, if any, associated with the last operation on + * the statement handle. + * + * @return string error code. + */ + public function errorCode() + { + if (!$this->_stmt) { + return false; + } + + $error = sqlsrv_errors(); + if (!$error) { + return false; + } + + return $error[0]['code']; + } + + + /** + * Retrieves an array of error information, if any, associated with the + * last operation on the statement handle. + * + * @return array + */ + public function errorInfo() + { + if (!$this->_stmt) { + return false; + } + + $error = sqlsrv_errors(); + if (!$error) { + return false; + } + + return array( + $error[0]['code'], + $error[0]['message'], + ); + } + + + /** + * Executes a prepared statement. + * + * @param array $params OPTIONAL Values to bind to parameter placeholders. + * @return bool + * @throws Zend_Db_Statement_Exception + */ + public function _execute(array $params = null) + { + $connection = $this->_adapter->getConnection(); + if (!$this->_stmt) { + return false; + } + + if ($params !== null) { + if (!is_array($params)) { + $params = array($params); + } + $error = false; + + // make all params passed by reference + $params_ = array(); + $temp = array(); + $i = 1; + foreach ($params as $param) { + $temp[$i] = $param; + $params_[] = &$temp[$i]; + $i++; + } + $params = $params_; + } + + $this->_stmt = sqlsrv_query($connection, $this->_originalSQL, $params); + + if (!$this->_stmt) { + require_once 'Zend/Db/Statement/Sqlsrv/Exception.php'; + throw new Zend_Db_Statement_Sqlsrv_Exception(sqlsrv_errors()); + } + + $this->_executed = true; + + return (!$this->_stmt); + } + + /** + * Fetches a row from the result set. + * + * @param int $style OPTIONAL Fetch mode for this fetch operation. + * @param int $cursor OPTIONAL Absolute, relative, or other. + * @param int $offset OPTIONAL Number for absolute or relative cursors. + * @return mixed Array, object, or scalar depending on fetch mode. + * @throws Zend_Db_Statement_Exception + */ + public function fetch($style = null, $cursor = null, $offset = null) + { + if (!$this->_stmt) { + return false; + } + + if (null === $style) { + $style = $this->_fetchMode; + } + + $values = sqlsrv_fetch_array($this->_stmt, SQLSRV_FETCH_ASSOC); + + if (!$values && (null !== $error = sqlsrv_errors())) { + require_once 'Zend/Db/Statement/Sqlsrv/Exception.php'; + throw new Zend_Db_Statement_Sqlsrv_Exception($error); + } + + if (null === $values) { + return null; + } + + if (!$this->_keys) { + foreach ($values as $key => $value) { + $this->_keys[] = $this->_adapter->foldCase($key); + } + } + + $values = array_values($values); + + $row = false; + switch ($style) { + case Zend_Db::FETCH_NUM: + $row = $values; + break; + case Zend_Db::FETCH_ASSOC: + $row = array_combine($this->_keys, $values); + break; + case Zend_Db::FETCH_BOTH: + $assoc = array_combine($this->_keys, $values); + $row = array_merge($values, $assoc); + break; + case Zend_Db::FETCH_OBJ: + $row = (object) array_combine($this->_keys, $values); + break; + case Zend_Db::FETCH_BOUND: + $assoc = array_combine($this->_keys, $values); + $row = array_merge($values, $assoc); + $row = $this->_fetchBound($row); + break; + default: + require_once 'Zend/Db/Statement/Sqlsrv/Exception.php'; + throw new Zend_Db_Statement_Sqlsrv_Exception("Invalid fetch mode '$style' specified"); + break; + } + + return $row; + } + + /** + * Returns a single column from the next row of a result set. + * + * @param int $col OPTIONAL Position of the column to fetch. + * @return string + * @throws Zend_Db_Statement_Exception + */ + public function fetchColumn($col = 0) + { + if (!$this->_stmt) { + return false; + } + + if (!sqlsrv_fetch($this->_stmt)) { + if (null !== $error = sqlsrv_errors()) { + require_once 'Zend/Db/Statement/Sqlsrv/Exception.php'; + throw new Zend_Db_Statement_Sqlsrv_Exception($error); + } + + // If no error, there is simply no record + return false; + } + + $data = sqlsrv_get_field($this->_stmt, $col); //0-based + if ($data === false) { + require_once 'Zend/Db/Statement/Sqlsrv/Exception.php'; + throw new Zend_Db_Statement_Sqlsrv_Exception(sqlsrv_errors()); + } + + return $data; + } + + /** + * Fetches the next row and returns it as an object. + * + * @param string $class OPTIONAL Name of the class to create. + * @param array $config OPTIONAL Constructor arguments for the class. + * @return mixed One object instance of the specified class. + * @throws Zend_Db_Statement_Exception + */ + public function fetchObject($class = 'stdClass', array $config = array()) + { + if (!$this->_stmt) { + return false; + } + + $obj = sqlsrv_fetch_object($this->_stmt); + + if ($error = sqlsrv_errors()) { + require_once 'Zend/Db/Statement/Sqlsrv/Exception.php'; + throw new Zend_Db_Statement_Sqlsrv_Exception($error); + } + + /* @todo XXX handle parameters */ + + if (null === $obj) { + return false; + } + + return $obj; + } + + /** + * Returns metadata for a column in a result set. + * + * @param int $column + * @return mixed + * @throws Zend_Db_Statement_Sqlsrv_Exception + */ + public function getColumnMeta($column) + { + $fields = sqlsrv_field_metadata($this->_stmt); + + if (!$fields) { + throw new Zend_Db_Statement_Sqlsrv_Exception('Column metadata can not be fetched'); + } + + if (!isset($fields[$column])) { + throw new Zend_Db_Statement_Sqlsrv_Exception('Column index does not exist in statement'); + } + + return $fields[$column]; + } + + /** + * Retrieves the next rowset (result set) for a SQL statement that has + * multiple result sets. An example is a stored procedure that returns + * the results of multiple queries. + * + * @return bool + * @throws Zend_Db_Statement_Exception + */ + public function nextRowset() + { + if (sqlsrv_next_result($this->_stmt) === false) { + require_once 'Zend/Db/Statement/Sqlsrv/Exception.php'; + throw new Zend_Db_Statement_Sqlsrv_Exception(sqlsrv_errors()); + } + + //else - moved to next (or there are no more rows) + } + + /** + * Returns the number of rows affected by the execution of the + * last INSERT, DELETE, or UPDATE statement executed by this + * statement object. + * + * @return int The number of rows affected. + * @throws Zend_Db_Statement_Exception + */ + public function rowCount() + { + if (!$this->_stmt) { + return false; + } + + if (!$this->_executed) { + return 0; + } + + $num_rows = sqlsrv_rows_affected($this->_stmt); + + // Strict check is necessary; 0 is a valid return value + if ($num_rows === false) { + require_once 'Zend/Db/Statement/Sqlsrv/Exception.php'; + throw new Zend_Db_Statement_Sqlsrv_Exception(sqlsrv_errors()); + } + + return $num_rows; + } +} diff --git a/library/Zend/Db/Statement/Sqlsrv/Exception.php b/library/Zend/Db/Statement/Sqlsrv/Exception.php new file mode 100644 index 000000000..4c0fb095b --- /dev/null +++ b/library/Zend/Db/Statement/Sqlsrv/Exception.php @@ -0,0 +1,61 @@ + $config); + } else { + // process this as table with or without a definition + if ($definition instanceof Zend_Db_Table_Definition + && $definition->hasTableConfig($config)) { + // this will have DEFINITION_CONFIG_NAME & DEFINITION + $config = $definition->getTableConfig($config); + } else { + $config = array(self::NAME => $config); + } + } + } + + parent::__construct($config); + } +} diff --git a/library/Zend/Db/Table/Abstract.php b/library/Zend/Db/Table/Abstract.php new file mode 100644 index 000000000..24235eb01 --- /dev/null +++ b/library/Zend/Db/Table/Abstract.php @@ -0,0 +1,1510 @@ + $config); + } + + if ($config) { + $this->setOptions($config); + } + + $this->_setup(); + $this->init(); + } + + /** + * setOptions() + * + * @param array $options + * @return Zend_Db_Table_Abstract + */ + public function setOptions(Array $options) + { + foreach ($options as $key => $value) { + switch ($key) { + case self::ADAPTER: + $this->_setAdapter($value); + break; + case self::DEFINITION: + $this->setDefinition($value); + break; + case self::DEFINITION_CONFIG_NAME: + $this->setDefinitionConfigName($value); + break; + case self::SCHEMA: + $this->_schema = (string) $value; + break; + case self::NAME: + $this->_name = (string) $value; + break; + case self::PRIMARY: + $this->_primary = (array) $value; + break; + case self::ROW_CLASS: + $this->setRowClass($value); + break; + case self::ROWSET_CLASS: + $this->setRowsetClass($value); + break; + case self::REFERENCE_MAP: + $this->setReferences($value); + break; + case self::DEPENDENT_TABLES: + $this->setDependentTables($value); + break; + case self::METADATA_CACHE: + $this->_setMetadataCache($value); + break; + case self::METADATA_CACHE_IN_CLASS: + $this->setMetadataCacheInClass($value); + break; + case self::SEQUENCE: + $this->_setSequence($value); + break; + default: + // ignore unrecognized configuration directive + break; + } + } + + return $this; + } + + /** + * setDefinition() + * + * @param Zend_Db_Table_Definition $definition + * @return Zend_Db_Table_Abstract + */ + public function setDefinition(Zend_Db_Table_Definition $definition) + { + $this->_definition = $definition; + return $this; + } + + /** + * getDefinition() + * + * @return Zend_Db_Table_Definition|null + */ + public function getDefinition() + { + return $this->_definition; + } + + /** + * setDefinitionConfigName() + * + * @param string $definition + * @return Zend_Db_Table_Abstract + */ + public function setDefinitionConfigName($definitionConfigName) + { + $this->_definitionConfigName = $definitionConfigName; + return $this; + } + + /** + * getDefinitionConfigName() + * + * @return string + */ + public function getDefinitionConfigName() + { + return $this->_definitionConfigName; + } + + /** + * @param string $classname + * @return Zend_Db_Table_Abstract Provides a fluent interface + */ + public function setRowClass($classname) + { + $this->_rowClass = (string) $classname; + + return $this; + } + + /** + * @return string + */ + public function getRowClass() + { + return $this->_rowClass; + } + + /** + * @param string $classname + * @return Zend_Db_Table_Abstract Provides a fluent interface + */ + public function setRowsetClass($classname) + { + $this->_rowsetClass = (string) $classname; + + return $this; + } + + /** + * @return string + */ + public function getRowsetClass() + { + return $this->_rowsetClass; + } + + /** + * Add a reference to the reference map + * + * @param string $ruleKey + * @param string|array $columns + * @param string $refTableClass + * @param string|array $refColumns + * @param string $onDelete + * @param string $onUpdate + * @return Zend_Db_Table_Abstract + */ + public function addReference($ruleKey, $columns, $refTableClass, $refColumns, + $onDelete = null, $onUpdate = null) + { + $reference = array(self::COLUMNS => (array) $columns, + self::REF_TABLE_CLASS => $refTableClass, + self::REF_COLUMNS => (array) $refColumns); + + if (!empty($onDelete)) { + $reference[self::ON_DELETE] = $onDelete; + } + + if (!empty($onUpdate)) { + $reference[self::ON_UPDATE] = $onUpdate; + } + + $this->_referenceMap[$ruleKey] = $reference; + + return $this; + } + + /** + * @param array $referenceMap + * @return Zend_Db_Table_Abstract Provides a fluent interface + */ + public function setReferences(array $referenceMap) + { + $this->_referenceMap = $referenceMap; + + return $this; + } + + /** + * @param string $tableClassname + * @param string $ruleKey OPTIONAL + * @return array + * @throws Zend_Db_Table_Exception + */ + public function getReference($tableClassname, $ruleKey = null) + { + $thisClass = get_class($this); + if ($thisClass === 'Zend_Db_Table') { + $thisClass = $this->_definitionConfigName; + } + $refMap = $this->_getReferenceMapNormalized(); + if ($ruleKey !== null) { + if (!isset($refMap[$ruleKey])) { + require_once "Zend/Db/Table/Exception.php"; + throw new Zend_Db_Table_Exception("No reference rule \"$ruleKey\" from table $thisClass to table $tableClassname"); + } + if ($refMap[$ruleKey][self::REF_TABLE_CLASS] != $tableClassname) { + require_once "Zend/Db/Table/Exception.php"; + throw new Zend_Db_Table_Exception("Reference rule \"$ruleKey\" does not reference table $tableClassname"); + } + return $refMap[$ruleKey]; + } + foreach ($refMap as $reference) { + if ($reference[self::REF_TABLE_CLASS] == $tableClassname) { + return $reference; + } + } + require_once "Zend/Db/Table/Exception.php"; + throw new Zend_Db_Table_Exception("No reference from table $thisClass to table $tableClassname"); + } + + /** + * @param array $dependentTables + * @return Zend_Db_Table_Abstract Provides a fluent interface + */ + public function setDependentTables(array $dependentTables) + { + $this->_dependentTables = $dependentTables; + + return $this; + } + + /** + * @return array + */ + public function getDependentTables() + { + return $this->_dependentTables; + } + + /** + * set the defaultSource property - this tells the table class where to find default values + * + * @param string $defaultSource + * @return Zend_Db_Table_Abstract + */ + public function setDefaultSource($defaultSource = self::DEFAULT_NONE) + { + if (!in_array($defaultSource, array(self::DEFAULT_CLASS, self::DEFAULT_DB, self::DEFAULT_NONE))) { + $defaultSource = self::DEFAULT_NONE; + } + + $this->_defaultSource = $defaultSource; + return $this; + } + + /** + * returns the default source flag that determines where defaultSources come from + * + * @return unknown + */ + public function getDefaultSource() + { + return $this->_defaultSource; + } + + /** + * set the default values for the table class + * + * @param array $defaultValues + * @return Zend_Db_Table_Abstract + */ + public function setDefaultValues(Array $defaultValues) + { + foreach ($defaultValues as $defaultName => $defaultValue) { + if (array_key_exists($defaultName, $this->_metadata)) { + $this->_defaultValues[$defaultName] = $defaultValue; + } + } + return $this; + } + + public function getDefaultValues() + { + return $this->_defaultValues; + } + + + /** + * Sets the default Zend_Db_Adapter_Abstract for all Zend_Db_Table objects. + * + * @param mixed $db Either an Adapter object, or a string naming a Registry key + * @return void + */ + public static function setDefaultAdapter($db = null) + { + self::$_defaultDb = self::_setupAdapter($db); + } + + /** + * Gets the default Zend_Db_Adapter_Abstract for all Zend_Db_Table objects. + * + * @return Zend_Db_Adapter_Abstract or null + */ + public static function getDefaultAdapter() + { + return self::$_defaultDb; + } + + /** + * @param mixed $db Either an Adapter object, or a string naming a Registry key + * @return Zend_Db_Table_Abstract Provides a fluent interface + */ + protected function _setAdapter($db) + { + $this->_db = self::_setupAdapter($db); + return $this; + } + + /** + * Gets the Zend_Db_Adapter_Abstract for this particular Zend_Db_Table object. + * + * @return Zend_Db_Adapter_Abstract + */ + public function getAdapter() + { + return $this->_db; + } + + /** + * @param mixed $db Either an Adapter object, or a string naming a Registry key + * @return Zend_Db_Adapter_Abstract + * @throws Zend_Db_Table_Exception + */ + protected static function _setupAdapter($db) + { + if ($db === null) { + return null; + } + if (is_string($db)) { + require_once 'Zend/Registry.php'; + $db = Zend_Registry::get($db); + } + if (!$db instanceof Zend_Db_Adapter_Abstract) { + require_once 'Zend/Db/Table/Exception.php'; + throw new Zend_Db_Table_Exception('Argument must be of type Zend_Db_Adapter_Abstract, or a Registry key where a Zend_Db_Adapter_Abstract object is stored'); + } + return $db; + } + + /** + * Sets the default metadata cache for information returned by Zend_Db_Adapter_Abstract::describeTable(). + * + * If $defaultMetadataCache is null, then no metadata cache is used by default. + * + * @param mixed $metadataCache Either a Cache object, or a string naming a Registry key + * @return void + */ + public static function setDefaultMetadataCache($metadataCache = null) + { + self::$_defaultMetadataCache = self::_setupMetadataCache($metadataCache); + } + + /** + * Gets the default metadata cache for information returned by Zend_Db_Adapter_Abstract::describeTable(). + * + * @return Zend_Cache_Core or null + */ + public static function getDefaultMetadataCache() + { + return self::$_defaultMetadataCache; + } + + /** + * Sets the metadata cache for information returned by Zend_Db_Adapter_Abstract::describeTable(). + * + * If $metadataCache is null, then no metadata cache is used. Since there is no opportunity to reload metadata + * after instantiation, this method need not be public, particularly because that it would have no effect + * results in unnecessary API complexity. To configure the metadata cache, use the metadataCache configuration + * option for the class constructor upon instantiation. + * + * @param mixed $metadataCache Either a Cache object, or a string naming a Registry key + * @return Zend_Db_Table_Abstract Provides a fluent interface + */ + protected function _setMetadataCache($metadataCache) + { + $this->_metadataCache = self::_setupMetadataCache($metadataCache); + return $this; + } + + /** + * Gets the metadata cache for information returned by Zend_Db_Adapter_Abstract::describeTable(). + * + * @return Zend_Cache_Core or null + */ + public function getMetadataCache() + { + return $this->_metadataCache; + } + + /** + * Indicate whether metadata should be cached in the class for the duration + * of the instance + * + * @param bool $flag + * @return Zend_Db_Table_Abstract + */ + public function setMetadataCacheInClass($flag) + { + $this->_metadataCacheInClass = (bool) $flag; + return $this; + } + + /** + * Retrieve flag indicating if metadata should be cached for duration of + * instance + * + * @return bool + */ + public function metadataCacheInClass() + { + return $this->_metadataCacheInClass; + } + + /** + * @param mixed $metadataCache Either a Cache object, or a string naming a Registry key + * @return Zend_Cache_Core + * @throws Zend_Db_Table_Exception + */ + protected static function _setupMetadataCache($metadataCache) + { + if ($metadataCache === null) { + return null; + } + if (is_string($metadataCache)) { + require_once 'Zend/Registry.php'; + $metadataCache = Zend_Registry::get($metadataCache); + } + if (!$metadataCache instanceof Zend_Cache_Core) { + require_once 'Zend/Db/Table/Exception.php'; + throw new Zend_Db_Table_Exception('Argument must be of type Zend_Cache_Core, or a Registry key where a Zend_Cache_Core object is stored'); + } + return $metadataCache; + } + + /** + * Sets the sequence member, which defines the behavior for generating + * primary key values in new rows. + * - If this is a string, then the string names the sequence object. + * - If this is boolean true, then the key uses an auto-incrementing + * or identity mechanism. + * - If this is boolean false, then the key is user-defined. + * Use this for natural keys, for example. + * + * @param mixed $sequence + * @return Zend_Db_Table_Adapter_Abstract Provides a fluent interface + */ + protected function _setSequence($sequence) + { + $this->_sequence = $sequence; + + return $this; + } + + /** + * Turnkey for initialization of a table object. + * Calls other protected methods for individual tasks, to make it easier + * for a subclass to override part of the setup logic. + * + * @return void + */ + protected function _setup() + { + $this->_setupDatabaseAdapter(); + $this->_setupTableName(); + } + + /** + * Initialize database adapter. + * + * @return void + */ + protected function _setupDatabaseAdapter() + { + if (! $this->_db) { + $this->_db = self::getDefaultAdapter(); + if (!$this->_db instanceof Zend_Db_Adapter_Abstract) { + require_once 'Zend/Db/Table/Exception.php'; + throw new Zend_Db_Table_Exception('No adapter found for ' . get_class($this)); + } + } + } + + /** + * Initialize table and schema names. + * + * If the table name is not set in the class definition, + * use the class name itself as the table name. + * + * A schema name provided with the table name (e.g., "schema.table") overrides + * any existing value for $this->_schema. + * + * @return void + */ + protected function _setupTableName() + { + if (! $this->_name) { + $this->_name = get_class($this); + } else if (strpos($this->_name, '.')) { + list($this->_schema, $this->_name) = explode('.', $this->_name); + } + } + + /** + * Initializes metadata. + * + * If metadata cannot be loaded from cache, adapter's describeTable() method is called to discover metadata + * information. Returns true if and only if the metadata are loaded from cache. + * + * @return boolean + * @throws Zend_Db_Table_Exception + */ + protected function _setupMetadata() + { + if ($this->metadataCacheInClass() && (count($this->_metadata) > 0)) { + return true; + } + + // Assume that metadata will be loaded from cache + $isMetadataFromCache = true; + + // If $this has no metadata cache but the class has a default metadata cache + if (null === $this->_metadataCache && null !== self::$_defaultMetadataCache) { + // Make $this use the default metadata cache of the class + $this->_setMetadataCache(self::$_defaultMetadataCache); + } + + // If $this has a metadata cache + if (null !== $this->_metadataCache) { + // Define the cache identifier where the metadata are saved + + //get db configuration + $dbConfig = $this->_db->getConfig(); + + // Define the cache identifier where the metadata are saved + $cacheId = md5( // port:host/dbname:schema.table (based on availabilty) + (isset($dbConfig['options']['port']) ? ':'.$dbConfig['options']['port'] : null) + . (isset($dbConfig['options']['host']) ? ':'.$dbConfig['options']['host'] : null) + . '/'.$dbConfig['dbname'].':'.$this->_schema.'.'.$this->_name + ); + } + + // If $this has no metadata cache or metadata cache misses + if (null === $this->_metadataCache || !($metadata = $this->_metadataCache->load($cacheId))) { + // Metadata are not loaded from cache + $isMetadataFromCache = false; + // Fetch metadata from the adapter's describeTable() method + $metadata = $this->_db->describeTable($this->_name, $this->_schema); + // If $this has a metadata cache, then cache the metadata + if (null !== $this->_metadataCache && !$this->_metadataCache->save($metadata, $cacheId)) { + trigger_error('Failed saving metadata to metadataCache', E_USER_NOTICE); + } + } + + // Assign the metadata to $this + $this->_metadata = $metadata; + + // Return whether the metadata were loaded from cache + return $isMetadataFromCache; + } + + /** + * Retrieve table columns + * + * @return array + */ + protected function _getCols() + { + if (null === $this->_cols) { + $this->_setupMetadata(); + $this->_cols = array_keys($this->_metadata); + } + return $this->_cols; + } + + /** + * Initialize primary key from metadata. + * If $_primary is not defined, discover primary keys + * from the information returned by describeTable(). + * + * @return void + * @throws Zend_Db_Table_Exception + */ + protected function _setupPrimaryKey() + { + if (!$this->_primary) { + $this->_setupMetadata(); + $this->_primary = array(); + foreach ($this->_metadata as $col) { + if ($col['PRIMARY']) { + $this->_primary[ $col['PRIMARY_POSITION'] ] = $col['COLUMN_NAME']; + if ($col['IDENTITY']) { + $this->_identity = $col['PRIMARY_POSITION']; + } + } + } + // if no primary key was specified and none was found in the metadata + // then throw an exception. + if (empty($this->_primary)) { + require_once 'Zend/Db/Table/Exception.php'; + throw new Zend_Db_Table_Exception('A table must have a primary key, but none was found'); + } + } else if (!is_array($this->_primary)) { + $this->_primary = array(1 => $this->_primary); + } else if (isset($this->_primary[0])) { + array_unshift($this->_primary, null); + unset($this->_primary[0]); + } + + $cols = $this->_getCols(); + if (! array_intersect((array) $this->_primary, $cols) == (array) $this->_primary) { + require_once 'Zend/Db/Table/Exception.php'; + throw new Zend_Db_Table_Exception("Primary key column(s) (" + . implode(',', (array) $this->_primary) + . ") are not columns in this table (" + . implode(',', $cols) + . ")"); + } + + $primary = (array) $this->_primary; + $pkIdentity = $primary[(int) $this->_identity]; + + /** + * Special case for PostgreSQL: a SERIAL key implicitly uses a sequence + * object whose name is "__seq". + */ + if ($this->_sequence === true && $this->_db instanceof Zend_Db_Adapter_Pdo_Pgsql) { + $this->_sequence = $this->_db->quoteIdentifier("{$this->_name}_{$pkIdentity}_seq"); + if ($this->_schema) { + $this->_sequence = $this->_db->quoteIdentifier($this->_schema) . '.' . $this->_sequence; + } + } + } + + /** + * Returns a normalized version of the reference map + * + * @return array + */ + protected function _getReferenceMapNormalized() + { + $referenceMapNormalized = array(); + + foreach ($this->_referenceMap as $rule => $map) { + + $referenceMapNormalized[$rule] = array(); + + foreach ($map as $key => $value) { + switch ($key) { + + // normalize COLUMNS and REF_COLUMNS to arrays + case self::COLUMNS: + case self::REF_COLUMNS: + if (!is_array($value)) { + $referenceMapNormalized[$rule][$key] = array($value); + } else { + $referenceMapNormalized[$rule][$key] = $value; + } + break; + + // other values are copied as-is + default: + $referenceMapNormalized[$rule][$key] = $value; + break; + } + } + } + + return $referenceMapNormalized; + } + + /** + * Initialize object + * + * Called from {@link __construct()} as final step of object instantiation. + * + * @return void + */ + public function init() + { + } + + /** + * Returns table information. + * + * You can elect to return only a part of this information by supplying its key name, + * otherwise all information is returned as an array. + * + * @param $key The specific info part to return OPTIONAL + * @return mixed + */ + public function info($key = null) + { + $this->_setupPrimaryKey(); + + $info = array( + self::SCHEMA => $this->_schema, + self::NAME => $this->_name, + self::COLS => $this->_getCols(), + self::PRIMARY => (array) $this->_primary, + self::METADATA => $this->_metadata, + self::ROW_CLASS => $this->getRowClass(), + self::ROWSET_CLASS => $this->getRowsetClass(), + self::REFERENCE_MAP => $this->_referenceMap, + self::DEPENDENT_TABLES => $this->_dependentTables, + self::SEQUENCE => $this->_sequence + ); + + if ($key === null) { + return $info; + } + + if (!array_key_exists($key, $info)) { + require_once 'Zend/Db/Table/Exception.php'; + throw new Zend_Db_Table_Exception('There is no table information for the key "' . $key . '"'); + } + + return $info[$key]; + } + + /** + * Returns an instance of a Zend_Db_Table_Select object. + * + * @param bool $withFromPart Whether or not to include the from part of the select based on the table + * @return Zend_Db_Table_Select + */ + public function select($withFromPart = self::SELECT_WITHOUT_FROM_PART) + { + require_once 'Zend/Db/Table/Select.php'; + $select = new Zend_Db_Table_Select($this); + if ($withFromPart == self::SELECT_WITH_FROM_PART) { + $select->from($this->info(self::NAME), Zend_Db_Table_Select::SQL_WILDCARD, $this->info(self::SCHEMA)); + } + return $select; + } + + /** + * Inserts a new row. + * + * @param array $data Column-value pairs. + * @return mixed The primary key of the row inserted. + */ + public function insert(array $data) + { + $this->_setupPrimaryKey(); + + /** + * Zend_Db_Table assumes that if you have a compound primary key + * and one of the columns in the key uses a sequence, + * it's the _first_ column in the compound key. + */ + $primary = (array) $this->_primary; + $pkIdentity = $primary[(int)$this->_identity]; + + /** + * If this table uses a database sequence object and the data does not + * specify a value, then get the next ID from the sequence and add it + * to the row. We assume that only the first column in a compound + * primary key takes a value from a sequence. + */ + if (is_string($this->_sequence) && !isset($data[$pkIdentity])) { + $data[$pkIdentity] = $this->_db->nextSequenceId($this->_sequence); + } + + /** + * If the primary key can be generated automatically, and no value was + * specified in the user-supplied data, then omit it from the tuple. + */ + if (array_key_exists($pkIdentity, $data) && $data[$pkIdentity] === null) { + unset($data[$pkIdentity]); + } + + /** + * INSERT the new row. + */ + $tableSpec = ($this->_schema ? $this->_schema . '.' : '') . $this->_name; + $this->_db->insert($tableSpec, $data); + + /** + * Fetch the most recent ID generated by an auto-increment + * or IDENTITY column, unless the user has specified a value, + * overriding the auto-increment mechanism. + */ + if ($this->_sequence === true && !isset($data[$pkIdentity])) { + $data[$pkIdentity] = $this->_db->lastInsertId(); + } + + /** + * Return the primary key value if the PK is a single column, + * else return an associative array of the PK column/value pairs. + */ + $pkData = array_intersect_key($data, array_flip($primary)); + if (count($primary) == 1) { + reset($pkData); + return current($pkData); + } + + return $pkData; + } + + /** + * Check if the provided column is an identity of the table + * + * @param string $column + * @throws Zend_Db_Table_Exception + * @return boolean + */ + public function isIdentity($column) + { + $this->_setupPrimaryKey(); + + if (!isset($this->_metadata[$column])) { + /** + * @see Zend_Db_Table_Exception + */ + require_once 'Zend/Db/Table/Exception.php'; + + throw new Zend_Db_Table_Exception('Column "' . $column . '" not found in table.'); + } + + return (bool) $this->_metadata[$column]['IDENTITY']; + } + + /** + * Updates existing rows. + * + * @param array $data Column-value pairs. + * @param array|string $where An SQL WHERE clause, or an array of SQL WHERE clauses. + * @return int The number of rows updated. + */ + public function update(array $data, $where) + { + $tableSpec = ($this->_schema ? $this->_schema . '.' : '') . $this->_name; + return $this->_db->update($tableSpec, $data, $where); + } + + /** + * Called by a row object for the parent table's class during save() method. + * + * @param string $parentTableClassname + * @param array $oldPrimaryKey + * @param array $newPrimaryKey + * @return int + */ + public function _cascadeUpdate($parentTableClassname, array $oldPrimaryKey, array $newPrimaryKey) + { + $this->_setupMetadata(); + $rowsAffected = 0; + foreach ($this->_getReferenceMapNormalized() as $map) { + if ($map[self::REF_TABLE_CLASS] == $parentTableClassname && isset($map[self::ON_UPDATE])) { + switch ($map[self::ON_UPDATE]) { + case self::CASCADE: + $newRefs = array(); + $where = array(); + for ($i = 0; $i < count($map[self::COLUMNS]); ++$i) { + $col = $this->_db->foldCase($map[self::COLUMNS][$i]); + $refCol = $this->_db->foldCase($map[self::REF_COLUMNS][$i]); + if (array_key_exists($refCol, $newPrimaryKey)) { + $newRefs[$col] = $newPrimaryKey[$refCol]; + } + $type = $this->_metadata[$col]['DATA_TYPE']; + $where[] = $this->_db->quoteInto( + $this->_db->quoteIdentifier($col, true) . ' = ?', + $oldPrimaryKey[$refCol], $type); + } + $rowsAffected += $this->update($newRefs, $where); + break; + default: + // no action + break; + } + } + } + return $rowsAffected; + } + + /** + * Deletes existing rows. + * + * @param array|string $where SQL WHERE clause(s). + * @return int The number of rows deleted. + */ + public function delete($where) + { + $tableSpec = ($this->_schema ? $this->_schema . '.' : '') . $this->_name; + return $this->_db->delete($tableSpec, $where); + } + + /** + * Called by parent table's class during delete() method. + * + * @param string $parentTableClassname + * @param array $primaryKey + * @return int Number of affected rows + */ + public function _cascadeDelete($parentTableClassname, array $primaryKey) + { + $this->_setupMetadata(); + $rowsAffected = 0; + foreach ($this->_getReferenceMapNormalized() as $map) { + if ($map[self::REF_TABLE_CLASS] == $parentTableClassname && isset($map[self::ON_DELETE])) { + switch ($map[self::ON_DELETE]) { + case self::CASCADE: + $where = array(); + for ($i = 0; $i < count($map[self::COLUMNS]); ++$i) { + $col = $this->_db->foldCase($map[self::COLUMNS][$i]); + $refCol = $this->_db->foldCase($map[self::REF_COLUMNS][$i]); + $type = $this->_metadata[$col]['DATA_TYPE']; + $where[] = $this->_db->quoteInto( + $this->_db->quoteIdentifier($col, true) . ' = ?', + $primaryKey[$refCol], $type); + } + $rowsAffected += $this->delete($where); + break; + default: + // no action + break; + } + } + } + return $rowsAffected; + } + + /** + * Fetches rows by primary key. The argument specifies one or more primary + * key value(s). To find multiple rows by primary key, the argument must + * be an array. + * + * This method accepts a variable number of arguments. If the table has a + * multi-column primary key, the number of arguments must be the same as + * the number of columns in the primary key. To find multiple rows in a + * table with a multi-column primary key, each argument must be an array + * with the same number of elements. + * + * The find() method always returns a Rowset object, even if only one row + * was found. + * + * @param mixed $key The value(s) of the primary keys. + * @return Zend_Db_Table_Rowset_Abstract Row(s) matching the criteria. + * @throws Zend_Db_Table_Exception + */ + public function find() + { + $this->_setupPrimaryKey(); + $args = func_get_args(); + $keyNames = array_values((array) $this->_primary); + + if (count($args) < count($keyNames)) { + require_once 'Zend/Db/Table/Exception.php'; + throw new Zend_Db_Table_Exception("Too few columns for the primary key"); + } + + if (count($args) > count($keyNames)) { + require_once 'Zend/Db/Table/Exception.php'; + throw new Zend_Db_Table_Exception("Too many columns for the primary key"); + } + + $whereList = array(); + $numberTerms = 0; + foreach ($args as $keyPosition => $keyValues) { + $keyValuesCount = count($keyValues); + // Coerce the values to an array. + // Don't simply typecast to array, because the values + // might be Zend_Db_Expr objects. + if (!is_array($keyValues)) { + $keyValues = array($keyValues); + } + if ($numberTerms == 0) { + $numberTerms = $keyValuesCount; + } else if ($keyValuesCount != $numberTerms) { + require_once 'Zend/Db/Table/Exception.php'; + throw new Zend_Db_Table_Exception("Missing value(s) for the primary key"); + } + $keyValues = array_values($keyValues); + for ($i = 0; $i < $keyValuesCount; ++$i) { + if (!isset($whereList[$i])) { + $whereList[$i] = array(); + } + $whereList[$i][$keyPosition] = $keyValues[$i]; + } + } + + $whereClause = null; + if (count($whereList)) { + $whereOrTerms = array(); + $tableName = $this->_db->quoteTableAs($this->_name, null, true); + foreach ($whereList as $keyValueSets) { + $whereAndTerms = array(); + foreach ($keyValueSets as $keyPosition => $keyValue) { + $type = $this->_metadata[$keyNames[$keyPosition]]['DATA_TYPE']; + $columnName = $this->_db->quoteIdentifier($keyNames[$keyPosition], true); + $whereAndTerms[] = $this->_db->quoteInto( + $tableName . '.' . $columnName . ' = ?', + $keyValue, $type); + } + $whereOrTerms[] = '(' . implode(' AND ', $whereAndTerms) . ')'; + } + $whereClause = '(' . implode(' OR ', $whereOrTerms) . ')'; + } + + // issue ZF-5775 (empty where clause should return empty rowset) + if ($whereClause == null) { + $rowsetClass = $this->getRowsetClass(); + if (!class_exists($rowsetClass)) { + require_once 'Zend/Loader.php'; + Zend_Loader::loadClass($rowsetClass); + } + return new $rowsetClass(array('table' => $this, 'rowClass' => $this->getRowClass(), 'stored' => true)); + } + + return $this->fetchAll($whereClause); + } + + /** + * Fetches all rows. + * + * Honors the Zend_Db_Adapter fetch mode. + * + * @param string|array|Zend_Db_Table_Select $where OPTIONAL An SQL WHERE clause or Zend_Db_Table_Select object. + * @param string|array $order OPTIONAL An SQL ORDER clause. + * @param int $count OPTIONAL An SQL LIMIT count. + * @param int $offset OPTIONAL An SQL LIMIT offset. + * @return Zend_Db_Table_Rowset_Abstract The row results per the Zend_Db_Adapter fetch mode. + */ + public function fetchAll($where = null, $order = null, $count = null, $offset = null) + { + if (!($where instanceof Zend_Db_Table_Select)) { + $select = $this->select(); + + if ($where !== null) { + $this->_where($select, $where); + } + + if ($order !== null) { + $this->_order($select, $order); + } + + if ($count !== null || $offset !== null) { + $select->limit($count, $offset); + } + + } else { + $select = $where; + } + + $rows = $this->_fetch($select); + + $data = array( + 'table' => $this, + 'data' => $rows, + 'readOnly' => $select->isReadOnly(), + 'rowClass' => $this->getRowClass(), + 'stored' => true + ); + + $rowsetClass = $this->getRowsetClass(); + if (!class_exists($rowsetClass)) { + require_once 'Zend/Loader.php'; + Zend_Loader::loadClass($rowsetClass); + } + return new $rowsetClass($data); + } + + /** + * Fetches one row in an object of type Zend_Db_Table_Row_Abstract, + * or returns null if no row matches the specified criteria. + * + * @param string|array|Zend_Db_Table_Select $where OPTIONAL An SQL WHERE clause or Zend_Db_Table_Select object. + * @param string|array $order OPTIONAL An SQL ORDER clause. + * @return Zend_Db_Table_Row_Abstract|null The row results per the + * Zend_Db_Adapter fetch mode, or null if no row found. + */ + public function fetchRow($where = null, $order = null) + { + if (!($where instanceof Zend_Db_Table_Select)) { + $select = $this->select(); + + if ($where !== null) { + $this->_where($select, $where); + } + + if ($order !== null) { + $this->_order($select, $order); + } + + $select->limit(1); + + } else { + $select = $where->limit(1); + } + + $rows = $this->_fetch($select); + + if (count($rows) == 0) { + return null; + } + + $data = array( + 'table' => $this, + 'data' => $rows[0], + 'readOnly' => $select->isReadOnly(), + 'stored' => true + ); + + $rowClass = $this->getRowClass(); + if (!class_exists($rowClass)) { + require_once 'Zend/Loader.php'; + Zend_Loader::loadClass($rowClass); + } + return new $rowClass($data); + } + + /** + * Fetches a new blank row (not from the database). + * + * @return Zend_Db_Table_Row_Abstract + * @deprecated since 0.9.3 - use createRow() instead. + */ + public function fetchNew() + { + return $this->createRow(); + } + + /** + * Fetches a new blank row (not from the database). + * + * @param array $data OPTIONAL data to populate in the new row. + * @param string $defaultSource OPTIONAL flag to force default values into new row + * @return Zend_Db_Table_Row_Abstract + */ + public function createRow(array $data = array(), $defaultSource = null) + { + $cols = $this->_getCols(); + $defaults = array_combine($cols, array_fill(0, count($cols), null)); + + // nothing provided at call-time, take the class value + if ($defaultSource == null) { + $defaultSource = $this->_defaultSource; + } + + if (!in_array($defaultSource, array(self::DEFAULT_CLASS, self::DEFAULT_DB, self::DEFAULT_NONE))) { + $defaultSource = self::DEFAULT_NONE; + } + + if ($defaultSource == self::DEFAULT_DB) { + foreach ($this->_metadata as $metadataName => $metadata) { + if (($metadata['DEFAULT'] != null) && + ($metadata['NULLABLE'] !== true || ($metadata['NULLABLE'] === true && isset($this->_defaultValues[$metadataName]) && $this->_defaultValues[$metadataName] === true)) && + (!(isset($this->_defaultValues[$metadataName]) && $this->_defaultValues[$metadataName] === false))) { + $defaults[$metadataName] = $metadata['DEFAULT']; + } + } + } elseif ($defaultSource == self::DEFAULT_CLASS && $this->_defaultValues) { + foreach ($this->_defaultValues as $defaultName => $defaultValue) { + if (array_key_exists($defaultName, $defaults)) { + $defaults[$defaultName] = $defaultValue; + } + } + } + + $config = array( + 'table' => $this, + 'data' => $defaults, + 'readOnly' => false, + 'stored' => false + ); + + $rowClass = $this->getRowClass(); + if (!class_exists($rowClass)) { + require_once 'Zend/Loader.php'; + Zend_Loader::loadClass($rowClass); + } + $row = new $rowClass($config); + $row->setFromArray($data); + return $row; + } + + /** + * Generate WHERE clause from user-supplied string or array + * + * @param string|array $where OPTIONAL An SQL WHERE clause. + * @return Zend_Db_Table_Select + */ + protected function _where(Zend_Db_Table_Select $select, $where) + { + $where = (array) $where; + + foreach ($where as $key => $val) { + // is $key an int? + if (is_int($key)) { + // $val is the full condition + $select->where($val); + } else { + // $key is the condition with placeholder, + // and $val is quoted into the condition + $select->where($key, $val); + } + } + + return $select; + } + + /** + * Generate ORDER clause from user-supplied string or array + * + * @param string|array $order OPTIONAL An SQL ORDER clause. + * @return Zend_Db_Table_Select + */ + protected function _order(Zend_Db_Table_Select $select, $order) + { + if (!is_array($order)) { + $order = array($order); + } + + foreach ($order as $val) { + $select->order($val); + } + + return $select; + } + + /** + * Support method for fetching rows. + * + * @param Zend_Db_Table_Select $select query options. + * @return array An array containing the row results in FETCH_ASSOC mode. + */ + protected function _fetch(Zend_Db_Table_Select $select) + { + $stmt = $this->_db->query($select); + $data = $stmt->fetchAll(Zend_Db::FETCH_ASSOC); + return $data; + } + +} diff --git a/library/Zend/Db/Table/Definition.php b/library/Zend/Db/Table/Definition.php new file mode 100644 index 000000000..30e601a5b --- /dev/null +++ b/library/Zend/Db/Table/Definition.php @@ -0,0 +1,131 @@ +setConfig($options); + } elseif (is_array($options)) { + $this->setOptions($options); + } + } + + /** + * setConfig() + * + * @param Zend_Config $config + * @return Zend_Db_Table_Definition + */ + public function setConfig(Zend_Config $config) + { + $this->setOptions($config->toArray()); + return $this; + } + + /** + * setOptions() + * + * @param array $options + * @return Zend_Db_Table_Definition + */ + public function setOptions(Array $options) + { + foreach ($options as $optionName => $optionValue) { + $this->setTableConfig($optionName, $optionValue); + } + return $this; + } + + /** + * @param string $tableName + * @param array $tableConfig + * @return Zend_Db_Table_Definition + */ + public function setTableConfig($tableName, array $tableConfig) + { + // @todo logic here + $tableConfig[Zend_Db_Table::DEFINITION_CONFIG_NAME] = $tableName; + $tableConfig[Zend_Db_Table::DEFINITION] = $this; + + if (!isset($tableConfig[Zend_Db_Table::NAME])) { + $tableConfig[Zend_Db_Table::NAME] = $tableName; + } + + $this->_tableConfigs[$tableName] = $tableConfig; + return $this; + } + + /** + * getTableConfig() + * + * @param string $tableName + * @return array + */ + public function getTableConfig($tableName) + { + return $this->_tableConfigs[$tableName]; + } + + /** + * removeTableConfig() + * + * @param string $tableName + */ + public function removeTableConfig($tableName) + { + unset($this->_tableConfigs[$tableName]); + } + + /** + * hasTableConfig() + * + * @param string $tableName + * @return bool + */ + public function hasTableConfig($tableName) + { + return (isset($this->_tableConfigs[$tableName])); + } + +} diff --git a/library/Zend/Db/Table/Exception.php b/library/Zend/Db/Table/Exception.php new file mode 100644 index 000000000..7916b8499 --- /dev/null +++ b/library/Zend/Db/Table/Exception.php @@ -0,0 +1,38 @@ + value). + * The keys must match the physical names of columns in the + * table for which this row is defined. + * + * @var array + */ + protected $_data = array(); + + /** + * This is set to a copy of $_data when the data is fetched from + * a database, specified as a new tuple in the constructor, or + * when dirty data is posted to the database with save(). + * + * @var array + */ + protected $_cleanData = array(); + + /** + * Tracks columns where data has been updated. Allows more specific insert and + * update operations. + * + * @var array + */ + protected $_modifiedFields = array(); + + /** + * Zend_Db_Table_Abstract parent class or instance. + * + * @var Zend_Db_Table_Abstract + */ + protected $_table = null; + + /** + * Connected is true if we have a reference to a live + * Zend_Db_Table_Abstract object. + * This is false after the Rowset has been deserialized. + * + * @var boolean + */ + protected $_connected = true; + + /** + * A row is marked read only if it contains columns that are not physically represented within + * the database schema (e.g. evaluated columns/Zend_Db_Expr columns). This can also be passed + * as a run-time config options as a means of protecting row data. + * + * @var boolean + */ + protected $_readOnly = false; + + /** + * Name of the class of the Zend_Db_Table_Abstract object. + * + * @var string + */ + protected $_tableClass = null; + + /** + * Primary row key(s). + * + * @var array + */ + protected $_primary; + + /** + * Constructor. + * + * Supported params for $config are:- + * - table = class name or object of type Zend_Db_Table_Abstract + * - data = values of columns in this row. + * + * @param array $config OPTIONAL Array of user-specified config options. + * @return void + * @throws Zend_Db_Table_Row_Exception + */ + public function __construct(array $config = array()) + { + if (isset($config['table']) && $config['table'] instanceof Zend_Db_Table_Abstract) { + $this->_table = $config['table']; + $this->_tableClass = get_class($this->_table); + } elseif ($this->_tableClass !== null) { + $this->_table = $this->_getTableFromString($this->_tableClass); + } + + if (isset($config['data'])) { + if (!is_array($config['data'])) { + require_once 'Zend/Db/Table/Row/Exception.php'; + throw new Zend_Db_Table_Row_Exception('Data must be an array'); + } + $this->_data = $config['data']; + } + if (isset($config['stored']) && $config['stored'] === true) { + $this->_cleanData = $this->_data; + } + + if (isset($config['readOnly']) && $config['readOnly'] === true) { + $this->setReadOnly(true); + } + + // Retrieve primary keys from table schema + if (($table = $this->_getTable())) { + $info = $table->info(); + $this->_primary = (array) $info['primary']; + } + + $this->init(); + } + + /** + * Transform a column name from the user-specified form + * to the physical form used in the database. + * You can override this method in a custom Row class + * to implement column name mappings, for example inflection. + * + * @param string $columnName Column name given. + * @return string The column name after transformation applied (none by default). + * @throws Zend_Db_Table_Row_Exception if the $columnName is not a string. + */ + protected function _transformColumn($columnName) + { + if (!is_string($columnName)) { + require_once 'Zend/Db/Table/Row/Exception.php'; + throw new Zend_Db_Table_Row_Exception('Specified column is not a string'); + } + // Perform no transformation by default + return $columnName; + } + + /** + * Retrieve row field value + * + * @param string $columnName The user-specified column name. + * @return string The corresponding column value. + * @throws Zend_Db_Table_Row_Exception if the $columnName is not a column in the row. + */ + public function __get($columnName) + { + $columnName = $this->_transformColumn($columnName); + if (!array_key_exists($columnName, $this->_data)) { + require_once 'Zend/Db/Table/Row/Exception.php'; + throw new Zend_Db_Table_Row_Exception("Specified column \"$columnName\" is not in the row"); + } + return $this->_data[$columnName]; + } + + /** + * Set row field value + * + * @param string $columnName The column key. + * @param mixed $value The value for the property. + * @return void + * @throws Zend_Db_Table_Row_Exception + */ + public function __set($columnName, $value) + { + $columnName = $this->_transformColumn($columnName); + if (!array_key_exists($columnName, $this->_data)) { + require_once 'Zend/Db/Table/Row/Exception.php'; + throw new Zend_Db_Table_Row_Exception("Specified column \"$columnName\" is not in the row"); + } + $this->_data[$columnName] = $value; + $this->_modifiedFields[$columnName] = true; + } + + /** + * Unset row field value + * + * @param string $columnName The column key. + * @return Zend_Db_Table_Row_Abstract + * @throws Zend_Db_Table_Row_Exception + */ + public function __unset($columnName) + { + $columnName = $this->_transformColumn($columnName); + if (!array_key_exists($columnName, $this->_data)) { + require_once 'Zend/Db/Table/Row/Exception.php'; + throw new Zend_Db_Table_Row_Exception("Specified column \"$columnName\" is not in the row"); + } + if ($this->isConnected() && in_array($columnName, $this->_table->info('primary'))) { + require_once 'Zend/Db/Table/Row/Exception.php'; + throw new Zend_Db_Table_Row_Exception("Specified column \"$columnName\" is a primary key and should not be unset"); + } + unset($this->_data[$columnName]); + return $this; + } + + /** + * Test existence of row field + * + * @param string $columnName The column key. + * @return boolean + */ + public function __isset($columnName) + { + $columnName = $this->_transformColumn($columnName); + return array_key_exists($columnName, $this->_data); + } + + /** + * Store table, primary key and data in serialized object + * + * @return array + */ + public function __sleep() + { + return array('_tableClass', '_primary', '_data', '_cleanData', '_readOnly' ,'_modifiedFields'); + } + + /** + * Setup to do on wakeup. + * A de-serialized Row should not be assumed to have access to a live + * database connection, so set _connected = false. + * + * @return void + */ + public function __wakeup() + { + $this->_connected = false; + } + + /** + * Proxy to __isset + * Required by the ArrayAccess implementation + * + * @param string $offset + * @return boolean + */ + public function offsetExists($offset) + { + return $this->__isset($offset); + } + + /** + * Proxy to __get + * Required by the ArrayAccess implementation + * + * @param string $offset + * @return string + */ + public function offsetGet($offset) + { + return $this->__get($offset); + } + + /** + * Proxy to __set + * Required by the ArrayAccess implementation + * + * @param string $offset + * @param mixed $value + */ + public function offsetSet($offset, $value) + { + $this->__set($offset, $value); + } + + /** + * Proxy to __unset + * Required by the ArrayAccess implementation + * + * @param string $offset + */ + public function offsetUnset($offset) + { + return $this->__unset($offset); + } + + /** + * Initialize object + * + * Called from {@link __construct()} as final step of object instantiation. + * + * @return void + */ + public function init() + { + } + + /** + * Returns the table object, or null if this is disconnected row + * + * @return Zend_Db_Table_Abstract|null + */ + public function getTable() + { + return $this->_table; + } + + /** + * Set the table object, to re-establish a live connection + * to the database for a Row that has been de-serialized. + * + * @param Zend_Db_Table_Abstract $table + * @return boolean + * @throws Zend_Db_Table_Row_Exception + */ + public function setTable(Zend_Db_Table_Abstract $table = null) + { + if ($table == null) { + $this->_table = null; + $this->_connected = false; + return false; + } + + $tableClass = get_class($table); + if (! $table instanceof $this->_tableClass) { + require_once 'Zend/Db/Table/Row/Exception.php'; + throw new Zend_Db_Table_Row_Exception("The specified Table is of class $tableClass, expecting class to be instance of $this->_tableClass"); + } + + $this->_table = $table; + $this->_tableClass = $tableClass; + + $info = $this->_table->info(); + + if ($info['cols'] != array_keys($this->_data)) { + require_once 'Zend/Db/Table/Row/Exception.php'; + throw new Zend_Db_Table_Row_Exception('The specified Table does not have the same columns as the Row'); + } + + if (! array_intersect((array) $this->_primary, $info['primary']) == (array) $this->_primary) { + + require_once 'Zend/Db/Table/Row/Exception.php'; + throw new Zend_Db_Table_Row_Exception("The specified Table '$tableClass' does not have the same primary key as the Row"); + } + + $this->_connected = true; + return true; + } + + /** + * Query the class name of the Table object for which this + * Row was created. + * + * @return string + */ + public function getTableClass() + { + return $this->_tableClass; + } + + /** + * Test the connected status of the row. + * + * @return boolean + */ + public function isConnected() + { + return $this->_connected; + } + + /** + * Test the read-only status of the row. + * + * @return boolean + */ + public function isReadOnly() + { + return $this->_readOnly; + } + + /** + * Set the read-only status of the row. + * + * @param boolean $flag + * @return boolean + */ + public function setReadOnly($flag) + { + $this->_readOnly = (bool) $flag; + } + + /** + * Returns an instance of the parent table's Zend_Db_Table_Select object. + * + * @return Zend_Db_Table_Select + */ + public function select() + { + return $this->getTable()->select(); + } + + /** + * Saves the properties to the database. + * + * This performs an intelligent insert/update, and reloads the + * properties with fresh data from the table on success. + * + * @return mixed The primary key value(s), as an associative array if the + * key is compound, or a scalar if the key is single-column. + */ + public function save() + { + /** + * If the _cleanData array is empty, + * this is an INSERT of a new row. + * Otherwise it is an UPDATE. + */ + if (empty($this->_cleanData)) { + return $this->_doInsert(); + } else { + return $this->_doUpdate(); + } + } + + /** + * @return mixed The primary key value(s), as an associative array if the + * key is compound, or a scalar if the key is single-column. + */ + protected function _doInsert() + { + /** + * A read-only row cannot be saved. + */ + if ($this->_readOnly === true) { + require_once 'Zend/Db/Table/Row/Exception.php'; + throw new Zend_Db_Table_Row_Exception('This row has been marked read-only'); + } + + /** + * Run pre-INSERT logic + */ + $this->_insert(); + + /** + * Execute the INSERT (this may throw an exception) + */ + $data = array_intersect_key($this->_data, $this->_modifiedFields); + $primaryKey = $this->_getTable()->insert($data); + + /** + * Normalize the result to an array indexed by primary key column(s). + * The table insert() method may return a scalar. + */ + if (is_array($primaryKey)) { + $newPrimaryKey = $primaryKey; + } else { + //ZF-6167 Use tempPrimaryKey temporary to avoid that zend encoding fails. + $tempPrimaryKey = (array) $this->_primary; + $newPrimaryKey = array(current($tempPrimaryKey) => $primaryKey); + } + + /** + * Save the new primary key value in _data. The primary key may have + * been generated by a sequence or auto-increment mechanism, and this + * merge should be done before the _postInsert() method is run, so the + * new values are available for logging, etc. + */ + $this->_data = array_merge($this->_data, $newPrimaryKey); + + /** + * Run post-INSERT logic + */ + $this->_postInsert(); + + /** + * Update the _cleanData to reflect that the data has been inserted. + */ + $this->_refresh(); + + return $primaryKey; + } + + /** + * @return mixed The primary key value(s), as an associative array if the + * key is compound, or a scalar if the key is single-column. + */ + protected function _doUpdate() + { + /** + * A read-only row cannot be saved. + */ + if ($this->_readOnly === true) { + require_once 'Zend/Db/Table/Row/Exception.php'; + throw new Zend_Db_Table_Row_Exception('This row has been marked read-only'); + } + + /** + * Get expressions for a WHERE clause + * based on the primary key value(s). + */ + $where = $this->_getWhereQuery(false); + + /** + * Run pre-UPDATE logic + */ + $this->_update(); + + /** + * Compare the data to the modified fields array to discover + * which columns have been changed. + */ + $diffData = array_intersect_key($this->_data, $this->_modifiedFields); + + /** + * Were any of the changed columns part of the primary key? + */ + $pkDiffData = array_intersect_key($diffData, array_flip((array)$this->_primary)); + + /** + * Execute cascading updates against dependent tables. + * Do this only if primary key value(s) were changed. + */ + if (count($pkDiffData) > 0) { + $depTables = $this->_getTable()->getDependentTables(); + if (!empty($depTables)) { + $pkNew = $this->_getPrimaryKey(true); + $pkOld = $this->_getPrimaryKey(false); + foreach ($depTables as $tableClass) { + $t = $this->_getTableFromString($tableClass); + $t->_cascadeUpdate($this->getTableClass(), $pkOld, $pkNew); + } + } + } + + /** + * Execute the UPDATE (this may throw an exception) + * Do this only if data values were changed. + * Use the $diffData variable, so the UPDATE statement + * includes SET terms only for data values that changed. + */ + if (count($diffData) > 0) { + $this->_getTable()->update($diffData, $where); + } + + /** + * Run post-UPDATE logic. Do this before the _refresh() + * so the _postUpdate() function can tell the difference + * between changed data and clean (pre-changed) data. + */ + $this->_postUpdate(); + + /** + * Refresh the data just in case triggers in the RDBMS changed + * any columns. Also this resets the _cleanData. + */ + $this->_refresh(); + + /** + * Return the primary key value(s) as an array + * if the key is compound or a scalar if the key + * is a scalar. + */ + $primaryKey = $this->_getPrimaryKey(true); + if (count($primaryKey) == 1) { + return current($primaryKey); + } + + return $primaryKey; + } + + /** + * Deletes existing rows. + * + * @return int The number of rows deleted. + */ + public function delete() + { + /** + * A read-only row cannot be deleted. + */ + if ($this->_readOnly === true) { + require_once 'Zend/Db/Table/Row/Exception.php'; + throw new Zend_Db_Table_Row_Exception('This row has been marked read-only'); + } + + $where = $this->_getWhereQuery(); + + /** + * Execute pre-DELETE logic + */ + $this->_delete(); + + /** + * Execute cascading deletes against dependent tables + */ + $depTables = $this->_getTable()->getDependentTables(); + if (!empty($depTables)) { + $pk = $this->_getPrimaryKey(); + foreach ($depTables as $tableClass) { + $t = $this->_getTableFromString($tableClass); + $t->_cascadeDelete($this->getTableClass(), $pk); + } + } + + /** + * Execute the DELETE (this may throw an exception) + */ + $result = $this->_getTable()->delete($where); + + /** + * Execute post-DELETE logic + */ + $this->_postDelete(); + + /** + * Reset all fields to null to indicate that the row is not there + */ + $this->_data = array_combine( + array_keys($this->_data), + array_fill(0, count($this->_data), null) + ); + + return $result; + } + + /** + * Returns the column/value data as an array. + * + * @return array + */ + public function toArray() + { + return (array)$this->_data; + } + + /** + * Sets all data in the row from an array. + * + * @param array $data + * @return Zend_Db_Table_Row_Abstract Provides a fluent interface + */ + public function setFromArray(array $data) + { + $data = array_intersect_key($data, $this->_data); + + foreach ($data as $columnName => $value) { + $this->__set($columnName, $value); + } + + return $this; + } + + /** + * Refreshes properties from the database. + * + * @return void + */ + public function refresh() + { + return $this->_refresh(); + } + + /** + * Retrieves an instance of the parent table. + * + * @return Zend_Db_Table_Abstract + */ + protected function _getTable() + { + if (!$this->_connected) { + require_once 'Zend/Db/Table/Row/Exception.php'; + throw new Zend_Db_Table_Row_Exception('Cannot save a Row unless it is connected'); + } + return $this->_table; + } + + /** + * Retrieves an associative array of primary keys. + * + * @param bool $useDirty + * @return array + */ + protected function _getPrimaryKey($useDirty = true) + { + if (!is_array($this->_primary)) { + require_once 'Zend/Db/Table/Row/Exception.php'; + throw new Zend_Db_Table_Row_Exception("The primary key must be set as an array"); + } + + $primary = array_flip($this->_primary); + if ($useDirty) { + $array = array_intersect_key($this->_data, $primary); + } else { + $array = array_intersect_key($this->_cleanData, $primary); + } + if (count($primary) != count($array)) { + require_once 'Zend/Db/Table/Row/Exception.php'; + throw new Zend_Db_Table_Row_Exception("The specified Table '$this->_tableClass' does not have the same primary key as the Row"); + } + return $array; + } + + /** + * Constructs where statement for retrieving row(s). + * + * @param bool $useDirty + * @return array + */ + protected function _getWhereQuery($useDirty = true) + { + $where = array(); + $db = $this->_getTable()->getAdapter(); + $primaryKey = $this->_getPrimaryKey($useDirty); + $info = $this->_getTable()->info(); + $metadata = $info[Zend_Db_Table_Abstract::METADATA]; + + // retrieve recently updated row using primary keys + $where = array(); + foreach ($primaryKey as $column => $value) { + $tableName = $db->quoteIdentifier($info[Zend_Db_Table_Abstract::NAME], true); + $type = $metadata[$column]['DATA_TYPE']; + $columnName = $db->quoteIdentifier($column, true); + $where[] = $db->quoteInto("{$tableName}.{$columnName} = ?", $value, $type); + } + return $where; + } + + /** + * Refreshes properties from the database. + * + * @return void + */ + protected function _refresh() + { + $where = $this->_getWhereQuery(); + $row = $this->_getTable()->fetchRow($where); + + if (null === $row) { + require_once 'Zend/Db/Table/Row/Exception.php'; + throw new Zend_Db_Table_Row_Exception('Cannot refresh row as parent is missing'); + } + + $this->_data = $row->toArray(); + $this->_cleanData = $this->_data; + $this->_modifiedFields = array(); + } + + /** + * Allows pre-insert logic to be applied to row. + * Subclasses may override this method. + * + * @return void + */ + protected function _insert() + { + } + + /** + * Allows post-insert logic to be applied to row. + * Subclasses may override this method. + * + * @return void + */ + protected function _postInsert() + { + } + + /** + * Allows pre-update logic to be applied to row. + * Subclasses may override this method. + * + * @return void + */ + protected function _update() + { + } + + /** + * Allows post-update logic to be applied to row. + * Subclasses may override this method. + * + * @return void + */ + protected function _postUpdate() + { + } + + /** + * Allows pre-delete logic to be applied to row. + * Subclasses may override this method. + * + * @return void + */ + protected function _delete() + { + } + + /** + * Allows post-delete logic to be applied to row. + * Subclasses may override this method. + * + * @return void + */ + protected function _postDelete() + { + } + + /** + * Prepares a table reference for lookup. + * + * Ensures all reference keys are set and properly formatted. + * + * @param Zend_Db_Table_Abstract $dependentTable + * @param Zend_Db_Table_Abstract $parentTable + * @param string $ruleKey + * @return array + */ + protected function _prepareReference(Zend_Db_Table_Abstract $dependentTable, Zend_Db_Table_Abstract $parentTable, $ruleKey) + { + $parentTableName = (get_class($parentTable) === 'Zend_Db_Table') ? $parentTable->getDefinitionConfigName() : get_class($parentTable); + $map = $dependentTable->getReference($parentTableName, $ruleKey); + + if (!isset($map[Zend_Db_Table_Abstract::REF_COLUMNS])) { + $parentInfo = $parentTable->info(); + $map[Zend_Db_Table_Abstract::REF_COLUMNS] = array_values((array) $parentInfo['primary']); + } + + $map[Zend_Db_Table_Abstract::COLUMNS] = (array) $map[Zend_Db_Table_Abstract::COLUMNS]; + $map[Zend_Db_Table_Abstract::REF_COLUMNS] = (array) $map[Zend_Db_Table_Abstract::REF_COLUMNS]; + + return $map; + } + + /** + * Query a dependent table to retrieve rows matching the current row. + * + * @param string|Zend_Db_Table_Abstract $dependentTable + * @param string OPTIONAL $ruleKey + * @param Zend_Db_Table_Select OPTIONAL $select + * @return Zend_Db_Table_Rowset_Abstract Query result from $dependentTable + * @throws Zend_Db_Table_Row_Exception If $dependentTable is not a table or is not loadable. + */ + public function findDependentRowset($dependentTable, $ruleKey = null, Zend_Db_Table_Select $select = null) + { + $db = $this->_getTable()->getAdapter(); + + if (is_string($dependentTable)) { + $dependentTable = $this->_getTableFromString($dependentTable); + } + + if (!$dependentTable instanceof Zend_Db_Table_Abstract) { + $type = gettype($dependentTable); + if ($type == 'object') { + $type = get_class($dependentTable); + } + require_once 'Zend/Db/Table/Row/Exception.php'; + throw new Zend_Db_Table_Row_Exception("Dependent table must be a Zend_Db_Table_Abstract, but it is $type"); + } + + // even if we are interacting between a table defined in a class and a + // table via extension, ensure to persist the definition + if (($tableDefinition = $this->_table->getDefinition()) !== null + && ($dependentTable->getDefinition() == null)) { + $dependentTable->setOptions(array(Zend_Db_Table_Abstract::DEFINITION => $tableDefinition)); + } + + if ($select === null) { + $select = $dependentTable->select(); + } else { + $select->setTable($dependentTable); + } + + $map = $this->_prepareReference($dependentTable, $this->_getTable(), $ruleKey); + + for ($i = 0; $i < count($map[Zend_Db_Table_Abstract::COLUMNS]); ++$i) { + $parentColumnName = $db->foldCase($map[Zend_Db_Table_Abstract::REF_COLUMNS][$i]); + $value = $this->_data[$parentColumnName]; + // Use adapter from dependent table to ensure correct query construction + $dependentDb = $dependentTable->getAdapter(); + $dependentColumnName = $dependentDb->foldCase($map[Zend_Db_Table_Abstract::COLUMNS][$i]); + $dependentColumn = $dependentDb->quoteIdentifier($dependentColumnName, true); + $dependentInfo = $dependentTable->info(); + $type = $dependentInfo[Zend_Db_Table_Abstract::METADATA][$dependentColumnName]['DATA_TYPE']; + $select->where("$dependentColumn = ?", $value, $type); + } + + return $dependentTable->fetchAll($select); + } + + /** + * Query a parent table to retrieve the single row matching the current row. + * + * @param string|Zend_Db_Table_Abstract $parentTable + * @param string OPTIONAL $ruleKey + * @param Zend_Db_Table_Select OPTIONAL $select + * @return Zend_Db_Table_Row_Abstract Query result from $parentTable + * @throws Zend_Db_Table_Row_Exception If $parentTable is not a table or is not loadable. + */ + public function findParentRow($parentTable, $ruleKey = null, Zend_Db_Table_Select $select = null) + { + $db = $this->_getTable()->getAdapter(); + + if (is_string($parentTable)) { + $parentTable = $this->_getTableFromString($parentTable); + } + + if (!$parentTable instanceof Zend_Db_Table_Abstract) { + $type = gettype($parentTable); + if ($type == 'object') { + $type = get_class($parentTable); + } + require_once 'Zend/Db/Table/Row/Exception.php'; + throw new Zend_Db_Table_Row_Exception("Parent table must be a Zend_Db_Table_Abstract, but it is $type"); + } + + // even if we are interacting between a table defined in a class and a + // table via extension, ensure to persist the definition + if (($tableDefinition = $this->_table->getDefinition()) !== null + && ($parentTable->getDefinition() == null)) { + $parentTable->setOptions(array(Zend_Db_Table_Abstract::DEFINITION => $tableDefinition)); + } + + if ($select === null) { + $select = $parentTable->select(); + } else { + $select->setTable($parentTable); + } + + $map = $this->_prepareReference($this->_getTable(), $parentTable, $ruleKey); + + // iterate the map, creating the proper wheres + for ($i = 0; $i < count($map[Zend_Db_Table_Abstract::COLUMNS]); ++$i) { + $dependentColumnName = $db->foldCase($map[Zend_Db_Table_Abstract::COLUMNS][$i]); + $value = $this->_data[$dependentColumnName]; + // Use adapter from parent table to ensure correct query construction + $parentDb = $parentTable->getAdapter(); + $parentColumnName = $parentDb->foldCase($map[Zend_Db_Table_Abstract::REF_COLUMNS][$i]); + $parentColumn = $parentDb->quoteIdentifier($parentColumnName, true); + $parentInfo = $parentTable->info(); + + // determine where part + $type = $parentInfo[Zend_Db_Table_Abstract::METADATA][$parentColumnName]['DATA_TYPE']; + $nullable = $parentInfo[Zend_Db_Table_Abstract::METADATA][$parentColumnName]['NULLABLE']; + if ($value === null && $nullable == true) { + $select->where("$parentColumn IS NULL"); + } elseif ($value === null && $nullable == false) { + return null; + } else { + $select->where("$parentColumn = ?", $value, $type); + } + + } + + return $parentTable->fetchRow($select); + } + + /** + * @param string|Zend_Db_Table_Abstract $matchTable + * @param string|Zend_Db_Table_Abstract $intersectionTable + * @param string OPTIONAL $callerRefRule + * @param string OPTIONAL $matchRefRule + * @param Zend_Db_Table_Select OPTIONAL $select + * @return Zend_Db_Table_Rowset_Abstract Query result from $matchTable + * @throws Zend_Db_Table_Row_Exception If $matchTable or $intersectionTable is not a table class or is not loadable. + */ + public function findManyToManyRowset($matchTable, $intersectionTable, $callerRefRule = null, + $matchRefRule = null, Zend_Db_Table_Select $select = null) + { + $db = $this->_getTable()->getAdapter(); + + if (is_string($intersectionTable)) { + $intersectionTable = $this->_getTableFromString($intersectionTable); + } + + if (!$intersectionTable instanceof Zend_Db_Table_Abstract) { + $type = gettype($intersectionTable); + if ($type == 'object') { + $type = get_class($intersectionTable); + } + require_once 'Zend/Db/Table/Row/Exception.php'; + throw new Zend_Db_Table_Row_Exception("Intersection table must be a Zend_Db_Table_Abstract, but it is $type"); + } + + // even if we are interacting between a table defined in a class and a + // table via extension, ensure to persist the definition + if (($tableDefinition = $this->_table->getDefinition()) !== null + && ($intersectionTable->getDefinition() == null)) { + $intersectionTable->setOptions(array(Zend_Db_Table_Abstract::DEFINITION => $tableDefinition)); + } + + if (is_string($matchTable)) { + $matchTable = $this->_getTableFromString($matchTable); + } + + if (! $matchTable instanceof Zend_Db_Table_Abstract) { + $type = gettype($matchTable); + if ($type == 'object') { + $type = get_class($matchTable); + } + require_once 'Zend/Db/Table/Row/Exception.php'; + throw new Zend_Db_Table_Row_Exception("Match table must be a Zend_Db_Table_Abstract, but it is $type"); + } + + // even if we are interacting between a table defined in a class and a + // table via extension, ensure to persist the definition + if (($tableDefinition = $this->_table->getDefinition()) !== null + && ($matchTable->getDefinition() == null)) { + $matchTable->setOptions(array(Zend_Db_Table_Abstract::DEFINITION => $tableDefinition)); + } + + if ($select === null) { + $select = $matchTable->select(); + } else { + $select->setTable($matchTable); + } + + // Use adapter from intersection table to ensure correct query construction + $interInfo = $intersectionTable->info(); + $interDb = $intersectionTable->getAdapter(); + $interName = $interInfo['name']; + $interSchema = isset($interInfo['schema']) ? $interInfo['schema'] : null; + $matchInfo = $matchTable->info(); + $matchName = $matchInfo['name']; + $matchSchema = isset($matchInfo['schema']) ? $matchInfo['schema'] : null; + + $matchMap = $this->_prepareReference($intersectionTable, $matchTable, $matchRefRule); + + for ($i = 0; $i < count($matchMap[Zend_Db_Table_Abstract::COLUMNS]); ++$i) { + $interCol = $interDb->quoteIdentifier('i' . '.' . $matchMap[Zend_Db_Table_Abstract::COLUMNS][$i], true); + $matchCol = $interDb->quoteIdentifier('m' . '.' . $matchMap[Zend_Db_Table_Abstract::REF_COLUMNS][$i], true); + $joinCond[] = "$interCol = $matchCol"; + } + $joinCond = implode(' AND ', $joinCond); + + $select->from(array('i' => $interName), array(), $interSchema) + ->joinInner(array('m' => $matchName), $joinCond, Zend_Db_Select::SQL_WILDCARD, $matchSchema) + ->setIntegrityCheck(false); + + $callerMap = $this->_prepareReference($intersectionTable, $this->_getTable(), $callerRefRule); + + for ($i = 0; $i < count($callerMap[Zend_Db_Table_Abstract::COLUMNS]); ++$i) { + $callerColumnName = $db->foldCase($callerMap[Zend_Db_Table_Abstract::REF_COLUMNS][$i]); + $value = $this->_data[$callerColumnName]; + $interColumnName = $interDb->foldCase($callerMap[Zend_Db_Table_Abstract::COLUMNS][$i]); + $interCol = $interDb->quoteIdentifier("i.$interColumnName", true); + $interInfo = $intersectionTable->info(); + $type = $interInfo[Zend_Db_Table_Abstract::METADATA][$interColumnName]['DATA_TYPE']; + $select->where($interDb->quoteInto("$interCol = ?", $value, $type)); + } + + $stmt = $select->query(); + + $config = array( + 'table' => $matchTable, + 'data' => $stmt->fetchAll(Zend_Db::FETCH_ASSOC), + 'rowClass' => $matchTable->getRowClass(), + 'readOnly' => false, + 'stored' => true + ); + + $rowsetClass = $matchTable->getRowsetClass(); + if (!class_exists($rowsetClass)) { + try { + require_once 'Zend/Loader.php'; + Zend_Loader::loadClass($rowsetClass); + } catch (Zend_Exception $e) { + require_once 'Zend/Db/Table/Row/Exception.php'; + throw new Zend_Db_Table_Row_Exception($e->getMessage(), $e->getCode(), $e); + } + } + $rowset = new $rowsetClass($config); + return $rowset; + } + + /** + * Turn magic function calls into non-magic function calls + * to the above methods. + * + * @param string $method + * @param array $args OPTIONAL Zend_Db_Table_Select query modifier + * @return Zend_Db_Table_Row_Abstract|Zend_Db_Table_Rowset_Abstract + * @throws Zend_Db_Table_Row_Exception If an invalid method is called. + */ + public function __call($method, array $args) + { + $matches = array(); + + if (count($args) && $args[0] instanceof Zend_Db_Table_Select) { + $select = $args[0]; + } else { + $select = null; + } + + /** + * Recognize methods for Has-Many cases: + * findParent() + * findParentBy() + * Use the non-greedy pattern repeat modifier e.g. \w+? + */ + if (preg_match('/^findParent(\w+?)(?:By(\w+))?$/', $method, $matches)) { + $class = $matches[1]; + $ruleKey1 = isset($matches[2]) ? $matches[2] : null; + return $this->findParentRow($class, $ruleKey1, $select); + } + + /** + * Recognize methods for Many-to-Many cases: + * findVia() + * findViaBy() + * findViaByAnd() + * Use the non-greedy pattern repeat modifier e.g. \w+? + */ + if (preg_match('/^find(\w+?)Via(\w+?)(?:By(\w+?)(?:And(\w+))?)?$/', $method, $matches)) { + $class = $matches[1]; + $viaClass = $matches[2]; + $ruleKey1 = isset($matches[3]) ? $matches[3] : null; + $ruleKey2 = isset($matches[4]) ? $matches[4] : null; + return $this->findManyToManyRowset($class, $viaClass, $ruleKey1, $ruleKey2, $select); + } + + /** + * Recognize methods for Belongs-To cases: + * find() + * findBy() + * Use the non-greedy pattern repeat modifier e.g. \w+? + */ + if (preg_match('/^find(\w+?)(?:By(\w+))?$/', $method, $matches)) { + $class = $matches[1]; + $ruleKey1 = isset($matches[2]) ? $matches[2] : null; + return $this->findDependentRowset($class, $ruleKey1, $select); + } + + require_once 'Zend/Db/Table/Row/Exception.php'; + throw new Zend_Db_Table_Row_Exception("Unrecognized method '$method()'"); + } + + + /** + * _getTableFromString + * + * @param string $tableName + * @return Zend_Db_Table_Abstract + */ + protected function _getTableFromString($tableName) + { + + if ($this->_table instanceof Zend_Db_Table_Abstract) { + $tableDefinition = $this->_table->getDefinition(); + + if ($tableDefinition !== null && $tableDefinition->hasTableConfig($tableName)) { + return new Zend_Db_Table($tableName, $tableDefinition); + } + } + + // assume the tableName is the class name + if (!class_exists($tableName)) { + try { + require_once 'Zend/Loader.php'; + Zend_Loader::loadClass($tableName); + } catch (Zend_Exception $e) { + require_once 'Zend/Db/Table/Row/Exception.php'; + throw new Zend_Db_Table_Row_Exception($e->getMessage(), $e->getCode(), $e); + } + } + + $options = array(); + + if (($table = $this->_getTable())) { + $options['db'] = $table->getAdapter(); + } + + if (isset($tableDefinition) && $tableDefinition !== null) { + $options[Zend_Db_Table_Abstract::DEFINITION] = $tableDefinition; + } + + return new $tableName($options); + } + +} diff --git a/library/Zend/Db/Table/Row/Exception.php b/library/Zend/Db/Table/Row/Exception.php new file mode 100644 index 000000000..c3c717c17 --- /dev/null +++ b/library/Zend/Db/Table/Row/Exception.php @@ -0,0 +1,38 @@ +_table = $config['table']; + $this->_tableClass = get_class($this->_table); + } + if (isset($config['rowClass'])) { + $this->_rowClass = $config['rowClass']; + } + if (!class_exists($this->_rowClass)) { + require_once 'Zend/Loader.php'; + Zend_Loader::loadClass($this->_rowClass); + } + if (isset($config['data'])) { + $this->_data = $config['data']; + } + if (isset($config['readOnly'])) { + $this->_readOnly = $config['readOnly']; + } + if (isset($config['stored'])) { + $this->_stored = $config['stored']; + } + + // set the count of rows + $this->_count = count($this->_data); + + $this->init(); + } + + /** + * Store data, class names, and state in serialized object + * + * @return array + */ + public function __sleep() + { + return array('_data', '_tableClass', '_rowClass', '_pointer', '_count', '_rows', '_stored', + '_readOnly'); + } + + /** + * Setup to do on wakeup. + * A de-serialized Rowset should not be assumed to have access to a live + * database connection, so set _connected = false. + * + * @return void + */ + public function __wakeup() + { + $this->_connected = false; + } + + /** + * Initialize object + * + * Called from {@link __construct()} as final step of object instantiation. + * + * @return void + */ + public function init() + { + } + + /** + * Return the connected state of the rowset. + * + * @return boolean + */ + public function isConnected() + { + return $this->_connected; + } + + /** + * Returns the table object, or null if this is disconnected rowset + * + * @return Zend_Db_Table_Abstract + */ + public function getTable() + { + return $this->_table; + } + + /** + * Set the table object, to re-establish a live connection + * to the database for a Rowset that has been de-serialized. + * + * @param Zend_Db_Table_Abstract $table + * @return boolean + * @throws Zend_Db_Table_Row_Exception + */ + public function setTable(Zend_Db_Table_Abstract $table) + { + $this->_table = $table; + $this->_connected = false; + // @todo This works only if we have iterated through + // the result set once to instantiate the rows. + foreach ($this as $row) { + $connected = $row->setTable($table); + if ($connected == true) { + $this->_connected = true; + } + } + return $this->_connected; + } + + /** + * Query the class name of the Table object for which this + * Rowset was created. + * + * @return string + */ + public function getTableClass() + { + return $this->_tableClass; + } + + /** + * Rewind the Iterator to the first element. + * Similar to the reset() function for arrays in PHP. + * Required by interface Iterator. + * + * @return Zend_Db_Table_Rowset_Abstract Fluent interface. + */ + public function rewind() + { + $this->_pointer = 0; + return $this; + } + + /** + * Return the current element. + * Similar to the current() function for arrays in PHP + * Required by interface Iterator. + * + * @return Zend_Db_Table_Row_Abstract current element from the collection + */ + public function current() + { + if ($this->valid() === false) { + return null; + } + + // do we already have a row object for this position? + if (empty($this->_rows[$this->_pointer])) { + $this->_rows[$this->_pointer] = new $this->_rowClass( + array( + 'table' => $this->_table, + 'data' => $this->_data[$this->_pointer], + 'stored' => $this->_stored, + 'readOnly' => $this->_readOnly + ) + ); + } + + // return the row object + return $this->_rows[$this->_pointer]; + } + + /** + * Return the identifying key of the current element. + * Similar to the key() function for arrays in PHP. + * Required by interface Iterator. + * + * @return int + */ + public function key() + { + return $this->_pointer; + } + + /** + * Move forward to next element. + * Similar to the next() function for arrays in PHP. + * Required by interface Iterator. + * + * @return void + */ + public function next() + { + ++$this->_pointer; + } + + /** + * Check if there is a current element after calls to rewind() or next(). + * Used to check if we've iterated to the end of the collection. + * Required by interface Iterator. + * + * @return bool False if there's nothing more to iterate over + */ + public function valid() + { + return $this->_pointer < $this->_count; + } + + /** + * Returns the number of elements in the collection. + * + * Implements Countable::count() + * + * @return int + */ + public function count() + { + return $this->_count; + } + + /** + * Take the Iterator to position $position + * Required by interface SeekableIterator. + * + * @param int $position the position to seek to + * @return Zend_Db_Table_Rowset_Abstract + * @throws Zend_Db_Table_Rowset_Exception + */ + public function seek($position) + { + $position = (int) $position; + if ($position < 0 || $position >= $this->_count) { + require_once 'Zend/Db/Table/Rowset/Exception.php'; + throw new Zend_Db_Table_Rowset_Exception("Illegal index $position"); + } + $this->_pointer = $position; + return $this; + } + + /** + * Check if an offset exists + * Required by the ArrayAccess implementation + * + * @param string $offset + * @return boolean + */ + public function offsetExists($offset) + { + return isset($this->_data[(int) $offset]); + } + + /** + * Get the row for the given offset + * Required by the ArrayAccess implementation + * + * @param string $offset + * @return Zend_Db_Table_Row_Abstract + */ + public function offsetGet($offset) + { + $this->_pointer = (int) $offset; + + return $this->current(); + } + + /** + * Does nothing + * Required by the ArrayAccess implementation + * + * @param string $offset + * @param mixed $value + */ + public function offsetSet($offset, $value) + { + } + + /** + * Does nothing + * Required by the ArrayAccess implementation + * + * @param string $offset + */ + public function offsetUnset($offset) + { + } + + /** + * Returns a Zend_Db_Table_Row from a known position into the Iterator + * + * @param int $position the position of the row expected + * @param bool $seek wether or not seek the iterator to that position after + * @return Zend_Db_Table_Row + * @throws Zend_Db_Table_Rowset_Exception + */ + public function getRow($position, $seek = false) + { + $key = $this->key(); + try { + $this->seek($position); + $row = $this->current(); + } catch (Zend_Db_Table_Rowset_Exception $e) { + require_once 'Zend/Db/Table/Rowset/Exception.php'; + throw new Zend_Db_Table_Rowset_Exception('No row could be found at position ' . (int) $position, 0, $e); + } + if ($seek == false) { + $this->seek($key); + } + return $row; + } + + /** + * Returns all data as an array. + * + * Updates the $_data property with current row object values. + * + * @return array + */ + public function toArray() + { + // @todo This works only if we have iterated through + // the result set once to instantiate the rows. + foreach ($this->_rows as $i => $row) { + $this->_data[$i] = $row->toArray(); + } + return $this->_data; + } + +} diff --git a/library/Zend/Db/Table/Rowset/Exception.php b/library/Zend/Db/Table/Rowset/Exception.php new file mode 100644 index 000000000..8bdaedd17 --- /dev/null +++ b/library/Zend/Db/Table/Rowset/Exception.php @@ -0,0 +1,37 @@ +getAdapter()); + + $this->setTable($table); + } + + /** + * Return the table that created this select object + * + * @return Zend_Db_Table_Abstract + */ + public function getTable() + { + return $this->_table; + } + + /** + * Sets the primary table name and retrieves the table schema. + * + * @param Zend_Db_Table_Abstract $adapter + * @return Zend_Db_Select This Zend_Db_Select object. + */ + public function setTable(Zend_Db_Table_Abstract $table) + { + $this->_adapter = $table->getAdapter(); + $this->_info = $table->info(); + $this->_table = $table; + + return $this; + } + + /** + * Sets the integrity check flag. + * + * Setting this flag to false skips the checks for table joins, allowing + * 'hybrid' table rows to be created. + * + * @param Zend_Db_Table_Abstract $adapter + * @return Zend_Db_Select This Zend_Db_Select object. + */ + public function setIntegrityCheck($flag = true) + { + $this->_integrityCheck = $flag; + return $this; + } + + /** + * Tests query to determine if expressions or aliases columns exist. + * + * @return boolean + */ + public function isReadOnly() + { + $readOnly = false; + $fields = $this->getPart(Zend_Db_Table_Select::COLUMNS); + $cols = $this->_info[Zend_Db_Table_Abstract::COLS]; + + if (!count($fields)) { + return $readOnly; + } + + foreach ($fields as $columnEntry) { + $column = $columnEntry[1]; + $alias = $columnEntry[2]; + + if ($alias !== null) { + $column = $alias; + } + + switch (true) { + case ($column == self::SQL_WILDCARD): + break; + + case ($column instanceof Zend_Db_Expr): + case (!in_array($column, $cols)): + $readOnly = true; + break 2; + } + } + + return $readOnly; + } + + /** + * Adds a FROM table and optional columns to the query. + * + * The table name can be expressed + * + * @param array|string|Zend_Db_Expr|Zend_Db_Table_Abstract $name The table name or an + associative array relating + table name to correlation + name. + * @param array|string|Zend_Db_Expr $cols The columns to select from this table. + * @param string $schema The schema name to specify, if any. + * @return Zend_Db_Table_Select This Zend_Db_Table_Select object. + */ + public function from($name, $cols = self::SQL_WILDCARD, $schema = null) + { + if ($name instanceof Zend_Db_Table_Abstract) { + $info = $name->info(); + $name = $info[Zend_Db_Table_Abstract::NAME]; + if (isset($info[Zend_Db_Table_Abstract::SCHEMA])) { + $schema = $info[Zend_Db_Table_Abstract::SCHEMA]; + } + } + + return $this->joinInner($name, null, $cols, $schema); + } + + /** + * Performs a validation on the select query before passing back to the parent class. + * Ensures that only columns from the primary Zend_Db_Table are returned in the result. + * + * @return string|null This object as a SELECT string (or null if a string cannot be produced) + */ + public function assemble() + { + $fields = $this->getPart(Zend_Db_Table_Select::COLUMNS); + $primary = $this->_info[Zend_Db_Table_Abstract::NAME]; + $schema = $this->_info[Zend_Db_Table_Abstract::SCHEMA]; + + + if (count($this->_parts[self::UNION]) == 0) { + + // If no fields are specified we assume all fields from primary table + if (!count($fields)) { + $this->from($primary, self::SQL_WILDCARD, $schema); + $fields = $this->getPart(Zend_Db_Table_Select::COLUMNS); + } + + $from = $this->getPart(Zend_Db_Table_Select::FROM); + + if ($this->_integrityCheck !== false) { + foreach ($fields as $columnEntry) { + list($table, $column) = $columnEntry; + + // Check each column to ensure it only references the primary table + if ($column) { + if (!isset($from[$table]) || $from[$table]['tableName'] != $primary) { + require_once 'Zend/Db/Table/Select/Exception.php'; + throw new Zend_Db_Table_Select_Exception('Select query cannot join with another table'); + } + } + } + } + } + + return parent::assemble(); + } +} \ No newline at end of file diff --git a/library/Zend/Db/Table/Select/Exception.php b/library/Zend/Db/Table/Select/Exception.php new file mode 100644 index 000000000..c93c86131 --- /dev/null +++ b/library/Zend/Db/Table/Select/Exception.php @@ -0,0 +1,39 @@ + tags, cleans up newlines and indents, and runs + * htmlentities() before output. + * + * @param mixed $var The variable to dump. + * @param string $label OPTIONAL Label to prepend to output. + * @param bool $echo OPTIONAL Echo output if true. + * @return string + */ + public static function dump($var, $label=null, $echo=true) + { + // format the label + $label = ($label===null) ? '' : rtrim($label) . ' '; + + // var_dump the variable into a buffer and keep the output + ob_start(); + var_dump($var); + $output = ob_get_clean(); + + // neaten the newlines and indents + $output = preg_replace("/\]\=\>\n(\s+)/m", "] => ", $output); + if (self::getSapi() == 'cli') { + $output = PHP_EOL . $label + . PHP_EOL . $output + . PHP_EOL; + } else { + if(!extension_loaded('xdebug')) { + $output = htmlspecialchars($output, ENT_QUOTES); + } + + $output = '
    '
    +                    . $label
    +                    . $output
    +                    . '
    '; + } + + if ($echo) { + echo($output); + } + return $output; + } + +} diff --git a/library/Zend/Dojo.php b/library/Zend/Dojo.php new file mode 100644 index 000000000..b3745cb77 --- /dev/null +++ b/library/Zend/Dojo.php @@ -0,0 +1,87 @@ +addPrefixPath('Zend_Dojo_Form_Decorator', 'Zend/Dojo/Form/Decorator', 'decorator') + ->addPrefixPath('Zend_Dojo_Form_Element', 'Zend/Dojo/Form/Element', 'element') + ->addElementPrefixPath('Zend_Dojo_Form_Decorator', 'Zend/Dojo/Form/Decorator', 'decorator') + ->addDisplayGroupPrefixPath('Zend_Dojo_Form_Decorator', 'Zend/Dojo/Form/Decorator') + ->setDefaultDisplayGroupClass('Zend_Dojo_Form_DisplayGroup'); + + foreach ($form->getSubForms() as $subForm) { + self::enableForm($subForm); + } + + if (null !== ($view = $form->getView())) { + self::enableView($view); + } + } + + /** + * Dojo-enable a view instance + * + * @param Zend_View_Interface $view + * @return void + */ + public static function enableView(Zend_View_Interface $view) + { + if (false === $view->getPluginLoader('helper')->getPaths('Zend_Dojo_View_Helper')) { + $view->addHelperPath('Zend/Dojo/View/Helper', 'Zend_Dojo_View_Helper'); + } + } +} + diff --git a/library/Zend/Dojo/BuildLayer.php b/library/Zend/Dojo/BuildLayer.php new file mode 100644 index 000000000..2fb0cd513 --- /dev/null +++ b/library/Zend/Dojo/BuildLayer.php @@ -0,0 +1,536 @@ + 'release', + 'optimize' => 'shrinksafe', + 'layerOptimize' => 'shrinksafe', + 'copyTests' => false, + 'loader' => 'default', + 'cssOptimize' => 'comments', + ); + + /** + * Associative array of module/path pairs for the build profile + * @var array + */ + protected $_profilePrefixes = array(); + + /** + * Zend_View reference + * @var Zend_View_Interface + */ + protected $_view; + + /** + * Constructor + * + * @param array|Zend_Config $options + * @return void + * @throws Zend_Dojo_Exception for invalid option argument + */ + public function __construct($options = null) + { + if (null !== $options) { + if ($options instanceof Zend_Config) { + $options = $options->toArray(); + } elseif (!is_array($options)) { + require_once 'Zend/Dojo/Exception.php'; + throw new Zend_Dojo_Exception('Invalid options provided to constructor'); + } + $this->setOptions($options); + } + } + + /** + * Set options + * + * Proxies to any setter that matches an option key. + * + * @param array $options + * @return Zend_Dojo_BuildLayer + */ + public function setOptions(array $options) + { + $methods = get_class_methods($this); + foreach ($options as $key => $value) { + $method = 'set' . ucfirst($key); + if (in_array($method, $methods)) { + $this->$method($value); + } + } + return $this; + } + + /** + * Set View object + * + * @param Zend_View_Interface $view + * @return Zend_Dojo_BuildLayer + */ + public function setView(Zend_View_Interface $view) + { + $this->_view = $view; + return $this; + } + + /** + * Retrieve view object + * + * @return Zend_View_Interface|null + */ + public function getView() + { + return $this->_view; + } + + /** + * Set dojo() view helper instance + * + * @param Zend_Dojo_View_Helper_Dojo_Container $helper + * @return Zend_Dojo_BuildLayer + */ + public function setDojoHelper(Zend_Dojo_View_Helper_Dojo_Container $helper) + { + $this->_dojo = $helper; + return $this; + } + + /** + * Retrieve dojo() view helper instance + * + * Will retrieve it from the view object if not registered. + * + * @return Zend_Dojo_View_Helper_Dojo_Container + * @throws Zend_Dojo_Exception if not registered and no view object found + */ + public function getDojoHelper() + { + if (null === $this->_dojo) { + if (null === ($view = $this->getView())) { + require_once 'Zend/Dojo/Exception.php'; + throw new Zend_Dojo_Exception('View object not registered; cannot retrieve dojo helper'); + } + $helper = $view->getHelper('dojo'); + $this->setDojoHelper($view->dojo()); + } + return $this->_dojo; + } + + /** + * Set custom layer name; e.g. "custom.main" + * + * @param string $name + * @return Zend_Dojo_BuildLayer + */ + public function setLayerName($name) + { + if (!preg_match('/^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)+$/i', $name)) { + require_once 'Zend/Dojo/Exception.php'; + throw new Zend_Dojo_Exception('Invalid layer name provided; must be of form[a-z][a-z0-9_](\.[a-z][a-z0-9_])+'); + } + $this->_layerName = $name; + return $this; + } + + /** + * Retrieve custom layer name + * + * @return string|null + */ + public function getLayerName() + { + return $this->_layerName; + } + + /** + * Set the path to the custom layer script + * + * Should be a path relative to dojo.js + * + * @param string $path + * @return Zend_Dojo_BuildLayer + */ + public function setLayerScriptPath($path) + { + $this->_layerScriptPath = (string) $path; + return $this; + } + + /** + * Get custom layer script path + * + * @return string|null + */ + public function getLayerScriptPath() + { + return $this->_layerScriptPath; + } + + /** + * Set flag indicating whether or not to consume JS aggregated in dojo() + * view helper + * + * @param bool $flag + * @return Zend_Dojo_BuildLayer + */ + public function setConsumeJavascript($flag) + { + $this->_consumeJavascript = (bool) $flag; + return $this; + } + + /** + * Get flag indicating whether or not to consume JS aggregated in dojo() + * view helper + * + * @return bool + */ + public function consumeJavascript() + { + return $this->_consumeJavascript; + } + + /** + * Set flag indicating whether or not to consume dojo.addOnLoad events + * aggregated in dojo() view helper + * + * @param bool $flag + * @return Zend_Dojo_BuildLayer + */ + public function setConsumeOnLoad($flag) + { + $this->_consumeOnLoad = (bool) $flag; + return $this; + } + + /** + * Get flag indicating whether or not to consume dojo.addOnLoad events aggregated in dojo() view helper + * + * @return bool + */ + public function consumeOnLoad() + { + return $this->_consumeOnLoad; + } + + /** + * Set many build profile options at once + * + * @param array $options + * @return Zend_Dojo_BuildLayer + */ + public function setProfileOptions(array $options) + { + $this->_profileOptions += $options; + return $this; + } + + /** + * Add many build profile options at once + * + * @param array $options + * @return Zend_Dojo_BuildLayer + */ + public function addProfileOptions(array $options) + { + $this->_profileOptions = $this->_profileOptions + $options; + return $this; + } + + /** + * Add a single build profile option + * + * @param string $key + * @param value $value + * @return Zend_Dojo_BuildLayer + */ + public function addProfileOption($key, $value) + { + $this->_profileOptions[(string) $key] = $value; + return $this; + } + + /** + * Is a given build profile option set? + * + * @param string $key + * @return bool + */ + public function hasProfileOption($key) + { + return array_key_exists((string) $key, $this->_profileOptions); + } + + /** + * Retrieve a single build profile option + * + * Returns null if profile option does not exist. + * + * @param string $key + * @return mixed + */ + public function getProfileOption($key) + { + if ($this->hasProfileOption($key)) { + return $this->_profileOptions[(string) $key]; + } + return null; + } + + /** + * Get all build profile options + * + * @return array + */ + public function getProfileOptions() + { + return $this->_profileOptions; + } + + /** + * Remove a build profile option + * + * @param string $name + * @return Zend_Dojo_BuildLayer + */ + public function removeProfileOption($name) + { + if ($this->hasProfileOption($name)) { + unset($this->_profileOptions[(string) $name]); + } + return $this; + } + + /** + * Remove all build profile options + * + * @return Zend_Dojo_BuildLayer + */ + public function clearProfileOptions() + { + $this->_profileOptions = array(); + return $this; + } + + /** + * Add a build profile dependency prefix + * + * If just the prefix is passed, sets path to "../$prefix". + * + * @param string $prefix + * @param null|string $path + * @return Zend_Dojo_BuildLayer + */ + public function addProfilePrefix($prefix, $path = null) + { + if (null === $path) { + $path = '../' . $prefix; + } + $this->_profilePrefixes[$prefix] = array($prefix, $path); + return $this; + } + + /** + * Set multiple dependency prefixes for bulid profile + * + * @param array $prefixes + * @return Zend_Dojo_BuildLayer + */ + public function setProfilePrefixes(array $prefixes) + { + foreach ($prefixes as $prefix => $path) { + $this->addProfilePrefix($prefix, $path); + } + return $this; + } + + /** + * Get build profile dependency prefixes + * + * @return array + */ + public function getProfilePrefixes() + { + $layerName = $this->getLayerName(); + if (null !== $layerName) { + $prefix = $this->_getPrefix($layerName); + if (!array_key_exists($prefix, $this->_profilePrefixes)) { + $this->addProfilePrefix($prefix); + } + } + $view = $this->getView(); + if (!empty($view)) { + $helper = $this->getDojoHelper(); + if ($helper) { + $modules = $helper->getModules(); + foreach ($modules as $module) { + $prefix = $this->_getPrefix($module); + if (!array_key_exists($prefix, $this->_profilePrefixes)) { + $this->addProfilePrefix($prefix); + } + } + } + } + return $this->_profilePrefixes; + } + + /** + * Generate module layer script + * + * @return string + */ + public function generateLayerScript() + { + $helper = $this->getDojoHelper(); + $layerName = $this->getLayerName(); + $modulePaths = $helper->getModulePaths(); + $modules = $helper->getModules(); + $onLoadActions = $helper->getOnLoadActions(); + $javascript = $helper->getJavascript(); + + $content = 'dojo.provide("' . $layerName . '");' . "\n\n(function(){\n"; + + foreach ($modulePaths as $module => $path) { + $content .= sprintf("dojo.registerModulePath(\"%s\", \"%s\");\n", $module, $path); + } + foreach ($modules as $module) { + $content .= sprintf("dojo.require(\"%s\");\n", $module); + } + + if ($this->consumeOnLoad()) { + foreach ($helper->getOnLoadActions() as $callback) { + $content .= sprintf("dojo.addOnLoad(%s);\n", $callback); + } + } + if ($this->consumeJavascript()) { + $javascript = implode("\n", $helper->getJavascript()); + if (!empty($javascript)) { + $content .= "\n" . $javascript . "\n"; + } + } + + $content .= "})();"; + + return $content; + } + + /** + * Generate build profile + * + * @return string + */ + public function generateBuildProfile() + { + $profileOptions = $this->getProfileOptions(); + $layerName = $this->getLayerName(); + $layerScriptPath = $this->getLayerScriptPath(); + $profilePrefixes = $this->getProfilePrefixes(); + + if (!array_key_exists('releaseName', $profileOptions)) { + $profileOptions['releaseName'] = substr($layerName, 0, strpos($layerName, '.')); + } + + $profile = $profileOptions; + $profile['layers'] = array(array( + 'name' => $layerScriptPath, + 'layerDependencies' => array(), + 'dependencies' => array($layerName), + )); + $profile['prefixes'] = array_values($profilePrefixes); + + return 'dependencies = ' . $this->_filterJsonProfileToJavascript($profile) . ';'; + } + + /** + * Retrieve module prefix + * + * @param string $module + * @return void + */ + protected function _getPrefix($module) + { + $segments = explode('.', $module, 2); + return $segments[0]; + } + + /** + * Filter a JSON build profile to JavaScript + * + * @param string $profile + * @return string + */ + protected function _filterJsonProfileToJavascript($profile) + { + require_once 'Zend/Json.php'; + $profile = Zend_Json::encode($profile); + $profile = preg_replace('/"([^"]*)":/', '$1:', $profile); + $profile = preg_replace('/' . preg_quote('\\') . '/', '', $profile); + return $profile; + } +} diff --git a/library/Zend/Dojo/Data.php b/library/Zend/Dojo/Data.php new file mode 100644 index 000000000..a90d329b5 --- /dev/null +++ b/library/Zend/Dojo/Data.php @@ -0,0 +1,563 @@ +setIdentifier($identifier); + } + if (null !== $items) { + $this->setItems($items); + } + if (null !== $label) { + $this->setLabel($label); + } + } + + /** + * Set the items to collect + * + * @param array|Traversable $items + * @return Zend_Dojo_Data + */ + public function setItems($items) + { + $this->clearItems(); + return $this->addItems($items); + } + + /** + * Set an individual item, optionally by identifier (overwrites) + * + * @param array|object $item + * @param string|null $identifier + * @return Zend_Dojo_Data + */ + public function setItem($item, $id = null) + { + $item = $this->_normalizeItem($item, $id); + $this->_items[$item['id']] = $item['data']; + return $this; + } + + /** + * Add an individual item, optionally by identifier + * + * @param array|object $item + * @param string|null $id + * @return Zend_Dojo_Data + */ + public function addItem($item, $id = null) + { + $item = $this->_normalizeItem($item, $id); + + if ($this->hasItem($item['id'])) { + require_once 'Zend/Dojo/Exception.php'; + throw new Zend_Dojo_Exception('Overwriting items using addItem() is not allowed'); + } + + $this->_items[$item['id']] = $item['data']; + + return $this; + } + + /** + * Add multiple items at once + * + * @param array|Traversable $items + * @return Zend_Dojo_Data + */ + public function addItems($items) + { + if (!is_array($items) && (!is_object($items) || !($items instanceof Traversable))) { + require_once 'Zend/Dojo/Exception.php'; + throw new Zend_Dojo_Exception('Only arrays and Traversable objects may be added to ' . __CLASS__); + } + + foreach ($items as $item) { + $this->addItem($item); + } + + return $this; + } + + /** + * Get all items as an array + * + * Serializes items to arrays. + * + * @return array + */ + public function getItems() + { + return $this->_items; + } + + /** + * Does an item with the given identifier exist? + * + * @param string|int $id + * @return bool + */ + public function hasItem($id) + { + return array_key_exists($id, $this->_items); + } + + /** + * Retrieve an item by identifier + * + * Item retrieved will be flattened to an array. + * + * @param string $id + * @return array + */ + public function getItem($id) + { + if (!$this->hasItem($id)) { + return null; + } + + return $this->_items[$id]; + } + + /** + * Remove item by identifier + * + * @param string $id + * @return Zend_Dojo_Data + */ + public function removeItem($id) + { + if ($this->hasItem($id)) { + unset($this->_items[$id]); + } + + return $this; + } + + /** + * Remove all items at once + * + * @return Zend_Dojo_Data + */ + public function clearItems() + { + $this->_items = array(); + return $this; + } + + + /** + * Set identifier for item lookups + * + * @param string|int|null $identifier + * @return Zend_Dojo_Data + */ + public function setIdentifier($identifier) + { + if (null === $identifier) { + $this->_identifier = null; + } elseif (is_string($identifier)) { + $this->_identifier = $identifier; + } elseif (is_numeric($identifier)) { + $this->_identifier = (int) $identifier; + } else { + require_once 'Zend/Dojo/Exception.php'; + throw new Zend_Dojo_Exception('Invalid identifier; please use a string or integer'); + } + + return $this; + } + + /** + * Retrieve current item identifier + * + * @return string|int|null + */ + public function getIdentifier() + { + return $this->_identifier; + } + + + /** + * Set label to use for displaying item associations + * + * @param string|null $label + * @return Zend_Dojo_Data + */ + public function setLabel($label) + { + if (null === $label) { + $this->_label = null; + } else { + $this->_label = (string) $label; + } + return $this; + } + + /** + * Retrieve item association label + * + * @return string|null + */ + public function getLabel() + { + return $this->_label; + } + + /** + * Set metadata by key or en masse + * + * @param string|array $spec + * @param mixed $value + * @return Zend_Dojo_Data + */ + public function setMetadata($spec, $value = null) + { + if (is_string($spec) && (null !== $value)) { + $this->_metadata[$spec] = $value; + } elseif (is_array($spec)) { + foreach ($spec as $key => $value) { + $this->setMetadata($key, $value); + } + } + return $this; + } + + /** + * Get metadata item or all metadata + * + * @param null|string $key Metadata key when pulling single metadata item + * @return mixed + */ + public function getMetadata($key = null) + { + if (null === $key) { + return $this->_metadata; + } + + if (array_key_exists($key, $this->_metadata)) { + return $this->_metadata[$key]; + } + + return null; + } + + /** + * Clear individual or all metadata item(s) + * + * @param null|string $key + * @return Zend_Dojo_Data + */ + public function clearMetadata($key = null) + { + if (null === $key) { + $this->_metadata = array(); + } elseif (array_key_exists($key, $this->_metadata)) { + unset($this->_metadata[$key]); + } + return $this; + } + + /** + * Load object from array + * + * @param array $data + * @return Zend_Dojo_Data + */ + public function fromArray(array $data) + { + if (array_key_exists('identifier', $data)) { + $this->setIdentifier($data['identifier']); + } + if (array_key_exists('label', $data)) { + $this->setLabel($data['label']); + } + if (array_key_exists('items', $data) && is_array($data['items'])) { + $this->setItems($data['items']); + } else { + $this->clearItems(); + } + return $this; + } + + /** + * Load object from JSON + * + * @param string $json + * @return Zend_Dojo_Data + */ + public function fromJson($json) + { + if (!is_string($json)) { + require_once 'Zend/Dojo/Exception.php'; + throw new Zend_Dojo_Exception('fromJson() expects JSON input'); + } + require_once 'Zend/Json.php'; + $data = Zend_Json::decode($json); + return $this->fromArray($data); + } + + /** + * Seralize entire data structure, including identifier and label, to array + * + * @return array + */ + public function toArray() + { + if (null === ($identifier = $this->getIdentifier())) { + require_once 'Zend/Dojo/Exception.php'; + throw new Zend_Dojo_Exception('Serialization requires that an identifier be present in the object; first call setIdentifier()'); + } + + $array = array( + 'identifier' => $identifier, + 'items' => array_values($this->getItems()), + ); + + $metadata = $this->getMetadata(); + if (!empty($metadata)) { + foreach ($metadata as $key => $value) { + $array[$key] = $value; + } + } + + if (null !== ($label = $this->getLabel())) { + $array['label'] = $label; + } + + return $array; + } + + /** + * Serialize to JSON (dojo.data format) + * + * @return string + */ + public function toJson() + { + require_once 'Zend/Json.php'; + return Zend_Json::encode($this->toArray()); + } + + /** + * Serialize to string (proxy to {@link toJson()}) + * + * @return string + */ + public function __toString() + { + return $this->toJson(); + } + + /** + * ArrayAccess: does offset exist? + * + * @param string|int $offset + * @return bool + */ + public function offsetExists($offset) + { + return (null !== $this->getItem($offset)); + } + + /** + * ArrayAccess: retrieve by offset + * + * @param string|int $offset + * @return array + */ + public function offsetGet($offset) + { + return $this->getItem($offset); + } + + /** + * ArrayAccess: set value by offset + * + * @param string $offset + * @param array|object|null $value + * @return void + */ + public function offsetSet($offset, $value) + { + $this->setItem($value, $offset); + } + + /** + * ArrayAccess: unset value by offset + * + * @param string $offset + * @return void + */ + public function offsetUnset($offset) + { + $this->removeItem($offset); + } + + /** + * Iterator: get current value + * + * @return array + */ + public function current() + { + return current($this->_items); + } + + /** + * Iterator: get current key + * + * @return string|int + */ + public function key() + { + return key($this->_items); + } + + /** + * Iterator: get next item + * + * @return void + */ + public function next() + { + return next($this->_items); + } + + /** + * Iterator: rewind to first value in collection + * + * @return void + */ + public function rewind() + { + return reset($this->_items); + } + + /** + * Iterator: is item valid? + * + * @return bool + */ + public function valid() + { + return (bool) $this->current(); + } + + /** + * Countable: how many items are present + * + * @return int + */ + public function count() + { + return count($this->_items); + } + + /** + * Normalize an item to attach to the collection + * + * @param array|object $item + * @param string|int|null $id + * @return array + */ + protected function _normalizeItem($item, $id) + { + if (null === ($identifier = $this->getIdentifier())) { + require_once 'Zend/Dojo/Exception.php'; + throw new Zend_Dojo_Exception('You must set an identifier prior to adding items'); + } + + if (!is_object($item) && !is_array($item)) { + require_once 'Zend/Dojo/Exception.php'; + throw new Zend_Dojo_Exception('Only arrays and objects may be attached'); + } + + if (is_object($item)) { + if (method_exists($item, 'toArray')) { + $item = $item->toArray(); + } else { + $item = get_object_vars($item); + } + } + + if ((null === $id) && !array_key_exists($identifier, $item)) { + require_once 'Zend/Dojo/Exception.php'; + throw new Zend_Dojo_Exception('Item must contain a column matching the currently set identifier'); + } elseif (null === $id) { + $id = $item[$identifier]; + } else { + $item[$identifier] = $id; + } + + return array( + 'id' => $id, + 'data' => $item, + ); + } +} diff --git a/library/Zend/Dojo/Exception.php b/library/Zend/Dojo/Exception.php new file mode 100644 index 000000000..a057d5e9d --- /dev/null +++ b/library/Zend/Dojo/Exception.php @@ -0,0 +1,35 @@ +addPrefixPath('Zend_Dojo_Form_Decorator', 'Zend/Dojo/Form/Decorator', 'decorator') + ->addPrefixPath('Zend_Dojo_Form_Element', 'Zend/Dojo/Form/Element', 'element') + ->addElementPrefixPath('Zend_Dojo_Form_Decorator', 'Zend/Dojo/Form/Decorator', 'decorator') + ->addDisplayGroupPrefixPath('Zend_Dojo_Form_Decorator', 'Zend/Dojo/Form/Decorator') + ->setDefaultDisplayGroupClass('Zend_Dojo_Form_DisplayGroup'); + parent::__construct($options); + } + + /** + * Load the default decorators + * + * @return void + */ + public function loadDefaultDecorators() + { + if ($this->loadDefaultDecoratorsIsDisabled()) { + return; + } + + $decorators = $this->getDecorators(); + if (empty($decorators)) { + $this->addDecorator('FormElements') + ->addDecorator('HtmlTag', array('tag' => 'dl', 'class' => 'zend_form_dojo')) + ->addDecorator('DijitForm'); + } + } + + /** + * Set the view object + * + * Ensures that the view object has the dojo view helper path set. + * + * @param Zend_View_Interface $view + * @return Zend_Dojo_Form_Element_Dijit + */ + public function setView(Zend_View_Interface $view = null) + { + if (null !== $view) { + if (false === $view->getPluginLoader('helper')->getPaths('Zend_Dojo_View_Helper')) { + $view->addHelperPath('Zend/Dojo/View/Helper', 'Zend_Dojo_View_Helper'); + } + } + return parent::setView($view); + } +} diff --git a/library/Zend/Dojo/Form/Decorator/AccordionContainer.php b/library/Zend/Dojo/Form/Decorator/AccordionContainer.php new file mode 100644 index 000000000..4740d7030 --- /dev/null +++ b/library/Zend/Dojo/Form/Decorator/AccordionContainer.php @@ -0,0 +1,43 @@ +_helper) { + require_once 'Zend/Form/Decorator/Exception.php'; + throw new Zend_Form_Decorator_Exception('No view helper specified fo DijitContainer decorator'); + } + return $this->_helper; + } + + /** + * Get element attributes + * + * @return array + */ + public function getAttribs() + { + if (null === $this->_attribs) { + $attribs = $this->getElement()->getAttribs(); + if (array_key_exists('dijitParams', $attribs)) { + unset($attribs['dijitParams']); + } + $this->_attribs = $attribs; + } + return $this->_attribs; + } + + /** + * Get dijit option parameters + * + * @return array + */ + public function getDijitParams() + { + if (null === $this->_dijitParams) { + $attribs = $this->getElement()->getAttribs(); + if (array_key_exists('dijitParams', $attribs)) { + $this->_dijitParams = $attribs['dijitParams']; + } else { + $this->_dijitParams = array(); + } + + $options = $this->getOptions(); + if (array_key_exists('dijitParams', $options)) { + $this->_dijitParams = array_merge($this->_dijitParams, $options['dijitParams']); + $this->removeOption('dijitParams'); + } + } + + // Ensure we have a title param + if (!array_key_exists('title', $this->_dijitParams)) { + $this->_dijitParams['title'] = $this->getTitle(); + } + + return $this->_dijitParams; + } + + /** + * Get title + * + * @return string + */ + public function getTitle() + { + if (null === $this->_title) { + $title = null; + if (null !== ($element = $this->getElement())) { + if (method_exists($element, 'getLegend')) { + $title = $element->getLegend(); + } + } + if (empty($title) && (null !== ($title = $this->getOption('legend')))) { + $this->removeOption('legend'); + } + if (empty($title) && (null !== ($title = $this->getOption('title')))) { + $this->removeOption('title'); + } + + if (!empty($title)) { + if (null !== ($translator = $element->getTranslator())) { + $title = $translator->translate($title); + } + $this->_title = $title; + } + } + + return (empty($this->_title) ? '' : $this->_title); + } + + /** + * Render a dijit layout container + * + * Replaces $content entirely from currently set element. + * + * @param string $content + * @return string + */ + public function render($content) + { + $element = $this->getElement(); + $view = $element->getView(); + if (null === $view) { + return $content; + } + + $dijitParams = $this->getDijitParams(); + $attribs = array_merge($this->getAttribs(), $this->getOptions()); + + if (array_key_exists('legend', $attribs)) { + if (!array_key_exists('title', $dijitParams) || empty($dijitParams['title'])) { + $dijitParams['title'] = $attribs['legend']; + } + unset($attribs['legend']); + } + + $helper = $this->getHelper(); + $id = $element->getId() . '-' . $helper; + + if ($view->dojo()->hasDijit($id)) { + trigger_error(sprintf('Duplicate dijit ID detected for id "%s; temporarily generating uniqid"', $id), E_USER_WARNING); + $base = $id; + do { + $id = $base . '-' . uniqid(); + } while ($view->dojo()->hasDijit($id)); + } + + return $view->$helper($id, $content, $dijitParams, $attribs); + } +} diff --git a/library/Zend/Dojo/Form/Decorator/DijitElement.php b/library/Zend/Dojo/Form/Decorator/DijitElement.php new file mode 100644 index 000000000..9f6e06180 --- /dev/null +++ b/library/Zend/Dojo/Form/Decorator/DijitElement.php @@ -0,0 +1,193 @@ +_attribs) { + $this->_attribs = parent::getElementAttribs(); + if (array_key_exists('dijitParams', $this->_attribs)) { + $this->setDijitParams($this->_attribs['dijitParams']); + unset($this->_attribs['dijitParams']); + } + } + + return $this->_attribs; + } + + /** + * Set a single dijit option parameter + * + * @param string $key + * @param mixed $value + * @return Zend_Dojo_Form_Decorator_DijitContainer + */ + public function setDijitParam($key, $value) + { + $this->_dijitParams[(string) $key] = $value; + return $this; + } + + /** + * Set dijit option parameters + * + * @param array $params + * @return Zend_Dojo_Form_Decorator_DijitContainer + */ + public function setDijitParams(array $params) + { + $this->_dijitParams = array_merge($this->_dijitParams, $params); + return $this; + } + + /** + * Retrieve a single dijit option parameter + * + * @param string $key + * @return mixed|null + */ + public function getDijitParam($key) + { + $this->getElementAttribs(); + $key = (string) $key; + if (array_key_exists($key, $this->_dijitParams)) { + return $this->_dijitParams[$key]; + } + + return null; + } + + /** + * Get dijit option parameters + * + * @return array + */ + public function getDijitParams() + { + $this->getElementAttribs(); + return $this->_dijitParams; + } + + /** + * Render an element using a view helper + * + * Determine view helper from 'helper' option, or, if none set, from + * the element type. Then call as + * helper($element->getName(), $element->getValue(), $element->getAttribs()) + * + * @param string $content + * @return string + * @throws Zend_Form_Decorator_Exception if element or view are not registered + */ + public function render($content) + { + $element = $this->getElement(); + $view = $element->getView(); + if (null === $view) { + require_once 'Zend/Form/Decorator/Exception.php'; + throw new Zend_Form_Decorator_Exception('DijitElement decorator cannot render without a registered view object'); + } + + $options = null; + $helper = $this->getHelper(); + $separator = $this->getSeparator(); + $value = $this->getValue($element); + $attribs = $this->getElementAttribs(); + $name = $element->getFullyQualifiedName(); + + $dijitParams = $this->getDijitParams(); + $dijitParams['required'] = $element->isRequired(); + + $id = $element->getId(); + if ($view->dojo()->hasDijit($id)) { + trigger_error(sprintf('Duplicate dijit ID detected for id "%s; temporarily generating uniqid"', $id), E_USER_NOTICE); + $base = $id; + do { + $id = $base . '-' . uniqid(); + } while ($view->dojo()->hasDijit($id)); + } + $attribs['id'] = $id; + + if (array_key_exists('options', $attribs)) { + $options = $attribs['options']; + } + + $elementContent = $view->$helper($name, $value, $dijitParams, $attribs, $options); + switch ($this->getPlacement()) { + case self::APPEND: + return $content . $separator . $elementContent; + case self::PREPEND: + return $elementContent . $separator . $content; + default: + return $elementContent; + } + } +} diff --git a/library/Zend/Dojo/Form/Decorator/DijitForm.php b/library/Zend/Dojo/Form/Decorator/DijitForm.php new file mode 100644 index 000000000..83c1c3f32 --- /dev/null +++ b/library/Zend/Dojo/Form/Decorator/DijitForm.php @@ -0,0 +1,61 @@ +getElement(); + $view = $element->getView(); + if (null === $view) { + return $content; + } + + $dijitParams = $this->getDijitParams(); + $attribs = array_merge($this->getAttribs(), $this->getOptions()); + + return $view->form($element->getName(), $attribs, $content); + } +} diff --git a/library/Zend/Dojo/Form/Decorator/SplitContainer.php b/library/Zend/Dojo/Form/Decorator/SplitContainer.php new file mode 100644 index 000000000..db7de0bd9 --- /dev/null +++ b/library/Zend/Dojo/Form/Decorator/SplitContainer.php @@ -0,0 +1,43 @@ +addPrefixPath('Zend_Dojo_Form_Decorator', 'Zend/Dojo/Form/Decorator'); + } + + /** + * Set the view object + * + * Ensures that the view object has the dojo view helper path set. + * + * @param Zend_View_Interface $view + * @return Zend_Dojo_Form_Element_Dijit + */ + public function setView(Zend_View_Interface $view = null) + { + if (null !== $view) { + if (false === $view->getPluginLoader('helper')->getPaths('Zend_Dojo_View_Helper')) { + $view->addHelperPath('Zend/Dojo/View/Helper', 'Zend_Dojo_View_Helper'); + } + } + return parent::setView($view); + } +} diff --git a/library/Zend/Dojo/Form/Element/Button.php b/library/Zend/Dojo/Form/Element/Button.php new file mode 100644 index 000000000..f22e6fd9f --- /dev/null +++ b/library/Zend/Dojo/Form/Element/Button.php @@ -0,0 +1,121 @@ + $options); + } + + parent::__construct($spec, $options); + } + + /** + * Return label + * + * If no label is present, returns the currently set name. + * + * If a translator is present, returns the translated label. + * + * @return string + */ + public function getLabel() + { + $value = parent::getLabel(); + + if (null === $value) { + $value = $this->getName(); + } + + if (null !== ($translator = $this->getTranslator())) { + return $translator->translate($value); + } + + return $value; + } + + /** + * Has this submit button been selected? + * + * @return bool + */ + public function isChecked() + { + $value = $this->getValue(); + + if (empty($value)) { + return false; + } + if ($value != $this->getLabel()) { + return false; + } + + return true; + } + + /** + * Default decorators + * + * Uses only 'DijitElement' and 'DtDdWrapper' decorators by default. + * + * @return void + */ + public function loadDefaultDecorators() + { + if ($this->loadDefaultDecoratorsIsDisabled()) { + return; + } + + $decorators = $this->getDecorators(); + if (empty($decorators)) { + $this->addDecorator('DijitElement') + ->addDecorator('DtDdWrapper'); + } + } +} diff --git a/library/Zend/Dojo/Form/Element/CheckBox.php b/library/Zend/Dojo/Form/Element/CheckBox.php new file mode 100644 index 000000000..7a259c78e --- /dev/null +++ b/library/Zend/Dojo/Form/Element/CheckBox.php @@ -0,0 +1,206 @@ + '1', + 'uncheckedValue' => '0', + ); + + /** + * Value when checked + * @var string + */ + protected $_checkedValue = '1'; + + /** + * Value when not checked + * @var string + */ + protected $_uncheckedValue = '0'; + + /** + * Current value + * @var string 0 or 1 + */ + protected $_value = '0'; + + /** + * Set options + * + * Intercept checked and unchecked values and set them early; test stored + * value against checked and unchecked values after configuration. + * + * @param array $options + * @return Zend_Form_Element_Checkbox + */ + public function setOptions(array $options) + { + if (array_key_exists('checkedValue', $options)) { + $this->setCheckedValue($options['checkedValue']); + unset($options['checkedValue']); + } + if (array_key_exists('uncheckedValue', $options)) { + $this->setUncheckedValue($options['uncheckedValue']); + unset($options['uncheckedValue']); + } + parent::setOptions($options); + + $curValue = $this->getValue(); + $test = array($this->getCheckedValue(), $this->getUncheckedValue()); + if (!in_array($curValue, $test)) { + $this->setValue($curValue); + } + + return $this; + } + + /** + * Set value + * + * If value matches checked value, sets to that value, and sets the checked + * flag to true. + * + * Any other value causes the unchecked value to be set as the current + * value, and the checked flag to be set as false. + * + * + * @param mixed $value + * @return Zend_Form_Element_Checkbox + */ + public function setValue($value) + { + if ($value == $this->getCheckedValue()) { + parent::setValue($value); + $this->checked = true; + } else { + parent::setValue($this->getUncheckedValue()); + $this->checked = false; + } + return $this; + } + + /** + * Set checked value + * + * @param string $value + * @return Zend_Form_Element_Checkbox + */ + public function setCheckedValue($value) + { + $this->_checkedValue = (string) $value; + $this->options['checkedValue'] = $value; + return $this; + } + + /** + * Get value when checked + * + * @return string + */ + public function getCheckedValue() + { + return $this->_checkedValue; + } + + /** + * Set unchecked value + * + * @param string $value + * @return Zend_Form_Element_Checkbox + */ + public function setUncheckedValue($value) + { + $this->_uncheckedValue = (string) $value; + $this->options['uncheckedValue'] = $value; + return $this; + } + + /** + * Get value when not checked + * + * @return string + */ + public function getUncheckedValue() + { + return $this->_uncheckedValue; + } + + /** + * Set checked flag + * + * @param bool $flag + * @return Zend_Form_Element_Checkbox + */ + public function setChecked($flag) + { + $this->checked = (bool) $flag; + if ($this->checked) { + $this->setValue($this->getCheckedValue()); + } else { + $this->setValue($this->getUncheckedValue()); + } + return $this; + } + + /** + * Get checked flag + * + * @return bool + */ + public function isChecked() + { + return $this->checked; + } +} + diff --git a/library/Zend/Dojo/Form/Element/ComboBox.php b/library/Zend/Dojo/Form/Element/ComboBox.php new file mode 100644 index 000000000..50ef006d8 --- /dev/null +++ b/library/Zend/Dojo/Form/Element/ComboBox.php @@ -0,0 +1,186 @@ +hasDijitParam('store')) { + $this->dijitParams['store'] = array(); + } + return $this->dijitParams['store']; + } + + /** + * Set datastore identifier + * + * @param string $identifier + * @return Zend_Dojo_Form_Element_ComboBox + */ + public function setStoreId($identifier) + { + $store = $this->getStoreInfo(); + $store['store'] = (string) $identifier; + $this->setDijitParam('store', $store); + return $this; + } + + /** + * Get datastore identifier + * + * @return string|null + */ + public function getStoreId() + { + $store = $this->getStoreInfo(); + if (array_key_exists('store', $store)) { + return $store['store']; + } + return null; + } + + /** + * Set datastore dijit type + * + * @param string $dojoType + * @return Zend_Dojo_Form_Element_ComboBox + */ + public function setStoreType($dojoType) + { + $store = $this->getStoreInfo(); + $store['type'] = (string) $dojoType; + $this->setDijitParam('store', $store); + return $this; + } + + /** + * Get datastore dijit type + * + * @return string|null + */ + public function getStoreType() + { + $store = $this->getStoreInfo(); + if (array_key_exists('type', $store)) { + return $store['type']; + } + return null; + } + + /** + * Set datastore parameters + * + * @param array $params + * @return Zend_Dojo_Form_Element_ComboBox + */ + public function setStoreParams(array $params) + { + $store = $this->getStoreInfo(); + $store['params'] = $params; + $this->setDijitParam('store', $store); + return $this; + } + + /** + * Get datastore params + * + * @return array + */ + public function getStoreParams() + { + $store = $this->getStoreInfo(); + if (array_key_exists('params', $store)) { + return $store['params']; + } + return array(); + } + + /** + * Set autocomplete flag + * + * @param bool $flag + * @return Zend_Dojo_Form_Element_ComboBox + */ + public function setAutocomplete($flag) + { + $this->setDijitParam('autocomplete', (bool) $flag); + return $this; + } + + /** + * Get autocomplete flag + * + * @return bool + */ + public function getAutocomplete() + { + if (!$this->hasDijitParam('autocomplete')) { + return false; + } + return $this->getDijitParam('autocomplete'); + } + + /** + * Is the value valid? + * + * @param string $value + * @param mixed $context + * @return bool + */ + public function isValid($value, $context = null) + { + $storeInfo = $this->getStoreInfo(); + if (!empty($storeInfo)) { + $this->setRegisterInArrayValidator(false); + } + return parent::isValid($value, $context); + } +} diff --git a/library/Zend/Dojo/Form/Element/CurrencyTextBox.php b/library/Zend/Dojo/Form/Element/CurrencyTextBox.php new file mode 100644 index 000000000..0ae161f07 --- /dev/null +++ b/library/Zend/Dojo/Form/Element/CurrencyTextBox.php @@ -0,0 +1,120 @@ +setDijitParam('currency', (string) $currency); + return $this; + } + + /** + * Retrieve currency + * + * @return string|null + */ + public function getCurrency() + { + return $this->getDijitParam('currency'); + } + + /** + * Set currency symbol + * + * Casts to string, uppercases, and trims to three characters. + * + * @param string $symbol + * @return Zend_Dojo_Form_Element_CurrencyTextBox + */ + public function setSymbol($symbol) + { + $symbol = strtoupper((string) $symbol); + $length = strlen($symbol); + if (3 > $length) { + require_once 'Zend/Form/Element/Exception.php'; + throw new Zend_Form_Element_Exception('Invalid symbol provided; please provide ISO 4217 alphabetic currency code'); + } + if (3 < $length) { + $symbol = substr($symbol, 0, 3); + } + + $this->setConstraint('symbol', $symbol); + return $this; + } + + /** + * Retrieve symbol + * + * @return string|null + */ + public function getSymbol() + { + return $this->getConstraint('symbol'); + } + + /** + * Set whether currency is fractional + * + * @param bool $flag + * @return Zend_Dojo_Form_Element_CurrencyTextBox + */ + public function setFractional($flag) + { + $this->setConstraint('fractional', (bool) $flag); + return $this; + } + + /** + * Get whether or not to present fractional values + * + * @return bool + */ + public function getFractional() + { + return ('true' == $this->getConstraint('fractional')); + } +} diff --git a/library/Zend/Dojo/Form/Element/DateTextBox.php b/library/Zend/Dojo/Form/Element/DateTextBox.php new file mode 100644 index 000000000..23a3cbab6 --- /dev/null +++ b/library/Zend/Dojo/Form/Element/DateTextBox.php @@ -0,0 +1,214 @@ +setConstraint('am,pm', (bool) $flag); + return $this; + } + + /** + * Retrieve am,pm flag + * + * @return bool + */ + public function getAmPm() + { + if (!$this->hasConstraint('am,pm')) { + return false; + } + return ('true' ==$this->getConstraint('am,pm')); + } + + /** + * Set strict flag + * + * @param bool $strict + * @return Zend_Dojo_Form_Element_DateTextBox + */ + public function setStrict($flag) + { + $this->setConstraint('strict', (bool) $flag); + return $this; + } + + /** + * Retrieve strict flag + * + * @return bool + */ + public function getStrict() + { + if (!$this->hasConstraint('strict')) { + return false; + } + return ('true' == $this->getConstraint('strict')); + } + + /** + * Set locale + * + * @param string $locale + * @return Zend_Dojo_Form_Element_DateTextBox + */ + public function setLocale($locale) + { + $this->setConstraint('locale', (string) $locale); + return $this; + } + + /** + * Retrieve locale + * + * @return string|null + */ + public function getLocale() + { + return $this->getConstraint('locale'); + } + + /** + * Set date format pattern + * + * @param string $pattern + * @return Zend_Dojo_Form_Element_NumberTextBox + */ + public function setDatePattern($pattern) + { + $this->setConstraint('datePattern', (string) $pattern); + return $this; + } + + /** + * Retrieve date format pattern + * + * @return string|null + */ + public function getDatePattern() + { + return $this->getConstraint('datePattern'); + } + + /** + * Set numeric format formatLength + * + * @see $_allowedFormatTypes + * @param string $formatLength + * @return Zend_Dojo_Form_Element_NumberTextBox + */ + public function setFormatLength($formatLength) + { + $formatLength = strtolower($formatLength); + if (!in_array($formatLength, $this->_allowedFormatTypes)) { + require_once 'Zend/Form/Element/Exception.php'; + throw new Zend_Form_Element_Exception(sprintf('Invalid formatLength "%s" specified', $formatLength)); + } + + $this->setConstraint('formatLength', $formatLength); + return $this; + } + + /** + * Retrieve formatLength + * + * @return string|null + */ + public function getFormatLength() + { + return $this->getConstraint('formatLength'); + } + + /** + * Set numeric format Selector + * + * @see $_allowedSelectorTypes + * @param string $selector + * @return Zend_Dojo_Form_Element_NumberTextBox + */ + public function setSelector($selector) + { + $selector = strtolower($selector); + if (!in_array($selector, $this->_allowedSelectorTypes)) { + require_once 'Zend/Form/Element/Exception.php'; + throw new Zend_Form_Element_Exception(sprintf('Invalid Selector "%s" specified', $selector)); + } + + $this->setConstraint('selector', $selector); + return $this; + } + + /** + * Retrieve selector + * + * @return string|null + */ + public function getSelector() + { + return $this->getConstraint('selector'); + } +} diff --git a/library/Zend/Dojo/Form/Element/Dijit.php b/library/Zend/Dojo/Form/Element/Dijit.php new file mode 100644 index 000000000..e8cfe19d8 --- /dev/null +++ b/library/Zend/Dojo/Form/Element/Dijit.php @@ -0,0 +1,189 @@ +addPrefixPath('Zend_Dojo_Form_Decorator', 'Zend/Dojo/Form/Decorator', 'decorator'); + parent::__construct($spec, $options); + } + + /** + * Set a dijit parameter + * + * @param string $key + * @param mixed $value + * @return Zend_Dojo_Form_Element_Dijit + */ + public function setDijitParam($key, $value) + { + $key = (string) $key; + $this->dijitParams[$key] = $value; + return $this; + } + + /** + * Set multiple dijit params at once + * + * @param array $params + * @return Zend_Dojo_Form_Element_Dijit + */ + public function setDijitParams(array $params) + { + $this->dijitParams = array_merge($this->dijitParams, $params); + return $this; + } + + /** + * Does the given dijit parameter exist? + * + * @param string $key + * @return bool + */ + public function hasDijitParam($key) + { + return array_key_exists($key, $this->dijitParams); + } + + /** + * Get a single dijit parameter + * + * @param string $key + * @return mixed + */ + public function getDijitParam($key) + { + $key = (string) $key; + if ($this->hasDijitParam($key)) { + return $this->dijitParams[$key]; + } + return null; + } + + /** + * Retrieve all dijit parameters + * + * @return array + */ + public function getDijitParams() + { + return $this->dijitParams; + } + + /** + * Remove a single dijit parameter + * + * @param string $key + * @return Zend_Dojo_Form_Element_Dijit + */ + public function removeDijitParam($key) + { + $key = (string) $key; + if (array_key_exists($key, $this->dijitParams)) { + unset($this->dijitParams[$key]); + } + return $this; + } + + /** + * Clear all dijit parameters + * + * @return Zend_Dojo_Form_Element_Dijit + */ + public function clearDijitParams() + { + $this->dijitParams = array(); + return $this; + } + + /** + * Load default decorators + * + * @return void + */ + public function loadDefaultDecorators() + { + if ($this->loadDefaultDecoratorsIsDisabled()) { + return; + } + + $decorators = $this->getDecorators(); + if (empty($decorators)) { + $this->addDecorator('DijitElement') + ->addDecorator('Errors') + ->addDecorator('Description', array('tag' => 'p', 'class' => 'description')) + ->addDecorator('HtmlTag', array('tag' => 'dd')) + ->addDecorator('Label', array('tag' => 'dt')); + } + } + + /** + * Set the view object + * + * Ensures that the view object has the dojo view helper path set. + * + * @param Zend_View_Interface $view + * @return Zend_Dojo_Form_Element_Dijit + */ + public function setView(Zend_View_Interface $view = null) + { + if (null !== $view) { + if (false === $view->getPluginLoader('helper')->getPaths('Zend_Dojo_View_Helper')) { + $view->addHelperPath('Zend/Dojo/View/Helper', 'Zend_Dojo_View_Helper'); + } + } + return parent::setView($view); + } +} diff --git a/library/Zend/Dojo/Form/Element/DijitMulti.php b/library/Zend/Dojo/Form/Element/DijitMulti.php new file mode 100644 index 000000000..a8e828d55 --- /dev/null +++ b/library/Zend/Dojo/Form/Element/DijitMulti.php @@ -0,0 +1,304 @@ +'. + * @var string + */ + protected $_separator = '
    '; + + /** + * Which values are translated already? + * @var array + */ + protected $_translated = array(); + + /** + * Retrieve separator + * + * @return mixed + */ + public function getSeparator() + { + return $this->_separator; + } + + /** + * Set separator + * + * @param mixed $separator + * @return self + */ + public function setSeparator($separator) + { + $this->_separator = $separator; + return $this; + } + + /** + * Retrieve options array + * + * @return array + */ + protected function _getMultiOptions() + { + if (null === $this->options || !is_array($this->options)) { + $this->options = array(); + } + + return $this->options; + } + + /** + * Add an option + * + * @param string $option + * @param string $value + * @return Zend_Form_Element_Multi + */ + public function addMultiOption($option, $value = '') + { + $option = (string) $option; + $this->_getMultiOptions(); + if (!$this->_translateOption($option, $value)) { + $this->options[$option] = $value; + } + + return $this; + } + + /** + * Add many options at once + * + * @param array $options + * @return Zend_Form_Element_Multi + */ + public function addMultiOptions(array $options) + { + foreach ($options as $option => $value) { + if (is_array($value) + && array_key_exists('key', $value) + && array_key_exists('value', $value) + ) { + $this->addMultiOption($value['key'], $value['value']); + } else { + $this->addMultiOption($option, $value); + } + } + return $this; + } + + /** + * Set all options at once (overwrites) + * + * @param array $options + * @return Zend_Form_Element_Multi + */ + public function setMultiOptions(array $options) + { + $this->clearMultiOptions(); + return $this->addMultiOptions($options); + } + + /** + * Retrieve single multi option + * + * @param string $option + * @return mixed + */ + public function getMultiOption($option) + { + $option = (string) $option; + $this->_getMultiOptions(); + if (isset($this->options[$option])) { + $this->_translateOption($option, $this->options[$option]); + return $this->options[$option]; + } + + return null; + } + + /** + * Retrieve options + * + * @return array + */ + public function getMultiOptions() + { + $this->_getMultiOptions(); + foreach ($this->options as $option => $value) { + $this->_translateOption($option, $value); + } + return $this->options; + } + + /** + * Remove a single multi option + * + * @param string $option + * @return bool + */ + public function removeMultiOption($option) + { + $option = (string) $option; + $this->_getMultiOptions(); + if (isset($this->options[$option])) { + unset($this->options[$option]); + if (isset($this->_translated[$option])) { + unset($this->_translated[$option]); + } + return true; + } + + return false; + } + + /** + * Clear all options + * + * @return Zend_Form_Element_Multi + */ + public function clearMultiOptions() + { + $this->options = array(); + $this->_translated = array(); + return $this; + } + + /** + * Set flag indicating whether or not to auto-register inArray validator + * + * @param bool $flag + * @return Zend_Form_Element_Multi + */ + public function setRegisterInArrayValidator($flag) + { + $this->_registerInArrayValidator = (bool) $flag; + return $this; + } + + /** + * Get status of auto-register inArray validator flag + * + * @return bool + */ + public function registerInArrayValidator() + { + return $this->_registerInArrayValidator; + } + + /** + * Is the value provided valid? + * + * Autoregisters InArray validator if necessary. + * + * @param string $value + * @param mixed $context + * @return bool + */ + public function isValid($value, $context = null) + { + if ($this->registerInArrayValidator()) { + if (!$this->getValidator('InArray')) { + $options = $this->getMultiOptions(); + $this->addValidator( + 'InArray', + true, + array(array_keys($options)) + ); + } + } + return parent::isValid($value, $context); + } + + /** + * Translate an option + * + * @param string $option + * @param string $value + * @return bool + */ + protected function _translateOption($option, $value) + { + if (!isset($this->_translated[$option])) { + $this->options[$option] = $this->_translateValue($value); + if ($this->options[$option] === $value) { + return false; + } + $this->_translated[$option] = true; + return true; + } + + return false; + } + + /** + * Translate a value + * + * @param array|string $value + * @return array|string + */ + protected function _translateValue($value) + { + if (is_array($value)) { + foreach ($value as $key => $val) { + $value[$key] = $this->_translateValue($val); + } + return $value; + } else { + if (null !== ($translator = $this->getTranslator())) { + if ($translator->isTranslated($value)) { + return $translator->translate($value); + } + } + return $value; + } + } +} diff --git a/library/Zend/Dojo/Form/Element/Editor.php b/library/Zend/Dojo/Form/Element/Editor.php new file mode 100644 index 000000000..79260c122 --- /dev/null +++ b/library/Zend/Dojo/Form/Element/Editor.php @@ -0,0 +1,599 @@ +getCaptureEvents(); + if (in_array($event, $captureEvents)) { + return $this; + } + + $captureEvents[] = (string) $event; + $this->setDijitParam('captureEvents', $captureEvents); + return $this; + } + + /** + * Add multiple capture events + * + * @param array $events + * @return Zend_Dojo_Form_Element_Editor + */ + public function addCaptureEvents(array $events) + { + foreach ($events as $event) { + $this->addCaptureEvent($event); + } + return $this; + } + + /** + * Overwrite many capture events at once + * + * @param array $events + * @return Zend_Dojo_Form_Element_Editor + */ + public function setCaptureEvents(array $events) + { + $this->clearCaptureEvents(); + $this->addCaptureEvents($events); + return $this; + } + + /** + * Get all capture events + * + * @return array + */ + public function getCaptureEvents() + { + if (!$this->hasDijitParam('captureEvents')) { + return array(); + } + return $this->getDijitParam('captureEvents'); + } + + /** + * Is a given capture event registered? + * + * @param string $event + * @return bool + */ + public function hasCaptureEvent($event) + { + $captureEvents = $this->getCaptureEvents(); + return in_array((string) $event, $captureEvents); + } + + /** + * Remove a given capture event + * + * @param string $event + * @return Zend_Dojo_Form_Element_Editor + */ + public function removeCaptureEvent($event) + { + $event = (string) $event; + $captureEvents = $this->getCaptureEvents(); + if (false === ($index = array_search($event, $captureEvents))) { + return $this; + } + unset($captureEvents[$index]); + $this->setDijitParam('captureEvents', $captureEvents); + return $this; + } + + /** + * Clear all capture events + * + * @return Zend_Dojo_Form_Element_Editor + */ + public function clearCaptureEvents() + { + return $this->removeDijitParam('captureEvents'); + } + + /** + * Add a single event to the dijit + * + * @param string $event + * @return Zend_Dojo_Form_Element_Editor + */ + public function addEvent($event) + { + $event = (string) $event; + $events = $this->getEvents(); + if (in_array($event, $events)) { + return $this; + } + + $events[] = (string) $event; + $this->setDijitParam('events', $events); + return $this; + } + + /** + * Add multiple events + * + * @param array $events + * @return Zend_Dojo_Form_Element_Editor + */ + public function addEvents(array $events) + { + foreach ($events as $event) { + $this->addEvent($event); + } + return $this; + } + + /** + * Overwrite many events at once + * + * @param array $events + * @return Zend_Dojo_Form_Element_Editor + */ + public function setEvents(array $events) + { + $this->clearEvents(); + $this->addEvents($events); + return $this; + } + + /** + * Get all events + * + * @return array + */ + public function getEvents() + { + if (!$this->hasDijitParam('events')) { + return array(); + } + return $this->getDijitParam('events'); + } + + /** + * Is a given event registered? + * + * @param string $event + * @return bool + */ + public function hasEvent($event) + { + $events = $this->getEvents(); + return in_array((string) $event, $events); + } + + /** + * Remove a given event + * + * @param string $event + * @return Zend_Dojo_Form_Element_Editor + */ + public function removeEvent($event) + { + $events = $this->getEvents(); + if (false === ($index = array_search($event, $events))) { + return $this; + } + unset($events[$index]); + $this->setDijitParam('events', $events); + return $this; + } + + /** + * Clear all events + * + * @return Zend_Dojo_Form_Element_Editor + */ + public function clearEvents() + { + return $this->removeDijitParam('events'); + } + + /** + * Add a single editor plugin + * + * @param string $plugin + * @return Zend_Dojo_Form_Element_Editor + */ + public function addPlugin($plugin) + { + $plugin = (string) $plugin; + $plugins = $this->getPlugins(); + if (in_array($plugin, $plugins)) { + return $this; + } + + $plugins[] = (string) $plugin; + $this->setDijitParam('plugins', $plugins); + return $this; + } + + /** + * Add multiple plugins + * + * @param array $plugins + * @return Zend_Dojo_Form_Element_Editor + */ + public function addPlugins(array $plugins) + { + foreach ($plugins as $plugin) { + $this->addPlugin($plugin); + } + return $this; + } + + /** + * Overwrite many plugins at once + * + * @param array $plugins + * @return Zend_Dojo_Form_Element_Editor + */ + public function setPlugins(array $plugins) + { + $this->clearPlugins(); + $this->addPlugins($plugins); + return $this; + } + + /** + * Get all plugins + * + * @return array + */ + public function getPlugins() + { + if (!$this->hasDijitParam('plugins')) { + return array(); + } + return $this->getDijitParam('plugins'); + } + + /** + * Is a given plugin registered? + * + * @param string $plugin + * @return bool + */ + public function hasPlugin($plugin) + { + $plugins = $this->getPlugins(); + return in_array((string) $plugin, $plugins); + } + + /** + * Remove a given plugin + * + * @param string $plugin + * @return Zend_Dojo_Form_Element_Editor + */ + public function removePlugin($plugin) + { + $plugins = $this->getPlugins(); + if (false === ($index = array_search($plugin, $plugins))) { + return $this; + } + unset($plugins[$index]); + $this->setDijitParam('plugins', $plugins); + return $this; + } + + /** + * Clear all plugins + * + * @return Zend_Dojo_Form_Element_Editor + */ + public function clearPlugins() + { + return $this->removeDijitParam('plugins'); + } + + /** + * Set edit action interval + * + * @param int $interval + * @return Zend_Dojo_Form_Element_Editor + */ + public function setEditActionInterval($interval) + { + return $this->setDijitParam('editActionInterval', (int) $interval); + } + + /** + * Get edit action interval; defaults to 3 + * + * @return int + */ + public function getEditActionInterval() + { + if (!$this->hasDijitParam('editActionInterval')) { + $this->setEditActionInterval(3); + } + return $this->getDijitParam('editActionInterval'); + } + + /** + * Set focus on load flag + * + * @param bool $flag + * @return Zend_Dojo_Form_Element_Editor + */ + public function setFocusOnLoad($flag) + { + return $this->setDijitParam('focusOnLoad', (bool) $flag); + } + + /** + * Retrieve focus on load flag + * + * @return bool + */ + public function getFocusOnLoad() + { + if (!$this->hasDijitParam('focusOnLoad')) { + return false; + } + return $this->getDijitParam('focusOnLoad'); + } + + /** + * Set editor height + * + * @param string|int $height + * @return Zend_Dojo_Form_Element_Editor + */ + public function setHeight($height) + { + if (!preg_match('/^\d+(em|px|%)?$/i', $height)) { + require_once 'Zend/Form/Element/Exception.php'; + throw new Zend_Form_Element_Exception('Invalid height provided; must be integer or CSS measurement'); + } + if (!preg_match('/(em|px|%)$/', $height)) { + $height .= 'px'; + } + return $this->setDijitParam('height', $height); + } + + /** + * Retrieve height + * + * @return string + */ + public function getHeight() + { + if (!$this->hasDijitParam('height')) { + return '300px'; + } + return $this->getDijitParam('height'); + } + + /** + * Set whether or not to inherit parent's width + * + * @param bool $flag + * @return Zend_Dojo_Form_Element_Editor + */ + public function setInheritWidth($flag) + { + return $this->setDijitParam('inheritWidth', (bool) $flag); + } + + /** + * Whether or not to inherit the parent's width + * + * @return bool + */ + public function getInheritWidth() + { + if (!$this->hasDijitParam('inheritWidth')) { + return false; + } + return $this->getDijitParam('inheritWidth'); + } + + /** + * Set minimum height of editor + * + * @param string|int $minHeight + * @return Zend_Dojo_Form_Element_Editor + */ + public function setMinHeight($minHeight) + { + if (!preg_match('/^\d+(em)?$/i', $minHeight)) { + require_once 'Zend/Form/Element/Exception.php'; + throw new Zend_Form_Element_Exception('Invalid minHeight provided; must be integer or CSS measurement'); + } + if ('em' != substr($minHeight, -2)) { + $minHeight .= 'em'; + } + return $this->setDijitParam('minHeight', $minHeight); + } + + /** + * Get minimum height of editor + * + * @return string + */ + public function getMinHeight() + { + if (!$this->hasDijitParam('minHeight')) { + return '1em'; + } + return $this->getDijitParam('minHeight'); + } + + /** + * Add a custom stylesheet + * + * @param string $styleSheet + * @return Zend_Dojo_Form_Element_Editor + */ + public function addStyleSheet($styleSheet) + { + $stylesheets = $this->getStyleSheets(); + if (strstr($stylesheets, ';')) { + $stylesheets = explode(';', $stylesheets); + } elseif (!empty($stylesheets)) { + $stylesheets = (array) $stylesheets; + } else { + $stylesheets = array(); + } + if (!in_array($styleSheet, $stylesheets)) { + $stylesheets[] = (string) $styleSheet; + } + return $this->setDijitParam('styleSheets', implode(';', $stylesheets)); + } + + /** + * Add multiple custom stylesheets + * + * @param array $styleSheets + * @return Zend_Dojo_Form_Element_Editor + */ + public function addStyleSheets(array $styleSheets) + { + foreach ($styleSheets as $styleSheet) { + $this->addStyleSheet($styleSheet); + } + return $this; + } + + /** + * Overwrite all stylesheets + * + * @param array $styleSheets + * @return Zend_Dojo_Form_Element_Editor + */ + public function setStyleSheets(array $styleSheets) + { + $this->clearStyleSheets(); + return $this->addStyleSheets($styleSheets); + } + + /** + * Get all stylesheets + * + * @return string + */ + public function getStyleSheets() + { + if (!$this->hasDijitParam('styleSheets')) { + return ''; + } + return $this->getDijitParam('styleSheets'); + } + + /** + * Is a given stylesheet registered? + * + * @param string $styleSheet + * @return bool + */ + public function hasStyleSheet($styleSheet) + { + $styleSheets = $this->getStyleSheets(); + $styleSheets = explode(';', $styleSheets); + return in_array($styleSheet, $styleSheets); + } + + /** + * Remove a single stylesheet + * + * @param string $styleSheet + * @return Zend_Dojo_Form_Element_Editor + */ + public function removeStyleSheet($styleSheet) + { + $styleSheets = $this->getStyleSheets(); + $styleSheets = explode(';', $styleSheets); + if (false !== ($index = array_search($styleSheet, $styleSheets))) { + unset($styleSheets[$index]); + $this->setDijitParam('styleSheets', implode(';', $styleSheets)); + } + return $this; + } + + /** + * Clear all stylesheets + * + * @return Zend_Dojo_Form_Element_Editor + */ + public function clearStyleSheets() + { + if ($this->hasDijitParam('styleSheets')) { + $this->removeDijitParam('styleSheets'); + } + return $this; + } + + /** + * Set update interval + * + * @param int $interval + * @return Zend_Dojo_Form_Element_Editor + */ + public function setUpdateInterval($interval) + { + return $this->setDijitParam('updateInterval', (int) $interval); + } + + /** + * Get update interval + * + * @return int + */ + public function getUpdateInterval() + { + if (!$this->hasDijitParam('updateInterval')) { + return 200; + } + return $this->getDijitParam('updateInterval'); + } +} diff --git a/library/Zend/Dojo/Form/Element/FilteringSelect.php b/library/Zend/Dojo/Form/Element/FilteringSelect.php new file mode 100644 index 000000000..50bc26198 --- /dev/null +++ b/library/Zend/Dojo/Form/Element/FilteringSelect.php @@ -0,0 +1,48 @@ +hasDijitParam('topDecoration')) { + return $this->getDijitParam('topDecoration'); + } + return array(); + } + + /** + * Set dijit to use with top decoration + * + * @param mixed $dijit + * @return Zend_Dojo_Form_Element_HorizontalSlider + */ + public function setTopDecorationDijit($dijit) + { + $decoration = $this->getTopDecoration(); + $decoration['dijit'] = (string) $dijit; + $this->setDijitParam('topDecoration', $decoration); + return $this; + } + + /** + * Set container to use with top decoration + * + * @param mixed $container + * @return Zend_Dojo_Form_Element_HorizontalSlider + */ + public function setTopDecorationContainer($container) + { + $decoration = $this->getTopDecoration(); + $decoration['container'] = (string) $container; + $this->setDijitParam('topDecoration', $decoration); + return $this; + } + + /** + * Set labels to use with top decoration + * + * @param array $labels + * @return Zend_Dojo_Form_Element_HorizontalSlider + */ + public function setTopDecorationLabels(array $labels) + { + $decoration = $this->getTopDecoration(); + $decoration['labels'] = array_values($labels); + $this->setDijitParam('topDecoration', $decoration); + return $this; + } + + /** + * Set params to use with top decoration + * + * @param array $params + * @return Zend_Dojo_Form_Element_HorizontalSlider + */ + public function setTopDecorationParams(array $params) + { + $decoration = $this->getTopDecoration(); + $decoration['params'] = $params; + $this->setDijitParam('topDecoration', $decoration); + return $this; + } + + /** + * Set attribs to use with top decoration + * + * @param array $attribs + * @return Zend_Dojo_Form_Element_HorizontalSlider + */ + public function setTopDecorationAttribs(array $attribs) + { + $decoration = $this->getTopDecoration(); + $decoration['attribs'] = $attribs; + $this->setDijitParam('topDecoration', $decoration); + return $this; + } + + /** + * Get bottom decoration data + * + * @return array + */ + public function getBottomDecoration() + { + if ($this->hasDijitParam('bottomDecoration')) { + return $this->getDijitParam('bottomDecoration'); + } + return array(); + } + + /** + * Set dijit to use with bottom decoration + * + * @param mixed $dijit + * @return Zend_Dojo_Form_Element_HorizontalSlider + */ + public function setBottomDecorationDijit($dijit) + { + $decoration = $this->getBottomDecoration(); + $decoration['dijit'] = (string) $dijit; + $this->setDijitParam('bottomDecoration', $decoration); + return $this; + } + + /** + * Set container to use with bottom decoration + * + * @param mixed $container + * @return Zend_Dojo_Form_Element_HorizontalSlider + */ + public function setBottomDecorationContainer($container) + { + $decoration = $this->getBottomDecoration(); + $decoration['container'] = (string) $container; + $this->setDijitParam('bottomDecoration', $decoration); + return $this; + } + + /** + * Set labels to use with bottom decoration + * + * @param array $labels + * @return Zend_Dojo_Form_Element_HorizontalSlider + */ + public function setBottomDecorationLabels(array $labels) + { + $decoration = $this->getBottomDecoration(); + $decoration['labels'] = array_values($labels); + $this->setDijitParam('bottomDecoration', $decoration); + return $this; + } + + /** + * Set params to use with bottom decoration + * + * @param array $params + * @return Zend_Dojo_Form_Element_HorizontalSlider + */ + public function setBottomDecorationParams(array $params) + { + $decoration = $this->getBottomDecoration(); + $decoration['params'] = $params; + $this->setDijitParam('bottomDecoration', $decoration); + return $this; + } + + /** + * Set attribs to use with bottom decoration + * + * @param array $attribs + * @return Zend_Dojo_Form_Element_HorizontalSlider + */ + public function setBottomDecorationAttribs(array $attribs) + { + $decoration = $this->getBottomDecoration(); + $decoration['attribs'] = $attribs; + $this->setDijitParam('bottomDecoration', $decoration); + return $this; + } +} diff --git a/library/Zend/Dojo/Form/Element/NumberSpinner.php b/library/Zend/Dojo/Form/Element/NumberSpinner.php new file mode 100644 index 000000000..c91b82352 --- /dev/null +++ b/library/Zend/Dojo/Form/Element/NumberSpinner.php @@ -0,0 +1,245 @@ +setDijitParam('defaultTimeout', (int) $timeout); + return $this; + } + + /** + * Retrieve defaultTimeout + * + * @return int|null + */ + public function getDefaultTimeout() + { + return $this->getDijitParam('defaultTimeout'); + } + + /** + * Set timeoutChangeRate + * + * @param int $rate + * @return Zend_Dojo_Form_Element_NumberSpinner + */ + public function setTimeoutChangeRate($rate) + { + $this->setDijitParam('timeoutChangeRate', (int) $rate); + return $this; + } + + /** + * Retrieve timeoutChangeRate + * + * @return int|null + */ + public function getTimeoutChangeRate() + { + return $this->getDijitParam('timeoutChangeRate'); + } + + /** + * Set largeDelta + * + * @param int $delta + * @return Zend_Dojo_Form_Element_NumberSpinner + */ + public function setLargeDelta($delta) + { + $this->setDijitParam('largeDelta', (int) $delta); + return $this; + } + + /** + * Retrieve largeDelta + * + * @return int|null + */ + public function getLargeDelta() + { + return $this->getDijitParam('largeDelta'); + } + + /** + * Set smallDelta + * + * @param int $delta + * @return Zend_Dojo_Form_Element_NumberSpinner + */ + public function setSmallDelta($delta) + { + $this->setDijitParam('smallDelta', (int) $delta); + return $this; + } + + /** + * Retrieve smallDelta + * + * @return int|null + */ + public function getSmallDelta() + { + return $this->getDijitParam('smallDelta'); + } + + /** + * Set intermediateChanges flag + * + * @param bool $flag + * @return Zend_Dojo_Form_Element_TextBox + */ + public function setIntermediateChanges($flag) + { + $this->setDijitParam('intermediateChanges', (bool) $flag); + return $this; + } + + /** + * Retrieve intermediateChanges flag + * + * @return bool + */ + public function getIntermediateChanges() + { + if (!$this->hasDijitParam('intermediateChanges')) { + return false; + } + return $this->getDijitParam('intermediateChanges'); + } + + /** + * Set rangeMessage + * + * @param string $message + * @return Zend_Dojo_Form_Element_NumberSpinner + */ + public function setRangeMessage($message) + { + $this->setDijitParam('rangeMessage', (string) $message); + return $this; + } + + /** + * Retrieve rangeMessage + * + * @return string|null + */ + public function getRangeMessage() + { + return $this->getDijitParam('rangeMessage'); + } + + /** + * Set minimum value + * + * @param int $value + * @return Zend_Dojo_Form_Element_NumberSpinner + */ + public function setMin($value) + { + $constraints = array(); + if ($this->hasDijitParam('constraints')) { + $constraints = $this->getDijitParam('constraints'); + } + $constraints['min'] = (int) $value; + $this->setDijitParam('constraints', $constraints); + return $this; + } + + /** + * Get minimum value + * + * @return null|int + */ + public function getMin() + { + if (!$this->hasDijitParam('constraints')) { + return null; + } + $constraints = $this->getDijitParam('constraints'); + if (!array_key_exists('min', $constraints)) { + return null; + } + return $constraints['min']; + } + + /** + * Set maximum value + * + * @param int $value + * @return Zend_Dojo_Form_Element_NumberSpinner + */ + public function setMax($value) + { + $constraints = array(); + if ($this->hasDijitParam('constraints')) { + $constraints = $this->getDijitParam('constraints'); + } + $constraints['max'] = (int) $value; + $this->setDijitParam('constraints', $constraints); + return $this; + } + + /** + * Get maximum value + * + * @return null|int + */ + public function getMax() + { + if (!$this->hasDijitParam('constraints')) { + return null; + } + $constraints = $this->getDijitParam('constraints'); + if (!array_key_exists('max', $constraints)) { + return null; + } + return $constraints['max']; + } +} diff --git a/library/Zend/Dojo/Form/Element/NumberTextBox.php b/library/Zend/Dojo/Form/Element/NumberTextBox.php new file mode 100644 index 000000000..274591faf --- /dev/null +++ b/library/Zend/Dojo/Form/Element/NumberTextBox.php @@ -0,0 +1,173 @@ +setConstraint('locale', (string) $locale); + return $this; + } + + /** + * Retrieve locale + * + * @return string|null + */ + public function getLocale() + { + return $this->getConstraint('locale'); + } + + /** + * Set numeric format pattern + * + * @param string $pattern + * @return Zend_Dojo_Form_Element_NumberTextBox + */ + public function setPattern($pattern) + { + $this->setConstraint('pattern', (string) $pattern); + return $this; + } + + /** + * Retrieve numeric format pattern + * + * @return string|null + */ + public function getPattern() + { + return $this->getConstraint('pattern'); + } + + /** + * Set numeric format type + * + * @see $_allowedTypes + * @param string $type + * @return Zend_Dojo_Form_Element_NumberTextBox + */ + public function setType($type) + { + $type = strtolower($type); + if (!in_array($type, $this->_allowedTypes)) { + require_once 'Zend/Form/Element/Exception.php'; + throw new Zend_Form_Element_Exception(sprintf('Invalid numeric type "%s" specified', $type)); + } + + $this->setConstraint('type', $type); + return $this; + } + + /** + * Retrieve type + * + * @return string|null + */ + public function getType() + { + return $this->getConstraint('type'); + } + + /** + * Set decimal places + * + * @param int $places + * @return Zend_Dojo_Form_Element_NumberTextBox + */ + public function setPlaces($places) + { + $this->setConstraint('places', (int) $places); + return $this; + } + + /** + * Retrieve decimal places + * + * @return int|null + */ + public function getPlaces() + { + return $this->getConstraint('places'); + } + + /** + * Set strict flag + * + * @param bool $strict + * @return Zend_Dojo_Form_Element_NumberTextBox + */ + public function setStrict($flag) + { + $this->setConstraint('strict', (bool) $flag); + return $this; + } + + /** + * Retrieve strict flag + * + * @return bool + */ + public function getStrict() + { + if (!$this->hasConstraint('strict')) { + return false; + } + return ('true' == $this->getConstraint('strict')); + } +} diff --git a/library/Zend/Dojo/Form/Element/PasswordTextBox.php b/library/Zend/Dojo/Form/Element/PasswordTextBox.php new file mode 100644 index 000000000..9a1a96d4d --- /dev/null +++ b/library/Zend/Dojo/Form/Element/PasswordTextBox.php @@ -0,0 +1,42 @@ +setDijitParam('clickSelect', (bool) $flag); + return $this; + } + + /** + * Retrieve clickSelect flag + * + * @return bool + */ + public function getClickSelect() + { + if (!$this->hasDijitParam('clickSelect')) { + return false; + } + return $this->getDijitParam('clickSelect'); + } + + /** + * Set intermediateChanges flag + * + * @param bool $intermediateChanges + * @return Zend_Dojo_Form_Element_TextBox + */ + public function setIntermediateChanges($flag) + { + $this->setDijitParam('intermediateChanges', (bool) $flag); + return $this; + } + + /** + * Retrieve intermediateChanges flag + * + * @return bool + */ + public function getIntermediateChanges() + { + if (!$this->hasDijitParam('intermediateChanges')) { + return false; + } + return $this->getDijitParam('intermediateChanges'); + } + + /** + * Set showButtons flag + * + * @param bool $showButtons + * @return Zend_Dojo_Form_Element_TextBox + */ + public function setShowButtons($flag) + { + $this->setDijitParam('showButtons', (bool) $flag); + return $this; + } + + /** + * Retrieve showButtons flag + * + * @return bool + */ + public function getShowButtons() + { + if (!$this->hasDijitParam('showButtons')) { + return false; + } + return $this->getDijitParam('showButtons'); + } + + /** + * Set discreteValues + * + * @param int $value + * @return Zend_Dojo_Form_Element_TextBox + */ + public function setDiscreteValues($value) + { + $this->setDijitParam('discreteValues', (int) $value); + return $this; + } + + /** + * Retrieve discreteValues + * + * @return int|null + */ + public function getDiscreteValues() + { + return $this->getDijitParam('discreteValues'); + } + + /** + * Set maximum + * + * @param int $value + * @return Zend_Dojo_Form_Element_TextBox + */ + public function setMaximum($value) + { + $this->setDijitParam('maximum', (int) $value); + return $this; + } + + /** + * Retrieve maximum + * + * @return int|null + */ + public function getMaximum() + { + return $this->getDijitParam('maximum'); + } + + /** + * Set minimum + * + * @param int $value + * @return Zend_Dojo_Form_Element_TextBox + */ + public function setMinimum($value) + { + $this->setDijitParam('minimum', (int) $value); + return $this; + } + + /** + * Retrieve minimum + * + * @return int|null + */ + public function getMinimum() + { + return $this->getDijitParam('minimum'); + } + + /** + * Set pageIncrement + * + * @param int $value + * @return Zend_Dojo_Form_Element_TextBox + */ + public function setPageIncrement($value) + { + $this->setDijitParam('pageIncrement', (int) $value); + return $this; + } + + /** + * Retrieve pageIncrement + * + * @return int|null + */ + public function getPageIncrement() + { + return $this->getDijitParam('pageIncrement'); + } +} diff --git a/library/Zend/Dojo/Form/Element/SubmitButton.php b/library/Zend/Dojo/Form/Element/SubmitButton.php new file mode 100644 index 000000000..bdf83ac27 --- /dev/null +++ b/library/Zend/Dojo/Form/Element/SubmitButton.php @@ -0,0 +1,42 @@ +setDijitParam('lowercase', (bool) $flag); + return $this; + } + + /** + * Retrieve lowercase flag + * + * @return bool + */ + public function getLowercase() + { + if (!$this->hasDijitParam('lowercase')) { + return false; + } + return $this->getDijitParam('lowercase'); + } + + /** + * Set propercase flag + * + * @param bool $propercase + * @return Zend_Dojo_Form_Element_TextBox + */ + public function setPropercase($flag) + { + $this->setDijitParam('propercase', (bool) $flag); + return $this; + } + + /** + * Retrieve propercase flag + * + * @return bool + */ + public function getPropercase() + { + if (!$this->hasDijitParam('propercase')) { + return false; + } + return $this->getDijitParam('propercase'); + } + + /** + * Set uppercase flag + * + * @param bool $uppercase + * @return Zend_Dojo_Form_Element_TextBox + */ + public function setUppercase($flag) + { + $this->setDijitParam('uppercase', (bool) $flag); + return $this; + } + + /** + * Retrieve uppercase flag + * + * @return bool + */ + public function getUppercase() + { + if (!$this->hasDijitParam('uppercase')) { + return false; + } + return $this->getDijitParam('uppercase'); + } + + /** + * Set trim flag + * + * @param bool $trim + * @return Zend_Dojo_Form_Element_TextBox + */ + public function setTrim($flag) + { + $this->setDijitParam('trim', (bool) $flag); + return $this; + } + + /** + * Retrieve trim flag + * + * @return bool + */ + public function getTrim() + { + if (!$this->hasDijitParam('trim')) { + return false; + } + return $this->getDijitParam('trim'); + } + + /** + * Set maxLength + * + * @param int $length + * @return Zend_Dojo_Form_Element_TextBox + */ + public function setMaxLength($length) + { + $this->setDijitParam('maxLength', (int) $length); + return $this; + } + + /** + * Retrieve maxLength + * + * @return int|null + */ + public function getMaxLength() + { + return $this->getDijitParam('maxLength'); + } +} diff --git a/library/Zend/Dojo/Form/Element/Textarea.php b/library/Zend/Dojo/Form/Element/Textarea.php new file mode 100644 index 000000000..319bebeb1 --- /dev/null +++ b/library/Zend/Dojo/Form/Element/Textarea.php @@ -0,0 +1,42 @@ +setConstraint('timePattern', (string) $pattern); + return $this; + } + + /** + * Retrieve time format pattern + * + * @return string|null + */ + public function getTimePattern() + { + return $this->getConstraint('timePattern'); + } + + /** + * Set clickableIncrement + * + * @param string $format + * @return Zend_Dojo_Form_Element_NumberTextBox + */ + public function setClickableIncrement($format) + { + $format = (string) $format; + $this->_validateIso8601($format); + $this->setConstraint('clickableIncrement', $format); + return $this; + } + + /** + * Retrieve clickableIncrement + * + * @return string|null + */ + public function getClickableIncrement() + { + return $this->getConstraint('clickableIncrement'); + } + + /** + * Set visibleIncrement + * + * @param string $format + * @return Zend_Dojo_Form_Element_NumberTextBox + */ + public function setVisibleIncrement($format) + { + $format = (string) $format; + $this->_validateIso8601($format); + $this->setConstraint('visibleIncrement', $format); + return $this; + } + + /** + * Retrieve visibleIncrement + * + * @return string|null + */ + public function getVisibleIncrement() + { + return $this->getConstraint('visibleIncrement'); + } + + /** + * Set visibleRange + * + * @param string $format + * @return Zend_Dojo_Form_Element_NumberTextBox + */ + public function setVisibleRange($format) + { + $format = (string) $format; + $this->_validateIso8601($format); + $this->setConstraint('visibleRange', $format); + return $this; + } + + /** + * Retrieve visibleRange + * + * @return string|null + */ + public function getVisibleRange() + { + return $this->getConstraint('visibleRange'); + } +} diff --git a/library/Zend/Dojo/Form/Element/ValidationTextBox.php b/library/Zend/Dojo/Form/Element/ValidationTextBox.php new file mode 100644 index 000000000..eb875a847 --- /dev/null +++ b/library/Zend/Dojo/Form/Element/ValidationTextBox.php @@ -0,0 +1,218 @@ +setDijitParam('invalidMessage', (string) $message); + return $this; + } + + /** + * Retrieve invalidMessage + * + * @return string|null + */ + public function getInvalidMessage() + { + return $this->getDijitParam('invalidMessage'); + } + + /** + * Set promptMessage + * + * @param string $message + * @return Zend_Dojo_Form_Element_ValidationTextBox + */ + public function setPromptMessage($message) + { + $this->setDijitParam('promptMessage', (string) $message); + return $this; + } + + /** + * Retrieve promptMessage + * + * @return string|null + */ + public function getPromptMessage() + { + return $this->getDijitParam('promptMessage'); + } + + /** + * Set regExp + * + * @param string $regexp + * @return Zend_Dojo_Form_Element_ValidationTextBox + */ + public function setRegExp($regexp) + { + $this->setDijitParam('regExp', (string) $regexp); + return $this; + } + + /** + * Retrieve regExp + * + * @return string|null + */ + public function getRegExp() + { + return $this->getDijitParam('regExp'); + } + + /** + * Set an individual constraint + * + * @param string $key + * @param mixed $value + * @return Zend_Dojo_Form_Element_ValidationTextBox + */ + public function setConstraint($key, $value) + { + $constraints = $this->getConstraints(); + $constraints[(string) $key] = $value; + $this->setConstraints($constraints); + return $this; + } + + /** + * Set validation constraints + * + * Refer to Dojo dijit.form.ValidationTextBox documentation for valid + * structure. + * + * @param array $constraints + * @return Zend_Dojo_Form_Element_ValidationTextBox + */ + public function setConstraints(array $constraints) + { + array_walk_recursive($constraints, array($this, '_castBoolToString')); + $this->setDijitParam('constraints', $constraints); + return $this; + } + + /** + * Is the given constraint set? + * + * @param string $key + * @return bool + */ + public function hasConstraint($key) + { + $constraints = $this->getConstraints(); + return array_key_exists((string)$key, $constraints); + } + + /** + * Get an individual constraint + * + * @param string $key + * @return mixed + */ + public function getConstraint($key) + { + $key = (string) $key; + if (!$this->hasConstraint($key)) { + return null; + } + return $this->dijitParams['constraints'][$key]; + } + + /** + * Get constraints + * + * @return array + */ + public function getConstraints() + { + if ($this->hasDijitParam('constraints')) { + return $this->getDijitParam('constraints'); + } + return array(); + } + + /** + * Remove a single constraint + * + * @param string $key + * @return Zend_Dojo_Form_Element_ValidationTextBox + */ + public function removeConstraint($key) + { + $key = (string) $key; + if ($this->hasConstraint($key)) { + unset($this->dijitParams['constraints'][$key]); + } + return $this; + } + + /** + * Clear all constraints + * + * @return Zend_Dojo_Form_Element_ValidationTextBox + */ + public function clearConstraints() + { + return $this->removeDijitParam('constraints'); + } + + /** + * Cast a boolean value to a string + * + * @param mixed $item + * @param string $key + * @return void + */ + protected function _castBoolToString(&$item, $key) + { + if (is_bool($item)) { + $item = ($item) ? 'true' : 'false'; + } + } +} diff --git a/library/Zend/Dojo/Form/Element/VerticalSlider.php b/library/Zend/Dojo/Form/Element/VerticalSlider.php new file mode 100644 index 000000000..1c90a2bac --- /dev/null +++ b/library/Zend/Dojo/Form/Element/VerticalSlider.php @@ -0,0 +1,208 @@ +hasDijitParam('leftDecoration')) { + return $this->getDijitParam('leftDecoration'); + } + return array(); + } + + /** + * Set dijit to use with left decoration + * + * @param mixed $dijit + * @return Zend_Dojo_Form_Element_HorizontalSlider + */ + public function setLeftDecorationDijit($dijit) + { + $decoration = $this->getLeftDecoration(); + $decoration['dijit'] = (string) $dijit; + $this->setDijitParam('leftDecoration', $decoration); + return $this; + } + + /** + * Set container to use with left decoration + * + * @param mixed $container + * @return Zend_Dojo_Form_Element_HorizontalSlider + */ + public function setLeftDecorationContainer($container) + { + $decoration = $this->getLeftDecoration(); + $decoration['container'] = (string) $container; + $this->setDijitParam('leftDecoration', $decoration); + return $this; + } + + /** + * Set labels to use with left decoration + * + * @param array $labels + * @return Zend_Dojo_Form_Element_HorizontalSlider + */ + public function setLeftDecorationLabels(array $labels) + { + $decoration = $this->getLeftDecoration(); + $decoration['labels'] = array_values($labels); + $this->setDijitParam('leftDecoration', $decoration); + return $this; + } + + /** + * Set params to use with left decoration + * + * @param array $params + * @return Zend_Dojo_Form_Element_HorizontalSlider + */ + public function setLeftDecorationParams(array $params) + { + $decoration = $this->getLeftDecoration(); + $decoration['params'] = $params; + $this->setDijitParam('leftDecoration', $decoration); + return $this; + } + + /** + * Set attribs to use with left decoration + * + * @param array $attribs + * @return Zend_Dojo_Form_Element_HorizontalSlider + */ + public function setLeftDecorationAttribs(array $attribs) + { + $decoration = $this->getLeftDecoration(); + $decoration['attribs'] = $attribs; + $this->setDijitParam('leftDecoration', $decoration); + return $this; + } + + /** + * Get right decoration data + * + * @return array + */ + public function getRightDecoration() + { + if ($this->hasDijitParam('rightDecoration')) { + return $this->getDijitParam('rightDecoration'); + } + return array(); + } + + /** + * Set dijit to use with right decoration + * + * @param mixed $dijit + * @return Zend_Dojo_Form_Element_HorizontalSlider + */ + public function setRightDecorationDijit($dijit) + { + $decoration = $this->getRightDecoration(); + $decoration['dijit'] = (string) $dijit; + $this->setDijitParam('rightDecoration', $decoration); + return $this; + } + + /** + * Set container to use with right decoration + * + * @param mixed $container + * @return Zend_Dojo_Form_Element_HorizontalSlider + */ + public function setRightDecorationContainer($container) + { + $decoration = $this->getRightDecoration(); + $decoration['container'] = (string) $container; + $this->setDijitParam('rightDecoration', $decoration); + return $this; + } + + /** + * Set labels to use with right decoration + * + * @param array $labels + * @return Zend_Dojo_Form_Element_HorizontalSlider + */ + public function setRightDecorationLabels(array $labels) + { + $decoration = $this->getRightDecoration(); + $decoration['labels'] = array_values($labels); + $this->setDijitParam('rightDecoration', $decoration); + return $this; + } + + /** + * Set params to use with right decoration + * + * @param array $params + * @return Zend_Dojo_Form_Element_HorizontalSlider + */ + public function setRightDecorationParams(array $params) + { + $decoration = $this->getRightDecoration(); + $decoration['params'] = $params; + $this->setDijitParam('rightDecoration', $decoration); + return $this; + } + + /** + * Set attribs to use with right decoration + * + * @param array $attribs + * @return Zend_Dojo_Form_Element_HorizontalSlider + */ + public function setRightDecorationAttribs(array $attribs) + { + $decoration = $this->getRightDecoration(); + $decoration['attribs'] = $attribs; + $this->setDijitParam('rightDecoration', $decoration); + return $this; + } +} diff --git a/library/Zend/Dojo/Form/SubForm.php b/library/Zend/Dojo/Form/SubForm.php new file mode 100644 index 000000000..8202d2545 --- /dev/null +++ b/library/Zend/Dojo/Form/SubForm.php @@ -0,0 +1,94 @@ +addPrefixPath('Zend_Dojo_Form_Decorator', 'Zend/Dojo/Form/Decorator', 'decorator') + ->addPrefixPath('Zend_Dojo_Form_Element', 'Zend/Dojo/Form/Element', 'element') + ->addElementPrefixPath('Zend_Dojo_Form_Decorator', 'Zend/Dojo/Form/Decorator', 'decorator') + ->addDisplayGroupPrefixPath('Zend_Dojo_Form_Decorator', 'Zend/Dojo/Form/Decorator') + ->setDefaultDisplayGroupClass('Zend_Dojo_Form_DisplayGroup'); + parent::__construct($options); + } + + /** + * Load the default decorators + * + * @return void + */ + public function loadDefaultDecorators() + { + if ($this->loadDefaultDecoratorsIsDisabled()) { + return; + } + + $decorators = $this->getDecorators(); + if (empty($decorators)) { + $this->addDecorator('FormElements') + ->addDecorator('HtmlTag', array('tag' => 'dl')) + ->addDecorator('ContentPane'); + } + } + + /** + * Get view + * + * @return Zend_View_Interface + */ + public function getView() + { + $view = parent::getView(); + if (!$this->_dojoViewPathRegistered) { + if (false === $view->getPluginLoader('helper')->getPaths('Zend_Dojo_View_Helper')) { + $view->addHelperPath('Zend/Dojo/View/Helper', 'Zend_Dojo_View_Helper'); + } + $this->_dojoViewPathRegistered = true; + } + return $view; + } +} diff --git a/library/Zend/Dojo/View/Exception.php b/library/Zend/Dojo/View/Exception.php new file mode 100644 index 000000000..17ac74008 --- /dev/null +++ b/library/Zend/Dojo/View/Exception.php @@ -0,0 +1,37 @@ +_createLayoutContainer($id, $content, $params, $attribs); + } +} diff --git a/library/Zend/Dojo/View/Helper/AccordionPane.php b/library/Zend/Dojo/View/Helper/AccordionPane.php new file mode 100644 index 000000000..c1e6e4a1f --- /dev/null +++ b/library/Zend/Dojo/View/Helper/AccordionPane.php @@ -0,0 +1,66 @@ +_createLayoutContainer($id, $content, $params, $attribs); + } +} diff --git a/library/Zend/Dojo/View/Helper/BorderContainer.php b/library/Zend/Dojo/View/Helper/BorderContainer.php new file mode 100644 index 000000000..7e3542be6 --- /dev/null +++ b/library/Zend/Dojo/View/Helper/BorderContainer.php @@ -0,0 +1,79 @@ +_styleIsRegistered) { + $this->view->headStyle()->appendStyle('html, body { height: 100%; width: 100%; margin: 0; padding: 0; }'); + $this->_styleIsRegistered = true; + } + + // and now we create it: + return $this->_createLayoutContainer($id, $content, $params, $attribs); + } +} diff --git a/library/Zend/Dojo/View/Helper/Button.php b/library/Zend/Dojo/View/Helper/Button.php new file mode 100644 index 000000000..dea3f8a7e --- /dev/null +++ b/library/Zend/Dojo/View/Helper/Button.php @@ -0,0 +1,68 @@ +_prepareDijit($attribs, $params, 'element'); + + return $this->view->formButton($id, $value, $attribs); + } +} diff --git a/library/Zend/Dojo/View/Helper/CheckBox.php b/library/Zend/Dojo/View/Helper/CheckBox.php new file mode 100644 index 000000000..4fe04a27b --- /dev/null +++ b/library/Zend/Dojo/View/Helper/CheckBox.php @@ -0,0 +1,100 @@ +_prepareDijit($attribs, $params, 'element'); + + // strip options so they don't show up in markup + if (array_key_exists('options', $attribs)) { + unset($attribs['options']); + } + + // and now we create it: + $html = ''; + if (!strstr($id, '[]')) { + // hidden element for unchecked value + $html .= $this->_renderHiddenElement($id, $checkboxInfo['uncheckedValue']); + } + + // and final element + $html .= $this->_createFormElement($id, $checkboxInfo['checkedValue'], $params, $attribs); + + return $html; + } +} diff --git a/library/Zend/Dojo/View/Helper/ComboBox.php b/library/Zend/Dojo/View/Helper/ComboBox.php new file mode 100644 index 000000000..989474b0d --- /dev/null +++ b/library/Zend/Dojo/View/Helper/ComboBox.php @@ -0,0 +1,151 @@ +_renderStore($params['store'], $id))) { + $params['store'] = $params['store']['store']; + if (is_string($store)) { + $html .= $store; + } + $html .= $this->_createFormElement($id, $value, $params, $attribs); + return $html; + } + unset($params['store']); + } elseif (array_key_exists('store', $params)) { + if (array_key_exists('storeType', $params)) { + $storeParams = array( + 'store' => $params['store'], + 'type' => $params['storeType'], + ); + unset($params['storeType']); + if (array_key_exists('storeParams', $params)) { + $storeParams['params'] = $params['storeParams']; + unset($params['storeParams']); + } + if (false !== ($store = $this->_renderStore($storeParams, $id))) { + if (is_string($store)) { + $html .= $store; + } + } + } + $html .= $this->_createFormElement($id, $value, $params, $attribs); + return $html; + } + + // do as normal select + $attribs = $this->_prepareDijit($attribs, $params, 'element'); + return $this->view->formSelect($id, $value, $attribs, $options); + } + + /** + * Render data store element + * + * Renders to dojo view helper + * + * @param array $params + * @return string|false + */ + protected function _renderStore(array $params, $id) + { + if (!array_key_exists('store', $params) || !array_key_exists('type', $params)) { + return false; + } + + $this->dojo->requireModule($params['type']); + + $extraParams = array(); + $storeParams = array( + 'dojoType' => $params['type'], + 'jsId' => $params['store'], + ); + + if (array_key_exists('params', $params)) { + $storeParams = array_merge($storeParams, $params['params']); + $extraParams = $params['params']; + } + + if ($this->_useProgrammatic()) { + if (!$this->_useProgrammaticNoScript()) { + require_once 'Zend/Json.php'; + $this->dojo->addJavascript('var ' . $storeParams['jsId'] . ";\n"); + $js = $storeParams['jsId'] . ' = ' + . 'new ' . $storeParams['dojoType'] . '(' + . Zend_Json::encode($extraParams) + . ");\n"; + $js = "function() {\n$js\n}"; + $this->dojo->_addZendLoad($js); + } + return true; + } + + return '_htmlAttribs($storeParams) . '>'; + } +} diff --git a/library/Zend/Dojo/View/Helper/ContentPane.php b/library/Zend/Dojo/View/Helper/ContentPane.php new file mode 100644 index 000000000..42bea292c --- /dev/null +++ b/library/Zend/Dojo/View/Helper/ContentPane.php @@ -0,0 +1,66 @@ +_createLayoutContainer($id, $content, $params, $attribs); + } +} diff --git a/library/Zend/Dojo/View/Helper/CurrencyTextBox.php b/library/Zend/Dojo/View/Helper/CurrencyTextBox.php new file mode 100644 index 000000000..325b13287 --- /dev/null +++ b/library/Zend/Dojo/View/Helper/CurrencyTextBox.php @@ -0,0 +1,68 @@ +_createFormElement($id, $value, $params, $attribs); + } +} diff --git a/library/Zend/Dojo/View/Helper/CustomDijit.php b/library/Zend/Dojo/View/Helper/CustomDijit.php new file mode 100644 index 000000000..cb21c35b0 --- /dev/null +++ b/library/Zend/Dojo/View/Helper/CustomDijit.php @@ -0,0 +1,112 @@ +_defaultDojoType) + ) { + require_once 'Zend/Dojo/View/Exception.php'; + throw new Zend_Dojo_View_Exception('No dojoType specified; cannot create dijit'); + } elseif (array_key_exists('dojoType', $params)) { + $this->_dijit = $params['dojoType']; + $this->_module = $params['dojoType']; + unset($params['dojoType']); + } else { + $this->_dijit = $this->_defaultDojoType; + $this->_module = $this->_defaultDojoType; + } + + if (array_key_exists('rootNode', $params)) { + $this->setRootNode($params['rootNode']); + unset($params['rootNode']); + } + + return $this->_createLayoutContainer($id, $value, $params, $attribs); + } + + /** + * Begin capturing content. + * + * Requires that either the {@link $_defaultDojotype} property is set, or + * that you pass a value to the "dojoType" key of the $params argument. + * + * @param string $id + * @param array $params + * @param array $attribs + * @return void + */ + public function captureStart($id, array $params = array(), array $attribs = array()) + { + if (!array_key_exists('dojoType', $params) + && (null === $this->_defaultDojoType) + ) { + require_once 'Zend/Dojo/View/Exception.php'; + throw new Zend_Dojo_View_Exception('No dojoType specified; cannot create dijit'); + } elseif (array_key_exists('dojoType', $params)) { + $this->_dijit = $params['dojoType']; + $this->_module = $params['dojoType']; + unset($params['dojoType']); + } else { + $this->_dijit = $this->_defaultDojoType; + $this->_module = $this->_defaultDojoType; + } + + return parent::captureStart($id, $params, $attribs); + } +} diff --git a/library/Zend/Dojo/View/Helper/DateTextBox.php b/library/Zend/Dojo/View/Helper/DateTextBox.php new file mode 100644 index 000000000..8a6e0ee90 --- /dev/null +++ b/library/Zend/Dojo/View/Helper/DateTextBox.php @@ -0,0 +1,68 @@ +_createFormElement($id, $value, $params, $attribs); + } +} diff --git a/library/Zend/Dojo/View/Helper/Dijit.php b/library/Zend/Dojo/View/Helper/Dijit.php new file mode 100644 index 000000000..98ad3f698 --- /dev/null +++ b/library/Zend/Dojo/View/Helper/Dijit.php @@ -0,0 +1,344 @@ +dojo = $this->view->dojo(); + $this->dojo->enable(); + return $this; + } + + + /** + * Get root node type + * + * @return string + */ + public function getRootNode() + { + return $this->_rootNode; + } + + /** + * Set root node type + * + * @param string $value + * @return Zend_Dojo_View_Helper_Dijit + */ + public function setRootNode($value) + { + $this->_rootNode = $value; + return $this; + } + + /** + * Whether or not to use declarative dijit creation + * + * @return bool + */ + protected function _useDeclarative() + { + return Zend_Dojo_View_Helper_Dojo::useDeclarative(); + } + + /** + * Whether or not to use programmatic dijit creation + * + * @return bool + */ + protected function _useProgrammatic() + { + return Zend_Dojo_View_Helper_Dojo::useProgrammatic(); + } + + /** + * Whether or not to use programmatic dijit creation w/o script creation + * + * @return bool + */ + protected function _useProgrammaticNoScript() + { + return Zend_Dojo_View_Helper_Dojo::useProgrammaticNoScript(); + } + + /** + * Create a layout container + * + * @param int $id + * @param string $content + * @param array $params + * @param array $attribs + * @param string|null $dijit + * @return string + */ + protected function _createLayoutContainer($id, $content, array $params, array $attribs, $dijit = null) + { + $attribs['id'] = $id; + $attribs = $this->_prepareDijit($attribs, $params, 'layout', $dijit); + + $nodeType = $this->getRootNode(); + $html = '<' . $nodeType . $this->_htmlAttribs($attribs) . '>' + . $content + . "\n"; + + return $html; + } + + /** + * Create HTML representation of a dijit form element + * + * @param string $id + * @param string $value + * @param array $params + * @param array $attribs + * @param string|null $dijit + * @return string + */ + public function _createFormElement($id, $value, array $params, array $attribs, $dijit = null) + { + if (!array_key_exists('id', $attribs)) { + $attribs['id'] = $id; + } + $attribs['name'] = $id; + $attribs['value'] = (string) $value; + $attribs['type'] = $this->_elementType; + + $attribs = $this->_prepareDijit($attribs, $params, 'element', $dijit); + + $html = '_htmlAttribs($attribs) + . $this->getClosingBracket(); + return $html; + } + + /** + * Merge attributes and parameters + * + * Also sets up requires + * + * @param array $attribs + * @param array $params + * @param string $type + * @param string $dijit Dijit type to use (otherwise, pull from $_dijit) + * @return array + */ + protected function _prepareDijit(array $attribs, array $params, $type, $dijit = null) + { + $this->dojo->requireModule($this->_module); + + switch ($type) { + case 'layout': + $stripParams = array('id'); + break; + case 'element': + $stripParams = array('id', 'name', 'value', 'type'); + foreach (array('checked', 'disabled', 'readonly') as $attrib) { + if (array_key_exists($attrib, $attribs)) { + if ($attribs[$attrib]) { + $attribs[$attrib] = $attrib; + } else { + unset($attribs[$attrib]); + } + } + } + break; + case 'textarea': + $stripParams = array('id', 'name', 'type', 'degrade'); + break; + default: + } + + foreach ($stripParams as $param) { + if (array_key_exists($param, $params)) { + unset($params[$param]); + } + } + + // Normalize constraints, if present + foreach ($this->_jsonParams as $param) { + if (array_key_exists($param, $params)) { + require_once 'Zend/Json.php'; + + if (is_array($params[$param])) { + $values = array(); + foreach ($params[$param] as $key => $value) { + if (!is_scalar($value)) { + continue; + } + $values[$key] = $value; + } + } elseif (is_string($params[$param])) { + $values = (array) $params[$param]; + } else { + $values = array(); + } + $values = Zend_Json::encode($values); + if ($this->_useDeclarative()) { + $values = str_replace('"', "'", $values); + } + $params[$param] = $values; + } + } + + $dijit = (null === $dijit) ? $this->_dijit : $dijit; + if ($this->_useDeclarative()) { + $attribs = array_merge($attribs, $params); + if (isset($attribs['required'])) { + $attribs['required'] = ($attribs['required']) ? 'true' : 'false'; + } + $attribs['dojoType'] = $dijit; + } elseif (!$this->_useProgrammaticNoScript()) { + $this->_createDijit($dijit, $attribs['id'], $params); + } + + return $attribs; + } + + /** + * Create a dijit programmatically + * + * @param string $dijit + * @param string $id + * @param array $params + * @return void + */ + protected function _createDijit($dijit, $id, array $params) + { + $params['dojoType'] = $dijit; + + array_walk_recursive($params, array($this, '_castBoolToString')); + + $this->dojo->setDijit($id, $params); + } + + /** + * Cast a boolean to a string value + * + * @param mixed $item + * @param string $key + * @return void + */ + protected function _castBoolToString(&$item, $key) + { + if (!is_bool($item)) { + return; + } + $item = ($item) ? "true" : "false"; + } + + /** + * Render a hidden element to hold a value + * + * @param string $id + * @param string|int|float $value + * @return string + */ + protected function _renderHiddenElement($id, $value) + { + $hiddenAttribs = array( + 'name' => $id, + 'value' => (string) $value, + 'type' => 'hidden', + ); + return '_htmlAttribs($hiddenAttribs) . $this->getClosingBracket(); + } + + /** + * Create JS function for retrieving parent form + * + * @return void + */ + protected function _createGetParentFormFunction() + { + $function =<<dojo->addJavascript($function); + } +} diff --git a/library/Zend/Dojo/View/Helper/DijitContainer.php b/library/Zend/Dojo/View/Helper/DijitContainer.php new file mode 100644 index 000000000..94022bdef --- /dev/null +++ b/library/Zend/Dojo/View/Helper/DijitContainer.php @@ -0,0 +1,92 @@ +_captureLock)) { + require_once 'Zend/Dojo/View/Exception.php'; + throw new Zend_Dojo_View_Exception(sprintf('Lock already exists for id "%s"', $id)); + } + + $this->_captureLock[$id] = true; + $this->_captureInfo[$id] = array( + 'params' => $params, + 'attribs' => $attribs, + ); + + ob_start(); + return; + } + + /** + * Finish capturing content for layout container + * + * @param string $id + * @return string + */ + public function captureEnd($id) + { + if (!array_key_exists($id, $this->_captureLock)) { + require_once 'Zend/Dojo/View/Exception.php'; + throw new Zend_Dojo_View_Exception(sprintf('No capture lock exists for id "%s"; nothing to capture', $id)); + } + + $content = ob_get_clean(); + extract($this->_captureInfo[$id]); + unset($this->_captureLock[$id], $this->_captureInfo[$id]); + return $this->_createLayoutContainer($id, $content, $params, $attribs); + } +} diff --git a/library/Zend/Dojo/View/Helper/Dojo.php b/library/Zend/Dojo/View/Helper/Dojo.php new file mode 100644 index 000000000..597e6a3fa --- /dev/null +++ b/library/Zend/Dojo/View/Helper/Dojo.php @@ -0,0 +1,176 @@ +_container = $registry[__CLASS__]; + } + + /** + * Set view object + * + * @param Zend_Dojo_View_Interface $view + * @return void + */ + public function setView(Zend_View_Interface $view) + { + $this->view = $view; + $this->_container->setView($view); + } + + /** + * Return dojo container + * + * @return Zend_Dojo_View_Helper_Dojo_Container + */ + public function dojo() + { + return $this->_container; + } + + /** + * Proxy to container methods + * + * @param string $method + * @param array $args + * @return mixed + * @throws Zend_Dojo_View_Exception For invalid method calls + */ + public function __call($method, $args) + { + if (!method_exists($this->_container, $method)) { + require_once 'Zend/Dojo/View/Exception.php'; + throw new Zend_Dojo_View_Exception(sprintf('Invalid method "%s" called on dojo view helper', $method)); + } + + return call_user_func_array(array($this->_container, $method), $args); + } + + /** + * Set whether or not dijits should be created declaratively + * + * @return void + */ + public static function setUseDeclarative() + { + self::$_useProgrammatic = false; + } + + /** + * Set whether or not dijits should be created programmatically + * + * Optionally, specifiy whether or not dijit helpers should generate the + * programmatic dojo. + * + * @param int $style + * @return void + */ + public static function setUseProgrammatic($style = self::PROGRAMMATIC_SCRIPT) + { + if (!in_array($style, array(self::PROGRAMMATIC_SCRIPT, self::PROGRAMMATIC_NOSCRIPT))) { + $style = self::PROGRAMMATIC_SCRIPT; + } + self::$_useProgrammatic = $style; + } + + /** + * Should dijits be created declaratively? + * + * @return bool + */ + public static function useDeclarative() + { + return (false === self::$_useProgrammatic); + } + + /** + * Should dijits be created programmatically? + * + * @return bool + */ + public static function useProgrammatic() + { + return (false !== self::$_useProgrammatic); + } + + /** + * Should dijits be created programmatically but without scripts? + * + * @return bool + */ + public static function useProgrammaticNoScript() + { + return (self::PROGRAMMATIC_NOSCRIPT === self::$_useProgrammatic); + } +} diff --git a/library/Zend/Dojo/View/Helper/Dojo/Container.php b/library/Zend/Dojo/View/Helper/Dojo/Container.php new file mode 100644 index 000000000..931f96089 --- /dev/null +++ b/library/Zend/Dojo/View/Helper/Dojo/Container.php @@ -0,0 +1,1199 @@ +view = $view; + } + + /** + * Enable dojo + * + * @return Zend_Dojo_View_Helper_Dojo_Container + */ + public function enable() + { + $this->_enabled = true; + return $this; + } + + /** + * Disable dojo + * + * @return Zend_Dojo_View_Helper_Dojo_Container + */ + public function disable() + { + $this->_enabled = false; + return $this; + } + + /** + * Is dojo enabled? + * + * @return bool + */ + public function isEnabled() + { + return $this->_enabled; + } + + /** + * Add options for the Dojo Container to use + * + * @param array|Zend_Config Array or Zend_Config object with options to use + * @return Zend_Dojo_View_Helper_Dojo_Container + */ + public function setOptions($options) + { + if($options instanceof Zend_Config) { + $options = $options->toArray(); + } + + foreach($options as $key => $value) { + $key = strtolower($key); + switch($key) { + case 'requiremodules': + $this->requireModule($value); + break; + case 'modulepaths': + foreach($value as $module => $path) { + $this->registerModulePath($module, $path); + } + break; + case 'layers': + $value = (array) $value; + foreach($value as $layer) { + $this->addLayer($layer); + } + break; + case 'cdnbase': + $this->setCdnBase($value); + break; + case 'cdnversion': + $this->setCdnVersion($value); + break; + case 'cdndojopath': + $this->setCdnDojoPath($value); + break; + case 'localpath': + $this->setLocalPath($value); + break; + case 'djconfig': + $this->setDjConfig($value); + break; + case 'stylesheetmodules': + $value = (array) $value; + foreach($value as $module) { + $this->addStylesheetModule($module); + } + break; + case 'stylesheets': + $value = (array) $value; + foreach($value as $stylesheet) { + $this->addStylesheet($stylesheet); + } + break; + case 'registerdojostylesheet': + $this->registerDojoStylesheet($value); + break; + } + } + + return $this; + } + + /** + * Specify one or multiple modules to require + * + * @param string|array $modules + * @return Zend_Dojo_View_Helper_Dojo_Container + */ + public function requireModule($modules) + { + if (!is_string($modules) && !is_array($modules)) { + require_once 'Zend/Dojo/View/Exception.php'; + throw new Zend_Dojo_View_Exception('Invalid module name specified; must be a string or an array of strings'); + } + + $modules = (array) $modules; + + foreach ($modules as $mod) { + if (!preg_match('/^[a-z][a-z0-9._-]+$/i', $mod)) { + require_once 'Zend/Dojo/View/Exception.php'; + throw new Zend_Dojo_View_Exception(sprintf('Module name specified, "%s", contains invalid characters', (string) $mod)); + } + + if (!in_array($mod, $this->_modules)) { + $this->_modules[] = $mod; + } + } + + return $this; + } + + /** + * Retrieve list of modules to require + * + * @return array + */ + public function getModules() + { + return $this->_modules; + } + + /** + * Register a module path + * + * @param string $module The module to register a path for + * @param string $path The path to register for the module + * @return Zend_Dojo_View_Helper_Dojo_Container + */ + public function registerModulePath($module, $path) + { + $path = (string) $path; + if (!in_array($module, $this->_modulePaths)) { + $this->_modulePaths[$module] = $path; + } + + return $this; + } + + /** + * List registered module paths + * + * @return array + */ + public function getModulePaths() + { + return $this->_modulePaths; + } + + /** + * Add layer (custom build) path + * + * @param string $path + * @return Zend_Dojo_View_Helper_Dojo_Container + */ + public function addLayer($path) + { + $path = (string) $path; + if (!in_array($path, $this->_layers)) { + $this->_layers[] = $path; + } + + return $this; + } + + /** + * Get registered layers + * + * @return array + */ + public function getLayers() + { + return $this->_layers; + } + + /** + * Remove a registered layer + * + * @param string $path + * @return Zend_Dojo_View_Helper_Dojo_Container + */ + public function removeLayer($path) + { + $path = (string) $path; + $layers = array_flip($this->_layers); + if (array_key_exists($path, $layers)) { + unset($layers[$path]); + $this->_layers = array_keys($layers); + } + return $this; + } + + /** + * Clear all registered layers + * + * @return Zend_Dojo_View_Helper_Dojo_Container + */ + public function clearLayers() + { + $this->_layers = array(); + return $this; + } + + /** + * Set CDN base path + * + * @param string $url + * @return Zend_Dojo_View_Helper_Dojo_Container + */ + public function setCdnBase($url) + { + $this->_cdnBase = (string) $url; + return $this; + } + + /** + * Return CDN base URL + * + * @return string + */ + public function getCdnBase() + { + return $this->_cdnBase; + } + + /** + * Use CDN, using version specified + * + * @param string $version + * @return Zend_Dojo_View_Helper_Dojo_Container + */ + public function setCdnVersion($version = null) + { + $this->enable(); + if (preg_match('/^[1-9]\.[0-9](\.[0-9])?$/', $version)) { + $this->_cdnVersion = $version; + } + return $this; + } + + /** + * Get CDN version + * + * @return string + */ + public function getCdnVersion() + { + return $this->_cdnVersion; + } + + /** + * Set CDN path to dojo (relative to CDN base + version) + * + * @param string $path + * @return Zend_Dojo_View_Helper_Dojo_Container + */ + public function setCdnDojoPath($path) + { + $this->_cdnDojoPath = (string) $path; + return $this; + } + + /** + * Get CDN path to dojo (relative to CDN base + version) + * + * @return string + */ + public function getCdnDojoPath() + { + return $this->_cdnDojoPath; + } + + /** + * Are we using the CDN? + * + * @return bool + */ + public function useCdn() + { + return !$this->useLocalPath(); + } + + /** + * Set path to local dojo + * + * @param string $path + * @return Zend_Dojo_View_Helper_Dojo_Container + */ + public function setLocalPath($path) + { + $this->enable(); + $this->_localPath = (string) $path; + return $this; + } + + /** + * Get local path to dojo + * + * @return string + */ + public function getLocalPath() + { + return $this->_localPath; + } + + /** + * Are we using a local path? + * + * @return bool + */ + public function useLocalPath() + { + return (null === $this->_localPath) ? false : true; + } + + /** + * Set Dojo configuration + * + * @param string $option + * @param mixed $value + * @return Zend_Dojo_View_Helper_Dojo_Container + */ + public function setDjConfig(array $config) + { + $this->_djConfig = $config; + return $this; + } + + /** + * Set Dojo configuration option + * + * @param string $option + * @param mixed $value + * @return Zend_Dojo_View_Helper_Dojo_Container + */ + public function setDjConfigOption($option, $value) + { + $option = (string) $option; + $this->_djConfig[$option] = $value; + return $this; + } + + /** + * Retrieve dojo configuration values + * + * @return array + */ + public function getDjConfig() + { + return $this->_djConfig; + } + + /** + * Get dojo configuration value + * + * @param string $option + * @param mixed $default + * @return mixed + */ + public function getDjConfigOption($option, $default = null) + { + $option = (string) $option; + if (array_key_exists($option, $this->_djConfig)) { + return $this->_djConfig[$option]; + } + return $default; + } + + /** + * Add a stylesheet by module name + * + * @param string $module + * @return Zend_Dojo_View_Helper_Dojo_Container + */ + public function addStylesheetModule($module) + { + if (!preg_match('/^[a-z0-9]+\.[a-z0-9_-]+(\.[a-z0-9_-]+)*$/i', $module)) { + require_once 'Zend/Dojo/View/Exception.php'; + throw new Zend_Dojo_View_Exception('Invalid stylesheet module specified'); + } + if (!in_array($module, $this->_stylesheetModules)) { + $this->_stylesheetModules[] = $module; + } + return $this; + } + + /** + * Get all stylesheet modules currently registered + * + * @return array + */ + public function getStylesheetModules() + { + return $this->_stylesheetModules; + } + + /** + * Add a stylesheet + * + * @param string $path + * @return Zend_Dojo_View_Helper_Dojo_Container + */ + public function addStylesheet($path) + { + $path = (string) $path; + if (!in_array($path, $this->_stylesheets)) { + $this->_stylesheets[] = (string) $path; + } + return $this; + } + + /** + * Register the dojo.css stylesheet? + * + * With no arguments, returns the status of the flag; with arguments, sets + * the flag and returns the object. + * + * @param null|bool $flag + * @return Zend_Dojo_View_Helper_Dojo_Container|bool + */ + public function registerDojoStylesheet($flag = null) + { + if (null === $flag) { + return $this->_registerDojoStylesheet; + } + + $this->_registerDojoStylesheet = (bool) $flag; + return $this; + } + + /** + * Retrieve registered stylesheets + * + * @return array + */ + public function getStylesheets() + { + return $this->_stylesheets; + } + + /** + * Add a script to execute onLoad + * + * dojo.addOnLoad accepts: + * - function name + * - lambda + * + * @param string $callback Lambda + * @return Zend_Dojo_View_Helper_Dojo_Container + */ + public function addOnLoad($callback) + { + if (!in_array($callback, $this->_onLoadActions, true)) { + $this->_onLoadActions[] = $callback; + } + return $this; + } + + /** + * Prepend an onLoad event to the list of onLoad actions + * + * @param string $callback Lambda + * @return Zend_Dojo_View_Helper_Dojo_Container + */ + public function prependOnLoad($callback) + { + if (!in_array($callback, $this->_onLoadActions, true)) { + array_unshift($this->_onLoadActions, $callback); + } + return $this; + } + + /** + * Retrieve all registered onLoad actions + * + * @return array + */ + public function getOnLoadActions() + { + return $this->_onLoadActions; + } + + /** + * Start capturing routines to run onLoad + * + * @return bool + */ + public function onLoadCaptureStart() + { + if ($this->_captureLock) { + require_once 'Zend/Dojo/View/Exception.php'; + throw new Zend_Dojo_View_Exception('Cannot nest onLoad captures'); + } + + $this->_captureLock = true; + ob_start(); + return; + } + + /** + * Stop capturing routines to run onLoad + * + * @return bool + */ + public function onLoadCaptureEnd() + { + $data = ob_get_clean(); + $this->_captureLock = false; + + $this->addOnLoad($data); + return true; + } + + /** + * Add a programmatic dijit + * + * @param string $id + * @param array $params + * @return Zend_Dojo_View_Helper_Dojo_Container + */ + public function addDijit($id, array $params) + { + if (array_key_exists($id, $this->_dijits)) { + require_once 'Zend/Dojo/View/Exception.php'; + throw new Zend_Dojo_View_Exception(sprintf('Duplicate dijit with id "%s" already registered', $id)); + } + + $this->_dijits[$id] = array( + 'id' => $id, + 'params' => $params, + ); + + return $this; + } + + /** + * Set a programmatic dijit (overwrites) + * + * @param string $id + * @param array $params + * @return Zend_Dojo_View_Helper_Dojo_Container + */ + public function setDijit($id, array $params) + { + $this->removeDijit($id); + return $this->addDijit($id, $params); + } + + /** + * Add multiple dijits at once + * + * Expects an array of id => array $params pairs + * + * @param array $dijits + * @return Zend_Dojo_View_Helper_Dojo_Container + */ + public function addDijits(array $dijits) + { + foreach ($dijits as $id => $params) { + $this->addDijit($id, $params); + } + return $this; + } + + /** + * Set multiple dijits at once (overwrites) + * + * Expects an array of id => array $params pairs + * + * @param array $dijits + * @return Zend_Dojo_View_Helper_Dojo_Container + */ + public function setDijits(array $dijits) + { + $this->clearDijits(); + return $this->addDijits($dijits); + } + + /** + * Is the given programmatic dijit already registered? + * + * @param string $id + * @return bool + */ + public function hasDijit($id) + { + return array_key_exists($id, $this->_dijits); + } + + /** + * Retrieve a dijit by id + * + * @param string $id + * @return array|null + */ + public function getDijit($id) + { + if ($this->hasDijit($id)) { + return $this->_dijits[$id]['params']; + } + return null; + } + + /** + * Retrieve all dijits + * + * Returns dijits as an array of assoc arrays + * + * @return array + */ + public function getDijits() + { + return array_values($this->_dijits); + } + + /** + * Remove a programmatic dijit if it exists + * + * @param string $id + * @return Zend_Dojo_View_Helper_Dojo_Container + */ + public function removeDijit($id) + { + if (array_key_exists($id, $this->_dijits)) { + unset($this->_dijits[$id]); + } + + return $this; + } + + /** + * Clear all dijits + * + * @return Zend_Dojo_View_Helper_Dojo_Container + */ + public function clearDijits() + { + $this->_dijits = array(); + return $this; + } + + /** + * Render dijits as JSON structure + * + * @return string + */ + public function dijitsToJson() + { + require_once 'Zend/Json.php'; + return Zend_Json::encode($this->getDijits()); + } + + /** + * Create dijit loader functionality + * + * @return void + */ + public function registerDijitLoader() + { + if (!$this->_dijitLoaderRegistered) { + $js =<<requireModule('dojo.parser'); + $this->_addZendLoad($js); + $this->addJavascript('var zendDijits = ' . $this->dijitsToJson() . ';'); + $this->_dijitLoaderRegistered = true; + } + } + + /** + * Add arbitrary javascript to execute in dojo JS container + * + * @param string $js + * @return Zend_Dojo_View_Helper_Dojo_Container + */ + public function addJavascript($js) + { + $js = preg_replace('/^\s*(.*?)\s*$/s', '$1', $js); + if (!in_array(substr($js, -1), array(';', '}'))) { + $js .= ';'; + } + + if (in_array($js, $this->_javascriptStatements)) { + return $this; + } + + $this->_javascriptStatements[] = $js; + return $this; + } + + /** + * Return all registered javascript statements + * + * @return array + */ + public function getJavascript() + { + return $this->_javascriptStatements; + } + + /** + * Clear arbitrary javascript stack + * + * @return Zend_Dojo_View_Helper_Dojo_Container + */ + public function clearJavascript() + { + $this->_javascriptStatements = array(); + return $this; + } + + /** + * Capture arbitrary javascript to include in dojo script + * + * @return void + */ + public function javascriptCaptureStart() + { + if ($this->_captureLock) { + require_once 'Zend/Dojo/View/Exception.php'; + throw new Zend_Dojo_View_Exception('Cannot nest captures'); + } + + $this->_captureLock = true; + ob_start(); + return; + } + + /** + * Finish capturing arbitrary javascript to include in dojo script + * + * @return true + */ + public function javascriptCaptureEnd() + { + $data = ob_get_clean(); + $this->_captureLock = false; + + $this->addJavascript($data); + return true; + } + + /** + * String representation of dojo environment + * + * @return string + */ + public function __toString() + { + if (!$this->isEnabled()) { + return ''; + } + + $this->_isXhtml = $this->view->doctype()->isXhtml(); + + if (Zend_Dojo_View_Helper_Dojo::useDeclarative()) { + if (null === $this->getDjConfigOption('parseOnLoad')) { + $this->setDjConfigOption('parseOnLoad', true); + } + } + + if (!empty($this->_dijits)) { + $this->registerDijitLoader(); + } + + $html = $this->_renderStylesheets() . PHP_EOL + . $this->_renderDjConfig() . PHP_EOL + . $this->_renderDojoScriptTag() . PHP_EOL + . $this->_renderLayers() . PHP_EOL + . $this->_renderExtras(); + return $html; + } + + /** + * Retrieve local path to dojo resources for building relative paths + * + * @return string + */ + protected function _getLocalRelativePath() + { + if (null === $this->_localRelativePath) { + $localPath = $this->getLocalPath(); + $localPath = preg_replace('|[/\\\\]dojo[/\\\\]dojo.js[^/\\\\]*$|i', '', $localPath); + $this->_localRelativePath = $localPath; + } + return $this->_localRelativePath; + } + + /** + * Render dojo stylesheets + * + * @return string + */ + protected function _renderStylesheets() + { + if ($this->useCdn()) { + $base = $this->getCdnBase() + . $this->getCdnVersion(); + } else { + $base = $this->_getLocalRelativePath(); + } + + $registeredStylesheets = $this->getStylesheetModules(); + foreach ($registeredStylesheets as $stylesheet) { + $themeName = substr($stylesheet, strrpos($stylesheet, '.') + 1); + $stylesheet = str_replace('.', '/', $stylesheet); + $stylesheets[] = $base . '/' . $stylesheet . '/' . $themeName . '.css'; + } + + foreach ($this->getStylesheets() as $stylesheet) { + $stylesheets[] = $stylesheet; + } + + if ($this->_registerDojoStylesheet) { + $stylesheets[] = $base . '/dojo/resources/dojo.css'; + } + + if (empty($stylesheets)) { + return ''; + } + + array_reverse($stylesheets); + $style = ''; + + return $style; + } + + /** + * Render DjConfig values + * + * @return string + */ + protected function _renderDjConfig() + { + $djConfigValues = $this->getDjConfig(); + if (empty($djConfigValues)) { + return ''; + } + + require_once 'Zend/Json.php'; + $scriptTag = ''; + + return $scriptTag; + } + + /** + * Render dojo script tag + * + * Renders Dojo script tag by utilizing either local path provided or the + * CDN. If any djConfig values were set, they will be serialized and passed + * with that attribute. + * + * @return string + */ + protected function _renderDojoScriptTag() + { + if ($this->useCdn()) { + $source = $this->getCdnBase() + . $this->getCdnVersion() + . $this->getCdnDojoPath(); + } else { + $source = $this->getLocalPath(); + } + + $scriptTag = ''; + return $scriptTag; + } + + /** + * Render layers (custom builds) as script tags + * + * @return string + */ + protected function _renderLayers() + { + $layers = $this->getLayers(); + if (empty($layers)) { + return ''; + } + + $enc = 'UTF-8'; + if ($this->view instanceof Zend_View_Interface + && method_exists($this->view, 'getEncoding') + ) { + $enc = $this->view->getEncoding(); + } + + $html = array(); + foreach ($layers as $path) { + $html[] = sprintf( + '', + htmlspecialchars($path, ENT_QUOTES, $enc) + ); + } + + return implode("\n", $html); + } + + /** + * Render dojo module paths and requires + * + * @return string + */ + protected function _renderExtras() + { + $js = array(); + $modulePaths = $this->getModulePaths(); + if (!empty($modulePaths)) { + foreach ($modulePaths as $module => $path) { + $js[] = 'dojo.registerModulePath("' . $this->view->escape($module) . '", "' . $this->view->escape($path) . '");'; + } + } + + $modules = $this->getModules(); + if (!empty($modules)) { + foreach ($modules as $module) { + $js[] = 'dojo.require("' . $this->view->escape($module) . '");'; + } + } + + $onLoadActions = array(); + // Get Zend specific onLoad actions; these will always be first to + // ensure that dijits are created in the correct order + foreach ($this->_getZendLoadActions() as $callback) { + $onLoadActions[] = 'dojo.addOnLoad(' . $callback . ');'; + } + + // Get all other onLoad actions + foreach ($this->getOnLoadActions() as $callback) { + $onLoadActions[] = 'dojo.addOnLoad(' . $callback . ');'; + } + + $javascript = implode("\n ", $this->getJavascript()); + + $content = ''; + if (!empty($js)) { + $content .= implode("\n ", $js) . "\n"; + } + + if (!empty($onLoadActions)) { + $content .= implode("\n ", $onLoadActions) . "\n"; + } + + if (!empty($javascript)) { + $content .= $javascript . "\n"; + } + + if (preg_match('/^\s*$/s', $content)) { + return ''; + } + + $html = ''; + return $html; + } + + /** + * Add an onLoad action related to ZF dijit creation + * + * This method is public, but prefixed with an underscore to indicate that + * it should not normally be called by userland code. It is pertinent to + * ensuring that the correct order of operations occurs during dijit + * creation. + * + * @param string $callback + * @return Zend_Dojo_View_Helper_Dojo_Container + */ + public function _addZendLoad($callback) + { + if (!in_array($callback, $this->_zendLoadActions, true)) { + $this->_zendLoadActions[] = $callback; + } + return $this; + } + + /** + * Retrieve all ZF dijit callbacks + * + * @return array + */ + public function _getZendLoadActions() + { + return $this->_zendLoadActions; + } +} diff --git a/library/Zend/Dojo/View/Helper/Editor.php b/library/Zend/Dojo/View/Helper/Editor.php new file mode 100644 index 000000000..dfe591609 --- /dev/null +++ b/library/Zend/Dojo/View/Helper/Editor.php @@ -0,0 +1,187 @@ + 'LinkDialog', + 'insertImage' => 'LinkDialog', + 'fontName' => 'FontChoice', + 'fontSize' => 'FontChoice', + 'formatBlock' => 'FontChoice', + 'foreColor' => 'TextColor', + 'hiliteColor' => 'TextColor' + ); + + /** + * JSON-encoded parameters + * @var array + */ + protected $_jsonParams = array('captureEvents', 'events', 'plugins'); + + /** + * dijit.Editor + * + * @param string $id + * @param string $value + * @param array $params + * @param array $attribs + * @return string + */ + public function editor($id, $value = null, $params = array(), $attribs = array()) + { + if (isset($params['plugins'])) { + foreach ($this->_getRequiredModules($params['plugins']) as $module) { + $this->dojo->requireModule($module); + } + } + + // Previous versions allowed specifying "degrade" to allow using a + // textarea instead of a div -- but this is insecure. Removing the + // parameter if set to prevent its injection in the dijit. + if (isset($params['degrade'])) { + unset($params['degrade']); + } + + $hiddenName = $id; + if (array_key_exists('id', $attribs)) { + $hiddenId = $attribs['id']; + } else { + $hiddenId = $hiddenName; + } + $hiddenId = $this->_normalizeId($hiddenId); + + $textareaName = $this->_normalizeEditorName($hiddenName); + $textareaId = $hiddenId . '-Editor'; + + $hiddenAttribs = array( + 'id' => $hiddenId, + 'name' => $hiddenName, + 'value' => $value, + 'type' => 'hidden', + ); + $attribs['id'] = $textareaId; + + $this->_createGetParentFormFunction(); + $this->_createEditorOnSubmit($hiddenId, $textareaId); + + $attribs = $this->_prepareDijit($attribs, $params, 'textarea'); + + $html = '_htmlAttribs($hiddenAttribs) . $this->getClosingBracket(); + $html .= '_htmlAttribs($attribs) . '>' + . $value + . "\n"; + + // Embed a textarea in a
    '; + * } + * if ($Day->isEmpty()) { + * echo ''; + * } else { + * echo ''; + * } + * if ($Day->isLast()) { + * echo ''; + * } + * } + * + * + * @category Date and Time + * @package Calendar + * @author Harry Fuecks + * @copyright 2003-2007 Harry Fuecks + * @license http://www.debian.org/misc/bsd.license BSD License (3 Clause) + * @link http://pear.php.net/package/Calendar + * @access public + */ +class Calendar_Month_Weekdays extends Calendar_Month +{ + /** + * Instance of Calendar_Table_Helper + * @var Calendar_Table_Helper + * @access private + */ + var $tableHelper; + + /** + * First day of the week + * @access private + * @var string + */ + var $firstDay; + + /** + * Constructs Calendar_Month_Weekdays + * + * @param int $y year e.g. 2003 + * @param int $m month e.g. 5 + * @param int $firstDay (optional) first day of week (e.g. 0 for Sunday, 2 for Tuesday etc.) + * + * @access public + */ + function Calendar_Month_Weekdays($y, $m, $firstDay=null) + { + parent::Calendar_Month($y, $m, $firstDay); + } + + /** + * Builds Day objects in tabular form, to allow display of calendar month + * with empty cells if the first day of the week does not fall on the first + * day of the month. + * + * @param array $sDates (optional) Calendar_Day objects representing selected dates + * + * @return boolean + * @access public + * @see Calendar_Day::isEmpty() + * @see Calendar_Day_Base::isFirst() + * @see Calendar_Day_Base::isLast() + */ + function build($sDates = array()) + { + include_once CALENDAR_ROOT.'Table/Helper.php'; + $this->tableHelper = new Calendar_Table_Helper($this, $this->firstDay); + Calendar_Month::build($sDates); + $this->buildEmptyDaysBefore(); + $this->shiftDays(); + $this->buildEmptyDaysAfter(); + $this->setWeekMarkers(); + return true; + } + + /** + * Prepends empty days before the real days in the month + * + * @return void + * @access private + */ + function buildEmptyDaysBefore() + { + $eBefore = $this->tableHelper->getEmptyDaysBefore(); + for ($i=0; $i < $eBefore; $i++) { + $stamp = $this->cE->dateToStamp($this->year, $this->month, -$i); + $Day = new Calendar_Day( + $this->cE->stampToYear($stamp), + $this->cE->stampToMonth($stamp), + $this->cE->stampToDay($stamp)); + $Day->setEmpty(); + $Day->adjust(); + array_unshift($this->children, $Day); + } + } + + /** + * Shifts the array of children forward, if necessary + * + * @return void + * @access private + */ + function shiftDays() + { + if (isset($this->children[0])) { + array_unshift($this->children, null); + unset($this->children[0]); + } + } + + /** + * Appends empty days after the real days in the month + * + * @return void + * @access private + */ + function buildEmptyDaysAfter() + { + $eAfter = $this->tableHelper->getEmptyDaysAfter(); + $sDOM = $this->tableHelper->getNumTableDaysInMonth(); + for ($i=1; $i <= $sDOM-$eAfter; $i++) { + $Day = new Calendar_Day($this->year, $this->month+1, $i); + $Day->setEmpty(); + $Day->adjust(); + array_push($this->children, $Day); + } + } + + /** + * Sets the "markers" for the beginning and of a of week, in the + * built Calendar_Day children + * + * @return void + * @access private + */ + function setWeekMarkers() + { + $dIW = $this->cE->getDaysInWeek( + $this->thisYear(), + $this->thisMonth(), + $this->thisDay() + ); + $sDOM = $this->tableHelper->getNumTableDaysInMonth(); + for ($i=1; $i <= $sDOM; $i+= $dIW) { + $this->children[$i]->setFirst(); + $this->children[$i+($dIW-1)]->setLast(); + } + } +} +?> \ No newline at end of file diff --git a/library/pear/Calendar/Month/Weeks.php b/library/pear/Calendar/Month/Weeks.php new file mode 100644 index 000000000..aab651392 --- /dev/null +++ b/library/pear/Calendar/Month/Weeks.php @@ -0,0 +1,166 @@ + + * @author Lorenzo Alberton + * @copyright 2003-2007 Harry Fuecks, Lorenzo Alberton + * @license http://www.debian.org/misc/bsd.license BSD License (3 Clause) + * @version CVS: $Id: Weeks.php 300729 2010-06-24 12:05:53Z quipo $ + * @link http://pear.php.net/package/Calendar + */ + +/** + * Allows Calendar include path to be redefined + * @ignore + */ +if (!defined('CALENDAR_ROOT')) { + define('CALENDAR_ROOT', 'Calendar'.DIRECTORY_SEPARATOR); +} + +/** + * Load Calendar base class + */ +require_once CALENDAR_ROOT.'Calendar.php'; + +/** + * Load base month + */ +require_once CALENDAR_ROOT.'Month.php'; + +/** + * Represents a Month and builds Weeks + * + * require_once 'Calendar'.DIRECTORY_SEPARATOR.'Month'.DIRECTORY_SEPARATOR.'Weeks.php'; + * $Month = new Calendar_Month_Weeks(2003, 10); // Oct 2003 + * $Month->build(); // Build Calendar_Day objects + * while ($Week = & $Month->fetch()) { + * echo $Week->thisWeek().'
    '; + * } + *
    + * + * @category Date and Time + * @package Calendar + * @author Harry Fuecks + * @author Lorenzo Alberton + * @copyright 2003-2007 Harry Fuecks, Lorenzo Alberton + * @license http://www.debian.org/misc/bsd.license BSD License (3 Clause) + * @link http://pear.php.net/package/Calendar + * @access public + */ +class Calendar_Month_Weeks extends Calendar_Month +{ + /** + * Instance of Calendar_Table_Helper + * @var Calendar_Table_Helper + * @access private + */ + var $tableHelper; + + /** + * First day of the week + * @access private + * @var string + */ + var $firstDay; + + /** + * Constructs Calendar_Month_Weeks + * + * @param int $y year e.g. 2003 + * @param int $m month e.g. 5 + * @param int $firstDay (optional) first day of week (e.g. 0 for Sunday, 2 for Tuesday etc.) + * + * @access public + */ + function Calendar_Month_Weeks($y, $m, $firstDay=null) + { + parent::Calendar_Month($y, $m, $firstDay); + } + + /** + * Builds Calendar_Week objects for the Month. Note that Calendar_Week + * builds Calendar_Day object in tabular form (with Calendar_Day->empty) + * + * @param array $sDates (optional) Calendar_Week objects representing selected dates + * + * @return boolean + * @access public + */ + function build($sDates = array()) + { + include_once CALENDAR_ROOT.'Table/Helper.php'; + $this->tableHelper = new Calendar_Table_Helper($this, $this->firstDay); + include_once CALENDAR_ROOT.'Week.php'; + $numWeeks = $this->tableHelper->getNumWeeks(); + for ($i=1, $d=1; $i<=$numWeeks; $i++, + $d+=$this->cE->getDaysInWeek( + $this->thisYear(), + $this->thisMonth(), + $this->thisDay() + ) + ) { + $this->children[$i] = new Calendar_Week( + $this->year, $this->month, $d, $this->tableHelper->getFirstDay()); + } + //used to set empty days + $this->children[1]->setFirst(true); + $this->children[$numWeeks]->setLast(true); + + // Handle selected weeks here + if (count($sDates) > 0) { + $this->setSelection($sDates); + } + return true; + } + + /** + * Called from build() + * + * @param array $sDates Calendar_Week objects representing selected dates + * + * @return void + * @access private + */ + function setSelection($sDates) + { + foreach ($sDates as $sDate) { + if ($this->year == $sDate->thisYear() + && $this->month == $sDate->thisMonth()) + { + $key = $sDate->thisWeek('n_in_month'); + if (isset($this->children[$key])) { + $this->children[$key]->setSelected(); + } + } + } + } +} +?> \ No newline at end of file diff --git a/library/pear/Calendar/Second.php b/library/pear/Calendar/Second.php new file mode 100644 index 000000000..b1b962b8b --- /dev/null +++ b/library/pear/Calendar/Second.php @@ -0,0 +1,122 @@ + + * @copyright 2003-2007 Harry Fuecks + * @license http://www.debian.org/misc/bsd.license BSD License (3 Clause) + * @version CVS: $Id: Second.php 300728 2010-06-24 11:43:56Z quipo $ + * @link http://pear.php.net/package/Calendar + */ + +/** + * Allows Calendar include path to be redefined + * @ignore + */ +if (!defined('CALENDAR_ROOT')) { + define('CALENDAR_ROOT', 'Calendar'.DIRECTORY_SEPARATOR); +} + +/** + * Load Calendar base class + */ +require_once CALENDAR_ROOT.'Calendar.php'; + +/** + * Represents a Second
    + * Note: Seconds do not build other objects + * so related methods are overridden to return NULL + * + * @category Date and Time + * @package Calendar + * @author Harry Fuecks + * @copyright 2003-2007 Harry Fuecks + * @license http://www.debian.org/misc/bsd.license BSD License (3 Clause) + * @link http://pear.php.net/package/Calendar + * @access public + */ +class Calendar_Second extends Calendar +{ + /** + * Constructs Second + * + * @param int $y year e.g. 2003 + * @param int $m month e.g. 5 + * @param int $d day e.g. 11 + * @param int $h hour e.g. 13 + * @param int $i minute e.g. 31 + * @param int $s second e.g. 45 + */ + function Calendar_Second($y, $m, $d, $h, $i, $s) + { + parent::Calendar($y, $m, $d, $h, $i, $s); + } + + /** + * Overwrite build + * + * @return NULL + */ + function build() + { + return null; + } + + /** + * Overwrite fetch + * + * @return NULL + */ + function fetch() + { + return null; + } + + /** + * Overwrite fetchAll + * + * @return NULL + */ + function fetchAll() + { + return null; + } + + /** + * Overwrite size + * + * @return NULL + */ + function size() + { + return null; + } +} +?> \ No newline at end of file diff --git a/library/pear/Calendar/Table/Helper.php b/library/pear/Calendar/Table/Helper.php new file mode 100644 index 000000000..c852dc083 --- /dev/null +++ b/library/pear/Calendar/Table/Helper.php @@ -0,0 +1,316 @@ + + * @copyright 2003-2007 Harry Fuecks + * @license http://www.debian.org/misc/bsd.license BSD License (3 Clause) + * @version CVS: $Id: Helper.php 246317 2007-11-16 20:05:32Z quipo $ + * @link http://pear.php.net/package/Calendar + */ + +/** + * Used by Calendar_Month_Weekdays, Calendar_Month_Weeks and Calendar_Week to + * help with building the calendar in tabular form + * + * @category Date and Time + * @package Calendar + * @author Harry Fuecks + * @copyright 2003-2007 Harry Fuecks + * @license http://www.debian.org/misc/bsd.license BSD License (3 Clause) + * @link http://pear.php.net/package/Calendar + * @access public + */ +class Calendar_Table_Helper +{ + /** + * Instance of the Calendar object being helped. + * @var object + * @access private + */ + var $calendar; + + /** + * Instance of the Calendar_Engine + * @var object + * @access private + */ + var $cE; + + /** + * First day of the week + * @access private + * @var string + */ + var $firstDay; + + /** + * The seven days of the week named + * @access private + * @var array + */ + var $weekDays; + + /** + * Days of the week ordered with $firstDay at the beginning + * @access private + * @var array + */ + var $daysOfWeek = array(); + + /** + * Days of the month built from days of the week + * @access private + * @var array + */ + var $daysOfMonth = array(); + + /** + * Number of weeks in month + * @var int + * @access private + */ + var $numWeeks = null; + + /** + * Number of emtpy days before real days begin in month + * @var int + * @access private + */ + var $emptyBefore = 0; + + /** + * Constructs Calendar_Table_Helper + * + * @param object &$calendar Calendar_Month_Weekdays, Calendar_Month_Weeks, Calendar_Week + * @param int $firstDay (optional) first day of the week e.g. 1 for Monday + * + * @access protected + */ + function Calendar_Table_Helper(& $calendar, $firstDay=null) + { + $this->calendar = & $calendar; + $this->cE = & $calendar->getEngine(); + if (is_null($firstDay)) { + $firstDay = $this->cE->getFirstDayOfWeek( + $this->calendar->thisYear(), + $this->calendar->thisMonth(), + $this->calendar->thisDay() + ); + } + $this->firstDay = $firstDay; + $this->setFirstDay(); + $this->setDaysOfMonth(); + } + + /** + * Constructs $this->daysOfWeek based on $this->firstDay + * + * @return void + * @access private + */ + function setFirstDay() + { + $weekDays = $this->cE->getWeekDays( + $this->calendar->thisYear(), + $this->calendar->thisMonth(), + $this->calendar->thisDay() + ); + $endDays = array(); + $tmpDays = array(); + $begin = false; + foreach ($weekDays as $day) { + if ($begin) { + $endDays[] = $day; + } else if ($day === $this->firstDay) { + $begin = true; + $endDays[] = $day; + } else { + $tmpDays[] = $day; + } + } + $this->daysOfWeek = array_merge($endDays, $tmpDays); + } + + /** + * Constructs $this->daysOfMonth + * + * @return void + * @access private + */ + function setDaysOfMonth() + { + $this->daysOfMonth = $this->daysOfWeek; + $daysInMonth = $this->cE->getDaysInMonth( + $this->calendar->thisYear(), $this->calendar->thisMonth()); + $firstDayInMonth = $this->cE->getFirstDayInMonth( + $this->calendar->thisYear(), $this->calendar->thisMonth()); + $this->emptyBefore=0; + foreach ($this->daysOfMonth as $dayOfWeek) { + if ($firstDayInMonth == $dayOfWeek) { + break; + } + $this->emptyBefore++; + } + $this->numWeeks = ceil( + ($daysInMonth + $this->emptyBefore) + / + $this->cE->getDaysInWeek( + $this->calendar->thisYear(), + $this->calendar->thisMonth(), + $this->calendar->thisDay() + ) + ); + for ($i=1; $i < $this->numWeeks; $i++) { + $this->daysOfMonth = + array_merge($this->daysOfMonth, $this->daysOfWeek); + } + } + + /** + * Returns the first day of the month + * + * @return int + * @access protected + * @see Calendar_Engine_Interface::getFirstDayOfWeek() + */ + function getFirstDay() + { + return $this->firstDay; + } + + /** + * Returns the order array of days in a week + * + * @return int + * @access protected + */ + function getDaysOfWeek() + { + return $this->daysOfWeek; + } + + /** + * Returns the number of tabular weeks in a month + * + * @return int + * @access protected + */ + function getNumWeeks() + { + return $this->numWeeks; + } + + /** + * Returns the number of real days + empty days + * + * @return int + * @access protected + */ + function getNumTableDaysInMonth() + { + return count($this->daysOfMonth); + } + + /** + * Returns the number of empty days before the real days begin + * + * @return int + * @access protected + */ + function getEmptyDaysBefore() + { + return $this->emptyBefore; + } + + /** + * Returns the index of the last real day in the month + * + * @todo Potential performance optimization with static + * @return int + * @access protected + */ + function getEmptyDaysAfter() + { + // Causes bug when displaying more than one month + //static $index; + //if (!isset($index)) { + $index = $this->getEmptyDaysBefore() + $this->cE->getDaysInMonth( + $this->calendar->thisYear(), $this->calendar->thisMonth()); + //} + return $index; + } + + /** + * Returns the index of the last real day in the month, relative to the + * beginning of the tabular week it is part of + * + * @return int + * @access protected + */ + function getEmptyDaysAfterOffset() + { + $eAfter = $this->getEmptyDaysAfter(); + return $eAfter - ( + $this->cE->getDaysInWeek( + $this->calendar->thisYear(), + $this->calendar->thisMonth(), + $this->calendar->thisDay() + ) * ($this->numWeeks-1)); + } + + /** + * Returns the timestamp of the first day of the current week + * + * @param int $y year + * @param int $m month + * @param int $d day + * @param int $firstDay first day of the week (default 1 = Monday) + * + * @return int timestamp + */ + function getWeekStart($y, $m, $d, $firstDay=1) + { + $dow = $this->cE->getDayOfWeek($y, $m, $d); + if ($dow > $firstDay) { + $d -= ($dow - $firstDay); + } + if ($dow < $firstDay) { + $d -= ( + $this->cE->getDaysInWeek( + $this->calendar->thisYear(), + $this->calendar->thisMonth(), + $this->calendar->thisDay() + ) - $firstDay + $dow); + } + return $this->cE->dateToStamp($y, $m, $d); + } +} +?> \ No newline at end of file diff --git a/library/pear/Calendar/Util/Textual.php b/library/pear/Calendar/Util/Textual.php new file mode 100644 index 000000000..109a00ba0 --- /dev/null +++ b/library/pear/Calendar/Util/Textual.php @@ -0,0 +1,304 @@ + + * @author Lorenzo Alberton + * @copyright 2003-2007 Harry Fuecks, Lorenzo Alberton + * @license http://www.debian.org/misc/bsd.license BSD License (3 Clause) + * @version CVS: $Id: Textual.php 247250 2007-11-28 19:42:01Z quipo $ + * @link http://pear.php.net/package/Calendar + */ + +/** + * @package Calendar + * @version $Id: Textual.php 247250 2007-11-28 19:42:01Z quipo $ + */ + +/** + * Allows Calendar include path to be redefined + * @ignore + */ +if (!defined('CALENDAR_ROOT')) { + define('CALENDAR_ROOT', 'Calendar'.DIRECTORY_SEPARATOR); +} + +/** + * Load Calendar decorator base class + */ +require_once CALENDAR_ROOT.'Decorator.php'; + +/** + * Static utlities to help with fetching textual representations of months and + * days of the week. + * + * @category Date and Time + * @package Calendar + * @author Harry Fuecks + * @author Lorenzo Alberton + * @copyright 2003-2007 Harry Fuecks, Lorenzo Alberton + * @license http://www.debian.org/misc/bsd.license BSD License (3 Clause) + * @link http://pear.php.net/package/Calendar + * @access public + */ +class Calendar_Util_Textual +{ + + /** + * Returns an array of 12 month names (first index = 1) + * + * @param string $format (optional) format of returned months (one|two|short|long) + * + * @return array + * @access public + * @static + */ + function monthNames($format = 'long') + { + $formats = array( + 'one' => '%b', + 'two' => '%b', + 'short' => '%b', + 'long' => '%B', + ); + if (!array_key_exists($format, $formats)) { + $format = 'long'; + } + $months = array(); + for ($i=1; $i<=12; $i++) { + $stamp = mktime(0, 0, 0, $i, 1, 2003); + $month = strftime($formats[$format], $stamp); + switch($format) { + case 'one': + $month = substr($month, 0, 1); + break; + case 'two': + $month = substr($month, 0, 2); + break; + } + $months[$i] = $month; + } + return $months; + } + + /** + * Returns an array of 7 week day names (first index = 0) + * + * @param string $format (optional) format of returned days (one,two,short or long) + * + * @return array + * @access public + * @static + */ + function weekdayNames($format = 'long') + { + $formats = array( + 'one' => '%a', + 'two' => '%a', + 'short' => '%a', + 'long' => '%A', + ); + if (!array_key_exists($format, $formats)) { + $format = 'long'; + } + $days = array(); + for ($i=0; $i<=6; $i++) { + $stamp = mktime(0, 0, 0, 11, $i+2, 2003); + $day = strftime($formats[$format], $stamp); + switch($format) { + case 'one': + $day = substr($day, 0, 1); + break; + case 'two': + $day = substr($day, 0, 2); + break; + } + $days[$i] = $day; + } + return $days; + } + + /** + * Returns textual representation of the previous month of the decorated calendar object + * + * @param object $Calendar subclass of Calendar e.g. Calendar_Month + * @param string $format (optional) format of returned months (one,two,short or long) + * + * @return string + * @access public + * @static + */ + function prevMonthName($Calendar, $format = 'long') + { + $months = Calendar_Util_Textual::monthNames($format); + return $months[$Calendar->prevMonth()]; + } + + /** + * Returns textual representation of the month of the decorated calendar object + * + * @param object $Calendar subclass of Calendar e.g. Calendar_Month + * @param string $format (optional) format of returned months (one,two,short or long) + * + * @return string + * @access public + * @static + */ + function thisMonthName($Calendar, $format = 'long') + { + $months = Calendar_Util_Textual::monthNames($format); + return $months[$Calendar->thisMonth()]; + } + + /** + * Returns textual representation of the next month of the decorated calendar object + * + * @param object $Calendar subclass of Calendar e.g. Calendar_Month + * @param string $format (optional) format of returned months (one,two,short or long) + * + * @return string + * @access public + * @static + */ + function nextMonthName($Calendar, $format = 'long') + { + $months = Calendar_Util_Textual::monthNames($format); + return $months[$Calendar->nextMonth()]; + } + + /** + * Returns textual representation of the previous day of week of the decorated calendar object + * Note: Requires PEAR::Date + * + * @param object $Calendar subclass of Calendar e.g. Calendar_Month + * @param string $format (optional) format of returned months (one,two,short or long) + * + * @return string + * @access public + * @static + */ + function prevDayName($Calendar, $format = 'long') + { + $days = Calendar_Util_Textual::weekdayNames($format); + $stamp = $Calendar->prevDay('timestamp'); + $cE = $Calendar->getEngine(); + include_once 'Date/Calc.php'; + $day = Date_Calc::dayOfWeek($cE->stampToDay($stamp), + $cE->stampToMonth($stamp), $cE->stampToYear($stamp)); + return $days[$day]; + } + + /** + * Returns textual representation of the day of week of the decorated calendar object + * Note: Requires PEAR::Date + * + * @param object $Calendar subclass of Calendar e.g. Calendar_Month + * @param string $format (optional) format of returned months (one,two,short or long) + * + * @return string + * @access public + * @static + */ + function thisDayName($Calendar, $format='long') + { + $days = Calendar_Util_Textual::weekdayNames($format); + include_once 'Date/Calc.php'; + $day = Date_Calc::dayOfWeek($Calendar->thisDay(), $Calendar->thisMonth(), $Calendar->thisYear()); + return $days[$day]; + } + + /** + * Returns textual representation of the next day of week of the decorated calendar object + * + * @param object $Calendar subclass of Calendar e.g. Calendar_Month + * @param string $format (optional) format of returned months (one,two,short or long) + * + * @return string + * @access public + * @static + */ + function nextDayName($Calendar, $format='long') + { + $days = Calendar_Util_Textual::weekdayNames($format); + $stamp = $Calendar->nextDay('timestamp'); + $cE = $Calendar->getEngine(); + include_once 'Date/Calc.php'; + $day = Date_Calc::dayOfWeek($cE->stampToDay($stamp), + $cE->stampToMonth($stamp), $cE->stampToYear($stamp)); + return $days[$day]; + } + + /** + * Returns the days of the week using the order defined in the decorated + * calendar object. Only useful for Calendar_Month_Weekdays, Calendar_Month_Weeks + * and Calendar_Week. Otherwise the returned array will begin on Sunday + * + * @param object $Calendar subclass of Calendar e.g. Calendar_Month + * @param string $format (optional) format of returned months (one,two,short or long) + * + * @return array ordered array of week day names + * @access public + * @static + */ + function orderedWeekdays($Calendar, $format = 'long') + { + $days = Calendar_Util_Textual::weekdayNames($format); + + if (isset($Calendar->tableHelper)) { + $ordereddays = $Calendar->tableHelper->getDaysOfWeek(); + } else { + //default: start from Sunday + $firstDay = 0; + //check if defined / set + if (defined('CALENDAR_FIRST_DAY_OF_WEEK')) { + $firstDay = CALENDAR_FIRST_DAY_OF_WEEK; + } elseif(isset($Calendar->firstDay)) { + $firstDay = $Calendar->firstDay; + } + $ordereddays = array(); + for ($i = $firstDay; $i < 7; $i++) { + $ordereddays[] = $i; + } + for ($i = 0; $i < $firstDay; $i++) { + $ordereddays[] = $i; + } + } + + $ordereddays = array_flip($ordereddays); + $i = 0; + $returndays = array(); + foreach ($ordereddays as $key => $value) { + $returndays[$i] = $days[$key]; + $i++; + } + return $returndays; + } +} +?> \ No newline at end of file diff --git a/library/pear/Calendar/Util/Uri.php b/library/pear/Calendar/Util/Uri.php new file mode 100644 index 000000000..1490501f7 --- /dev/null +++ b/library/pear/Calendar/Util/Uri.php @@ -0,0 +1,204 @@ + + * @author Lorenzo Alberton + * @copyright 2003-2007 Harry Fuecks, Lorenzo Alberton + * @license http://www.debian.org/misc/bsd.license BSD License (3 Clause) + * @version CVS: $Id: Uri.php 300729 2010-06-24 12:05:53Z quipo $ + * @link http://pear.php.net/package/Calendar + */ + +/** + * Utility to help building HTML links for navigating the calendar
    + * + * $Day = new Calendar_Day(2003, 10, 23); + * $Uri = new Calendar_Util_Uri('year', 'month', 'day'); + * echo $Uri->prev($Day,'month'); // Displays year=2003&month=10 + * echo $Uri->prev($Day,'day'); // Displays year=2003&month=10&day=22 + * $Uri->seperator = '/'; + * $Uri->scalar = true; + * echo $Uri->prev($Day,'month'); // Displays 2003/10 + * echo $Uri->prev($Day,'day'); // Displays 2003/10/22 + * + * + * @category Date and Time + * @package Calendar + * @author Harry Fuecks + * @author Lorenzo Alberton + * @copyright 2003-2007 Harry Fuecks, Lorenzo Alberton + * @license http://www.debian.org/misc/bsd.license BSD License (3 Clause) + * @link http://pear.php.net/package/Calendar + * @access public + */ +class Calendar_Util_Uri +{ + /** + * Uri fragments for year, month, day etc. + * @var array + * @access private + */ + var $uris = array(); + + /** + * String to separate fragments with. + * Set to just & for HTML. + * For a scalar URL you might use / as the seperator + * @var string (default XHTML &) + * @access public + */ + var $separator = '&'; + + /** + * To output a "scalar" string - variable names omitted. + * Used for urls like index.php/2004/8/12 + * @var boolean (default false) + * @access public + */ + var $scalar = false; + + /** + * Constructs Calendar_Decorator_Uri + * The term "fragment" means name of a calendar GET variables in the URL + * + * @param string $y URI fragment for year + * @param string $m (optional) URI fragment for month + * @param string $d (optional) URI fragment for day + * @param string $h (optional) URI fragment for hour + * @param string $i (optional) URI fragment for minute + * @param string $s (optional) URI fragment for second + * + * @access public + */ + function Calendar_Util_Uri($y, $m=null, $d=null, $h=null, $i=null, $s=null) + { + $this->setFragments($y, $m, $d, $h, $i, $s); + } + + /** + * Sets the URI fragment names + * + * @param string $y URI fragment for year + * @param string $m (optional) URI fragment for month + * @param string $d (optional) URI fragment for day + * @param string $h (optional) URI fragment for hour + * @param string $i (optional) URI fragment for minute + * @param string $s (optional) URI fragment for second + * + * @return void + * @access public + */ + function setFragments($y, $m=null, $d=null, $h=null, $i=null, $s=null) + { + if (!is_null($y)) $this->uris['Year'] = $y; + if (!is_null($m)) $this->uris['Month'] = $m; + if (!is_null($d)) $this->uris['Day'] = $d; + if (!is_null($h)) $this->uris['Hour'] = $h; + if (!is_null($i)) $this->uris['Minute'] = $i; + if (!is_null($s)) $this->uris['Second'] = $s; + } + + /** + * Gets the URI string for the previous calendar unit + * + * @param object $Calendar subclassed from Calendar e.g. Calendar_Month + * @param string $unit calendar unit (year|month|week|day|hour|minute|second) + * + * @return string + * @access public + */ + function prev($Calendar, $unit) + { + $method = 'prev'.$unit; + $stamp = $Calendar->{$method}('timestamp'); + return $this->buildUriString($Calendar, $method, $stamp); + } + + /** + * Gets the URI string for the current calendar unit + * + * @param object $Calendar subclassed from Calendar e.g. Calendar_Month + * @param string $unit calendar unit (year|month|week|day|hour|minute|second) + * + * @return string + * @access public + */ + function this($Calendar, $unit) + { + $method = 'this'.$unit; + $stamp = $Calendar->{$method}('timestamp'); + return $this->buildUriString($Calendar, $method, $stamp); + } + + /** + * Gets the URI string for the next calendar unit + * + * @param object $Calendar subclassed from Calendar e.g. Calendar_Month + * @param string $unit calendar unit (year|month|week|day|hour|minute|second) + * + * @return string + * @access public + */ + function next($Calendar, $unit) + { + $method = 'next'.$unit; + $stamp = $Calendar->{$method}('timestamp'); + return $this->buildUriString($Calendar, $method, $stamp); + } + + /** + * Build the URI string + * + * @param object $Calendar subclassed from Calendar e.g. Calendar_Month + * @param string $method method substring + * @param int $stamp timestamp + * + * @return string build uri string + * @access private + */ + function buildUriString($Calendar, $method, $stamp) + { + $uriString = ''; + $cE = & $Calendar->getEngine(); + $separator = ''; + foreach ($this->uris as $unit => $uri) { + $call = 'stampTo'.$unit; + $uriString .= $separator; + if (!$this->scalar) { + $uriString .= $uri.'='; + } + $uriString .= $cE->{$call}($stamp); + $separator = $this->separator; + } + return $uriString; + } +} +?> \ No newline at end of file diff --git a/library/pear/Calendar/Validator.php b/library/pear/Calendar/Validator.php new file mode 100644 index 000000000..a96c8e223 --- /dev/null +++ b/library/pear/Calendar/Validator.php @@ -0,0 +1,377 @@ + + * @copyright 2003-2007 Harry Fuecks + * @license http://www.debian.org/misc/bsd.license BSD License (3 Clause) + * @version CVS: $Id: Validator.php 247251 2007-11-28 19:42:33Z quipo $ + * @link http://pear.php.net/package/Calendar + */ + +/** + * Validation Error Messages + */ +if (!defined('CALENDAR_VALUE_TOOSMALL')) { + define('CALENDAR_VALUE_TOOSMALL', 'Too small: min = '); +} +if (!defined('CALENDAR_VALUE_TOOLARGE')) { + define('CALENDAR_VALUE_TOOLARGE', 'Too large: max = '); +} + +/** + * Used to validate any given Calendar date object. Instances of this class + * can be obtained from any data object using the getValidator method + * + * @category Date and Time + * @package Calendar + * @author Harry Fuecks + * @copyright 2003-2007 Harry Fuecks + * @license http://www.debian.org/misc/bsd.license BSD License (3 Clause) + * @link http://pear.php.net/package/Calendar + * @see Calendar::getValidator() + * @access public + */ +class Calendar_Validator +{ + /** + * Instance of the Calendar date object to validate + * @var object + * @access private + */ + var $calendar; + + /** + * Instance of the Calendar_Engine + * @var object + * @access private + */ + var $cE; + + /** + * Array of errors for validation failures + * @var array + * @access private + */ + var $errors = array(); + + /** + * Constructs Calendar_Validator + * + * @param object &$calendar subclass of Calendar + * + * @access public + */ + function Calendar_Validator(&$calendar) + { + $this->calendar = & $calendar; + $this->cE = & $calendar->getEngine(); + } + + /** + * Calls all the other isValidXXX() methods in the validator + * + * @return boolean + * @access public + */ + function isValid() + { + $checks = array('isValidYear', 'isValidMonth', 'isValidDay', + 'isValidHour', 'isValidMinute', 'isValidSecond'); + $valid = true; + foreach ($checks as $check) { + if (!$this->{$check}()) { + $valid = false; + } + } + return $valid; + } + + /** + * Check whether this is a valid year + * + * @return boolean + * @access public + */ + function isValidYear() + { + $y = $this->calendar->thisYear(); + $min = $this->cE->getMinYears(); + if ($min > $y) { + $this->errors[] = new Calendar_Validation_Error( + 'Year', $y, CALENDAR_VALUE_TOOSMALL.$min); + return false; + } + $max = $this->cE->getMaxYears(); + if ($y > $max) { + $this->errors[] = new Calendar_Validation_Error( + 'Year', $y, CALENDAR_VALUE_TOOLARGE.$max); + return false; + } + return true; + } + + /** + * Check whether this is a valid month + * + * @return boolean + * @access public + */ + function isValidMonth() + { + $m = $this->calendar->thisMonth(); + $min = 1; + if ($min > $m) { + $this->errors[] = new Calendar_Validation_Error( + 'Month', $m, CALENDAR_VALUE_TOOSMALL.$min); + return false; + } + $max = $this->cE->getMonthsInYear($this->calendar->thisYear()); + if ($m > $max) { + $this->errors[] = new Calendar_Validation_Error( + 'Month', $m, CALENDAR_VALUE_TOOLARGE.$max); + return false; + } + return true; + } + + /** + * Check whether this is a valid day + * + * @return boolean + * @access public + */ + function isValidDay() + { + $d = $this->calendar->thisDay(); + $min = 1; + if ($min > $d) { + $this->errors[] = new Calendar_Validation_Error( + 'Day', $d, CALENDAR_VALUE_TOOSMALL.$min); + return false; + } + $max = $this->cE->getDaysInMonth( + $this->calendar->thisYear(), + $this->calendar->thisMonth() + ); + if ($d > $max) { + $this->errors[] = new Calendar_Validation_Error( + 'Day', $d, CALENDAR_VALUE_TOOLARGE.$max); + return false; + } + return true; + } + + /** + * Check whether this is a valid hour + * + * @return boolean + * @access public + */ + function isValidHour() + { + $h = $this->calendar->thisHour(); + $min = 0; + if ($min > $h) { + $this->errors[] = new Calendar_Validation_Error( + 'Hour', $h, CALENDAR_VALUE_TOOSMALL.$min); + return false; + } + $max = ($this->cE->getHoursInDay($this->calendar->thisDay())-1); + if ($h > $max) { + $this->errors[] = new Calendar_Validation_Error( + 'Hour', $h, CALENDAR_VALUE_TOOLARGE.$max); + return false; + } + return true; + } + + /** + * Check whether this is a valid minute + * + * @return boolean + * @access public + */ + function isValidMinute() + { + $i = $this->calendar->thisMinute(); + $min = 0; + if ($min > $i) { + $this->errors[] = new Calendar_Validation_Error( + 'Minute', $i, CALENDAR_VALUE_TOOSMALL.$min); + return false; + } + $max = ($this->cE->getMinutesInHour($this->calendar->thisHour())-1); + if ($i > $max) { + $this->errors[] = new Calendar_Validation_Error( + 'Minute', $i, CALENDAR_VALUE_TOOLARGE.$max); + return false; + } + return true; + } + + /** + * Check whether this is a valid second + * + * @return boolean + * @access public + */ + function isValidSecond() + { + $s = $this->calendar->thisSecond(); + $min = 0; + if ($min > $s) { + $this->errors[] = new Calendar_Validation_Error( + 'Second', $s, CALENDAR_VALUE_TOOSMALL.$min); + return false; + } + $max = ($this->cE->getSecondsInMinute($this->calendar->thisMinute())-1); + if ($s > $max) { + $this->errors[] = new Calendar_Validation_Error( + 'Second', $s, CALENDAR_VALUE_TOOLARGE.$max); + return false; + } + return true; + } + + /** + * Iterates over any validation errors + * + * @return mixed either Calendar_Validation_Error or false + * @access public + */ + function fetch() + { + $error = each($this->errors); + if ($error) { + return $error['value']; + } else { + reset($this->errors); + return false; + } + } +} + +/** + * For Validation Error messages + * + * @category Date and Time + * @package Calendar + * @author Harry Fuecks + * @copyright 2003-2007 Harry Fuecks + * @license http://www.debian.org/misc/bsd.license BSD License (3 Clause) + * @link http://pear.php.net/package/Calendar + * @see Calendar::fetch() + * @access public + */ +class Calendar_Validation_Error +{ + /** + * Date unit (e.g. month,hour,second) which failed test + * @var string + * @access private + */ + var $unit; + + /** + * Value of unit which failed test + * @var int + * @access private + */ + var $value; + + /** + * Validation error message + * @var string + * @access private + */ + var $message; + + /** + * Constructs Calendar_Validation_Error + * + * @param string $unit Date unit (e.g. month,hour,second) + * @param int $value Value of unit which failed test + * @param string $message Validation error message + * + * @access protected + */ + function Calendar_Validation_Error($unit, $value, $message) + { + $this->unit = $unit; + $this->value = $value; + $this->message = $message; + } + + /** + * Returns the Date unit + * + * @return string + * @access public + */ + function getUnit() + { + return $this->unit; + } + + /** + * Returns the value of the unit + * + * @return int + * @access public + */ + function getValue() + { + return $this->value; + } + + /** + * Returns the validation error message + * + * @return string + * @access public + */ + function getMessage() + { + return $this->message; + } + + /** + * Returns a string containing the unit, value and error message + * + * @return string + * @access public + */ + function toString () + { + return $this->unit.' = '.$this->value.' ['.$this->message.']'; + } +} +?> \ No newline at end of file diff --git a/library/pear/Calendar/Week.php b/library/pear/Calendar/Week.php new file mode 100644 index 000000000..966128f2c --- /dev/null +++ b/library/pear/Calendar/Week.php @@ -0,0 +1,470 @@ + + * @author Lorenzo Alberton + * @copyright 2003-2007 Harry Fuecks, Lorenzo Alberton + * @license http://www.debian.org/misc/bsd.license BSD License (3 Clause) + * @version CVS: $Id: Week.php 300729 2010-06-24 12:05:53Z quipo $ + * @link http://pear.php.net/package/Calendar + */ + +/** + * Allows Calendar include path to be redefined + * @ignore + */ +if (!defined('CALENDAR_ROOT')) { + define('CALENDAR_ROOT', 'Calendar'.DIRECTORY_SEPARATOR); +} + +/** + * Load Calendar base class + */ +require_once CALENDAR_ROOT.'Calendar.php'; + +/** + * Represents a Week and builds Days in tabular format
    + * + * require_once 'Calendar/Week.php'; + * $Week = new Calendar_Week(2003, 10, 1); Oct 2003, 1st tabular week + * echo '
    '; + * while ($Day = & $Week->fetch()) { + * if ($Day->isEmpty()) { + * echo ''; + * } else { + * echo ''; + * } + * } + * echo ''; + * + * + * @category Date and Time + * @package Calendar + * @author Harry Fuecks + * @author Lorenzo Alberton + * @copyright 2003-2007 Harry Fuecks, Lorenzo Alberton + * @license http://www.debian.org/misc/bsd.license BSD License (3 Clause) + * @link http://pear.php.net/package/Calendar + */ +class Calendar_Week extends Calendar +{ + /** + * Instance of Calendar_Table_Helper + * @var Calendar_Table_Helper + * @access private + */ + var $tableHelper; + + /** + * Stores the timestamp of the first day of this week + * @access private + * @var object + */ + var $thisWeek; + + /** + * Stores the timestamp of first day of previous week + * @access private + * @var object + */ + var $prevWeek; + + /** + * Stores the timestamp of first day of next week + * @access private + * @var object + */ + var $nextWeek; + + /** + * Used by build() to set empty days + * @access private + * @var boolean + */ + var $firstWeek = false; + + /** + * Used by build() to set empty days + * @access private + * @var boolean + */ + var $lastWeek = false; + + /** + * First day of the week (0=sunday, 1=monday...) + * @access private + * @var boolean + */ + var $firstDay = 1; + + /** + * Constructs Week + * + * @param int $y year e.g. 2003 + * @param int $m month e.g. 5 + * @param int $d a day of the desired week + * @param int $firstDay (optional) first day of week (e.g. 0 for Sunday, 2 for Tuesday etc.) + * + * @access public + */ + function Calendar_Week($y, $m, $d, $firstDay = null) + { + include_once CALENDAR_ROOT.'Table/Helper.php'; + parent::Calendar($y, $m, $d); + $this->firstDay = $this->defineFirstDayOfWeek($firstDay); + $this->tableHelper = new Calendar_Table_Helper($this, $this->firstDay); + $this->thisWeek = $this->tableHelper->getWeekStart($y, $m, $d, $this->firstDay); + $this->prevWeek = $this->tableHelper->getWeekStart( + $y, + $m, + $d - $this->cE->getDaysInWeek( + $this->thisYear(), + $this->thisMonth(), + $this->thisDay() + ), + $this->firstDay + ); + $this->nextWeek = $this->tableHelper->getWeekStart( + $y, + $m, + $d + $this->cE->getDaysInWeek( + $this->thisYear(), + $this->thisMonth(), + $this->thisDay() + ), + $this->firstDay + ); + } + + /** + * Defines the calendar by a timestamp (Unix or ISO-8601), replacing values + * passed to the constructor + * + * @param int|string $ts Unix or ISO-8601 timestamp + * + * @return void + * @access public + */ + function setTimestamp($ts) + { + parent::setTimestamp($ts); + $this->thisWeek = $this->tableHelper->getWeekStart( + $this->year, $this->month, $this->day, $this->firstDay + ); + $this->prevWeek = $this->tableHelper->getWeekStart( + $this->year, + $this->month, + $this->day - $this->cE->getDaysInWeek( + $this->thisYear(), + $this->thisMonth(), + $this->thisDay() + ), + $this->firstDay + ); + $this->nextWeek = $this->tableHelper->getWeekStart( + $this->year, + $this->month, + $this->day + $this->cE->getDaysInWeek( + $this->thisYear(), + $this->thisMonth(), + $this->thisDay() + ), + $this->firstDay + ); + } + + /** + * Builds Calendar_Day objects for this Week + * + * @param array $sDates (optional) Calendar_Day objects representing selected dates + * + * @return boolean + * @access public + */ + function build($sDates = array()) + { + include_once CALENDAR_ROOT.'Day.php'; + $year = $this->cE->stampToYear($this->thisWeek); + $month = $this->cE->stampToMonth($this->thisWeek); + $day = $this->cE->stampToDay($this->thisWeek); + $end = $this->cE->getDaysInWeek( + $this->thisYear(), + $this->thisMonth(), + $this->thisDay() + ); + + for ($i=1; $i <= $end; $i++) { + $stamp = $this->cE->dateToStamp($year, $month, $day++); + $this->children[$i] = new Calendar_Day( + $this->cE->stampToYear($stamp), + $this->cE->stampToMonth($stamp), + $this->cE->stampToDay($stamp) + ); + } + + //set empty days (@see Calendar_Month_Weeks::build()) + if ($this->firstWeek) { + $eBefore = $this->tableHelper->getEmptyDaysBefore(); + for ($i=1; $i <= $eBefore; $i++) { + $this->children[$i]->setEmpty(); + } + } + if ($this->lastWeek) { + $eAfter = $this->tableHelper->getEmptyDaysAfterOffset(); + for ($i = $eAfter+1; $i <= $end; $i++) { + $this->children[$i]->setEmpty(); + } + } + + if (count($sDates) > 0) { + $this->setSelection($sDates); + } + return true; + } + + /** + * Set as first week of the month + * + * @param boolean $state whether it's first or not + * + * @return void + * @access private + */ + function setFirst($state = true) + { + $this->firstWeek = $state; + } + + /** + * Set as last week of the month + * + * @param boolean $state whether it's lasst or not + * + * @return void + * @access private + */ + function setLast($state = true) + { + $this->lastWeek = $state; + } + + /** + * Called from build() + * + * @param array $sDates Calendar_Day objects representing selected dates + * + * @return void + * @access private + */ + function setSelection($sDates) + { + foreach ($sDates as $sDate) { + foreach ($this->children as $key => $child) { + if ($child->thisDay() == $sDate->thisDay() && + $child->thisMonth() == $sDate->thisMonth() && + $child->thisYear() == $sDate->thisYear() + ) { + $this->children[$key] = $sDate; + $this->children[$key]->setSelected(); + } + } + } + reset($this->children); + } + + /** + * Returns the value for this year + * + * When a on the first/last week of the year, the year of the week is + * calculated according to ISO-8601 + * + * @param string $format return value format ['int' | 'timestamp' | 'object' | 'array'] + * + * @return int e.g. 2003 or timestamp + * @access public + */ + function thisYear($format = 'int') + { + if (null !== $this->thisWeek) { + $tmp_cal = new Calendar(); + $tmp_cal->setTimestamp($this->thisWeek); + $first_dow = $tmp_cal->thisDay('array'); + $days_in_week = $tmp_cal->cE->getDaysInWeek($tmp_cal->year, $tmp_cal->month, $tmp_cal->day); + $tmp_cal->day += $days_in_week; + $last_dow = $tmp_cal->thisDay('array'); + + if ($first_dow['year'] == $last_dow['year']) { + return $first_dow['year']; + } + + if ($last_dow['day'] > floor($days_in_week / 2)) { + return $last_dow['year']; + } + return $first_dow['year']; + } + return parent::thisYear(); + } + + /** + * Gets the value of the previous week, according to the requested format + * + * @param string $format ['timestamp' | 'n_in_month' | 'n_in_year' | 'array'] + * + * @return mixed + * @access public + */ + function prevWeek($format = 'n_in_month') + { + switch (strtolower($format)) { + case 'int': + case 'n_in_month': + return ($this->firstWeek) ? null : $this->thisWeek('n_in_month') -1; + case 'n_in_year': + return $this->cE->getWeekNInYear( + $this->cE->stampToYear($this->prevWeek), + $this->cE->stampToMonth($this->prevWeek), + $this->cE->stampToDay($this->prevWeek)); + case 'array': + return $this->toArray($this->prevWeek); + case 'object': + include_once CALENDAR_ROOT.'Factory.php'; + return Calendar_Factory::createByTimestamp('Week', $this->prevWeek); + case 'timestamp': + default: + return $this->prevWeek; + } + } + + /** + * Gets the value of the current week, according to the requested format + * + * @param string $format ['timestamp' | 'n_in_month' | 'n_in_year' | 'array'] + * + * @return mixed + * @access public + */ + function thisWeek($format = 'n_in_month') + { + switch (strtolower($format)) { + case 'int': + case 'n_in_month': + if ($this->firstWeek) { + return 1; + } + if ($this->lastWeek) { + return $this->cE->getWeeksInMonth( + $this->thisYear(), + $this->thisMonth(), + $this->firstDay); + } + return $this->cE->getWeekNInMonth( + $this->thisYear(), + $this->thisMonth(), + $this->thisDay(), + $this->firstDay); + case 'n_in_year': + return $this->cE->getWeekNInYear( + $this->cE->stampToYear($this->thisWeek), + $this->cE->stampToMonth($this->thisWeek), + $this->cE->stampToDay($this->thisWeek)); + case 'array': + return $this->toArray($this->thisWeek); + case 'object': + include_once CALENDAR_ROOT.'Factory.php'; + return Calendar_Factory::createByTimestamp('Week', $this->thisWeek); + case 'timestamp': + default: + return $this->thisWeek; + } + } + + /** + * Gets the value of the following week, according to the requested format + * + * @param string $format ['timestamp' | 'n_in_month' | 'n_in_year' | 'array'] + * + * @return mixed + * @access public + */ + function nextWeek($format = 'n_in_month') + { + switch (strtolower($format)) { + case 'int': + case 'n_in_month': + return ($this->lastWeek) ? null : $this->thisWeek('n_in_month') +1; + case 'n_in_year': + return $this->cE->getWeekNInYear( + $this->cE->stampToYear($this->nextWeek), + $this->cE->stampToMonth($this->nextWeek), + $this->cE->stampToDay($this->nextWeek)); + case 'array': + return $this->toArray($this->nextWeek); + case 'object': + include_once CALENDAR_ROOT.'Factory.php'; + return Calendar_Factory::createByTimestamp('Week', $this->nextWeek); + case 'timestamp': + default: + return $this->nextWeek; + } + } + + /** + * Returns the instance of Calendar_Table_Helper. + * Called from Calendar_Validator::isValidWeek + * + * @return Calendar_Table_Helper + * @access protected + */ + function & getHelper() + { + return $this->tableHelper; + } + + /** + * Makes sure theres a value for $this->day + * + * @return void + * @access private + */ + function findFirstDay() + { + if (!count($this->children) > 0) { + $this->build(); + foreach ($this->children as $Day) { + if (!$Day->isEmpty()) { + $this->day = $Day->thisDay(); + break; + } + } + } + } +} +?> \ No newline at end of file diff --git a/library/pear/Calendar/Year.php b/library/pear/Calendar/Year.php new file mode 100644 index 000000000..a52f8dd25 --- /dev/null +++ b/library/pear/Calendar/Year.php @@ -0,0 +1,140 @@ + + * @copyright 2003-2007 Harry Fuecks + * @license http://www.debian.org/misc/bsd.license BSD License (3 Clause) + * @version CVS: $Id: Year.php 300728 2010-06-24 11:43:56Z quipo $ + * @link http://pear.php.net/package/Calendar + */ + +/** + * Allows Calendar include path to be redefined + * @ignore + */ +if (!defined('CALENDAR_ROOT')) { + define('CALENDAR_ROOT', 'Calendar'.DIRECTORY_SEPARATOR); +} + +/** + * Load Calendar base class + */ +require_once CALENDAR_ROOT.'Calendar.php'; + +/** + * Represents a Year and builds Months
    + * + * require_once 'Calendar'.DIRECTORY_SEPARATOR.'Year.php'; + * $Year = & new Calendar_Year(2003, 10, 21); // 21st Oct 2003 + * $Year->build(); // Build Calendar_Month objects + * while ($Month = & $Year->fetch()) { + * echo $Month->thisMonth().'
    '; + * } + *
    + * + * @category Date and Time + * @package Calendar + * @author Harry Fuecks + * @copyright 2003-2007 Harry Fuecks + * @license http://www.debian.org/misc/bsd.license BSD License (3 Clause) + * @link http://pear.php.net/package/Calendar + * @access public + */ +class Calendar_Year extends Calendar +{ + /** + * Constructs Calendar_Year + * + * @param int $y year e.g. 2003 + * + * @access public + */ + function Calendar_Year($y) + { + parent::Calendar($y); + } + + /** + * Builds the Months of the Year.
    + * Note: by defining the constant CALENDAR_MONTH_STATE you can + * control what class of Calendar_Month is built e.g.; + * + * require_once 'Calendar/Calendar_Year.php'; + * define ('CALENDAR_MONTH_STATE',CALENDAR_USE_MONTH_WEEKDAYS); // Use Calendar_Month_Weekdays + * // define ('CALENDAR_MONTH_STATE',CALENDAR_USE_MONTH_WEEKS); // Use Calendar_Month_Weeks + * // define ('CALENDAR_MONTH_STATE',CALENDAR_USE_MONTH); // Use Calendar_Month + * + * It defaults to building Calendar_Month objects. + * + * @param array $sDates (optional) array of Calendar_Month objects + * representing selected dates + * @param int $firstDay (optional) first day of week + * (e.g. 0 for Sunday, 2 for Tuesday etc.) + * + * @return boolean + * @access public + */ + function build($sDates = array(), $firstDay = null) + { + include_once CALENDAR_ROOT.'Factory.php'; + $this->firstDay = $this->defineFirstDayOfWeek($firstDay); + $monthsInYear = $this->cE->getMonthsInYear($this->thisYear()); + for ($i=1; $i <= $monthsInYear; $i++) { + $this->children[$i] = Calendar_Factory::create('Month', $this->year, $i); + } + if (count($sDates) > 0) { + $this->setSelection($sDates); + } + return true; + } + + /** + * Called from build() + * + * @param array $sDates array of Calendar_Month objects representing selected dates + * + * @return void + * @access private + */ + function setSelection($sDates) + { + foreach ($sDates as $sDate) { + if ($this->year == $sDate->thisYear()) { + $key = $sDate->thisMonth(); + if (isset($this->children[$key])) { + $sDate->setSelected(); + $this->children[$key] = $sDate; + } + } + } + } +} +?> \ No newline at end of file diff --git a/library/pear/Calendar/docs/Readme b/library/pear/Calendar/docs/Readme new file mode 100644 index 000000000..6bacca0f3 --- /dev/null +++ b/library/pear/Calendar/docs/Readme @@ -0,0 +1,3 @@ +Readme + +See the PEAR manual at http://pear.php.net/manual/en/package.datetime.calendar.php for details. \ No newline at end of file diff --git a/library/pear/Calendar/docs/examples/1.php b/library/pear/Calendar/docs/examples/1.php new file mode 100644 index 000000000..662a17da5 --- /dev/null +++ b/library/pear/Calendar/docs/examples/1.php @@ -0,0 +1,92 @@ +' ); +echo ( 'The time is now: '.date('Y M d H:i:s',$c->getTimestamp()).'
    ' ); + +$i = 1; +echo ( '

    First Iteration

    ' ); +echo ( '

    The first iteration is more "expensive", the calendar data + structures having to be built.

    ' ); +$start = getmicrotime(); +$c->build(); +while ( $e = $c->fetch() ) { + $class = strtolower(get_class($e)); + $link ="&y=".$e->thisYear()."&m=".$e->thisMonth()."&d=".$e->thisDay(). + "&h=".$e->thisHour()."&i=".$e->thisMinute()."&s=".$e->thisSecond(); + $method = 'this'.str_replace('calendar_','',$class); + echo ( "".$e->{$method}()." : " ); + if ( ($i % 10) == 0 ) { + echo ( '
    ' ); + } + $i++; +} +echo ( '

    Took: '.(getmicrotime()-$start).' seconds

    ' ); + +$i = 1; +echo ( '

    Second Iteration

    ' ); +echo ( '

    This second iteration is faster, the data structures + being re-used

    ' ); +$start = getmicrotime(); +while ( $e = $c->fetch() ) { + $class = strtolower(get_class($e)); + $link ="&y=".$e->thisYear()."&m=".$e->thisMonth()."&d=".$e->thisDay(). + "&h=".$e->thisHour()."&i=".$e->thisMinute()."&s=".$e->thisSecond(); + $method = 'this'.str_replace('calendar_','',$class); + echo ( "".$e->{$method}()." : " ); + if ( ($i % 10) == 0 ) { + echo ( '
    ' ); + } + $i++; +} +echo ( '

    Took: '.(getmicrotime()-$start).' seconds

    ' ); +?> \ No newline at end of file diff --git a/library/pear/Calendar/docs/examples/1.phps b/library/pear/Calendar/docs/examples/1.phps new file mode 100644 index 000000000..662a17da5 --- /dev/null +++ b/library/pear/Calendar/docs/examples/1.phps @@ -0,0 +1,92 @@ +' ); +echo ( 'The time is now: '.date('Y M d H:i:s',$c->getTimestamp()).'
    ' ); + +$i = 1; +echo ( '

    First Iteration

    ' ); +echo ( '

    The first iteration is more "expensive", the calendar data + structures having to be built.

    ' ); +$start = getmicrotime(); +$c->build(); +while ( $e = $c->fetch() ) { + $class = strtolower(get_class($e)); + $link ="&y=".$e->thisYear()."&m=".$e->thisMonth()."&d=".$e->thisDay(). + "&h=".$e->thisHour()."&i=".$e->thisMinute()."&s=".$e->thisSecond(); + $method = 'this'.str_replace('calendar_','',$class); + echo ( "".$e->{$method}()." : " ); + if ( ($i % 10) == 0 ) { + echo ( '
    ' ); + } + $i++; +} +echo ( '

    Took: '.(getmicrotime()-$start).' seconds

    ' ); + +$i = 1; +echo ( '

    Second Iteration

    ' ); +echo ( '

    This second iteration is faster, the data structures + being re-used

    ' ); +$start = getmicrotime(); +while ( $e = $c->fetch() ) { + $class = strtolower(get_class($e)); + $link ="&y=".$e->thisYear()."&m=".$e->thisMonth()."&d=".$e->thisDay(). + "&h=".$e->thisHour()."&i=".$e->thisMinute()."&s=".$e->thisSecond(); + $method = 'this'.str_replace('calendar_','',$class); + echo ( "".$e->{$method}()." : " ); + if ( ($i % 10) == 0 ) { + echo ( '
    ' ); + } + $i++; +} +echo ( '

    Took: '.(getmicrotime()-$start).' seconds

    ' ); +?> \ No newline at end of file diff --git a/library/pear/Calendar/docs/examples/10.php b/library/pear/Calendar/docs/examples/10.php new file mode 100644 index 000000000..c9d92e026 --- /dev/null +++ b/library/pear/Calendar/docs/examples/10.php @@ -0,0 +1,93 @@ +build(); +?> + + + + + A Simple Decorator + + +

    A Simple Decorator

    +
     '.$Day->thisDay().'
     '.$Day->thisDay().'
    + +fetch() ) { + if ( $Day->isFirst() ) { + echo ( "\n\n" ); + } + if ( $Day->isEmpty() ) { + echo ( "" ); + } else { + echo ( "" ); + } + if ( $Day->isLast() ) { + echo ( "\n\n" ); + } +} +?> + + + + + +
    thisMonth() ); ?>
     ".$Day->thisDay()."
    Prev Next
    + + \ No newline at end of file diff --git a/library/pear/Calendar/docs/examples/10.phps b/library/pear/Calendar/docs/examples/10.phps new file mode 100644 index 000000000..c9d92e026 --- /dev/null +++ b/library/pear/Calendar/docs/examples/10.phps @@ -0,0 +1,93 @@ +build(); +?> + + + + + A Simple Decorator + + +

    A Simple Decorator

    + + +fetch() ) { + if ( $Day->isFirst() ) { + echo ( "\n\n" ); + } + if ( $Day->isEmpty() ) { + echo ( "" ); + } else { + echo ( "" ); + } + if ( $Day->isLast() ) { + echo ( "\n\n" ); + } +} +?> + + + + + +
    thisMonth() ); ?>
     ".$Day->thisDay()."
    Prev Next
    + + \ No newline at end of file diff --git a/library/pear/Calendar/docs/examples/11.php b/library/pear/Calendar/docs/examples/11.php new file mode 100644 index 000000000..281dc8c70 --- /dev/null +++ b/library/pear/Calendar/docs/examples/11.php @@ -0,0 +1,109 @@ +entry = $entry; + } + function getEntry() { + return $this->entry; + } +} + +// Create a day to view the hours for +$Day = & new Calendar_Day(2003,10,24); + +// A sample query to get the data for today (NOT ACTUALLY USED HERE) +$sql = " + SELECT + * + FROM + diary + WHERE + eventtime >= '".$Day->thisDay(TRUE)."' + AND + eventtime < '".$Day->nextDay(TRUE)."';"; + +// An array simulating data from a database +$result = array ( + array('eventtime'=>mktime(9,0,0,10,24,2003),'entry'=>'Meeting with sales team'), + array('eventtime'=>mktime(11,0,0,10,24,2003),'entry'=>'Conference call with Widget Inc.'), + array('eventtime'=>mktime(15,0,0,10,24,2003),'entry'=>'Presentation to board of directors') + ); + +// An array to place selected hours in +$selection = array(); + +// Loop through the "database result" +foreach ( $result as $row ) { + $Hour = new Calendar_Hour(2000,1,1,1); // Create Hour with dummy values + $Hour->setTimeStamp($row['eventtime']); // Set the real time with setTimeStamp + + // Create the decorator, passing it the Hour + $DiaryEvent = new DiaryEvent($Hour); + + // Attach the payload + $DiaryEvent->setEntry($row['entry']); + + // Add the decorator to the selection + $selection[] = $DiaryEvent; +} + +// Build the hours in that day, passing the selection +$Day->build($selection); +?> + + + + Passing a Selection Payload with a Decorator + + +

    Passing a Selection "Payload" using a Decorator

    + + + + + + +fetch() ) { + + $hour = $Hour->thisHour(); + $minute = $Hour->thisMinute(); + + // Office hours only... + if ( $hour >= 8 && $hour <= 18 ) { + echo ( "\n" ); + echo ( "\n" ); + + // If the hour is selected, call the decorator method... + if ( $Hour->isSelected() ) { + echo ( "\n" ); + } else { + echo ( "\n" ); + } + echo ( "\n" ); + } +} +?> +
    Your Schedule for thisDay(TRUE)) ); ?>
    TimeEntry
    $hour:$minute".$Hour->getEntry()." 
    +

    The query to fetch this data, with help from PEAR::Calendar, might be;

    +
    +
    +
    + + \ No newline at end of file diff --git a/library/pear/Calendar/docs/examples/11.phps b/library/pear/Calendar/docs/examples/11.phps new file mode 100644 index 000000000..281dc8c70 --- /dev/null +++ b/library/pear/Calendar/docs/examples/11.phps @@ -0,0 +1,109 @@ +entry = $entry; + } + function getEntry() { + return $this->entry; + } +} + +// Create a day to view the hours for +$Day = & new Calendar_Day(2003,10,24); + +// A sample query to get the data for today (NOT ACTUALLY USED HERE) +$sql = " + SELECT + * + FROM + diary + WHERE + eventtime >= '".$Day->thisDay(TRUE)."' + AND + eventtime < '".$Day->nextDay(TRUE)."';"; + +// An array simulating data from a database +$result = array ( + array('eventtime'=>mktime(9,0,0,10,24,2003),'entry'=>'Meeting with sales team'), + array('eventtime'=>mktime(11,0,0,10,24,2003),'entry'=>'Conference call with Widget Inc.'), + array('eventtime'=>mktime(15,0,0,10,24,2003),'entry'=>'Presentation to board of directors') + ); + +// An array to place selected hours in +$selection = array(); + +// Loop through the "database result" +foreach ( $result as $row ) { + $Hour = new Calendar_Hour(2000,1,1,1); // Create Hour with dummy values + $Hour->setTimeStamp($row['eventtime']); // Set the real time with setTimeStamp + + // Create the decorator, passing it the Hour + $DiaryEvent = new DiaryEvent($Hour); + + // Attach the payload + $DiaryEvent->setEntry($row['entry']); + + // Add the decorator to the selection + $selection[] = $DiaryEvent; +} + +// Build the hours in that day, passing the selection +$Day->build($selection); +?> + + + + Passing a Selection Payload with a Decorator + + +

    Passing a Selection "Payload" using a Decorator

    + + + + + + +fetch() ) { + + $hour = $Hour->thisHour(); + $minute = $Hour->thisMinute(); + + // Office hours only... + if ( $hour >= 8 && $hour <= 18 ) { + echo ( "\n" ); + echo ( "\n" ); + + // If the hour is selected, call the decorator method... + if ( $Hour->isSelected() ) { + echo ( "\n" ); + } else { + echo ( "\n" ); + } + echo ( "\n" ); + } +} +?> +
    Your Schedule for thisDay(TRUE)) ); ?>
    TimeEntry
    $hour:$minute".$Hour->getEntry()." 
    +

    The query to fetch this data, with help from PEAR::Calendar, might be;

    +
    +
    +
    + + \ No newline at end of file diff --git a/library/pear/Calendar/docs/examples/12.php b/library/pear/Calendar/docs/examples/12.php new file mode 100644 index 000000000..0096d21ce --- /dev/null +++ b/library/pear/Calendar/docs/examples/12.php @@ -0,0 +1,116 @@ +build(); +?> + + + + <?php echo ( $Year->thisYear() ); ?> + + + + + +fetch() ) { + + switch ( $i ) { + case 0: + echo ( "\n" ); + break; + case 3: + case 6: + case 9: + echo ( "\n\n" ); + break; + case 12: + echo ( "\n" ); + break; + } + + echo ( "\n" ); + + $i++; +} +?> +
    +thisYear() ); ?> + + +
    \n\n" ); + echo ( "" ); + echo ( "\n\n" ); + $Month->build(); + while ( $Day = $Month->fetch() ) { + if ( $Day->isFirst() ) { + echo ( "\n" ); + } + if ( $Day->isEmpty() ) { + echo ( "\n" ); + } else { + echo ( "\n" ); + } + if ( $Day->isLast() ) { + echo ( "\n" ); + } + } + echo ( "
    ".date('F',$Month->thisMonth(TRUE))."
    MTWTFSS
     ".$Day->thisDay()."
    \n
    +

    Took:

    + + \ No newline at end of file diff --git a/library/pear/Calendar/docs/examples/12.phps b/library/pear/Calendar/docs/examples/12.phps new file mode 100644 index 000000000..0096d21ce --- /dev/null +++ b/library/pear/Calendar/docs/examples/12.phps @@ -0,0 +1,116 @@ +build(); +?> + + + + <?php echo ( $Year->thisYear() ); ?> + + + + + +fetch() ) { + + switch ( $i ) { + case 0: + echo ( "\n" ); + break; + case 3: + case 6: + case 9: + echo ( "\n\n" ); + break; + case 12: + echo ( "\n" ); + break; + } + + echo ( "\n" ); + + $i++; +} +?> +
    +thisYear() ); ?> + + +
    \n\n" ); + echo ( "" ); + echo ( "\n\n" ); + $Month->build(); + while ( $Day = $Month->fetch() ) { + if ( $Day->isFirst() ) { + echo ( "\n" ); + } + if ( $Day->isEmpty() ) { + echo ( "\n" ); + } else { + echo ( "\n" ); + } + if ( $Day->isLast() ) { + echo ( "\n" ); + } + } + echo ( "
    ".date('F',$Month->thisMonth(TRUE))."
    MTWTFSS
     ".$Day->thisDay()."
    \n
    +

    Took:

    + + \ No newline at end of file diff --git a/library/pear/Calendar/docs/examples/13.php b/library/pear/Calendar/docs/examples/13.php new file mode 100644 index 000000000..4fb0f317e --- /dev/null +++ b/library/pear/Calendar/docs/examples/13.php @@ -0,0 +1,99 @@ +getTimestamp()); + +echo ( '

    Using PEAR::Date engine

    ' ); +echo ( 'Viewing: '.@$_GET['view'].'
    ' ); +echo ( 'The time is now: '.$date->format('%Y %a %e %T').'
    ' ); + +$i = 1; +echo ( '

    First Iteration

    ' ); +echo ( '

    The first iteration is more "expensive", the calendar data + structures having to be built.

    ' ); +$start = getmicrotime(); +$c->build(); +while ( $e = $c->fetch() ) { + $class = strtolower(get_class($e)); + $link ="&y=".$e->thisYear()."&m=".$e->thisMonth()."&d=".$e->thisDay(). + "&h=".$e->thisHour()."&i=".$e->thisMinute()."&s=".$e->thisSecond(); + $method = 'this'.str_replace('calendar_','',$class); + echo ( "".$e->{$method}()." : " ); + if ( ($i % 10) == 0 ) { + echo ( '
    ' ); + } + $i++; +} +echo ( '

    Took: '.(getmicrotime()-$start).' seconds

    ' ); + +$i = 1; +echo ( '

    Second Iteration

    ' ); +echo ( '

    This second iteration is faster, the data structures + being re-used

    ' ); +$start = getmicrotime(); +while ( $e = $c->fetch() ) { + $class = strtolower(get_class($e)); + $link ="&y=".$e->thisYear()."&m=".$e->thisMonth()."&d=".$e->thisDay(). + "&h=".$e->thisHour()."&i=".$e->thisMinute()."&s=".$e->thisSecond(); + $method = 'this'.str_replace('calendar_','',$class); + echo ( "".$e->{$method}()." : " ); + if ( ($i % 10) == 0 ) { + echo ( '
    ' ); + } + $i++; +} +echo ( '

    Took: '.(getmicrotime()-$start).' seconds

    ' ); +?> \ No newline at end of file diff --git a/library/pear/Calendar/docs/examples/13.phps b/library/pear/Calendar/docs/examples/13.phps new file mode 100644 index 000000000..4fb0f317e --- /dev/null +++ b/library/pear/Calendar/docs/examples/13.phps @@ -0,0 +1,99 @@ +getTimestamp()); + +echo ( '

    Using PEAR::Date engine

    ' ); +echo ( 'Viewing: '.@$_GET['view'].'
    ' ); +echo ( 'The time is now: '.$date->format('%Y %a %e %T').'
    ' ); + +$i = 1; +echo ( '

    First Iteration

    ' ); +echo ( '

    The first iteration is more "expensive", the calendar data + structures having to be built.

    ' ); +$start = getmicrotime(); +$c->build(); +while ( $e = $c->fetch() ) { + $class = strtolower(get_class($e)); + $link ="&y=".$e->thisYear()."&m=".$e->thisMonth()."&d=".$e->thisDay(). + "&h=".$e->thisHour()."&i=".$e->thisMinute()."&s=".$e->thisSecond(); + $method = 'this'.str_replace('calendar_','',$class); + echo ( "".$e->{$method}()." : " ); + if ( ($i % 10) == 0 ) { + echo ( '
    ' ); + } + $i++; +} +echo ( '

    Took: '.(getmicrotime()-$start).' seconds

    ' ); + +$i = 1; +echo ( '

    Second Iteration

    ' ); +echo ( '

    This second iteration is faster, the data structures + being re-used

    ' ); +$start = getmicrotime(); +while ( $e = $c->fetch() ) { + $class = strtolower(get_class($e)); + $link ="&y=".$e->thisYear()."&m=".$e->thisMonth()."&d=".$e->thisDay(). + "&h=".$e->thisHour()."&i=".$e->thisMinute()."&s=".$e->thisSecond(); + $method = 'this'.str_replace('calendar_','',$class); + echo ( "".$e->{$method}()." : " ); + if ( ($i % 10) == 0 ) { + echo ( '
    ' ); + } + $i++; +} +echo ( '

    Took: '.(getmicrotime()-$start).' seconds

    ' ); +?> \ No newline at end of file diff --git a/library/pear/Calendar/docs/examples/14.php b/library/pear/Calendar/docs/examples/14.php new file mode 100644 index 000000000..b1c520c80 --- /dev/null +++ b/library/pear/Calendar/docs/examples/14.php @@ -0,0 +1,141 @@ +build($selectedDays); + +// Construct strings for next/previous links +$PMonth = $month->prevMonth('object'); // Get previous month as object +$prev = $_SERVER['PHP_SELF'].'?y='.$PMonth->thisYear().'&m='.$PMonth->thisMonth().'&d='.$PMonth->thisDay(); +$NMonth = $month->nextMonth('object'); +$next = $_SERVER['PHP_SELF'].'?y='.$NMonth->thisYear().'&m='.$NMonth->thisMonth().'&d='.$NMonth->thisDay(); + +$thisDate = new Date($month->thisMonth('timestamp')); +?> + + + + Calendar using PEAR::Date Engine + + + + + +

    Calendar using PEAR::Date Engine

    + + + + + + + + + + + +fetch()) { + // Build a link string for each day + $link = $_SERVER['PHP_SELF']. + '?y='.$day->thisYear(). + '&m='.$day->thisMonth(). + '&d='.$day->thisDay(); + + // isFirst() to find start of week + if ($day->isFirst()) + echo "\n"; + + if ($day->isSelected()) { + echo ''."\n"; + } else if ($day->isEmpty()) { + echo ''."\n"; + } else { + echo ''."\n"; + } + + // isLast() to find end of week + if ($day->isLast()) { + echo "\n"; + } +} +?> + + + + + +
    +format('%B %Y'); ?> +
    MTWTFSS
    '.$day->thisDay().' '.$day->thisDay().'
    +<< +  + >> +
    +Took: '.(getmicrotime()-$start).' seconds

    '; +?> + + \ No newline at end of file diff --git a/library/pear/Calendar/docs/examples/14.phps b/library/pear/Calendar/docs/examples/14.phps new file mode 100644 index 000000000..b1c520c80 --- /dev/null +++ b/library/pear/Calendar/docs/examples/14.phps @@ -0,0 +1,141 @@ +build($selectedDays); + +// Construct strings for next/previous links +$PMonth = $month->prevMonth('object'); // Get previous month as object +$prev = $_SERVER['PHP_SELF'].'?y='.$PMonth->thisYear().'&m='.$PMonth->thisMonth().'&d='.$PMonth->thisDay(); +$NMonth = $month->nextMonth('object'); +$next = $_SERVER['PHP_SELF'].'?y='.$NMonth->thisYear().'&m='.$NMonth->thisMonth().'&d='.$NMonth->thisDay(); + +$thisDate = new Date($month->thisMonth('timestamp')); +?> + + + + Calendar using PEAR::Date Engine + + + + + +

    Calendar using PEAR::Date Engine

    + + + + + + + + + + + +fetch()) { + // Build a link string for each day + $link = $_SERVER['PHP_SELF']. + '?y='.$day->thisYear(). + '&m='.$day->thisMonth(). + '&d='.$day->thisDay(); + + // isFirst() to find start of week + if ($day->isFirst()) + echo "\n"; + + if ($day->isSelected()) { + echo ''."\n"; + } else if ($day->isEmpty()) { + echo ''."\n"; + } else { + echo ''."\n"; + } + + // isLast() to find end of week + if ($day->isLast()) { + echo "\n"; + } +} +?> + + + + + +
    +format('%B %Y'); ?> +
    MTWTFSS
    '.$day->thisDay().' '.$day->thisDay().'
    +<< +  + >> +
    +Took: '.(getmicrotime()-$start).' seconds

    '; +?> + + \ No newline at end of file diff --git a/library/pear/Calendar/docs/examples/15.php b/library/pear/Calendar/docs/examples/15.php new file mode 100644 index 000000000..c13adc563 --- /dev/null +++ b/library/pear/Calendar/docs/examples/15.php @@ -0,0 +1,58 @@ +getValidator(); +if (!$Validator->isValidWeek()) { + die ('Please enter a valid week!'); +} +*/ +?> + + + + Paging Weeks + + +

    Paging Weeks

    +

    Week: thisWeek().' '.date('F Y',$Week->thisMonth(true)); ?>

    +build(); +while ($Day = $Week->fetch()) { + echo '

    '.date('jS F',$Day->thisDay(true))."

    \n"; +} +$days = $Week->fetchAll(); + +$prevWeek = $Week->prevWeek('array'); +$prevWeekLink = $_SERVER['PHP_SELF']. + '?y='.$prevWeek['year']. + '&m='.$prevWeek['month']. + '&d='.$prevWeek['day']; + +$nextWeek = $Week->nextWeek('array'); +$nextWeekLink = $_SERVER['PHP_SELF']. + '?y='.$nextWeek['year']. + '&m='.$nextWeek['month']. + '&d='.$nextWeek['day']; +?> +

    << | >>

    + + \ No newline at end of file diff --git a/library/pear/Calendar/docs/examples/15.phps b/library/pear/Calendar/docs/examples/15.phps new file mode 100644 index 000000000..c13adc563 --- /dev/null +++ b/library/pear/Calendar/docs/examples/15.phps @@ -0,0 +1,58 @@ +getValidator(); +if (!$Validator->isValidWeek()) { + die ('Please enter a valid week!'); +} +*/ +?> + + + + Paging Weeks + + +

    Paging Weeks

    +

    Week: thisWeek().' '.date('F Y',$Week->thisMonth(true)); ?>

    +build(); +while ($Day = $Week->fetch()) { + echo '

    '.date('jS F',$Day->thisDay(true))."

    \n"; +} +$days = $Week->fetchAll(); + +$prevWeek = $Week->prevWeek('array'); +$prevWeekLink = $_SERVER['PHP_SELF']. + '?y='.$prevWeek['year']. + '&m='.$prevWeek['month']. + '&d='.$prevWeek['day']; + +$nextWeek = $Week->nextWeek('array'); +$nextWeekLink = $_SERVER['PHP_SELF']. + '?y='.$nextWeek['year']. + '&m='.$nextWeek['month']. + '&d='.$nextWeek['day']; +?> +

    << | >>

    + + \ No newline at end of file diff --git a/library/pear/Calendar/docs/examples/16.php b/library/pear/Calendar/docs/examples/16.php new file mode 100644 index 000000000..2551708a3 --- /dev/null +++ b/library/pear/Calendar/docs/examples/16.php @@ -0,0 +1,31 @@ +The current month is ' + .$Calendar->thisMonth().' of year '.$Calendar->thisYear().'

    '); + +$Uri = & new Calendar_Decorator_Uri($Calendar); +$Uri->setFragments('jahr','monat'); +// $Uri->setSeperator('/'); // Default is & +// $Uri->setScalar(); // Omit variable names +echo ( "
    Previous Uri:\t".$Uri->prev('month')."\n" );
    +echo ( "This Uri:\t".$Uri->this('month')."\n" );
    +echo ( "Next Uri:\t".$Uri->next('month')."\n
    " ); +?> +

    +Prev : +Next +

    \ No newline at end of file diff --git a/library/pear/Calendar/docs/examples/16.phps b/library/pear/Calendar/docs/examples/16.phps new file mode 100644 index 000000000..2551708a3 --- /dev/null +++ b/library/pear/Calendar/docs/examples/16.phps @@ -0,0 +1,31 @@ +The current month is ' + .$Calendar->thisMonth().' of year '.$Calendar->thisYear().'

    '); + +$Uri = & new Calendar_Decorator_Uri($Calendar); +$Uri->setFragments('jahr','monat'); +// $Uri->setSeperator('/'); // Default is & +// $Uri->setScalar(); // Omit variable names +echo ( "
    Previous Uri:\t".$Uri->prev('month')."\n" );
    +echo ( "This Uri:\t".$Uri->this('month')."\n" );
    +echo ( "Next Uri:\t".$Uri->next('month')."\n
    " ); +?> +

    +Prev : +Next +

    \ No newline at end of file diff --git a/library/pear/Calendar/docs/examples/17.php b/library/pear/Calendar/docs/examples/17.php new file mode 100644 index 000000000..0cfebce3c --- /dev/null +++ b/library/pear/Calendar/docs/examples/17.php @@ -0,0 +1,71 @@ +Calling: Calendar_Decorator_Textual::monthNames('long');
    ";
    +print_r(Calendar_Decorator_Textual::monthNames('long'));
    +echo '
    '; + +echo "
    Calling: Calendar_Decorator_Textual::weekdayNames('two');
    ";
    +print_r(Calendar_Decorator_Textual::weekdayNames('two'));
    +echo '
    '; + +echo "
    Creating: new Calendar_Day(date('Y'), date('n'), date('d'));
    "; +$Calendar = new Calendar_Day(date('Y'), date('n'), date('d')); + +// Decorate +$Textual = & new Calendar_Decorator_Textual($Calendar); + +echo '
    Previous month is: '.$Textual->prevMonthName('two').'
    '; +echo 'This month is: '.$Textual->thisMonthName('short').'
    '; +echo 'Next month is: '.$Textual->nextMonthName().'

    '; +echo 'Previous day is: '.$Textual->prevDayName().'
    '; +echo 'This day is: '.$Textual->thisDayName('short').'
    '; +echo 'Next day is: '.$Textual->nextDayName('one').'

    '; + +echo "Creating: new Calendar_Month_Weekdays(date('Y'), date('n'), 6); - Saturday is first day of week
    "; +$Calendar = new Calendar_Month_Weekdays(date('Y'), date('n'), 6); + +// Decorate +$Textual = & new Calendar_Decorator_Textual($Calendar); +?> +

    Rendering calendar....

    + + + +orderedWeekdays('short'); +foreach ($dayheaders as $dayheader) { + echo ''; +} +?> + +build(); +while ($Day = $Calendar->fetch()) { + if ($Day->isFirst()) { + echo "\n"; + } + if ($Day->isEmpty()) { + echo ''; + } else { + echo ''; + } + if ($Day->isLast()) { + echo "\n"; + } +} +?> +
    thisMonthName().' '.$Textual->thisYear(); ?>
    '.$dayheader.'
     '.$Day->thisDay().'
    \ No newline at end of file diff --git a/library/pear/Calendar/docs/examples/17.phps b/library/pear/Calendar/docs/examples/17.phps new file mode 100644 index 000000000..0cfebce3c --- /dev/null +++ b/library/pear/Calendar/docs/examples/17.phps @@ -0,0 +1,71 @@ +Calling: Calendar_Decorator_Textual::monthNames('long');
    ";
    +print_r(Calendar_Decorator_Textual::monthNames('long'));
    +echo '
    '; + +echo "
    Calling: Calendar_Decorator_Textual::weekdayNames('two');
    ";
    +print_r(Calendar_Decorator_Textual::weekdayNames('two'));
    +echo '
    '; + +echo "
    Creating: new Calendar_Day(date('Y'), date('n'), date('d'));
    "; +$Calendar = new Calendar_Day(date('Y'), date('n'), date('d')); + +// Decorate +$Textual = & new Calendar_Decorator_Textual($Calendar); + +echo '
    Previous month is: '.$Textual->prevMonthName('two').'
    '; +echo 'This month is: '.$Textual->thisMonthName('short').'
    '; +echo 'Next month is: '.$Textual->nextMonthName().'

    '; +echo 'Previous day is: '.$Textual->prevDayName().'
    '; +echo 'This day is: '.$Textual->thisDayName('short').'
    '; +echo 'Next day is: '.$Textual->nextDayName('one').'

    '; + +echo "Creating: new Calendar_Month_Weekdays(date('Y'), date('n'), 6); - Saturday is first day of week
    "; +$Calendar = new Calendar_Month_Weekdays(date('Y'), date('n'), 6); + +// Decorate +$Textual = & new Calendar_Decorator_Textual($Calendar); +?> +

    Rendering calendar....

    + + + +orderedWeekdays('short'); +foreach ($dayheaders as $dayheader) { + echo ''; +} +?> + +build(); +while ($Day = $Calendar->fetch()) { + if ($Day->isFirst()) { + echo "\n"; + } + if ($Day->isEmpty()) { + echo ''; + } else { + echo ''; + } + if ($Day->isLast()) { + echo "\n"; + } +} +?> +
    thisMonthName().' '.$Textual->thisYear(); ?>
    '.$dayheader.'
     '.$Day->thisDay().'
    \ No newline at end of file diff --git a/library/pear/Calendar/docs/examples/18.php b/library/pear/Calendar/docs/examples/18.php new file mode 100644 index 000000000..7ec2a4988 --- /dev/null +++ b/library/pear/Calendar/docs/examples/18.php @@ -0,0 +1,36 @@ +'.parent::thisDay().''; + } +} + +$Month = new Calendar_Month(date('Y'), date('n')); + +$Wrapper = & new Calendar_Decorator_Wrapper($Month); +$Wrapper->build(); + +echo '

    The Wrapper decorator

    '; +echo 'Day numbers are rendered in bold

    '; +while ($DecoratedDay = $Wrapper->fetch('MyBoldDecorator')) { + echo $DecoratedDay->thisDay().'
    '; +} +?> \ No newline at end of file diff --git a/library/pear/Calendar/docs/examples/18.phps b/library/pear/Calendar/docs/examples/18.phps new file mode 100644 index 000000000..7ec2a4988 --- /dev/null +++ b/library/pear/Calendar/docs/examples/18.phps @@ -0,0 +1,36 @@ +'.parent::thisDay().''; + } +} + +$Month = new Calendar_Month(date('Y'), date('n')); + +$Wrapper = & new Calendar_Decorator_Wrapper($Month); +$Wrapper->build(); + +echo '

    The Wrapper decorator

    '; +echo 'Day numbers are rendered in bold

    '; +while ($DecoratedDay = $Wrapper->fetch('MyBoldDecorator')) { + echo $DecoratedDay->thisDay().'
    '; +} +?> \ No newline at end of file diff --git a/library/pear/Calendar/docs/examples/19.php b/library/pear/Calendar/docs/examples/19.php new file mode 100644 index 000000000..e46d10759 --- /dev/null +++ b/library/pear/Calendar/docs/examples/19.php @@ -0,0 +1,24 @@ +setFirstDay(0); // Make Sunday first Day + +echo 'Yesterday: '.$WeekDay->prevWeekDay().'
    '; +echo 'Today: '.$WeekDay->thisWeekDay().'
    '; +echo 'Tomorrow: '.$WeekDay->nextWeekDay().'
    '; + +$WeekDay->build(); +echo 'Hours today:
    '; +while ( $Hour = $WeekDay->fetch() ) { + echo $Hour->thisHour().'
    '; +} +?> \ No newline at end of file diff --git a/library/pear/Calendar/docs/examples/19.phps b/library/pear/Calendar/docs/examples/19.phps new file mode 100644 index 000000000..e46d10759 --- /dev/null +++ b/library/pear/Calendar/docs/examples/19.phps @@ -0,0 +1,24 @@ +setFirstDay(0); // Make Sunday first Day + +echo 'Yesterday: '.$WeekDay->prevWeekDay().'
    '; +echo 'Today: '.$WeekDay->thisWeekDay().'
    '; +echo 'Tomorrow: '.$WeekDay->nextWeekDay().'
    '; + +$WeekDay->build(); +echo 'Hours today:
    '; +while ( $Hour = $WeekDay->fetch() ) { + echo $Hour->thisHour().'
    '; +} +?> \ No newline at end of file diff --git a/library/pear/Calendar/docs/examples/2.php b/library/pear/Calendar/docs/examples/2.php new file mode 100644 index 000000000..38de8a7ac --- /dev/null +++ b/library/pear/Calendar/docs/examples/2.php @@ -0,0 +1,142 @@ +build(); + +// Construct strings for next/previous links +$PMonth = $Month->prevMonth('object'); // Get previous month as object +$prev = $_SERVER['PHP_SELF'].'?y='.$PMonth->thisYear().'&m='.$PMonth->thisMonth().'&d='.$PMonth->thisDay(); +$NMonth = $Month->nextMonth('object'); +$next = $_SERVER['PHP_SELF'].'?y='.$NMonth->thisYear().'&m='.$NMonth->thisMonth().'&d='.$NMonth->thisDay(); +?> + + + + Calendar + + + + +

    Build with Calendar_Month_Weeks::build() then Calendar_Week::build()

    + + + + + + + + + + + +fetch()) { + echo "\n"; + // Build the days in the week, passing the selected days + $Week->build($selectedDays); + while ($Day = $Week->fetch()) { + + // Build a link string for each day + $link = $_SERVER['PHP_SELF']. + '?y='.$Day->thisYear(). + '&m='.$Day->thisMonth(). + '&d='.$Day->thisDay(); + + // Check to see if day is selected + if ($Day->isSelected()) { + echo ''."\n"; + // Check to see if day is empty + } else if ($Day->isEmpty()) { + echo ''."\n"; + } else { + echo ''."\n"; + } + } + echo ''."\n"; +} +?> + + + + + +
    +getTimeStamp()); ?> +
    MTWTFSS
    '.$Day->thisDay().''.$Day->thisDay().''.$Day->thisDay().'
    +<< +  + >> +
    +Took: '.(getmicrotime()-$start).' seconds

    '; +?> + + \ No newline at end of file diff --git a/library/pear/Calendar/docs/examples/2.phps b/library/pear/Calendar/docs/examples/2.phps new file mode 100644 index 000000000..38de8a7ac --- /dev/null +++ b/library/pear/Calendar/docs/examples/2.phps @@ -0,0 +1,142 @@ +build(); + +// Construct strings for next/previous links +$PMonth = $Month->prevMonth('object'); // Get previous month as object +$prev = $_SERVER['PHP_SELF'].'?y='.$PMonth->thisYear().'&m='.$PMonth->thisMonth().'&d='.$PMonth->thisDay(); +$NMonth = $Month->nextMonth('object'); +$next = $_SERVER['PHP_SELF'].'?y='.$NMonth->thisYear().'&m='.$NMonth->thisMonth().'&d='.$NMonth->thisDay(); +?> + + + + Calendar + + + + +

    Build with Calendar_Month_Weeks::build() then Calendar_Week::build()

    + + + + + + + + + + + +fetch()) { + echo "\n"; + // Build the days in the week, passing the selected days + $Week->build($selectedDays); + while ($Day = $Week->fetch()) { + + // Build a link string for each day + $link = $_SERVER['PHP_SELF']. + '?y='.$Day->thisYear(). + '&m='.$Day->thisMonth(). + '&d='.$Day->thisDay(); + + // Check to see if day is selected + if ($Day->isSelected()) { + echo ''."\n"; + // Check to see if day is empty + } else if ($Day->isEmpty()) { + echo ''."\n"; + } else { + echo ''."\n"; + } + } + echo ''."\n"; +} +?> + + + + + +
    +getTimeStamp()); ?> +
    MTWTFSS
    '.$Day->thisDay().''.$Day->thisDay().''.$Day->thisDay().'
    +<< +  + >> +
    +Took: '.(getmicrotime()-$start).' seconds

    '; +?> + + \ No newline at end of file diff --git a/library/pear/Calendar/docs/examples/20.php b/library/pear/Calendar/docs/examples/20.php new file mode 100644 index 000000000..31b868bbc --- /dev/null +++ b/library/pear/Calendar/docs/examples/20.php @@ -0,0 +1,240 @@ +entries[] = $entry; + } + + function getEntry() { + $entry = each($this->entries); + if ($entry) { + return $entry['value']; + } else { + reset($this->entries); + return false; + } + } +} + +class MonthPayload_Decorator extends Calendar_Decorator +{ + //Calendar engine + var $cE; + var $tableHelper; + + var $year; + var $month; + var $firstDay = false; + + function build($events=array()) + { + require_once CALENDAR_ROOT . 'Day.php'; + require_once CALENDAR_ROOT . 'Table/Helper.php'; + + $this->tableHelper = & new Calendar_Table_Helper($this, $this->firstDay); + $this->cE = & $this->getEngine(); + $this->year = $this->thisYear(); + $this->month = $this->thisMonth(); + + $daysInMonth = $this->cE->getDaysInMonth($this->year, $this->month); + for ($i=1; $i<=$daysInMonth; $i++) { + $Day = new Calendar_Day(2000,1,1); // Create Day with dummy values + $Day->setTimeStamp($this->cE->dateToStamp($this->year, $this->month, $i)); + $this->children[$i] = new DiaryEvent($Day); + } + if (count($events) > 0) { + $this->setSelection($events); + } + Calendar_Month_Weekdays::buildEmptyDaysBefore(); + Calendar_Month_Weekdays::shiftDays(); + Calendar_Month_Weekdays::buildEmptyDaysAfter(); + Calendar_Month_Weekdays::setWeekMarkers(); + return true; + } + + function setSelection($events) + { + $daysInMonth = $this->cE->getDaysInMonth($this->year, $this->month); + for ($i=1; $i<=$daysInMonth; $i++) { + $stamp1 = $this->cE->dateToStamp($this->year, $this->month, $i); + $stamp2 = $this->cE->dateToStamp($this->year, $this->month, $i+1); + foreach ($events as $event) { + if (($stamp1 >= $event['start'] && $stamp1 < $event['end']) || + ($stamp2 >= $event['start'] && $stamp2 < $event['end']) || + ($stamp1 <= $event['start'] && $stamp2 > $event['end']) + ) { + $this->children[$i]->addEntry($event); + $this->children[$i]->setSelected(); + } + } + } + } + + function fetch() + { + $child = each($this->children); + if ($child) { + return $child['value']; + } else { + reset($this->children); + return false; + } + } +} + +// Calendar instance used to get the dates in the preferred format: +// you can switch Calendar Engine and the example still works +$cal = new Calendar; + +$events = array(); +//add some events +$events[] = array( + 'start' => $cal->cE->dateToStamp(2004, 6, 1, 10), + 'end' => $cal->cE->dateToStamp(2004, 6, 1, 12), + 'desc' => 'Important meeting' +); +$events[] = array( + 'start' => $cal->cE->dateToStamp(2004, 6, 1, 21), + 'end' => $cal->cE->dateToStamp(2004, 6, 1, 23, 59), + 'desc' => 'Dinner with the boss' +); +$events[] = array( + 'start' => $cal->cE->dateToStamp(2004, 6, 5), + 'end' => $cal->cE->dateToStamp(2004, 6, 10, 23, 59), + 'desc' => 'Holidays!' +); + + + +$Month = & new Calendar_Month_Weekdays(2004, 6); +$MonthDecorator = new MonthPayload_Decorator($Month); +$MonthDecorator->build($events); + +?> + + + + Calendar + + + + + +

    Sample Calendar Payload Decorator (using engine)

    + + + + + + + + + + + +fetch()) { + + if ($Day->isFirst()) { + echo "\n"; + } + + echo ''; + + if ($Day->isLast()) { + echo "\n"; + } +} +?> +
    + thisMonth().' / '.$MonthDecorator->thisYear(); ?> +
    MondayTuesdayWednesdayThursdayFridaySaturdaySunday
    '; + echo '
    '.$Day->thisDay().'
    '; + + if ($Day->isEmpty()) { + echo ' '; + } else { + echo '
      '; + while ($entry = $Day->getEntry()) { + echo '
    • '.$entry['desc'].'
    • '; + //you can print the time range as well + } + echo '
    '; + } + echo '
    + + \ No newline at end of file diff --git a/library/pear/Calendar/docs/examples/20.phps b/library/pear/Calendar/docs/examples/20.phps new file mode 100644 index 000000000..31b868bbc --- /dev/null +++ b/library/pear/Calendar/docs/examples/20.phps @@ -0,0 +1,240 @@ +entries[] = $entry; + } + + function getEntry() { + $entry = each($this->entries); + if ($entry) { + return $entry['value']; + } else { + reset($this->entries); + return false; + } + } +} + +class MonthPayload_Decorator extends Calendar_Decorator +{ + //Calendar engine + var $cE; + var $tableHelper; + + var $year; + var $month; + var $firstDay = false; + + function build($events=array()) + { + require_once CALENDAR_ROOT . 'Day.php'; + require_once CALENDAR_ROOT . 'Table/Helper.php'; + + $this->tableHelper = & new Calendar_Table_Helper($this, $this->firstDay); + $this->cE = & $this->getEngine(); + $this->year = $this->thisYear(); + $this->month = $this->thisMonth(); + + $daysInMonth = $this->cE->getDaysInMonth($this->year, $this->month); + for ($i=1; $i<=$daysInMonth; $i++) { + $Day = new Calendar_Day(2000,1,1); // Create Day with dummy values + $Day->setTimeStamp($this->cE->dateToStamp($this->year, $this->month, $i)); + $this->children[$i] = new DiaryEvent($Day); + } + if (count($events) > 0) { + $this->setSelection($events); + } + Calendar_Month_Weekdays::buildEmptyDaysBefore(); + Calendar_Month_Weekdays::shiftDays(); + Calendar_Month_Weekdays::buildEmptyDaysAfter(); + Calendar_Month_Weekdays::setWeekMarkers(); + return true; + } + + function setSelection($events) + { + $daysInMonth = $this->cE->getDaysInMonth($this->year, $this->month); + for ($i=1; $i<=$daysInMonth; $i++) { + $stamp1 = $this->cE->dateToStamp($this->year, $this->month, $i); + $stamp2 = $this->cE->dateToStamp($this->year, $this->month, $i+1); + foreach ($events as $event) { + if (($stamp1 >= $event['start'] && $stamp1 < $event['end']) || + ($stamp2 >= $event['start'] && $stamp2 < $event['end']) || + ($stamp1 <= $event['start'] && $stamp2 > $event['end']) + ) { + $this->children[$i]->addEntry($event); + $this->children[$i]->setSelected(); + } + } + } + } + + function fetch() + { + $child = each($this->children); + if ($child) { + return $child['value']; + } else { + reset($this->children); + return false; + } + } +} + +// Calendar instance used to get the dates in the preferred format: +// you can switch Calendar Engine and the example still works +$cal = new Calendar; + +$events = array(); +//add some events +$events[] = array( + 'start' => $cal->cE->dateToStamp(2004, 6, 1, 10), + 'end' => $cal->cE->dateToStamp(2004, 6, 1, 12), + 'desc' => 'Important meeting' +); +$events[] = array( + 'start' => $cal->cE->dateToStamp(2004, 6, 1, 21), + 'end' => $cal->cE->dateToStamp(2004, 6, 1, 23, 59), + 'desc' => 'Dinner with the boss' +); +$events[] = array( + 'start' => $cal->cE->dateToStamp(2004, 6, 5), + 'end' => $cal->cE->dateToStamp(2004, 6, 10, 23, 59), + 'desc' => 'Holidays!' +); + + + +$Month = & new Calendar_Month_Weekdays(2004, 6); +$MonthDecorator = new MonthPayload_Decorator($Month); +$MonthDecorator->build($events); + +?> + + + + Calendar + + + + + +

    Sample Calendar Payload Decorator (using engine)

    + + + + + + + + + + + +fetch()) { + + if ($Day->isFirst()) { + echo "\n"; + } + + echo ''; + + if ($Day->isLast()) { + echo "\n"; + } +} +?> +
    + thisMonth().' / '.$MonthDecorator->thisYear(); ?> +
    MondayTuesdayWednesdayThursdayFridaySaturdaySunday
    '; + echo '
    '.$Day->thisDay().'
    '; + + if ($Day->isEmpty()) { + echo ' '; + } else { + echo '
      '; + while ($entry = $Day->getEntry()) { + echo '
    • '.$entry['desc'].'
    • '; + //you can print the time range as well + } + echo '
    '; + } + echo '
    + + \ No newline at end of file diff --git a/library/pear/Calendar/docs/examples/21.php b/library/pear/Calendar/docs/examples/21.php new file mode 100644 index 000000000..dbdd68aec --- /dev/null +++ b/library/pear/Calendar/docs/examples/21.php @@ -0,0 +1,139 @@ +build(); +?> + + + + <?php echo $Year->thisYear(); ?> + + + + + +fetch()) { + + switch ($i) { + case 0: + echo "\n"; + break; + case 3: + case 6: + case 9: + echo "\n\n"; + break; + case 12: + echo "\n"; + break; + } + + echo "\n"; + + $i++; +} +?> +
    +thisYear(); ?> + + + +
    \n\n"; + echo ''; + echo ''."\n"; + echo "\n\n"; + $Month->build(); + while ($Week = $Month->fetch()) { + echo "\n"; + echo '\n"; + $Week->build(); + + while ($Day = $Week->fetch()) { + if ($Day->isEmpty()) { + echo "\n"; + } else { + echo "\n"; + } + } + } + echo "
    '.date('F', $Month->thisMonth(TRUE)).'
    WeekMTWTFSS
    '.$Week->thisWeek($_GET['week_type'])." ".$Day->thisDay()."
    \n
    +

    Took:

    + + \ No newline at end of file diff --git a/library/pear/Calendar/docs/examples/21.phps b/library/pear/Calendar/docs/examples/21.phps new file mode 100644 index 000000000..dbdd68aec --- /dev/null +++ b/library/pear/Calendar/docs/examples/21.phps @@ -0,0 +1,139 @@ +build(); +?> + + + + <?php echo $Year->thisYear(); ?> + + + + + +fetch()) { + + switch ($i) { + case 0: + echo "\n"; + break; + case 3: + case 6: + case 9: + echo "\n\n"; + break; + case 12: + echo "\n"; + break; + } + + echo "\n"; + + $i++; +} +?> +
    +thisYear(); ?> + + + +
    \n\n"; + echo ''; + echo ''."\n"; + echo "\n\n"; + $Month->build(); + while ($Week = $Month->fetch()) { + echo "\n"; + echo '\n"; + $Week->build(); + + while ($Day = $Week->fetch()) { + if ($Day->isEmpty()) { + echo "\n"; + } else { + echo "\n"; + } + } + } + echo "
    '.date('F', $Month->thisMonth(TRUE)).'
    WeekMTWTFSS
    '.$Week->thisWeek($_GET['week_type'])." ".$Day->thisDay()."
    \n
    +

    Took:

    + + \ No newline at end of file diff --git a/library/pear/Calendar/docs/examples/22.php b/library/pear/Calendar/docs/examples/22.php new file mode 100644 index 000000000..089c53606 --- /dev/null +++ b/library/pear/Calendar/docs/examples/22.php @@ -0,0 +1,46 @@ +The current month is ' + .$Calendar->thisMonth().' of year '.$Calendar->thisYear().'

    '); + +$Uri = & new Calendar_Util_Uri('jahr','monat'); +$Uri->setFragments('jahr','monat'); + +echo "\"Vector\" URIs
    ";
    +echo ( "Previous Uri:\t".htmlentities($Uri->prev($Calendar, 'month'))."\n" );
    +echo ( "This Uri:\t".htmlentities($Uri->this($Calendar,  'month'))."\n" );
    +echo ( "Next Uri:\t".htmlentities($Uri->next($Calendar, 'month'))."\n" );
    +echo "
    "; + +// Switch to scalar URIs +$Uri->separator = '/'; // Default is & +$Uri->scalar = true; // Omit variable names + +echo "\"Scalar\" URIs
    ";
    +echo ( "Previous Uri:\t".$Uri->prev($Calendar, 'month')."\n" );
    +echo ( "This Uri:\t".$Uri->this($Calendar,  'month')."\n" );
    +echo ( "Next Uri:\t".$Uri->next($Calendar, 'month')."\n" );
    +echo "
    "; + +// Restore the vector URIs +$Uri->separator = '&'; +$Uri->scalar = false; +?> +

    +Prev : +Next +

    \ No newline at end of file diff --git a/library/pear/Calendar/docs/examples/22.phps b/library/pear/Calendar/docs/examples/22.phps new file mode 100644 index 000000000..089c53606 --- /dev/null +++ b/library/pear/Calendar/docs/examples/22.phps @@ -0,0 +1,46 @@ +The current month is ' + .$Calendar->thisMonth().' of year '.$Calendar->thisYear().'

    '); + +$Uri = & new Calendar_Util_Uri('jahr','monat'); +$Uri->setFragments('jahr','monat'); + +echo "\"Vector\" URIs
    ";
    +echo ( "Previous Uri:\t".htmlentities($Uri->prev($Calendar, 'month'))."\n" );
    +echo ( "This Uri:\t".htmlentities($Uri->this($Calendar,  'month'))."\n" );
    +echo ( "Next Uri:\t".htmlentities($Uri->next($Calendar, 'month'))."\n" );
    +echo "
    "; + +// Switch to scalar URIs +$Uri->separator = '/'; // Default is & +$Uri->scalar = true; // Omit variable names + +echo "\"Scalar\" URIs
    ";
    +echo ( "Previous Uri:\t".$Uri->prev($Calendar, 'month')."\n" );
    +echo ( "This Uri:\t".$Uri->this($Calendar,  'month')."\n" );
    +echo ( "Next Uri:\t".$Uri->next($Calendar, 'month')."\n" );
    +echo "
    "; + +// Restore the vector URIs +$Uri->separator = '&'; +$Uri->scalar = false; +?> +

    +Prev : +Next +

    \ No newline at end of file diff --git a/library/pear/Calendar/docs/examples/23.php b/library/pear/Calendar/docs/examples/23.php new file mode 100644 index 000000000..3f04b4486 --- /dev/null +++ b/library/pear/Calendar/docs/examples/23.php @@ -0,0 +1,66 @@ +Calling: Calendar_Util_Textual::monthNames('long');
    ";
    +print_r(Calendar_Util_Textual::monthNames('long'));
    +echo '
    '; + +echo "
    Calling: Calendar_Util_Textual::weekdayNames('two');
    ";
    +print_r(Calendar_Util_Textual::weekdayNames('two'));
    +echo '
    '; + +echo "
    Creating: new Calendar_Day(date('Y'), date('n'), date('d'));
    "; +$Calendar = new Calendar_Day(date('Y'), date('n'), date('d')); + +echo '
    Previous month is: '.Calendar_Util_Textual::prevMonthName($Calendar,'two').'
    '; +echo 'This month is: '.Calendar_Util_Textual::thisMonthName($Calendar,'short').'
    '; +echo 'Next month is: '.Calendar_Util_Textual::nextMonthName($Calendar).'

    '; +echo 'Previous day is: '.Calendar_Util_Textual::prevDayName($Calendar).'
    '; +echo 'This day is: '.Calendar_Util_Textual::thisDayName($Calendar,'short').'
    '; +echo 'Next day is: '.Calendar_Util_Textual::nextDayName($Calendar,'one').'

    '; + +echo "Creating: new Calendar_Month_Weekdays(date('Y'), date('n'), 6); - Saturday is first day of week
    "; +$Calendar = new Calendar_Month_Weekdays(date('Y'), date('n'), 6); + +?> +

    Rendering calendar....

    + + + +'.$dayheader.''; +} +?> + +build(); +while ($Day = $Calendar->fetch()) { + if ($Day->isFirst()) { + echo "\n"; + } + if ($Day->isEmpty()) { + echo ''; + } else { + echo ''; + } + if ($Day->isLast()) { + echo "\n"; + } +} +?> +
    thisYear(); ?>
     '.$Day->thisDay().'
    \ No newline at end of file diff --git a/library/pear/Calendar/docs/examples/23.phps b/library/pear/Calendar/docs/examples/23.phps new file mode 100644 index 000000000..3f04b4486 --- /dev/null +++ b/library/pear/Calendar/docs/examples/23.phps @@ -0,0 +1,66 @@ +Calling: Calendar_Util_Textual::monthNames('long');
    ";
    +print_r(Calendar_Util_Textual::monthNames('long'));
    +echo '
    '; + +echo "
    Calling: Calendar_Util_Textual::weekdayNames('two');
    ";
    +print_r(Calendar_Util_Textual::weekdayNames('two'));
    +echo '
    '; + +echo "
    Creating: new Calendar_Day(date('Y'), date('n'), date('d'));
    "; +$Calendar = new Calendar_Day(date('Y'), date('n'), date('d')); + +echo '
    Previous month is: '.Calendar_Util_Textual::prevMonthName($Calendar,'two').'
    '; +echo 'This month is: '.Calendar_Util_Textual::thisMonthName($Calendar,'short').'
    '; +echo 'Next month is: '.Calendar_Util_Textual::nextMonthName($Calendar).'

    '; +echo 'Previous day is: '.Calendar_Util_Textual::prevDayName($Calendar).'
    '; +echo 'This day is: '.Calendar_Util_Textual::thisDayName($Calendar,'short').'
    '; +echo 'Next day is: '.Calendar_Util_Textual::nextDayName($Calendar,'one').'

    '; + +echo "Creating: new Calendar_Month_Weekdays(date('Y'), date('n'), 6); - Saturday is first day of week
    "; +$Calendar = new Calendar_Month_Weekdays(date('Y'), date('n'), 6); + +?> +

    Rendering calendar....

    + + + +'.$dayheader.''; +} +?> + +build(); +while ($Day = $Calendar->fetch()) { + if ($Day->isFirst()) { + echo "\n"; + } + if ($Day->isEmpty()) { + echo ''; + } else { + echo ''; + } + if ($Day->isLast()) { + echo "\n"; + } +} +?> +
    thisYear(); ?>
     '.$Day->thisDay().'
    \ No newline at end of file diff --git a/library/pear/Calendar/docs/examples/3.php b/library/pear/Calendar/docs/examples/3.php new file mode 100644 index 000000000..f92dcbbc9 --- /dev/null +++ b/library/pear/Calendar/docs/examples/3.php @@ -0,0 +1,134 @@ +prevMonth('object'); // Get previous month as object +$prev = $_SERVER['PHP_SELF'].'?y='.$PMonth->thisYear().'&m='.$PMonth->thisMonth().'&d='.$PMonth->thisDay(); +$NMonth = $Month->nextMonth('object'); +$next = $_SERVER['PHP_SELF'].'?y='.$NMonth->thisYear().'&m='.$NMonth->thisMonth().'&d='.$NMonth->thisDay(); +?> + + + + Calendar + + + + + +build($selectedDays); +?> +

    Built with Calendar_Month_Weekday::build()

    + + + + + + + + + + + +fetch() ) { + + // Build a link string for each day + $link = $_SERVER['PHP_SELF']. + '?y='.$Day->thisYear(). + '&m='.$Day->thisMonth(). + '&d='.$Day->thisDay(); + + // isFirst() to find start of week + if ( $Day->isFirst() ) + echo ( "\n" ); + + if ( $Day->isSelected() ) { + echo ( "\n" ); + } else if ( $Day->isEmpty() ) { + echo ( "\n" ); + } else { + echo ( "\n" ); + } + + // isLast() to find end of week + if ( $Day->isLast() ) + echo ( "\n" ); +} +?> + + + + + +
    +getTimeStamp())); ?> +
    MTWTFSS
    ".$Day->thisDay()." ".$Day->thisDay()."
    +<< +  + >> +
    +Took: '.(getmicrotime()-$start).' seconds

    ' ); +?> + + \ No newline at end of file diff --git a/library/pear/Calendar/docs/examples/3.phps b/library/pear/Calendar/docs/examples/3.phps new file mode 100644 index 000000000..f92dcbbc9 --- /dev/null +++ b/library/pear/Calendar/docs/examples/3.phps @@ -0,0 +1,134 @@ +prevMonth('object'); // Get previous month as object +$prev = $_SERVER['PHP_SELF'].'?y='.$PMonth->thisYear().'&m='.$PMonth->thisMonth().'&d='.$PMonth->thisDay(); +$NMonth = $Month->nextMonth('object'); +$next = $_SERVER['PHP_SELF'].'?y='.$NMonth->thisYear().'&m='.$NMonth->thisMonth().'&d='.$NMonth->thisDay(); +?> + + + + Calendar + + + + + +build($selectedDays); +?> +

    Built with Calendar_Month_Weekday::build()

    + + + + + + + + + + + +fetch() ) { + + // Build a link string for each day + $link = $_SERVER['PHP_SELF']. + '?y='.$Day->thisYear(). + '&m='.$Day->thisMonth(). + '&d='.$Day->thisDay(); + + // isFirst() to find start of week + if ( $Day->isFirst() ) + echo ( "\n" ); + + if ( $Day->isSelected() ) { + echo ( "\n" ); + } else if ( $Day->isEmpty() ) { + echo ( "\n" ); + } else { + echo ( "\n" ); + } + + // isLast() to find end of week + if ( $Day->isLast() ) + echo ( "\n" ); +} +?> + + + + + +
    +getTimeStamp())); ?> +
    MTWTFSS
    ".$Day->thisDay()." ".$Day->thisDay()."
    +<< +  + >> +
    +Took: '.(getmicrotime()-$start).' seconds

    ' ); +?> + + \ No newline at end of file diff --git a/library/pear/Calendar/docs/examples/4.php b/library/pear/Calendar/docs/examples/4.php new file mode 100644 index 000000000..034813ee3 --- /dev/null +++ b/library/pear/Calendar/docs/examples/4.php @@ -0,0 +1,49 @@ +Result: '.$Unit->thisYear().'-'.$Unit->thisMonth().'-'.$Unit->thisDay(). + ' '.$Unit->thisHour().':'.$Unit->thisMinute().':'.$Unit->thisSecond(); +if ($Unit->isValid()) { + echo ' is valid!

    '; +} else { + $V= & $Unit->getValidator(); + echo ' is invalid:

    '; + while ($error = $V->fetch()) { + echo $error->toString() .'
    '; + } +} +?> +

    Enter a date / time to validate:

    +
    +Year:
    +Month:
    +Day:
    +Hour:
    +Minute:
    +Second:
    + +
    +

    Note: Error messages can be controlled with the constants CALENDAR_VALUE_TOOSMALL and CALENDAR_VALUE_TOOLARGE - see Calendar_Validator.php

    + +Took: '.(getmicrotime()-$start).' seconds

    '; ?> \ No newline at end of file diff --git a/library/pear/Calendar/docs/examples/4.phps b/library/pear/Calendar/docs/examples/4.phps new file mode 100644 index 000000000..034813ee3 --- /dev/null +++ b/library/pear/Calendar/docs/examples/4.phps @@ -0,0 +1,49 @@ +Result: '.$Unit->thisYear().'-'.$Unit->thisMonth().'-'.$Unit->thisDay(). + ' '.$Unit->thisHour().':'.$Unit->thisMinute().':'.$Unit->thisSecond(); +if ($Unit->isValid()) { + echo ' is valid!

    '; +} else { + $V= & $Unit->getValidator(); + echo ' is invalid:

    '; + while ($error = $V->fetch()) { + echo $error->toString() .'
    '; + } +} +?> +

    Enter a date / time to validate:

    +
    +Year:
    +Month:
    +Day:
    +Hour:
    +Minute:
    +Second:
    + +
    +

    Note: Error messages can be controlled with the constants CALENDAR_VALUE_TOOSMALL and CALENDAR_VALUE_TOOLARGE - see Calendar_Validator.php

    + +Took: '.(getmicrotime()-$start).' seconds

    '; ?> \ No newline at end of file diff --git a/library/pear/Calendar/docs/examples/5.php b/library/pear/Calendar/docs/examples/5.php new file mode 100644 index 000000000..ce5ae7e00 --- /dev/null +++ b/library/pear/Calendar/docs/examples/5.php @@ -0,0 +1,132 @@ + + + + + Select and Update + + +

    Select and Update

    +isValid() ) { + $V= & $Second->getValidator(); + echo ('

    Validation failed:

    ' ); + while ( $error = $V->fetch() ) { + echo ( $error->toString() .'
    ' ); + } + } else { + echo ('

    Validation success.

    ' ); + echo ( '

    New timestamp is: '.$Second->getTimeStamp().' which could be used to update a database, for example'); + } +} else { +$Year = new Calendar_Year($_POST['y']); +$Month = new Calendar_Month($_POST['y'],$_POST['m']); +$Day = new Calendar_Day($_POST['y'],$_POST['m'],$_POST['d']); +$Hour = new Calendar_Hour($_POST['y'],$_POST['m'],$_POST['d'],$_POST['h']); +$Minute = new Calendar_Minute($_POST['y'],$_POST['m'],$_POST['d'],$_POST['h'],$_POST['i']); +$Second = new Calendar_Second($_POST['y'],$_POST['m'],$_POST['d'],$_POST['h'],$_POST['i'],$_POST['s']); +?> +

    Set the alarm clock

    +
    +Year:   +Month:  +Day:  +Hour:  +Minute:  +Second:  +
    + +Took: '.(getmicrotime()-$start).' seconds

    ' ); ?> + + \ No newline at end of file diff --git a/library/pear/Calendar/docs/examples/5.phps b/library/pear/Calendar/docs/examples/5.phps new file mode 100644 index 000000000..ce5ae7e00 --- /dev/null +++ b/library/pear/Calendar/docs/examples/5.phps @@ -0,0 +1,132 @@ + + + + + Select and Update + + +

    Select and Update

    +isValid() ) { + $V= & $Second->getValidator(); + echo ('

    Validation failed:

    ' ); + while ( $error = $V->fetch() ) { + echo ( $error->toString() .'
    ' ); + } + } else { + echo ('

    Validation success.

    ' ); + echo ( '

    New timestamp is: '.$Second->getTimeStamp().' which could be used to update a database, for example'); + } +} else { +$Year = new Calendar_Year($_POST['y']); +$Month = new Calendar_Month($_POST['y'],$_POST['m']); +$Day = new Calendar_Day($_POST['y'],$_POST['m'],$_POST['d']); +$Hour = new Calendar_Hour($_POST['y'],$_POST['m'],$_POST['d'],$_POST['h']); +$Minute = new Calendar_Minute($_POST['y'],$_POST['m'],$_POST['d'],$_POST['h'],$_POST['i']); +$Second = new Calendar_Second($_POST['y'],$_POST['m'],$_POST['d'],$_POST['h'],$_POST['i'],$_POST['s']); +?> +

    Set the alarm clock

    + +Year:   +Month:  +Day:  +Hour:  +Minute:  +Second:  +
    + +Took: '.(getmicrotime()-$start).' seconds

    ' ); ?> + + \ No newline at end of file diff --git a/library/pear/Calendar/docs/examples/6.php b/library/pear/Calendar/docs/examples/6.php new file mode 100644 index 000000000..591c05d7e --- /dev/null +++ b/library/pear/Calendar/docs/examples/6.php @@ -0,0 +1,210 @@ +' ); +?> + + +Personal Planner Rendered with WML + +

    Viewing getTimeStamp()) ); ?>

    +

    + +Back to Month View +"/> + +

    + +build(); + while ( $Hour = & $Day->fetch() ) { + echo ( "\n" ); + echo ( "\n" ); + echo ( "\n" ); + } +?> +
    ".date('g a',$Hour->getTimeStamp())."Free time!
    + +

    getTimeStamp()) ); ?>

    + + + + +build($selection); +while ( $Day = $Month->fetch() ) { + if ( $Day->isFirst() ) { + echo ( "\n" ); + } + if ( $Day->isEmpty() ) { + echo ( "\n" ); + } else if ( $Day->isSelected() ) { + echo ( "\n" ); + } else { + echo ( "\n" ); + } + if ( $Day->isLast() ) { + echo ( "\n" ); + } +} +?> + + + + + +
    MTWTFSS
    ".$Day->thisDay()."\nthisYear()."&m=".$Day->thisMonth()."&d=".$Day->thisDay(). + "&mime=wml\" />\n".$Day->thisDay()."\nthisYear()."&m=".$Day->thisMonth()."&d=".$Day->thisDay(). + "&mime=wml\" />
    + +<< +"/> + + + +>> +"/> + +
    + + +

    Back to HTML

    +Took: '.(getmicrotime()-$start).' seconds

    ' ); ?> +
    + + + + + HTML (+WML) Personal Planner + + +

    Personal Planner Rendered with HTML

    +

    To view in WML, click here or place a ?mime=wml at the end of any URL. +Note that Opera supports WML natively and Mozilla / Firefox has the WMLBrowser +plugin: wmlbrowser.mozdev.org

    + +

    Viewing getTimeStamp()) ); ?>

    +

    + +Back to Month View +

    + +build(); + while ( $Hour = & $Day->fetch() ) { + echo ( "\n" ); + echo ( "\n" ); + echo ( "\n" ); + } +?> +
    ".date('g a',$Hour->getTimeStamp())."Free time!
    + +

    getTimeStamp()) ); ?>

    + + + + +build($selection); +while ( $Day = $Month->fetch() ) { + if ( $Day->isFirst() ) { + echo ( "\n" ); + } + if ( $Day->isEmpty() ) { + echo ( "\n" ); + } else if ( $Day->isSelected() ) { + echo ( "\n" ); + } else { + echo ( "\n" ); + } + if ( $Day->isLast() ) { + echo ( "\n" ); + } +} +?> + + + + + +
    MTWTFSS
    thisYear()."&m=".$Day->thisMonth()."&d=".$Day->thisDay(). + "&wml\">".$Day->thisDay()."thisYear()."&m=".$Day->thisMonth()."&d=".$Day->thisDay(). + "\">".$Day->thisDay()."
    + +<< + +>> +
    + + + + +Took: '.(getmicrotime()-$start).' seconds

    ' ); ?> + + + \ No newline at end of file diff --git a/library/pear/Calendar/docs/examples/6.phps b/library/pear/Calendar/docs/examples/6.phps new file mode 100644 index 000000000..591c05d7e --- /dev/null +++ b/library/pear/Calendar/docs/examples/6.phps @@ -0,0 +1,210 @@ +' ); +?> + + +Personal Planner Rendered with WML + +

    Viewing getTimeStamp()) ); ?>

    +

    + +Back to Month View +"/> + +

    + +build(); + while ( $Hour = & $Day->fetch() ) { + echo ( "\n" ); + echo ( "\n" ); + echo ( "\n" ); + } +?> +
    ".date('g a',$Hour->getTimeStamp())."Free time!
    + +

    getTimeStamp()) ); ?>

    + + + + +build($selection); +while ( $Day = $Month->fetch() ) { + if ( $Day->isFirst() ) { + echo ( "\n" ); + } + if ( $Day->isEmpty() ) { + echo ( "\n" ); + } else if ( $Day->isSelected() ) { + echo ( "\n" ); + } else { + echo ( "\n" ); + } + if ( $Day->isLast() ) { + echo ( "\n" ); + } +} +?> + + + + + +
    MTWTFSS
    ".$Day->thisDay()."\nthisYear()."&m=".$Day->thisMonth()."&d=".$Day->thisDay(). + "&mime=wml\" />\n".$Day->thisDay()."\nthisYear()."&m=".$Day->thisMonth()."&d=".$Day->thisDay(). + "&mime=wml\" />
    + +<< +"/> + + + +>> +"/> + +
    + + +

    Back to HTML

    +Took: '.(getmicrotime()-$start).' seconds

    ' ); ?> +
    + + + + + HTML (+WML) Personal Planner + + +

    Personal Planner Rendered with HTML

    +

    To view in WML, click here or place a ?mime=wml at the end of any URL. +Note that Opera supports WML natively and Mozilla / Firefox has the WMLBrowser +plugin: wmlbrowser.mozdev.org

    + +

    Viewing getTimeStamp()) ); ?>

    +

    + +Back to Month View +

    + +build(); + while ( $Hour = & $Day->fetch() ) { + echo ( "\n" ); + echo ( "\n" ); + echo ( "\n" ); + } +?> +
    ".date('g a',$Hour->getTimeStamp())."Free time!
    + +

    getTimeStamp()) ); ?>

    + + + + +build($selection); +while ( $Day = $Month->fetch() ) { + if ( $Day->isFirst() ) { + echo ( "\n" ); + } + if ( $Day->isEmpty() ) { + echo ( "\n" ); + } else if ( $Day->isSelected() ) { + echo ( "\n" ); + } else { + echo ( "\n" ); + } + if ( $Day->isLast() ) { + echo ( "\n" ); + } +} +?> + + + + + +
    MTWTFSS
    thisYear()."&m=".$Day->thisMonth()."&d=".$Day->thisDay(). + "&wml\">".$Day->thisDay()."thisYear()."&m=".$Day->thisMonth()."&d=".$Day->thisDay(). + "\">".$Day->thisDay()."
    + +<< + +>> +
    + + + + +Took: '.(getmicrotime()-$start).' seconds

    ' ); ?> + + + \ No newline at end of file diff --git a/library/pear/Calendar/docs/examples/7.php b/library/pear/Calendar/docs/examples/7.php new file mode 100644 index 000000000..bdffd956c --- /dev/null +++ b/library/pear/Calendar/docs/examples/7.php @@ -0,0 +1,92 @@ +__dispatch_map['getMonth'] = + array('in' => array('year' => 'int', 'month'=>'int'), + 'out' => array('month' => '{urn:PEAR_SOAP_Calendar}Month'), + ); + $this->__typedef['Month'] = array ( + 'monthname' => 'string', + 'days' => '{urn:PEAR_SOAP_Calendar}MonthDays' + ); + $this->__typedef['MonthDays'] = array (array ('{urn:PEAR_SOAP_Calendar}Day')); + $this->__typedef['Day'] = array ( + 'isFirst' => 'int', + 'isLast' => 'int', + 'isEmpty' => 'int', + 'day' => 'int' ); + } + + function __dispatch($methodname) + { + if (isset($this->__dispatch_map[$methodname])) + return $this->__dispatch_map[$methodname]; + return NULL; + } + + function getMonth($year, $month) + { + require_once(CALENDAR_ROOT.'Month'.DIRECTORY_SEPARATOR.'Weekdays.php'); + $Month = & new Calendar_Month_Weekdays($year,$month); + if (!$Month->isValid()) { + $V = & $Month->getValidator(); + $errorMsg = ''; + while ($error = $V->fetch()) { + $errorMsg .= $error->toString()."\n"; + } + return new SOAP_Fault($errorMsg, 'Client'); + } else { + $monthname = date('F Y', $Month->getTimeStamp()); + $days = array(); + $Month->build(); + while ($Day = & $Month->fetch()) { + $day = array( + 'isFirst' => (int)$Day->isFirst(), + 'isLast' => (int)$Day->isLast(), + 'isEmpty' => (int)$Day->isEmpty(), + 'day' => (int)$Day->thisDay(), + ); + $days[] = $day; + } + return array('monthname' => $monthname, 'days' => $days); + } + } +} + +$server = new SOAP_Server(); +$server->_auto_translation = true; +$calendar = new Calendar_Server(); +$server->addObjectMap($calendar, 'urn:PEAR_SOAP_Calendar'); + +if (strtoupper($_SERVER['REQUEST_METHOD'])=='POST') { + $server->service($GLOBALS['HTTP_RAW_POST_DATA']); +} else { + require_once 'SOAP'.DIRECTORY_SEPARATOR.'Disco.php'; + $disco = new SOAP_DISCO_Server($server, "PEAR_SOAP_Calendar"); + if (isset($_SERVER['QUERY_STRING']) && + strcasecmp($_SERVER['QUERY_STRING'], 'wsdl')==0) { + header("Content-type: text/xml"); + echo $disco->getWSDL(); + } else { + echo 'This is a PEAR::SOAP Calendar Server. For client try here
    '; + echo 'For WSDL try here'; + } + exit; +} +?> \ No newline at end of file diff --git a/library/pear/Calendar/docs/examples/7.phps b/library/pear/Calendar/docs/examples/7.phps new file mode 100644 index 000000000..bdffd956c --- /dev/null +++ b/library/pear/Calendar/docs/examples/7.phps @@ -0,0 +1,92 @@ +__dispatch_map['getMonth'] = + array('in' => array('year' => 'int', 'month'=>'int'), + 'out' => array('month' => '{urn:PEAR_SOAP_Calendar}Month'), + ); + $this->__typedef['Month'] = array ( + 'monthname' => 'string', + 'days' => '{urn:PEAR_SOAP_Calendar}MonthDays' + ); + $this->__typedef['MonthDays'] = array (array ('{urn:PEAR_SOAP_Calendar}Day')); + $this->__typedef['Day'] = array ( + 'isFirst' => 'int', + 'isLast' => 'int', + 'isEmpty' => 'int', + 'day' => 'int' ); + } + + function __dispatch($methodname) + { + if (isset($this->__dispatch_map[$methodname])) + return $this->__dispatch_map[$methodname]; + return NULL; + } + + function getMonth($year, $month) + { + require_once(CALENDAR_ROOT.'Month'.DIRECTORY_SEPARATOR.'Weekdays.php'); + $Month = & new Calendar_Month_Weekdays($year,$month); + if (!$Month->isValid()) { + $V = & $Month->getValidator(); + $errorMsg = ''; + while ($error = $V->fetch()) { + $errorMsg .= $error->toString()."\n"; + } + return new SOAP_Fault($errorMsg, 'Client'); + } else { + $monthname = date('F Y', $Month->getTimeStamp()); + $days = array(); + $Month->build(); + while ($Day = & $Month->fetch()) { + $day = array( + 'isFirst' => (int)$Day->isFirst(), + 'isLast' => (int)$Day->isLast(), + 'isEmpty' => (int)$Day->isEmpty(), + 'day' => (int)$Day->thisDay(), + ); + $days[] = $day; + } + return array('monthname' => $monthname, 'days' => $days); + } + } +} + +$server = new SOAP_Server(); +$server->_auto_translation = true; +$calendar = new Calendar_Server(); +$server->addObjectMap($calendar, 'urn:PEAR_SOAP_Calendar'); + +if (strtoupper($_SERVER['REQUEST_METHOD'])=='POST') { + $server->service($GLOBALS['HTTP_RAW_POST_DATA']); +} else { + require_once 'SOAP'.DIRECTORY_SEPARATOR.'Disco.php'; + $disco = new SOAP_DISCO_Server($server, "PEAR_SOAP_Calendar"); + if (isset($_SERVER['QUERY_STRING']) && + strcasecmp($_SERVER['QUERY_STRING'], 'wsdl')==0) { + header("Content-type: text/xml"); + echo $disco->getWSDL(); + } else { + echo 'This is a PEAR::SOAP Calendar Server. For client try here
    '; + echo 'For WSDL try here'; + } + exit; +} +?> \ No newline at end of file diff --git a/library/pear/Calendar/docs/examples/8.php b/library/pear/Calendar/docs/examples/8.php new file mode 100644 index 000000000..f84887953 --- /dev/null +++ b/library/pear/Calendar/docs/examples/8.php @@ -0,0 +1,70 @@ +") ) { + die('PHP 5 has problems with PEAR::SOAP Client (8.0RC3) + - remove @ before include below to see why'); +} + +if (!@include('SOAP'.DIRECTORY_SEPARATOR.'Client.php')) { + die('You must have PEAR::SOAP installed'); +} + +// Just to save manaul modification... +$basePath = explode('/', $_SERVER['SCRIPT_NAME']); +array_pop($basePath); +$basePath = implode('/', $basePath); +$url = 'http://'.$_SERVER['SERVER_NAME'].$basePath.'/7.php?wsdl'; + +if (!isset($_GET['y'])) $_GET['y'] = date('Y'); +if (!isset($_GET['m'])) $_GET['m'] = date('n'); + +$wsdl = new SOAP_WSDL ($url); + +echo ( '
    '.$wsdl->generateProxyCode().'
    ' ); + +$calendarClient = $wsdl->getProxy(); + +$month = $calendarClient->getMonth((int)$_GET['y'],(int)$_GET['m']); + +if ( PEAR::isError($month) ) { + die ( $month->toString() ); +} +?> + + + + Calendar over the Wire + + +

    Calendar Over the Wire (featuring PEAR::SOAP)

    + + + + + +days as $day ) { + + if ( $day->isFirst === 1 ) + echo ( "\n" ); + if ( $day->isEmpty === 1 ) { + echo ( "" ); + } else { + echo ( "" ); + } + if ( $day->isLast === 1 ) + echo ( "\n" ); +} +?> + +
    monthname );?>
    MTWTFSS
    ".$day->day."
    +

    Enter Year and Month to View:

    + +Year:   +Month:   + + + + \ No newline at end of file diff --git a/library/pear/Calendar/docs/examples/8.phps b/library/pear/Calendar/docs/examples/8.phps new file mode 100644 index 000000000..f84887953 --- /dev/null +++ b/library/pear/Calendar/docs/examples/8.phps @@ -0,0 +1,70 @@ +") ) { + die('PHP 5 has problems with PEAR::SOAP Client (8.0RC3) + - remove @ before include below to see why'); +} + +if (!@include('SOAP'.DIRECTORY_SEPARATOR.'Client.php')) { + die('You must have PEAR::SOAP installed'); +} + +// Just to save manaul modification... +$basePath = explode('/', $_SERVER['SCRIPT_NAME']); +array_pop($basePath); +$basePath = implode('/', $basePath); +$url = 'http://'.$_SERVER['SERVER_NAME'].$basePath.'/7.php?wsdl'; + +if (!isset($_GET['y'])) $_GET['y'] = date('Y'); +if (!isset($_GET['m'])) $_GET['m'] = date('n'); + +$wsdl = new SOAP_WSDL ($url); + +echo ( '
    '.$wsdl->generateProxyCode().'
    ' ); + +$calendarClient = $wsdl->getProxy(); + +$month = $calendarClient->getMonth((int)$_GET['y'],(int)$_GET['m']); + +if ( PEAR::isError($month) ) { + die ( $month->toString() ); +} +?> + + + + Calendar over the Wire + + +

    Calendar Over the Wire (featuring PEAR::SOAP)

    + + + + + +days as $day ) { + + if ( $day->isFirst === 1 ) + echo ( "\n" ); + if ( $day->isEmpty === 1 ) { + echo ( "" ); + } else { + echo ( "" ); + } + if ( $day->isLast === 1 ) + echo ( "\n" ); +} +?> + +
    monthname );?>
    MTWTFSS
    ".$day->day."
    +

    Enter Year and Month to View:

    +
    +Year:   +Month:   + +
    + + \ No newline at end of file diff --git a/library/pear/Calendar/docs/examples/9.php b/library/pear/Calendar/docs/examples/9.php new file mode 100644 index 000000000..4b6a937a4 --- /dev/null +++ b/library/pear/Calendar/docs/examples/9.php @@ -0,0 +1,16 @@ +getTimeStamp())); +?> \ No newline at end of file diff --git a/library/pear/Calendar/docs/examples/9.phps b/library/pear/Calendar/docs/examples/9.phps new file mode 100644 index 000000000..4b6a937a4 --- /dev/null +++ b/library/pear/Calendar/docs/examples/9.phps @@ -0,0 +1,16 @@ +getTimeStamp())); +?> \ No newline at end of file diff --git a/library/pear/Calendar/docs/examples/index.html b/library/pear/Calendar/docs/examples/index.html new file mode 100644 index 000000000..a68e996eb --- /dev/null +++ b/library/pear/Calendar/docs/examples/index.html @@ -0,0 +1,50 @@ + + + + PEAR::Calendar Examples + + + + +

    PEAR::Calendar Examples

    +

    $Id: index.html 166574 2004-08-17 09:10:53Z hfuecks $

    +
      +
    • 1.php [src] - shows basic usage, passing all the way down from Calendar_Year to Calendar_Second - more of a quick test it's working
    • +
    • 2.php [src] - shows how to build a tabular month using Calendar_Month_Weeks, Calendar_Week, Calendar_Day as well as selecting some dates.
    • +
    • 3.php [src] - shows how to build a tabular month using Calendar_Month_Weekdays and Calendar_Day, as well as selecting some dates (this method is faster).
    • +
    • 4.php [src] - shows how to use PEAR::Calendar for validation.
    • +
    • 5.php [src] - shows PEAR::Calendar in use to help generate a form.
    • +
    • 6.php [src] - a month and day "planner" calendar, which can be rendered both as HTML and WML.
    • +
    • 7.php [src] - a simple SOAP Calendar Server, using PEAR::SOAP and PEAR::Calendar
    • +
    • 8.php [src] - a WSDL SOAP client for the SOAP Calendar Server
    • +
    • 9.php [src] - quick example of i18n with setlocale (not working on SF)
    • +
    • 10.php [src] - an example of extending Calendar_Decorator to modify output
    • +
    • 11.php [src] - attaching a "payload" (e.g. results of a DB query) to a calendar using Calendar_Decorator to allow the payload to be available inside the main loop.
    • +
    • 12.php [src] - a complete year with months.
    • +
    • 13.php [src] - same as 1.php but using Calendar_Engine_PearDate, (see PEAR::Date).
    • +
    • 14.php [src] - same as 3.php but using Calendar_Engine_PearDate
    • +
    • 15.php [src] - paging through weeks
    • +
    • 16.php [src] - example of Calendar_Decorator_Uri. Note you should prefer Calendar_Util_Uri (see below) in most cases, for performance
    • +
    • 17.php [src] - example of Calendar_Decorator_Textual. Note you should prefer Calendar_Util_Textual (see below) in most cases, for performance
    • +
    • 18.php [src] - example of Calendar_Decorator_Wrapper.
    • +
    • 19.php [src] - example of Calendar_Decorator_Weekday.
    • +
    • 20.php [src] - shows how to attach a "payload" spanning multiple days, with more than one entry per day
    • +
    • 21.php [src] - same as 12.php but using Calendar_Month_Weeks instead of Calendar_Month_Weekdays to allow the week in the year or week in the month to be displayed.
    • +
    • 22.php [src] - demonstrates use of Calendar_Util_Uri.
    • +
    • 23.php [src] - demonstrates use of Calendar_Util_Textual.
    • +
    • 24.php [src] - Calendar_Decorator_Weekday combined with Calendar_Decorator_Wrapper to decorate days in the month.
    • +
    + + \ No newline at end of file diff --git a/library/pear/Calendar/tests/README b/library/pear/Calendar/tests/README new file mode 100644 index 000000000..cbae24227 --- /dev/null +++ b/library/pear/Calendar/tests/README @@ -0,0 +1,7 @@ +These tests require Simple Test: http://www.lastcraft.com/simple_test.php + +Ideally they would use PEAR::PHPUnit but the current version has bugs and +lacks alot of the functionality (e.g. Mock Objects) which Simple Test +provides. + +Modifying the simple_include.php script for your simple test install dir \ No newline at end of file diff --git a/library/pear/Calendar/tests/all_tests.php b/library/pear/Calendar/tests/all_tests.php new file mode 100644 index 000000000..bda396ada --- /dev/null +++ b/library/pear/Calendar/tests/all_tests.php @@ -0,0 +1,34 @@ +GroupTest('All PEAR::Calendar Tests'); + $this->AddTestCase(new CalendarTests()); + $this->AddTestCase(new CalendarTabularTests()); + $this->AddTestCase(new ValidatorTests()); + $this->AddTestCase(new CalendarEngineTests()); + $this->AddTestCase(new TableHelperTests()); + $this->AddTestCase(new DecoratorTests()); + $this->AddTestCase(new UtilTests()); + } +} + +$test = &new AllTests(); +$test->run(new HtmlReporter()); +?> \ No newline at end of file diff --git a/library/pear/Calendar/tests/calendar_engine_tests.php b/library/pear/Calendar/tests/calendar_engine_tests.php new file mode 100644 index 000000000..f44138189 --- /dev/null +++ b/library/pear/Calendar/tests/calendar_engine_tests.php @@ -0,0 +1,20 @@ +GroupTest('Calendar Engine Tests'); + $this->addTestFile('peardate_engine_test.php'); + $this->addTestFile('unixts_engine_test.php'); + } +} + +if (!defined('TEST_RUNNING')) { + define('TEST_RUNNING', true); + $test = &new CalendarEngineTests(); + $test->run(new HtmlReporter()); +} +?> \ No newline at end of file diff --git a/library/pear/Calendar/tests/calendar_include.php b/library/pear/Calendar/tests/calendar_include.php new file mode 100644 index 000000000..a375eafcf --- /dev/null +++ b/library/pear/Calendar/tests/calendar_include.php @@ -0,0 +1,28 @@ + \ No newline at end of file diff --git a/library/pear/Calendar/tests/calendar_tabular_tests.php b/library/pear/Calendar/tests/calendar_tabular_tests.php new file mode 100644 index 000000000..8dbaf4e67 --- /dev/null +++ b/library/pear/Calendar/tests/calendar_tabular_tests.php @@ -0,0 +1,22 @@ +GroupTest('Calendar Tabular Tests'); + $this->addTestFile('month_weekdays_test.php'); + $this->addTestFile('month_weeks_test.php'); + $this->addTestFile('week_test.php'); + //$this->addTestFile('week_firstday_0_test.php'); //switch with the above + } +} + +if (!defined('TEST_RUNNING')) { + define('TEST_RUNNING', true); + $test = &new CalendarTabularTests(); + $test->run(new HtmlReporter()); +} +?> \ No newline at end of file diff --git a/library/pear/Calendar/tests/calendar_test.php b/library/pear/Calendar/tests/calendar_test.php new file mode 100644 index 000000000..62c6a696c --- /dev/null +++ b/library/pear/Calendar/tests/calendar_test.php @@ -0,0 +1,124 @@ +UnitTestCase($name); + } + function setUp() { + $this->cal = new Calendar(2003,10,25,13,32,43); + } + function tearDown() { + unset($this->cal); + } + function testPrevYear () { + $this->assertEqual(2002,$this->cal->prevYear()); + } + function testPrevYear_Array () { + $this->assertEqual( + array( + 'year' => 2002, + 'month' => 1, + 'day' => 1, + 'hour' => 0, + 'minute' => 0, + 'second' => 0), + $this->cal->prevYear('array')); + } + function testThisYear () { + $this->assertEqual(2003,$this->cal->thisYear()); + } + function testNextYear () { + $this->assertEqual(2004,$this->cal->nextYear()); + } + function testPrevMonth () { + $this->assertEqual(9,$this->cal->prevMonth()); + } + function testPrevMonth_Array () { + $this->assertEqual( + array( + 'year' => 2003, + 'month' => 9, + 'day' => 1, + 'hour' => 0, + 'minute' => 0, + 'second' => 0), + $this->cal->prevMonth('array')); + } + function testThisMonth () { + $this->assertEqual(10,$this->cal->thisMonth()); + } + function testNextMonth () { + $this->assertEqual(11,$this->cal->nextMonth()); + } + function testPrevDay () { + $this->assertEqual(24,$this->cal->prevDay()); + } + function testPrevDay_Array () { + $this->assertEqual( + array( + 'year' => 2003, + 'month' => 10, + 'day' => 24, + 'hour' => 0, + 'minute' => 0, + 'second' => 0), + $this->cal->prevDay('array')); + } + function testThisDay () { + $this->assertEqual(25,$this->cal->thisDay()); + } + function testNextDay () { + $this->assertEqual(26,$this->cal->nextDay()); + } + function testPrevHour () { + $this->assertEqual(12,$this->cal->prevHour()); + } + function testThisHour () { + $this->assertEqual(13,$this->cal->thisHour()); + } + function testNextHour () { + $this->assertEqual(14,$this->cal->nextHour()); + } + function testPrevMinute () { + $this->assertEqual(31,$this->cal->prevMinute()); + } + function testThisMinute () { + $this->assertEqual(32,$this->cal->thisMinute()); + } + function testNextMinute () { + $this->assertEqual(33,$this->cal->nextMinute()); + } + function testPrevSecond () { + $this->assertEqual(42,$this->cal->prevSecond()); + } + function testThisSecond () { + $this->assertEqual(43,$this->cal->thisSecond()); + } + function testNextSecond () { + $this->assertEqual(44,$this->cal->nextSecond()); + } + function testSetTimeStamp() { + $stamp = mktime(13,32,43,10,25,2003); + $this->cal->setTimeStamp($stamp); + $this->assertEqual($stamp,$this->cal->getTimeStamp()); + } + function testGetTimeStamp() { + $stamp = mktime(13,32,43,10,25,2003); + $this->assertEqual($stamp,$this->cal->getTimeStamp()); + } + function testIsToday() { + $stamp = mktime(); + $this->cal->setTimestamp($stamp); + $this->assertTrue($this->cal->isToday()); + + $stamp += 1000000000; + $this->cal->setTimestamp($stamp); + $this->assertFalse($this->cal->isToday()); + } +} +?> \ No newline at end of file diff --git a/library/pear/Calendar/tests/calendar_tests.php b/library/pear/Calendar/tests/calendar_tests.php new file mode 100644 index 000000000..09513a307 --- /dev/null +++ b/library/pear/Calendar/tests/calendar_tests.php @@ -0,0 +1,25 @@ +GroupTest('Calendar Tests'); + $this->addTestFile('calendar_test.php'); + $this->addTestFile('year_test.php'); + $this->addTestFile('month_test.php'); + $this->addTestFile('day_test.php'); + $this->addTestFile('hour_test.php'); + $this->addTestFile('minute_test.php'); + $this->addTestFile('second_test.php'); + } +} + +if (!defined('TEST_RUNNING')) { + define('TEST_RUNNING', true); + $test = &new CalendarTests(); + $test->run(new HtmlReporter()); +} +?> \ No newline at end of file diff --git a/library/pear/Calendar/tests/day_test.php b/library/pear/Calendar/tests/day_test.php new file mode 100644 index 000000000..a812d4414 --- /dev/null +++ b/library/pear/Calendar/tests/day_test.php @@ -0,0 +1,107 @@ +UnitTestCase('Test of Day'); + } + function setUp() { + $this->cal = new Calendar_Day(2003,10,25); + } + function testPrevDay_Array () { + $this->assertEqual( + array( + 'year' => 2003, + 'month' => 10, + 'day' => 24, + 'hour' => 0, + 'minute' => 0, + 'second' => 0), + $this->cal->prevDay('array')); + } + function testPrevHour () { + $this->assertEqual(23,$this->cal->prevHour()); + } + function testThisHour () { + $this->assertEqual(0,$this->cal->thisHour()); + } + function testNextHour () { + $this->assertEqual(1,$this->cal->nextHour()); + } + function testPrevMinute () { + $this->assertEqual(59,$this->cal->prevMinute()); + } + function testThisMinute () { + $this->assertEqual(0,$this->cal->thisMinute()); + } + function testNextMinute () { + $this->assertEqual(1,$this->cal->nextMinute()); + } + function testPrevSecond () { + $this->assertEqual(59,$this->cal->prevSecond()); + } + function testThisSecond () { + $this->assertEqual(0,$this->cal->thisSecond()); + } + function testNextSecond () { + $this->assertEqual(1,$this->cal->nextSecond()); + } + function testGetTimeStamp() { + $stamp = mktime(0,0,0,10,25,2003); + $this->assertEqual($stamp,$this->cal->getTimeStamp()); + } +} + +class TestOfDayBuild extends TestOfDay { + function TestOfDayBuild() { + $this->UnitTestCase('Test of Day::build()'); + } + function testSize() { + $this->cal->build(); + $this->assertEqual(24,$this->cal->size()); + } + function testFetch() { + $this->cal->build(); + $i=0; + while ( $Child = $this->cal->fetch() ) { + $i++; + } + $this->assertEqual(24,$i); + } + function testFetchAll() { + $this->cal->build(); + $children = array(); + $i = 0; + while ( $Child = $this->cal->fetch() ) { + $children[$i]=$Child; + $i++; + } + $this->assertEqual($children,$this->cal->fetchAll()); + } + function testSelection() { + require_once(CALENDAR_ROOT . 'Hour.php'); + $selection = array(new Calendar_Hour(2003,10,25,13)); + $this->cal->build($selection); + $i = 0; + while ( $Child = $this->cal->fetch() ) { + if ( $i == 13 ) + break; + $i++; + } + $this->assertTrue($Child->isSelected()); + } +} + +if (!defined('TEST_RUNNING')) { + define('TEST_RUNNING', true); + $test = &new TestOfDay(); + $test->run(new HtmlReporter()); + $test = &new TestOfDayBuild(); + $test->run(new HtmlReporter()); +} +?> \ No newline at end of file diff --git a/library/pear/Calendar/tests/decorator_test.php b/library/pear/Calendar/tests/decorator_test.php new file mode 100644 index 000000000..6c6dd7974 --- /dev/null +++ b/library/pear/Calendar/tests/decorator_test.php @@ -0,0 +1,268 @@ +UnitTestCase('Test of Calendar_Decorator'); + } + function setUp() { + $this->mockengine = new Mock_Calendar_Engine($this); + $this->mockcal = new Mock_Calendar_Second($this); + $this->mockcal->setReturnValue('prevYear',2002); + $this->mockcal->setReturnValue('thisYear',2003); + $this->mockcal->setReturnValue('nextYear',2004); + $this->mockcal->setReturnValue('prevMonth',9); + $this->mockcal->setReturnValue('thisMonth',10); + $this->mockcal->setReturnValue('nextMonth',11); + $this->mockcal->setReturnValue('prevDay',14); + $this->mockcal->setReturnValue('thisDay',15); + $this->mockcal->setReturnValue('nextDay',16); + $this->mockcal->setReturnValue('prevHour',12); + $this->mockcal->setReturnValue('thisHour',13); + $this->mockcal->setReturnValue('nextHour',14); + $this->mockcal->setReturnValue('prevMinute',29); + $this->mockcal->setReturnValue('thisMinute',30); + $this->mockcal->setReturnValue('nextMinute',31); + $this->mockcal->setReturnValue('prevSecond',44); + $this->mockcal->setReturnValue('thisSecond',45); + $this->mockcal->setReturnValue('nextSecond',46); + $this->mockcal->setReturnValue('getEngine',$this->mockengine); + $this->mockcal->setReturnValue('getTimestamp',12345); + + } + function tearDown() { + unset ( $this->engine ); + unset ( $this->mockcal ); + } + function testPrevYear() { + $this->mockcal->expectOnce('prevYear',array('int')); + $Decorator = new Calendar_Decorator($this->mockcal); + $this->assertEqual(2002,$Decorator->prevYear()); + } + function testThisYear() { + $this->mockcal->expectOnce('thisYear',array('int')); + $Decorator = new Calendar_Decorator($this->mockcal); + $this->assertEqual(2003,$Decorator->thisYear()); + } + function testNextYear() { + $this->mockcal->expectOnce('nextYear',array('int')); + $Decorator = new Calendar_Decorator($this->mockcal); + $this->assertEqual(2004,$Decorator->nextYear()); + } + function testPrevMonth() { + $this->mockcal->expectOnce('prevMonth',array('int')); + $Decorator = new Calendar_Decorator($this->mockcal); + $this->assertEqual(9,$Decorator->prevMonth()); + } + function testThisMonth() { + $this->mockcal->expectOnce('thisMonth',array('int')); + $Decorator = new Calendar_Decorator($this->mockcal); + $this->assertEqual(10,$Decorator->thisMonth()); + } + function testNextMonth() { + $this->mockcal->expectOnce('nextMonth',array('int')); + $Decorator = new Calendar_Decorator($this->mockcal); + $this->assertEqual(11,$Decorator->nextMonth()); + } + function testPrevWeek() { + $mockweek = & new Mock_Calendar_Week($this); + $mockweek->setReturnValue('prevWeek',1); + $mockweek->expectOnce('prevWeek',array('n_in_month')); + $Decorator =& new Calendar_Decorator($mockweek); + $this->assertEqual(1,$Decorator->prevWeek()); + } + function testThisWeek() { + $mockweek = & new Mock_Calendar_Week($this); + $mockweek->setReturnValue('thisWeek',2); + $mockweek->expectOnce('thisWeek',array('n_in_month')); + $Decorator =& new Calendar_Decorator($mockweek); + $this->assertEqual(2,$Decorator->thisWeek()); + } + function testNextWeek() { + $mockweek = & new Mock_Calendar_Week($this); + $mockweek->setReturnValue('nextWeek',3); + $mockweek->expectOnce('nextWeek',array('n_in_month')); + $Decorator =& new Calendar_Decorator($mockweek); + $this->assertEqual(3,$Decorator->nextWeek()); + } + function testPrevDay() { + $this->mockcal->expectOnce('prevDay',array('int')); + $Decorator = new Calendar_Decorator($this->mockcal); + $this->assertEqual(14,$Decorator->prevDay()); + } + function testThisDay() { + $this->mockcal->expectOnce('thisDay',array('int')); + $Decorator = new Calendar_Decorator($this->mockcal); + $this->assertEqual(15,$Decorator->thisDay()); + } + function testNextDay() { + $this->mockcal->expectOnce('nextDay',array('int')); + $Decorator = new Calendar_Decorator($this->mockcal); + $this->assertEqual(16,$Decorator->nextDay()); + } + function testPrevHour() { + $this->mockcal->expectOnce('prevHour',array('int')); + $Decorator = new Calendar_Decorator($this->mockcal); + $this->assertEqual(12,$Decorator->prevHour()); + } + function testThisHour() { + $this->mockcal->expectOnce('thisHour',array('int')); + $Decorator = new Calendar_Decorator($this->mockcal); + $this->assertEqual(13,$Decorator->thisHour()); + } + function testNextHour() { + $this->mockcal->expectOnce('nextHour',array('int')); + $Decorator = new Calendar_Decorator($this->mockcal); + $this->assertEqual(14,$Decorator->nextHour()); + } + function testPrevMinute() { + $this->mockcal->expectOnce('prevMinute',array('int')); + $Decorator = new Calendar_Decorator($this->mockcal); + $this->assertEqual(29,$Decorator->prevMinute()); + } + function testThisMinute() { + $this->mockcal->expectOnce('thisMinute',array('int')); + $Decorator = new Calendar_Decorator($this->mockcal); + $this->assertEqual(30,$Decorator->thisMinute()); + } + function testNextMinute() { + $this->mockcal->expectOnce('nextMinute',array('int')); + $Decorator = new Calendar_Decorator($this->mockcal); + $this->assertEqual(31,$Decorator->nextMinute()); + } + function testPrevSecond() { + $this->mockcal->expectOnce('prevSecond',array('int')); + $Decorator = new Calendar_Decorator($this->mockcal); + $this->assertEqual(44,$Decorator->prevSecond()); + } + function testThisSecond() { + $this->mockcal->expectOnce('thisSecond',array('int')); + $Decorator = new Calendar_Decorator($this->mockcal); + $this->assertEqual(45,$Decorator->thisSecond()); + } + function testNextSecond() { + $this->mockcal->expectOnce('nextSecond',array('int')); + $Decorator = new Calendar_Decorator($this->mockcal); + $this->assertEqual(46,$Decorator->nextSecond()); + } + function testGetEngine() { + $Decorator = new Calendar_Decorator($this->mockcal); + $this->assertIsA($Decorator->getEngine(),'Mock_Calendar_Engine'); + } + function testSetTimestamp() { + $this->mockcal->expectOnce('setTimestamp',array('12345')); + $Decorator = new Calendar_Decorator($this->mockcal); + $Decorator->setTimestamp('12345'); + } + function testGetTimestamp() { + $Decorator = new Calendar_Decorator($this->mockcal); + $this->assertEqual(12345,$Decorator->getTimestamp()); + } + function testSetSelected() { + $this->mockcal->expectOnce('setSelected',array(true)); + $Decorator = new Calendar_Decorator($this->mockcal); + $Decorator->setSelected(); + } + function testIsSelected() { + $this->mockcal->setReturnValue('isSelected',true); + $Decorator = new Calendar_Decorator($this->mockcal); + $this->assertTrue($Decorator->isSelected()); + } + function testAdjust() { + $this->mockcal->expectOnce('adjust',array()); + $Decorator = new Calendar_Decorator($this->mockcal); + $Decorator->adjust(); + } + function testToArray() { + $this->mockcal->expectOnce('toArray',array(12345)); + $testArray = array('foo'=>'bar'); + $this->mockcal->setReturnValue('toArray',$testArray); + $Decorator = new Calendar_Decorator($this->mockcal); + $this->assertEqual($testArray,$Decorator->toArray(12345)); + } + function testReturnValue() { + $this->mockcal->expectOnce('returnValue',array('a','b','c','d')); + $this->mockcal->setReturnValue('returnValue','foo'); + $Decorator = new Calendar_Decorator($this->mockcal); + $this->assertEqual('foo',$Decorator->returnValue('a','b','c','d')); + } + function testSetFirst() { + $mockday = & new Mock_Calendar_Day($this); + $mockday->expectOnce('setFirst',array(true)); + $Decorator =& new Calendar_Decorator($mockday); + $Decorator->setFirst(); + } + function testSetLast() { + $mockday = & new Mock_Calendar_Day($this); + $mockday->expectOnce('setLast',array(true)); + $Decorator =& new Calendar_Decorator($mockday); + $Decorator->setLast(); + } + function testIsFirst() { + $mockday = & new Mock_Calendar_Day($this); + $mockday->setReturnValue('isFirst',TRUE); + $Decorator =& new Calendar_Decorator($mockday); + $this->assertTrue($Decorator->isFirst()); + } + function testIsLast() { + $mockday = & new Mock_Calendar_Day($this); + $mockday->setReturnValue('isLast',TRUE); + $Decorator =& new Calendar_Decorator($mockday); + $this->assertTrue($Decorator->isLast()); + } + function testSetEmpty() { + $mockday = & new Mock_Calendar_Day($this); + $mockday->expectOnce('setEmpty',array(true)); + $Decorator =& new Calendar_Decorator($mockday); + $Decorator->setEmpty(); + } + function testIsEmpty() { + $mockday = & new Mock_Calendar_Day($this); + $mockday->setReturnValue('isEmpty',TRUE); + $Decorator =& new Calendar_Decorator($mockday); + $this->assertTrue($Decorator->isEmpty()); + } + function testBuild() { + $testArray=array('foo'=>'bar'); + $this->mockcal->expectOnce('build',array($testArray)); + $Decorator = new Calendar_Decorator($this->mockcal); + $Decorator->build($testArray); + } + function testFetch() { + $this->mockcal->expectOnce('fetch',array()); + $Decorator = new Calendar_Decorator($this->mockcal); + $Decorator->fetch(); + } + function testFetchAll() { + $this->mockcal->expectOnce('fetchAll',array()); + $Decorator = new Calendar_Decorator($this->mockcal); + $Decorator->fetchAll(); + } + function testSize() { + $this->mockcal->expectOnce('size',array()); + $Decorator = new Calendar_Decorator($this->mockcal); + $Decorator->size(); + } + function testIsValid() { + $this->mockcal->expectOnce('isValid',array()); + $Decorator = new Calendar_Decorator($this->mockcal); + $Decorator->isValid(); + } + function testGetValidator() { + $this->mockcal->expectOnce('getValidator',array()); + $Decorator = new Calendar_Decorator($this->mockcal); + $Decorator->getValidator(); + } +} +?> diff --git a/library/pear/Calendar/tests/decorator_tests.php b/library/pear/Calendar/tests/decorator_tests.php new file mode 100644 index 000000000..6ebc47b77 --- /dev/null +++ b/library/pear/Calendar/tests/decorator_tests.php @@ -0,0 +1,21 @@ +GroupTest('Decorator Tests'); + $this->addTestFile('decorator_test.php'); + $this->addTestFile('decorator_textual_test.php'); + $this->addTestFile('decorator_uri_test.php'); + } +} + +if (!defined('TEST_RUNNING')) { + define('TEST_RUNNING', true); + $test = &new DecoratorTests(); + $test->run(new HtmlReporter()); +} +?> \ No newline at end of file diff --git a/library/pear/Calendar/tests/decorator_textual_test.php b/library/pear/Calendar/tests/decorator_textual_test.php new file mode 100644 index 000000000..f9dc737e5 --- /dev/null +++ b/library/pear/Calendar/tests/decorator_textual_test.php @@ -0,0 +1,179 @@ +UnitTestCase('Test of Calendar_Decorator_Textual'); + } + function testMonthNamesLong() { + $Textual = new Calendar_Decorator_Textual($this->mockcal); + $monthNames = array( + 1=>'January', + 2=>'February', + 3=>'March', + 4=>'April', + 5=>'May', + 6=>'June', + 7=>'July', + 8=>'August', + 9=>'September', + 10=>'October', + 11=>'November', + 12=>'December', + ); + $this->assertEqual($monthNames,$Textual->monthNames()); + } + function testMonthNamesShort() { + $Textual = new Calendar_Decorator_Textual($this->mockcal); + $monthNames = array( + 1=>'Jan', + 2=>'Feb', + 3=>'Mar', + 4=>'Apr', + 5=>'May', + 6=>'Jun', + 7=>'Jul', + 8=>'Aug', + 9=>'Sep', + 10=>'Oct', + 11=>'Nov', + 12=>'Dec', + ); + $this->assertEqual($monthNames,$Textual->monthNames('short')); + } + function testMonthNamesTwo() { + $Textual = new Calendar_Decorator_Textual($this->mockcal); + $monthNames = array( + 1=>'Ja', + 2=>'Fe', + 3=>'Ma', + 4=>'Ap', + 5=>'Ma', + 6=>'Ju', + 7=>'Ju', + 8=>'Au', + 9=>'Se', + 10=>'Oc', + 11=>'No', + 12=>'De', + ); + $this->assertEqual($monthNames,$Textual->monthNames('two')); + } + function testMonthNamesOne() { + $Textual = new Calendar_Decorator_Textual($this->mockcal); + $monthNames = array( + 1=>'J', + 2=>'F', + 3=>'M', + 4=>'A', + 5=>'M', + 6=>'J', + 7=>'J', + 8=>'A', + 9=>'S', + 10=>'O', + 11=>'N', + 12=>'D', + ); + $this->assertEqual($monthNames,$Textual->monthNames('one')); + } + function testWeekdayNamesLong() { + $Textual = new Calendar_Decorator_Textual($this->mockcal); + $weekdayNames = array( + 0=>'Sunday', + 1=>'Monday', + 2=>'Tuesday', + 3=>'Wednesday', + 4=>'Thursday', + 5=>'Friday', + 6=>'Saturday', + ); + $this->assertEqual($weekdayNames,$Textual->weekdayNames()); + } + function testWeekdayNamesShort() { + $Textual = new Calendar_Decorator_Textual($this->mockcal); + $weekdayNames = array( + 0=>'Sun', + 1=>'Mon', + 2=>'Tue', + 3=>'Wed', + 4=>'Thu', + 5=>'Fri', + 6=>'Sat', + ); + $this->assertEqual($weekdayNames,$Textual->weekdayNames('short')); + } + function testWeekdayNamesTwo() { + $Textual = new Calendar_Decorator_Textual($this->mockcal); + $weekdayNames = array( + 0=>'Su', + 1=>'Mo', + 2=>'Tu', + 3=>'We', + 4=>'Th', + 5=>'Fr', + 6=>'Sa', + ); + $this->assertEqual($weekdayNames,$Textual->weekdayNames('two')); + } + function testWeekdayNamesOne() { + $Textual = new Calendar_Decorator_Textual($this->mockcal); + $weekdayNames = array( + 0=>'S', + 1=>'M', + 2=>'T', + 3=>'W', + 4=>'T', + 5=>'F', + 6=>'S', + ); + $this->assertEqual($weekdayNames,$Textual->weekdayNames('one')); + } + function testPrevMonthNameShort() { + $Textual = new Calendar_Decorator_Textual($this->mockcal); + $this->assertEqual('Sep',$Textual->prevMonthName('short')); + } + function testThisMonthNameShort() { + $Textual = new Calendar_Decorator_Textual($this->mockcal); + $this->assertEqual('Oct',$Textual->thisMonthName('short')); + } + function testNextMonthNameShort() { + $Textual = new Calendar_Decorator_Textual($this->mockcal); + $this->assertEqual('Nov',$Textual->nextMonthName('short')); + } + function testThisDayNameShort() { + $Textual = new Calendar_Decorator_Textual($this->mockcal); + $this->assertEqual('Wed',$Textual->thisDayName('short')); + } + function testOrderedWeekdaysShort() { + $weekdayNames = array( + 0=>'Sun', + 1=>'Mon', + 2=>'Tue', + 3=>'Wed', + 4=>'Thu', + 5=>'Fri', + 6=>'Sat', + ); + $nShifts = CALENDAR_FIRST_DAY_OF_WEEK; + while ($nShifts-- > 0) { + $day = array_shift($weekdayNames); + array_push($weekdayNames, $day); + } + $Textual = new Calendar_Decorator_Textual($this->mockcal); + $this->assertEqual($weekdayNames,$Textual->orderedWeekdays('short')); + } + +} + +if (!defined('TEST_RUNNING')) { + define('TEST_RUNNING', true); + $test = &new TestOfDecoratorTextual(); + $test->run(new HtmlReporter()); +} +?> \ No newline at end of file diff --git a/library/pear/Calendar/tests/decorator_uri_test.php b/library/pear/Calendar/tests/decorator_uri_test.php new file mode 100644 index 000000000..5a3624cc7 --- /dev/null +++ b/library/pear/Calendar/tests/decorator_uri_test.php @@ -0,0 +1,37 @@ +UnitTestCase('Test of Calendar_Decorator_Uri'); + } + function testFragments() { + $Uri = new Calendar_Decorator_Uri($this->mockcal); + $Uri->setFragments('year','month','day','hour','minute','second'); + $this->assertEqual('year=&month=&day=&hour=&minute=&second=',$Uri->this('second')); + } + function testScalarFragments() { + $Uri = new Calendar_Decorator_Uri($this->mockcal); + $Uri->setFragments('year','month','day','hour','minute','second'); + $Uri->setScalar(); + $this->assertEqual('&&&&&',$Uri->this('second')); + } + function testSetSeperator() { + $Uri = new Calendar_Decorator_Uri($this->mockcal); + $Uri->setFragments('year','month','day','hour','minute','second'); + $Uri->setSeparator('/'); + $this->assertEqual('year=/month=/day=/hour=/minute=/second=',$Uri->this('second')); + } +} + +if (!defined('TEST_RUNNING')) { + define('TEST_RUNNING', true); + $test = &new TestOfDecoratorUri(); + $test->run(new HtmlReporter()); +} +?> \ No newline at end of file diff --git a/library/pear/Calendar/tests/helper_test.php b/library/pear/Calendar/tests/helper_test.php new file mode 100644 index 000000000..0f9702f43 --- /dev/null +++ b/library/pear/Calendar/tests/helper_test.php @@ -0,0 +1,83 @@ +UnitTestCase('Test of Calendar_Table_Helper'); + } + function setUp() { + $this->mockengine = new Mock_Calendar_Engine($this); + $this->mockengine->setReturnValue('getMinYears',1970); + $this->mockengine->setReturnValue('getMaxYears',2037); + $this->mockengine->setReturnValue('getMonthsInYear',12); + $this->mockengine->setReturnValue('getDaysInMonth',31); + $this->mockengine->setReturnValue('getHoursInDay',24); + $this->mockengine->setReturnValue('getMinutesInHour',60); + $this->mockengine->setReturnValue('getSecondsInMinute',60); + $this->mockengine->setReturnValue('getWeekDays',array(0,1,2,3,4,5,6)); + $this->mockengine->setReturnValue('getDaysInWeek',7); + $this->mockengine->setReturnValue('getFirstDayOfWeek',1); + $this->mockengine->setReturnValue('getFirstDayInMonth',3); + $this->mockcal = new Mock_Calendar_Second($this); + $this->mockcal->setReturnValue('thisYear',2003); + $this->mockcal->setReturnValue('thisMonth',10); + $this->mockcal->setReturnValue('thisDay',15); + $this->mockcal->setReturnValue('thisHour',13); + $this->mockcal->setReturnValue('thisMinute',30); + $this->mockcal->setReturnValue('thisSecond',45); + $this->mockcal->setReturnValue('getEngine',$this->mockengine); + } + function testGetFirstDay() { + for ( $i = 0; $i <= 7; $i++ ) { + $Helper = & new Calendar_Table_Helper($this->mockcal,$i); + $this->assertEqual($Helper->getFirstDay(),$i); + } + } + function testGetDaysOfWeekMonday() { + $Helper = new Calendar_Table_Helper($this->mockcal); + $this->assertEqual($Helper->getDaysOfWeek(),array(1,2,3,4,5,6,0)); + } + function testGetDaysOfWeekSunday() { + $Helper = new Calendar_Table_Helper($this->mockcal,0); + $this->assertEqual($Helper->getDaysOfWeek(),array(0,1,2,3,4,5,6)); + } + function testGetDaysOfWeekThursday() { + $Helper = new Calendar_Table_Helper($this->mockcal,4); + $this->assertEqual($Helper->getDaysOfWeek(),array(4,5,6,0,1,2,3)); + } + function testGetNumWeeks() { + $Helper = new Calendar_Table_Helper($this->mockcal); + $this->assertEqual($Helper->getNumWeeks(),5); + } + function testGetNumTableDaysInMonth() { + $Helper = new Calendar_Table_Helper($this->mockcal); + $this->assertEqual($Helper->getNumTableDaysInMonth(),35); + } + function testGetEmptyDaysBefore() { + $Helper = new Calendar_Table_Helper($this->mockcal); + $this->assertEqual($Helper->getEmptyDaysBefore(),2); + } + function testGetEmptyDaysAfter() { + $Helper = new Calendar_Table_Helper($this->mockcal); + $this->assertEqual($Helper->getEmptyDaysAfter(),33); + } + function testGetEmptyDaysAfterOffset() { + $Helper = new Calendar_Table_Helper($this->mockcal); + $this->assertEqual($Helper->getEmptyDaysAfterOffset(),5); + } +} + +if (!defined('TEST_RUNNING')) { + define('TEST_RUNNING', true); + $test = &new TestOfTableHelper(); + $test->run(new HtmlReporter()); +} +?> \ No newline at end of file diff --git a/library/pear/Calendar/tests/hour_test.php b/library/pear/Calendar/tests/hour_test.php new file mode 100644 index 000000000..9c04a9eb4 --- /dev/null +++ b/library/pear/Calendar/tests/hour_test.php @@ -0,0 +1,98 @@ +UnitTestCase('Test of Hour'); + } + function setUp() { + $this->cal = new Calendar_Hour(2003,10,25,13); + } + function testPrevDay_Array () { + $this->assertEqual( + array( + 'year' => 2003, + 'month' => 10, + 'day' => 24, + 'hour' => 0, + 'minute' => 0, + 'second' => 0), + $this->cal->prevDay('array')); + } + function testPrevMinute () { + $this->assertEqual(59,$this->cal->prevMinute()); + } + function testThisMinute () { + $this->assertEqual(0,$this->cal->thisMinute()); + } + function testNextMinute () { + $this->assertEqual(1,$this->cal->nextMinute()); + } + function testPrevSecond () { + $this->assertEqual(59,$this->cal->prevSecond()); + } + function testThisSecond () { + $this->assertEqual(0,$this->cal->thisSecond()); + } + function testNextSecond () { + $this->assertEqual(1,$this->cal->nextSecond()); + } + function testGetTimeStamp() { + $stamp = mktime(13,0,0,10,25,2003); + $this->assertEqual($stamp,$this->cal->getTimeStamp()); + } +} + +class TestOfHourBuild extends TestOfHour { + function TestOfHourBuild() { + $this->UnitTestCase('Test of Hour::build()'); + } + function testSize() { + $this->cal->build(); + $this->assertEqual(60,$this->cal->size()); + } + function testFetch() { + $this->cal->build(); + $i=0; + while ( $Child = $this->cal->fetch() ) { + $i++; + } + $this->assertEqual(60,$i); + } + function testFetchAll() { + $this->cal->build(); + $children = array(); + $i = 0; + while ( $Child = $this->cal->fetch() ) { + $children[$i]=$Child; + $i++; + } + $this->assertEqual($children,$this->cal->fetchAll()); + } + function testSelection() { + require_once(CALENDAR_ROOT . 'Minute.php'); + $selection = array(new Calendar_Minute(2003,10,25,13,32)); + $this->cal->build($selection); + $i = 0; + while ( $Child = $this->cal->fetch() ) { + if ( $i == 32 ) + break; + $i++; + } + $this->assertTrue($Child->isSelected()); + } +} + +if (!defined('TEST_RUNNING')) { + define('TEST_RUNNING', true); + $test = &new TestOfHour(); + $test->run(new HtmlReporter()); + $test = &new TestOfHourBuild(); + $test->run(new HtmlReporter()); +} +?> \ No newline at end of file diff --git a/library/pear/Calendar/tests/minute_test.php b/library/pear/Calendar/tests/minute_test.php new file mode 100644 index 000000000..07a7db242 --- /dev/null +++ b/library/pear/Calendar/tests/minute_test.php @@ -0,0 +1,99 @@ +UnitTestCase('Test of Minute'); + } + function setUp() { + $this->cal = new Calendar_Minute(2003,10,25,13,32); + } + function testPrevDay_Array () { + $this->assertEqual( + array( + 'year' => 2003, + 'month' => 10, + 'day' => 24, + 'hour' => 0, + 'minute' => 0, + 'second' => 0), + $this->cal->prevDay('array')); + } + function testPrevSecond () { + $this->assertEqual(59,$this->cal->prevSecond()); + } + function testThisSecond () { + $this->assertEqual(0,$this->cal->thisSecond()); + } + function testThisSecond_Timestamp () { + $this->assertEqual($this->cal->cE->dateToStamp( + 2003, 10, 25, 13, 32, 0), + $this->cal->thisSecond('timestamp')); + } + function testNextSecond () { + $this->assertEqual(1,$this->cal->nextSecond()); + } + function testNextSecond_Timestamp () { + $this->assertEqual($this->cal->cE->dateToStamp( + 2003, 10, 25, 13, 32, 1), + $this->cal->nextSecond('timestamp')); + } + function testGetTimeStamp() { + $stamp = mktime(13,32,0,10,25,2003); + $this->assertEqual($stamp,$this->cal->getTimeStamp()); + } +} + +class TestOfMinuteBuild extends TestOfMinute { + function TestOfMinuteBuild() { + $this->UnitTestCase('Test of Minute::build()'); + } + function testSize() { + $this->cal->build(); + $this->assertEqual(60,$this->cal->size()); + } + function testFetch() { + $this->cal->build(); + $i=0; + while ( $Child = $this->cal->fetch() ) { + $i++; + } + $this->assertEqual(60,$i); + } + function testFetchAll() { + $this->cal->build(); + $children = array(); + $i = 0; + while ( $Child = $this->cal->fetch() ) { + $children[$i]=$Child; + $i++; + } + $this->assertEqual($children,$this->cal->fetchAll()); + } + function testSelection() { + require_once(CALENDAR_ROOT . 'Second.php'); + $selection = array(new Calendar_Second(2003,10,25,13,32,43)); + $this->cal->build($selection); + $i = 0; + while ( $Child = $this->cal->fetch() ) { + if ( $i == 43 ) + break; + $i++; + } + $this->assertTrue($Child->isSelected()); + } +} + +if (!defined('TEST_RUNNING')) { + define('TEST_RUNNING', true); + $test = &new TestOfMinute(); + $test->run(new HtmlReporter()); + $test = &new TestOfMinuteBuild(); + $test->run(new HtmlReporter()); +} +?> \ No newline at end of file diff --git a/library/pear/Calendar/tests/month_test.php b/library/pear/Calendar/tests/month_test.php new file mode 100644 index 000000000..c2238406b --- /dev/null +++ b/library/pear/Calendar/tests/month_test.php @@ -0,0 +1,119 @@ +UnitTestCase('Test of Month'); + } + function setUp() { + $this->cal = new Calendar_Month(2003,10); + } + function testPrevMonth_Object() { + $this->assertEqual(new Calendar_Month(2003, 9), $this->cal->prevMonth('object')); + } + function testPrevDay () { + $this->assertEqual(30,$this->cal->prevDay()); + } + function testPrevDay_Array () { + $this->assertEqual( + array( + 'year' => 2003, + 'month' => 9, + 'day' => 30, + 'hour' => 0, + 'minute' => 0, + 'second' => 0), + $this->cal->prevDay('array')); + } + function testThisDay () { + $this->assertEqual(1,$this->cal->thisDay()); + } + function testNextDay () { + $this->assertEqual(2,$this->cal->nextDay()); + } + function testPrevHour () { + $this->assertEqual(23,$this->cal->prevHour()); + } + function testThisHour () { + $this->assertEqual(0,$this->cal->thisHour()); + } + function testNextHour () { + $this->assertEqual(1,$this->cal->nextHour()); + } + function testPrevMinute () { + $this->assertEqual(59,$this->cal->prevMinute()); + } + function testThisMinute () { + $this->assertEqual(0,$this->cal->thisMinute()); + } + function testNextMinute () { + $this->assertEqual(1,$this->cal->nextMinute()); + } + function testPrevSecond () { + $this->assertEqual(59,$this->cal->prevSecond()); + } + function testThisSecond () { + $this->assertEqual(0,$this->cal->thisSecond()); + } + function testNextSecond () { + $this->assertEqual(1,$this->cal->nextSecond()); + } + function testGetTimeStamp() { + $stamp = mktime(0,0,0,10,1,2003); + $this->assertEqual($stamp,$this->cal->getTimeStamp()); + } +} + +class TestOfMonthBuild extends TestOfMonth { + function TestOfMonthBuild() { + $this->UnitTestCase('Test of Month::build()'); + } + function testSize() { + $this->cal->build(); + $this->assertEqual(31,$this->cal->size()); + } + function testFetch() { + $this->cal->build(); + $i=0; + while ( $Child = $this->cal->fetch() ) { + $i++; + } + $this->assertEqual(31,$i); + } + function testFetchAll() { + $this->cal->build(); + $children = array(); + $i = 1; + while ( $Child = $this->cal->fetch() ) { + $children[$i]=$Child; + $i++; + } + $this->assertEqual($children,$this->cal->fetchAll()); + } + function testSelection() { + require_once(CALENDAR_ROOT . 'Day.php'); + $selection = array(new Calendar_Day(2003,10,25)); + $this->cal->build($selection); + $i = 1; + while ( $Child = $this->cal->fetch() ) { + if ( $i == 25 ) + break; + $i++; + } + $this->assertTrue($Child->isSelected()); + } +} + +if (!defined('TEST_RUNNING')) { + define('TEST_RUNNING', true); + $test = &new TestOfMonth(); + $test->run(new HtmlReporter()); + $test = &new TestOfMonthBuild(); + $test->run(new HtmlReporter()); +} +?> \ No newline at end of file diff --git a/library/pear/Calendar/tests/month_weekdays_test.php b/library/pear/Calendar/tests/month_weekdays_test.php new file mode 100644 index 000000000..b60005d7e --- /dev/null +++ b/library/pear/Calendar/tests/month_weekdays_test.php @@ -0,0 +1,182 @@ +UnitTestCase('Test of Month Weekdays'); + } + function setUp() { + $this->cal = new Calendar_Month_Weekdays(2003, 10); + } + function testPrevDay () { + $this->assertEqual(30,$this->cal->prevDay()); + } + function testPrevDay_Array () { + $this->assertEqual( + array( + 'year' => 2003, + 'month' => 9, + 'day' => 30, + 'hour' => 0, + 'minute' => 0, + 'second' => 0), + $this->cal->prevDay('array')); + } + function testThisDay () { + $this->assertEqual(1, $this->cal->thisDay()); + } + function testNextDay () { + $this->assertEqual(2, $this->cal->nextDay()); + } + function testPrevHour () { + $this->assertEqual(23, $this->cal->prevHour()); + } + function testThisHour () { + $this->assertEqual(0, $this->cal->thisHour()); + } + function testNextHour () { + $this->assertEqual(1, $this->cal->nextHour()); + } + function testPrevMinute () { + $this->assertEqual(59, $this->cal->prevMinute()); + } + function testThisMinute () { + $this->assertEqual(0, $this->cal->thisMinute()); + } + function testNextMinute () { + $this->assertEqual(1, $this->cal->nextMinute()); + } + function testPrevSecond () { + $this->assertEqual(59, $this->cal->prevSecond()); + } + function testThisSecond () { + $this->assertEqual(0, $this->cal->thisSecond()); + } + function testNextSecond () { + $this->assertEqual(1, $this->cal->nextSecond()); + } + function testGetTimeStamp() { + $stamp = mktime(0, 0, 0, 10, 1, 2003); + $this->assertEqual($stamp, $this->cal->getTimeStamp()); + } +} + +class TestOfMonthWeekdaysBuild extends TestOfMonthWeekdays { + function TestOfMonthWeekdaysBuild() { + $this->UnitTestCase('Test of Month_Weekdays::build()'); + } + function testSize() { + $this->cal->build(); + $this->assertEqual(35, $this->cal->size()); + } + function testFetch() { + $this->cal->build(); + $i=0; + while ($Child = $this->cal->fetch()) { + $i++; + } + $this->assertEqual(35, $i); + } + function testFetchAll() { + $this->cal->build(); + $children = array(); + $i = 1; + while ($Child = $this->cal->fetch()) { + $children[$i] = $Child; + $i++; + } + $this->assertEqual($children,$this->cal->fetchAll()); + } + function testSelection() { + include_once CALENDAR_ROOT . 'Day.php'; + $selection = array(new Calendar_Day(2003, 10, 25)); + $this->cal->build($selection); + $daysInPrevMonth = (0 == CALENDAR_FIRST_DAY_OF_WEEK) ? 3 : 2; + $end = 25 + $daysInPrevMonth; + $i = 1; + while ($Child = $this->cal->fetch()) { + if ($i == $end) { + break; + } + $i++; + } + $this->assertTrue($Child->isSelected()); + $this->assertEqual(25, $Child->day); + } + function testEmptyCount() { + $this->cal->build(); + $empty = 0; + while ($Child = $this->cal->fetch()) { + if ($Child->isEmpty()) { + $empty++; + } + } + $this->assertEqual(4, $empty); + } + function testEmptyCount2() { + $this->cal = new Calendar_Month_Weekdays(2010,3); + $this->cal->build(); + $empty = 0; + while ($Child = $this->cal->fetch()) { + if ($Child->isEmpty()) { + $empty++; + } + } + $this->assertEqual(4, $empty); + } + function testEmptyCount3() { + $this->cal = new Calendar_Month_Weekdays(2010,6); + $this->cal->build(); + $empty = 0; + while ($Child = $this->cal->fetch()) { + if ($Child->isEmpty()) { + $empty++; + } + } + $this->assertEqual(5, $empty); + } + function testEmptyDaysBefore_AfterAdjust() { + $this->cal = new Calendar_Month_Weekdays(2004, 0); + $this->cal->build(); + $expected = (CALENDAR_FIRST_DAY_OF_WEEK == 0) ? 1 : 0; + $this->assertEqual($expected, $this->cal->tableHelper->getEmptyDaysBefore()); + } + function testEmptyDaysBefore() { + $this->cal = new Calendar_Month_Weekdays(2010, 3); + $this->cal->build(); + $expected = (CALENDAR_FIRST_DAY_OF_WEEK == 0) ? 1 : 0; + $this->assertEqual($expected, $this->cal->tableHelper->getEmptyDaysBefore()); + } + function testEmptyDaysBefore2() { + $this->cal = new Calendar_Month_Weekdays(2010, 6); + $this->cal->build(); + $expected = (CALENDAR_FIRST_DAY_OF_WEEK == 0) ? 2 : 1; + $this->assertEqual($expected, $this->cal->tableHelper->getEmptyDaysBefore()); + } + function testEmptyDaysAfter() { + $this->cal = new Calendar_Month_Weekdays(2010, 3); + $this->cal->build(); + $expected = (CALENDAR_FIRST_DAY_OF_WEEK == 0) ? 30 : 31; + $this->assertEqual($expected, $this->cal->tableHelper->getEmptyDaysAfter()); + } + function testEmptyDaysAfter2() { + $this->cal = new Calendar_Month_Weekdays(2010, 6); + $this->cal->build(); + $expected = (CALENDAR_FIRST_DAY_OF_WEEK == 0) ? 30 : 31; + $this->assertEqual($expected, $this->cal->tableHelper->getEmptyDaysAfter()); + } +} + +if (!defined('TEST_RUNNING')) { + define('TEST_RUNNING', true); + $test = &new TestOfMonthWeekdays(); + $test->run(new HtmlReporter()); + $test = &new TestOfMonthWeekdaysBuild(); + $test->run(new HtmlReporter()); +} +?> \ No newline at end of file diff --git a/library/pear/Calendar/tests/month_weeks_test.php b/library/pear/Calendar/tests/month_weeks_test.php new file mode 100644 index 000000000..d8f130294 --- /dev/null +++ b/library/pear/Calendar/tests/month_weeks_test.php @@ -0,0 +1,129 @@ +UnitTestCase('Test of Month Weeks'); + } + function setUp() { + $this->cal = new Calendar_Month_Weeks(2003, 10); + } + function testPrevDay () { + $this->assertEqual(30, $this->cal->prevDay()); + } + function testPrevDay_Array () { + $this->assertEqual( + array( + 'year' => 2003, + 'month' => 9, + 'day' => 30, + 'hour' => 0, + 'minute' => 0, + 'second' => 0), + $this->cal->prevDay('array')); + } + function testThisDay () { + $this->assertEqual(1, $this->cal->thisDay()); + } + function testNextDay () { + $this->assertEqual(2, $this->cal->nextDay()); + } + function testPrevHour () { + $this->assertEqual(23, $this->cal->prevHour()); + } + function testThisHour () { + $this->assertEqual(0, $this->cal->thisHour()); + } + function testNextHour () { + $this->assertEqual(1, $this->cal->nextHour()); + } + function testPrevMinute () { + $this->assertEqual(59, $this->cal->prevMinute()); + } + function testThisMinute () { + $this->assertEqual(0, $this->cal->thisMinute()); + } + function testNextMinute () { + $this->assertEqual(1, $this->cal->nextMinute()); + } + function testPrevSecond () { + $this->assertEqual(59, $this->cal->prevSecond()); + } + function testThisSecond () { + $this->assertEqual(0, $this->cal->thisSecond()); + } + function testNextSecond () { + $this->assertEqual(1, $this->cal->nextSecond()); + } + function testGetTimeStamp() { + $stamp = mktime(0,0,0,10,1,2003); + $this->assertEqual($stamp,$this->cal->getTimeStamp()); + } +} + +class TestOfMonthWeeksBuild extends TestOfMonthWeeks { + function TestOfMonthWeeksBuild() { + $this->UnitTestCase('Test of Month_Weeks::build()'); + } + function testSize() { + $this->cal->build(); + $this->assertEqual(5,$this->cal->size()); + } + + function testFetch() { + $this->cal->build(); + $i=0; + while ($Child = $this->cal->fetch()) { + $i++; + } + $this->assertEqual(5,$i); + } +/* Recusive dependency issue with SimpleTest + function testFetchAll() { + $this->cal->build(); + $children = array(); + $i = 1; + while ( $Child = $this->cal->fetch() ) { + $children[$i]=$Child; + $i++; + } + $this->assertEqual($children,$this->cal->fetchAll()); + } +*/ + function testSelection() { + include_once CALENDAR_ROOT . 'Week.php'; + $selection = array(new Calendar_Week(2003, 10, 12)); + $this->cal->build($selection); + $i = 1; + $expected = (CALENDAR_FIRST_DAY_OF_WEEK == 0) ? 3 : 2; + while ($Child = $this->cal->fetch()) { + if ($i == $expected) { + //12-10-2003 is in the 2nd week of the month if firstDay is Monday, + //in the 3rd if firstDay is Sunday + break; + } + $i++; + } + $this->assertTrue($Child->isSelected()); + } + function testEmptyDaysBefore_AfterAdjust() { + $this->cal = new Calendar_Month_Weeks(2004, 0); + $this->cal->build(); + $expected = (CALENDAR_FIRST_DAY_OF_WEEK == 0) ? 1 : 0; + $this->assertEqual($expected, $this->cal->tableHelper->getEmptyDaysBefore()); + } +} + +if (!defined('TEST_RUNNING')) { + define('TEST_RUNNING', true); + $test = &new TestOfMonthWeeks(); + $test->run(new HtmlReporter()); + $test = &new TestOfMonthWeeksBuild(); + $test->run(new HtmlReporter()); +} +?> \ No newline at end of file diff --git a/library/pear/Calendar/tests/peardate_engine_test.php b/library/pear/Calendar/tests/peardate_engine_test.php new file mode 100644 index 000000000..c32c890d7 --- /dev/null +++ b/library/pear/Calendar/tests/peardate_engine_test.php @@ -0,0 +1,130 @@ +UnitTestCase('Test of Calendar_Engine_PearDate'); + } + function setUp() { + $this->engine = new Calendar_Engine_PearDate(); + } + function testGetSecondsInMinute() { + $this->assertEqual($this->engine->getSecondsInMinute(),60); + } + function testGetMinutesInHour() { + $this->assertEqual($this->engine->getMinutesInHour(),60); + } + function testGetHoursInDay() { + $this->assertEqual($this->engine->getHoursInDay(),24); + } + function testGetFirstDayOfWeek() { + $this->assertEqual($this->engine->getFirstDayOfWeek(),1); + } + function testGetWeekDays() { + $this->assertEqual($this->engine->getWeekDays(),array(0,1,2,3,4,5,6)); + } + function testGetDaysInWeek() { + $this->assertEqual($this->engine->getDaysInWeek(),7); + } + function testGetWeekNInYear() { + $this->assertEqual($this->engine->getWeekNInYear(2003, 11, 3), 45); + } + function testGetWeekNInMonth() { + $this->assertEqual($this->engine->getWeekNInMonth(2003, 11, 3), 2); + } + function testGetWeeksInMonth0() { + $this->assertEqual($this->engine->getWeeksInMonth(2003, 11, 0), 6); //week starts on sunday + } + function testGetWeeksInMonth1() { + $this->assertEqual($this->engine->getWeeksInMonth(2003, 11, 1), 5); //week starts on monday + } + function testGetWeeksInMonth2() { + $this->assertEqual($this->engine->getWeeksInMonth(2003, 2, 6), 4); //week starts on saturday + } + function testGetWeeksInMonth3() { + // Unusual cases that can cause fails (shows up with example 21.php) + $this->assertEqual($this->engine->getWeeksInMonth(2004,2,1),5); + $this->assertEqual($this->engine->getWeeksInMonth(2004,8,1),6); + } + function testGetDayOfWeek() { + $this->assertEqual($this->engine->getDayOfWeek(2003, 11, 18), 2); + } + function testGetFirstDayInMonth() { + $this->assertEqual($this->engine->getFirstDayInMonth(2003,10),3); + } + function testGetDaysInMonth() { + $this->assertEqual($this->engine->getDaysInMonth(2003,10),31); + } + function testGetMinYears() { + $this->assertEqual($this->engine->getMinYears(),0); + } + function testGetMaxYears() { + $this->assertEqual($this->engine->getMaxYears(),9999); + } + function testDateToStamp() { + $stamp = '2003-10-15 13:30:45'; + $this->assertEqual($this->engine->dateToStamp(2003,10,15,13,30,45),$stamp); + } + function testStampToSecond() { + $stamp = '2003-10-15 13:30:45'; + $this->assertEqual($this->engine->stampToSecond($stamp),45); + } + function testStampToMinute() { + $stamp = '2003-10-15 13:30:45'; + $this->assertEqual($this->engine->stampToMinute($stamp),30); + } + function testStampToHour() { + $stamp = '2003-10-15 13:30:45'; + $this->assertEqual($this->engine->stampToHour($stamp),13); + } + function testStampToDay() { + $stamp = '2003-10-15 13:30:45'; + $this->assertEqual($this->engine->stampToDay($stamp),15); + } + function testStampToMonth() { + $stamp = '2003-10-15 13:30:45'; + $this->assertEqual($this->engine->stampToMonth($stamp),10); + } + function testStampToYear() { + $stamp = '2003-10-15 13:30:45'; + $this->assertEqual($this->engine->stampToYear($stamp),2003); + } + function testAdjustDate() { + $stamp = '2004-01-01 13:30:45'; + $y = $this->engine->stampToYear($stamp); + $m = $this->engine->stampToMonth($stamp); + $d = $this->engine->stampToDay($stamp); + + //the first day of the month should be thursday + $this->assertEqual($this->engine->getDayOfWeek($y, $m, $d), 4); + + $m--; // 2004-00-01 => 2003-12-01 + $this->engine->adjustDate($y, $m, $d, $dummy, $dummy, $dummy); + + $this->assertEqual($y, 2003); + $this->assertEqual($m, 12); + $this->assertEqual($d, 1); + + // get last day and check if it's wednesday + $d = $this->engine->getDaysInMonth($y, $m); + + $this->assertEqual($this->engine->getDayOfWeek($y, $m, $d), 3); + } + function testIsToday() { + $stamp = date('Y-m-d H:i:s'); + $this->assertTrue($this->engine->isToday($stamp)); + $stamp = date('Y-m-d H:i:s', time() + 1000000000); + $this->assertFalse($this->engine->isToday($stamp)); + } +} + +if (!defined('TEST_RUNNING')) { + define('TEST_RUNNING', true); + $test = &new TestOfPearDateEngine(); + $test->run(new HtmlReporter()); +} +?> \ No newline at end of file diff --git a/library/pear/Calendar/tests/second_test.php b/library/pear/Calendar/tests/second_test.php new file mode 100644 index 000000000..78c56565a --- /dev/null +++ b/library/pear/Calendar/tests/second_test.php @@ -0,0 +1,34 @@ +UnitTestCase('Test of Second'); + } + function setUp() { + $this->cal = new Calendar_Second(2003,10,25,13,32,43); + } + function testPrevDay_Array () { + $this->assertEqual( + array( + 'year' => 2003, + 'month' => 10, + 'day' => 24, + 'hour' => 0, + 'minute' => 0, + 'second' => 0), + $this->cal->prevDay('array')); + } +} + +if (!defined('TEST_RUNNING')) { + define('TEST_RUNNING', true); + $test = &new TestOfSecond(); + $test->run(new HtmlReporter()); +} +?> \ No newline at end of file diff --git a/library/pear/Calendar/tests/simple_include.php b/library/pear/Calendar/tests/simple_include.php new file mode 100644 index 000000000..cd7ad4413 --- /dev/null +++ b/library/pear/Calendar/tests/simple_include.php @@ -0,0 +1,10 @@ + \ No newline at end of file diff --git a/library/pear/Calendar/tests/table_helper_tests.php b/library/pear/Calendar/tests/table_helper_tests.php new file mode 100644 index 000000000..c8abe64eb --- /dev/null +++ b/library/pear/Calendar/tests/table_helper_tests.php @@ -0,0 +1,19 @@ +GroupTest('Table Helper Tests'); + $this->addTestFile('helper_test.php'); + } +} + +if (!defined('TEST_RUNNING')) { + define('TEST_RUNNING', true); + $test = &new TableHelperTests(); + $test->run(new HtmlReporter()); +} +?> \ No newline at end of file diff --git a/library/pear/Calendar/tests/unixts_engine_test.php b/library/pear/Calendar/tests/unixts_engine_test.php new file mode 100644 index 000000000..2d7c11f97 --- /dev/null +++ b/library/pear/Calendar/tests/unixts_engine_test.php @@ -0,0 +1,110 @@ +UnitTestCase('Test of Calendar_Engine_UnixTs'); + } + function setUp() { + $this->engine = new Calendar_Engine_UnixTs(); + } + function testGetSecondsInMinute() { + $this->assertEqual($this->engine->getSecondsInMinute(),60); + } + function testGetMinutesInHour() { + $this->assertEqual($this->engine->getMinutesInHour(),60); + } + function testGetHoursInDay() { + $this->assertEqual($this->engine->getHoursInDay(),24); + } + function testGetFirstDayOfWeek() { + $this->assertEqual($this->engine->getFirstDayOfWeek(),1); + } + function testGetWeekDays() { + $this->assertEqual($this->engine->getWeekDays(),array(0,1,2,3,4,5,6)); + } + function testGetDaysInWeek() { + $this->assertEqual($this->engine->getDaysInWeek(),7); + } + function testGetWeekNInYear() { + $this->assertEqual($this->engine->getWeekNInYear(2003, 11, 3), 45); + } + function testGetWeekNInMonth() { + $this->assertEqual($this->engine->getWeekNInMonth(2003, 11, 3), 2); + } + function testGetWeeksInMonth0() { + $this->assertEqual($this->engine->getWeeksInMonth(2003, 11, 0), 6); //week starts on sunday + } + function testGetWeeksInMonth1() { + $this->assertEqual($this->engine->getWeeksInMonth(2003, 11, 1), 5); //week starts on monday + } + function testGetWeeksInMonth2() { + $this->assertEqual($this->engine->getWeeksInMonth(2003, 2, 6), 4); //week starts on saturday + } + function testGetWeeksInMonth3() { + // Unusual cases that can cause fails (shows up with example 21.php) + $this->assertEqual($this->engine->getWeeksInMonth(2004,2,1),5); + $this->assertEqual($this->engine->getWeeksInMonth(2004,8,1),6); + } + function testGetDayOfWeek() { + $this->assertEqual($this->engine->getDayOfWeek(2003, 11, 18), 2); + } + function testGetFirstDayInMonth() { + $this->assertEqual($this->engine->getFirstDayInMonth(2003,10),3); + } + function testGetDaysInMonth() { + $this->assertEqual($this->engine->getDaysInMonth(2003,10),31); + } + function testGetMinYears() { + $test = strpos(PHP_OS, 'WIN') >= 0 ? 1970 : 1902; + $this->assertEqual($this->engine->getMinYears(),$test); + } + function testGetMaxYears() { + $this->assertEqual($this->engine->getMaxYears(),2037); + } + function testDateToStamp() { + $stamp = mktime(0,0,0,10,15,2003); + $this->assertEqual($this->engine->dateToStamp(2003,10,15,0,0,0),$stamp); + } + function testStampToSecond() { + $stamp = mktime(13,30,45,10,15,2003); + $this->assertEqual($this->engine->stampToSecond($stamp),45); + } + function testStampToMinute() { + $stamp = mktime(13,30,45,10,15,2003); + $this->assertEqual($this->engine->stampToMinute($stamp),30); + } + function testStampToHour() { + $stamp = mktime(13,30,45,10,15,2003); + $this->assertEqual($this->engine->stampToHour($stamp),13); + } + function testStampToDay() { + $stamp = mktime(13,30,45,10,15,2003); + $this->assertEqual($this->engine->stampToDay($stamp),15); + } + function testStampToMonth() { + $stamp = mktime(13,30,45,10,15,2003); + $this->assertEqual($this->engine->stampToMonth($stamp),10); + } + function testStampToYear() { + $stamp = mktime(13,30,45,10,15,2003); + $this->assertEqual($this->engine->stampToYear($stamp),2003); + } + function testIsToday() { + $stamp = mktime(); + $this->assertTrue($this->engine->isToday($stamp)); + $stamp += 1000000000; + $this->assertFalse($this->engine->isToday($stamp)); + } +} + +if (!defined('TEST_RUNNING')) { + define('TEST_RUNNING', true); + $test = &new TestOfUnixTsEngine(); + $test->run(new HtmlReporter()); +} +?> \ No newline at end of file diff --git a/library/pear/Calendar/tests/util_tests.php b/library/pear/Calendar/tests/util_tests.php new file mode 100644 index 000000000..ce058ea4b --- /dev/null +++ b/library/pear/Calendar/tests/util_tests.php @@ -0,0 +1,20 @@ +GroupTest('Util Tests'); + $this->addTestFile('util_uri_test.php'); + $this->addTestFile('util_textual_test.php'); + } +} + +if (!defined('TEST_RUNNING')) { + define('TEST_RUNNING', true); + $test = &new UtilTests(); + $test->run(new HtmlReporter()); +} +?> \ No newline at end of file diff --git a/library/pear/Calendar/tests/util_textual_test.php b/library/pear/Calendar/tests/util_textual_test.php new file mode 100644 index 000000000..e47735dca --- /dev/null +++ b/library/pear/Calendar/tests/util_textual_test.php @@ -0,0 +1,196 @@ +UnitTestCase('Test of Calendar_Util_Textual'); + } + function setUp() { + $this->mockengine = new Mock_Calendar_Engine($this); + $this->mockcal = new Mock_Calendar_Second($this); + $this->mockcal->setReturnValue('prevYear',2002); + $this->mockcal->setReturnValue('thisYear',2003); + $this->mockcal->setReturnValue('nextYear',2004); + $this->mockcal->setReturnValue('prevMonth',9); + $this->mockcal->setReturnValue('thisMonth',10); + $this->mockcal->setReturnValue('nextMonth',11); + $this->mockcal->setReturnValue('prevDay',14); + $this->mockcal->setReturnValue('thisDay',15); + $this->mockcal->setReturnValue('nextDay',16); + $this->mockcal->setReturnValue('prevHour',12); + $this->mockcal->setReturnValue('thisHour',13); + $this->mockcal->setReturnValue('nextHour',14); + $this->mockcal->setReturnValue('prevMinute',29); + $this->mockcal->setReturnValue('thisMinute',30); + $this->mockcal->setReturnValue('nextMinute',31); + $this->mockcal->setReturnValue('prevSecond',44); + $this->mockcal->setReturnValue('thisSecond',45); + $this->mockcal->setReturnValue('nextSecond',46); + $this->mockcal->setReturnValue('getEngine',$this->mockengine); + $this->mockcal->setReturnValue('getTimestamp',12345); + } + function tearDown() { + unset ( $this->engine ); + unset ( $this->mockcal ); + } + function testMonthNamesLong() { + $monthNames = array( + 1=>'January', + 2=>'February', + 3=>'March', + 4=>'April', + 5=>'May', + 6=>'June', + 7=>'July', + 8=>'August', + 9=>'September', + 10=>'October', + 11=>'November', + 12=>'December', + ); + $this->assertEqual($monthNames,Calendar_Util_Textual::monthNames()); + } + function testMonthNamesShort() { + $monthNames = array( + 1=>'Jan', + 2=>'Feb', + 3=>'Mar', + 4=>'Apr', + 5=>'May', + 6=>'Jun', + 7=>'Jul', + 8=>'Aug', + 9=>'Sep', + 10=>'Oct', + 11=>'Nov', + 12=>'Dec', + ); + $this->assertEqual($monthNames,Calendar_Util_Textual::monthNames('short')); + } + function testMonthNamesTwo() { + $monthNames = array( + 1=>'Ja', + 2=>'Fe', + 3=>'Ma', + 4=>'Ap', + 5=>'Ma', + 6=>'Ju', + 7=>'Ju', + 8=>'Au', + 9=>'Se', + 10=>'Oc', + 11=>'No', + 12=>'De', + ); + $this->assertEqual($monthNames,Calendar_Util_Textual::monthNames('two')); + } + function testMonthNamesOne() { + $monthNames = array( + 1=>'J', + 2=>'F', + 3=>'M', + 4=>'A', + 5=>'M', + 6=>'J', + 7=>'J', + 8=>'A', + 9=>'S', + 10=>'O', + 11=>'N', + 12=>'D', + ); + $this->assertEqual($monthNames,Calendar_Util_Textual::monthNames('one')); + } + function testWeekdayNamesLong() { + $weekdayNames = array( + 0=>'Sunday', + 1=>'Monday', + 2=>'Tuesday', + 3=>'Wednesday', + 4=>'Thursday', + 5=>'Friday', + 6=>'Saturday', + ); + $this->assertEqual($weekdayNames,Calendar_Util_Textual::weekdayNames()); + } + function testWeekdayNamesShort() { + $weekdayNames = array( + 0=>'Sun', + 1=>'Mon', + 2=>'Tue', + 3=>'Wed', + 4=>'Thu', + 5=>'Fri', + 6=>'Sat', + ); + $this->assertEqual($weekdayNames,Calendar_Util_Textual::weekdayNames('short')); + } + function testWeekdayNamesTwo() { + $weekdayNames = array( + 0=>'Su', + 1=>'Mo', + 2=>'Tu', + 3=>'We', + 4=>'Th', + 5=>'Fr', + 6=>'Sa', + ); + $this->assertEqual($weekdayNames,Calendar_Util_Textual::weekdayNames('two')); + } + function testWeekdayNamesOne() { + $weekdayNames = array( + 0=>'S', + 1=>'M', + 2=>'T', + 3=>'W', + 4=>'T', + 5=>'F', + 6=>'S', + ); + $this->assertEqual($weekdayNames,Calendar_Util_Textual::weekdayNames('one')); + } + function testPrevMonthNameShort() { + $this->assertEqual('Sep',Calendar_Util_Textual::prevMonthName($this->mockcal,'short')); + } + function testThisMonthNameShort() { + $this->assertEqual('Oct',Calendar_Util_Textual::thisMonthName($this->mockcal,'short')); + } + function testNextMonthNameShort() { + $this->assertEqual('Nov',Calendar_Util_Textual::nextMonthName($this->mockcal,'short')); + } + function testThisDayNameShort() { + $this->assertEqual('Wed',Calendar_Util_Textual::thisDayName($this->mockcal,'short')); + } + function testOrderedWeekdaysShort() { + $weekdayNames = array( + 0=>'Sun', + 1=>'Mon', + 2=>'Tue', + 3=>'Wed', + 4=>'Thu', + 5=>'Fri', + 6=>'Sat', + ); + $nShifts = CALENDAR_FIRST_DAY_OF_WEEK; + while ($nShifts-- > 0) { + $day = array_shift($weekdayNames); + array_push($weekdayNames, $day); + } + $this->assertEqual($weekdayNames,Calendar_Util_Textual::orderedWeekdays($this->mockcal,'short')); + } + +} + +if (!defined('TEST_RUNNING')) { + define('TEST_RUNNING', true); + $test = &new TestOfUtilTextual(); + $test->run(new HtmlReporter()); +} +?> \ No newline at end of file diff --git a/library/pear/Calendar/tests/util_uri_test.php b/library/pear/Calendar/tests/util_uri_test.php new file mode 100644 index 000000000..970f26985 --- /dev/null +++ b/library/pear/Calendar/tests/util_uri_test.php @@ -0,0 +1,54 @@ +UnitTestCase('Test of Calendar_Util_Uri'); + } + + function setUp() { + $this->MockCal = & new Mock_Calendar_Day($this); + $this->MockCal->setReturnValue('getEngine',new Mock_Calendar_Engine($this)); + } + + function testFragments() { + $Uri = new Calendar_Util_Uri('y','m','d','h','m','s'); + $Uri->setFragments('year','month','day','hour','minute','second'); + $this->assertEqual( + 'year=&month=&day=&hour=&minute=&second=', + $Uri->this($this->MockCal, 'second') + ); + } + function testScalarFragments() { + $Uri = new Calendar_Util_Uri('year','month','day','hour','minute','second'); + $Uri->scalar = true; + $this->assertEqual( + '&&&&&', + $Uri->this($this->MockCal, 'second') + ); + } + function testSetSeperator() { + $Uri = new Calendar_Util_Uri('year','month','day','hour','minute','second'); + $Uri->separator = '/'; + $this->assertEqual( + 'year=/month=/day=/hour=/minute=/second=', + $Uri->this($this->MockCal, 'second') + ); + } +} + +if (!defined('TEST_RUNNING')) { + define('TEST_RUNNING', true); + $test = &new TestOfUtilUri(); + $test->run(new HtmlReporter()); +} +?> \ No newline at end of file diff --git a/library/pear/Calendar/tests/validator_error_test.php b/library/pear/Calendar/tests/validator_error_test.php new file mode 100644 index 000000000..ba47092cd --- /dev/null +++ b/library/pear/Calendar/tests/validator_error_test.php @@ -0,0 +1,34 @@ +UnitTestCase('Test of Validation Error'); + } + function setUp() { + $this->vError = new Calendar_Validation_Error('foo',20,'bar'); + } + function testGetUnit() { + $this->assertEqual($this->vError->getUnit(),'foo'); + } + function testGetValue() { + $this->assertEqual($this->vError->getValue(),20); + } + function testGetMessage() { + $this->assertEqual($this->vError->getMessage(),'bar'); + } + function testToString() { + $this->assertEqual($this->vError->toString(),'foo = 20 [bar]'); + } +} + +if (!defined('TEST_RUNNING')) { + define('TEST_RUNNING', true); + $test = &new TestOfValidationError(); + $test->run(new HtmlReporter()); +} +?> \ No newline at end of file diff --git a/library/pear/Calendar/tests/validator_tests.php b/library/pear/Calendar/tests/validator_tests.php new file mode 100644 index 000000000..9d06b7fff --- /dev/null +++ b/library/pear/Calendar/tests/validator_tests.php @@ -0,0 +1,20 @@ +GroupTest('Validator Tests'); + $this->addTestFile('validator_unit_test.php'); + $this->addTestFile('validator_error_test.php'); + } +} + +if (!defined('TEST_RUNNING')) { + define('TEST_RUNNING', true); + $test = &new ValidatorTests(); + $test->run(new HtmlReporter()); +} +?> \ No newline at end of file diff --git a/library/pear/Calendar/tests/validator_unit_test.php b/library/pear/Calendar/tests/validator_unit_test.php new file mode 100644 index 000000000..c655bee02 --- /dev/null +++ b/library/pear/Calendar/tests/validator_unit_test.php @@ -0,0 +1,210 @@ +UnitTestCase('Test of Validator'); + } + function setUp() { + $this->mockengine = new Mock_Calendar_Engine($this); + $this->mockengine->setReturnValue('getMinYears',1970); + $this->mockengine->setReturnValue('getMaxYears',2037); + $this->mockengine->setReturnValue('getMonthsInYear',12); + $this->mockengine->setReturnValue('getDaysInMonth',30); + $this->mockengine->setReturnValue('getHoursInDay',24); + $this->mockengine->setReturnValue('getMinutesInHour',60); + $this->mockengine->setReturnValue('getSecondsInMinute',60); + $this->mockcal = new Mock_Calendar_Second($this); + $this->mockcal->setReturnValue('getEngine',$this->mockengine); + } + function tearDown() { + unset ($this->mockengine); + unset ($this->mocksecond); + } + function testIsValidYear() { + $this->mockcal->setReturnValue('thisYear',2000); + $Validator = new Calendar_Validator($this->mockcal); + $this->assertTrue($Validator->isValidYear()); + } + function testIsValidYearTooSmall() { + $this->mockcal->setReturnValue('thisYear',1969); + $Validator = new Calendar_Validator($this->mockcal); + $this->assertFalse($Validator->isValidYear()); + } + function testIsValidYearTooLarge() { + $this->mockcal->setReturnValue('thisYear',2038); + $Validator = new Calendar_Validator($this->mockcal); + $this->assertFalse($Validator->isValidYear()); + } + function testIsValidMonth() { + $this->mockcal->setReturnValue('thisMonth',10); + $Validator = new Calendar_Validator($this->mockcal); + $this->assertTrue($Validator->isValidMonth()); + } + function testIsValidMonthTooSmall() { + $this->mockcal->setReturnValue('thisMonth',0); + $Validator = new Calendar_Validator($this->mockcal); + $this->assertFalse($Validator->isValidMonth()); + } + function testIsValidMonthTooLarge() { + $this->mockcal->setReturnValue('thisMonth',13); + $Validator = new Calendar_Validator($this->mockcal); + $this->assertFalse($Validator->isValidMonth()); + } + function testIsValidDay() { + $this->mockcal->setReturnValue('thisDay',10); + $Validator = new Calendar_Validator($this->mockcal); + $this->assertTrue($Validator->isValidDay()); + } + function testIsValidDayTooSmall() { + $this->mockcal->setReturnValue('thisDay',0); + $Validator = new Calendar_Validator($this->mockcal); + $this->assertFalse($Validator->isValidDay()); + } + function testIsValidDayTooLarge() { + $this->mockcal->setReturnValue('thisDay',31); + $Validator = new Calendar_Validator($this->mockcal); + $this->assertFalse($Validator->isValidDay()); + } + function testIsValidHour() { + $this->mockcal->setReturnValue('thisHour',10); + $Validator = new Calendar_Validator($this->mockcal); + $this->assertTrue($Validator->isValidHour()); + } + function testIsValidHourTooSmall() { + $this->mockcal->setReturnValue('thisHour',-1); + $Validator = new Calendar_Validator($this->mockcal); + $this->assertFalse($Validator->isValidHour()); + } + function testIsValidHourTooLarge() { + $this->mockcal->setReturnValue('thisHour',24); + $Validator = new Calendar_Validator($this->mockcal); + $this->assertFalse($Validator->isValidHour()); + } + function testIsValidMinute() { + $this->mockcal->setReturnValue('thisMinute',30); + $Validator = new Calendar_Validator($this->mockcal); + $this->assertTrue($Validator->isValidMinute()); + } + function testIsValidMinuteTooSmall() { + $this->mockcal->setReturnValue('thisMinute',-1); + $Validator = new Calendar_Validator($this->mockcal); + $this->assertFalse($Validator->isValidMinute()); + } + function testIsValidMinuteTooLarge() { + $this->mockcal->setReturnValue('thisMinute',60); + $Validator = new Calendar_Validator($this->mockcal); + $this->assertFalse($Validator->isValidMinute()); + } + function testIsValidSecond() { + $this->mockcal->setReturnValue('thisSecond',30); + $Validator = new Calendar_Validator($this->mockcal); + $this->assertTrue($Validator->isValidSecond()); + } + function testIsValidSecondTooSmall() { + $this->mockcal->setReturnValue('thisSecond',-1); + $Validator = new Calendar_Validator($this->mockcal); + $this->assertFalse($Validator->isValidSecond()); + } + function testIsValidSecondTooLarge() { + $this->mockcal->setReturnValue('thisSecond',60); + $Validator = new Calendar_Validator($this->mockcal); + $this->assertFalse($Validator->isValidSecond()); + } + function testIsValid() { + $this->mockcal->setReturnValue('thisYear',2000); + $this->mockcal->setReturnValue('thisMonth',5); + $this->mockcal->setReturnValue('thisDay',15); + $this->mockcal->setReturnValue('thisHour',13); + $this->mockcal->setReturnValue('thisMinute',30); + $this->mockcal->setReturnValue('thisSecond',40); + $Validator = new Calendar_Validator($this->mockcal); + $this->assertTrue($Validator->isValid()); + } + function testIsValidAllWrong() { + $this->mockcal->setReturnValue('thisYear',2038); + $this->mockcal->setReturnValue('thisMonth',13); + $this->mockcal->setReturnValue('thisDay',31); + $this->mockcal->day = 31; + $this->mockcal->setReturnValue('thisHour',24); + $this->mockcal->setReturnValue('thisMinute',60); + $this->mockcal->setReturnValue('thisSecond',60); + $Validator = new Calendar_Validator($this->mockcal); + $this->assertFalse($Validator->isValid()); + $i = 0; + while ( $Validator->fetch() ) { + $i++; + } + $this->assertEqual($i,6); + } +} + +class TestOfValidatorLive extends UnitTestCase { + function TestOfValidatorLive() { + $this->UnitTestCase('Test of Validator Live'); + } + function testYear() { + $Unit = new Calendar_Year(2038); + $Validator = & $Unit->getValidator(); + $this->assertFalse($Validator->isValidYear()); + } + function testMonth() { + $Unit = new Calendar_Month(2000,13); + $Validator = & $Unit->getValidator(); + $this->assertFalse($Validator->isValidMonth()); + } +/* + function testWeek() { + $Unit = new Calendar_Week(2000,12,7); + $Validator = & $Unit->getValidator(); + $this->assertFalse($Validator->isValidWeek()); + } +*/ + function testDay() { + $Unit = new Calendar_Day(2000,12,32); + $Validator = & $Unit->getValidator(); + $this->assertFalse($Validator->isValidDay()); + } + function testHour() { + $Unit = new Calendar_Hour(2000,12,20,24); + $Validator = & $Unit->getValidator(); + $this->assertFalse($Validator->isValidHour()); + } + function testMinute() { + $Unit = new Calendar_Minute(2000,12,20,23,60); + $Validator = & $Unit->getValidator(); + $this->assertFalse($Validator->isValidMinute()); + } + function testSecond() { + $Unit = new Calendar_Second(2000,12,20,23,59,60); + $Validator = & $Unit->getValidator(); + $this->assertFalse($Validator->isValidSecond()); + } + function testAllBad() { + $Unit = new Calendar_Second(2000,13,32,24,60,60); + $this->assertFalse($Unit->isValid()); + $Validator = & $Unit->getValidator(); + $i = 0; + while ( $Validator->fetch() ) { + $i++; + } + $this->assertEqual($i,5); + } +} + +if (!defined('TEST_RUNNING')) { + define('TEST_RUNNING', true); + $test = &new TestOfValidator(); + $test->run(new HtmlReporter()); + $test = &new TestOfValidatorLive(); + $test->run(new HtmlReporter()); +} +?> \ No newline at end of file diff --git a/library/pear/Calendar/tests/week_firstday_0_test.php b/library/pear/Calendar/tests/week_firstday_0_test.php new file mode 100644 index 000000000..4ba0c48f5 --- /dev/null +++ b/library/pear/Calendar/tests/week_firstday_0_test.php @@ -0,0 +1,241 @@ +UnitTestCase('Test of Week - Week Starting on Sunday'); + } + function setUp() { + $this->cal = Calendar_Factory::create('Week', 2003, 10, 9); + //print_r($this->cal); + } + function testPrevDay () { + $this->assertEqual(8, $this->cal->prevDay()); + } + function testPrevDay_Array () { + $this->assertEqual( + array( + 'year' => 2003, + 'month' => 10, + 'day' => 8, + 'hour' => 0, + 'minute' => 0, + 'second' => 0), + $this->cal->prevDay('array')); + } + function testThisDay () { + $this->assertEqual(9, $this->cal->thisDay()); + } + function testNextDay () { + $this->assertEqual(10, $this->cal->nextDay()); + } + function testPrevHour () { + $this->assertEqual(23, $this->cal->prevHour()); + } + function testThisHour () { + $this->assertEqual(0, $this->cal->thisHour()); + } + function testNextHour () { + $this->assertEqual(1, $this->cal->nextHour()); + } + function testPrevMinute () { + $this->assertEqual(59, $this->cal->prevMinute()); + } + function testThisMinute () { + $this->assertEqual(0, $this->cal->thisMinute()); + } + function testNextMinute () { + $this->assertEqual(1, $this->cal->nextMinute()); + } + function testPrevSecond () { + $this->assertEqual(59, $this->cal->prevSecond()); + } + function testThisSecond () { + $this->assertEqual(0, $this->cal->thisSecond()); + } + function testNextSecond () { + $this->assertEqual(1, $this->cal->nextSecond()); + } + function testGetTimeStamp() { + $stamp = mktime(0,0,0,10,9,2003); + $this->assertEqual($stamp,$this->cal->getTimeStamp()); + } + function testNewTimeStamp() { + $stamp = mktime(0,0,0,7,28,2004); + $this->cal->setTimestamp($stamp); + $this->assertEqual('29 2004', date('W Y', $this->cal->prevWeek(true))); + $this->assertEqual('30 2004', date('W Y', $this->cal->thisWeek(true))); + $this->assertEqual('31 2004', date('W Y', $this->cal->nextWeek(true))); + } + function testPrevWeekInMonth() { + $this->assertEqual(1, $this->cal->prevWeek()); + $stamp = mktime(0,0,0,2,3,2005); + $this->cal->setTimestamp($stamp); + $this->assertEqual(0, $this->cal->prevWeek()); + } + function testThisWeekInMonth() { + $this->assertEqual(2, $this->cal->thisWeek()); + $stamp = mktime(0,0,0,2,3,2005); + $this->cal->setTimestamp($stamp); + $this->assertEqual(1, $this->cal->thisWeek()); + $stamp = mktime(0,0,0,1,1,2005); + $this->cal->setTimestamp($stamp); + $this->assertEqual(1, $this->cal->thisWeek()); + $stamp = mktime(0,0,0,1,3,2005); + $this->cal->setTimestamp($stamp); + $this->assertEqual(2, $this->cal->thisWeek()); + } + function testNextWeekInMonth() { + $this->assertEqual(3, $this->cal->nextWeek()); + $stamp = mktime(0,0,0,2,3,2005); + $this->cal->setTimestamp($stamp); + $this->assertEqual(2, $this->cal->nextWeek()); + } + function testPrevWeekInYear() { + $this->assertEqual(date('W', $this->cal->prevWeek('timestamp')), $this->cal->prevWeek('n_in_year')); + $stamp = mktime(0,0,0,1,1,2004); + $this->cal->setTimestamp($stamp); + $this->assertEqual(date('W', $this->cal->nextWeek('timestamp')), $this->cal->nextWeek('n_in_year')); + } + function testThisWeekInYear() { + $this->assertEqual(date('W', $this->cal->thisWeek('timestamp')), $this->cal->thisWeek('n_in_year')); + $stamp = mktime(0,0,0,1,1,2004); + $this->cal->setTimestamp($stamp); + $this->assertEqual(date('W', $this->cal->thisWeek('timestamp')), $this->cal->thisWeek('n_in_year')); + } + function testFirstWeekInYear() { + $stamp = mktime(0,0,0,1,4,2004); + $this->cal->setTimestamp($stamp); + $this->assertEqual(1, $this->cal->thisWeek('n_in_year')); + } + function testNextWeekInYear() { + $this->assertEqual(date('W', $this->cal->nextWeek('timestamp')), $this->cal->nextWeek('n_in_year')); + } + function testPrevWeekArray() { + $testArray = array( + 'year'=>2003, + 'month'=>9, + 'day'=>28, + 'hour'=>0, + 'minute'=>0, + 'second'=>0 + ); + $this->assertEqual($testArray, $this->cal->prevWeek('array')); + } + function testThisWeekArray() { + $testArray = array( + 'year'=>2003, + 'month'=>10, + 'day'=>5, + 'hour'=>0, + 'minute'=>0, + 'second'=>0 + ); + $this->assertEqual($testArray, $this->cal->thisWeek('array')); + } + function testNextWeekArray() { + $testArray = array( + 'year'=>2003, + 'month'=>10, + 'day'=>12, + 'hour'=>0, + 'minute'=>0, + 'second'=>0 + ); + $this->assertEqual($testArray, $this->cal->nextWeek('array')); + } + function testPrevWeekObject() { + $testWeek = Calendar_Factory::create('Week', 2003,9,28); + $Week = $this->cal->prevWeek('object'); + $this->assertEqual($testWeek->getTimeStamp(),$Week->getTimeStamp()); + } + function testThisWeekObject() { + $testWeek = Calendar_Factory::create('Week', 2003,10,5); + $Week = $this->cal->thisWeek('object'); + $this->assertEqual($testWeek->getTimeStamp(),$Week->getTimeStamp()); + } + function testNextWeekObject() { + $testWeek = Calendar_Factory::create('Week', 2003,10,12); + $Week = $this->cal->nextWeek('object'); + $this->assertEqual($testWeek->getTimeStamp(),$Week->getTimeStamp()); + } +} + +class TestOfWeek_firstday_0_Build extends TestOfWeek_firstday_0 { + function TestOfWeek_firstday_0_Build() { + $this->UnitTestCase('Test of Week::build() - FirstDay = Sunday'); + } + function testSize() { + $this->cal->build(); + $this->assertEqual(7, $this->cal->size()); + } + + function testFetch() { + $this->cal->build(); + $i=0; + while ($Child = $this->cal->fetch()) { + $i++; + } + $this->assertEqual(7, $i); + } + function testFetchAll() { + $this->cal->build(); + $children = array(); + $i = 1; + while ( $Child = $this->cal->fetch() ) { + $children[$i]=$Child; + $i++; + } + $this->assertEqual($children,$this->cal->fetchAll()); + } + + function testSelection() { + require_once(CALENDAR_ROOT . 'Day.php'); + $selection = array(Calendar_Factory::create('Day', 2003, 10, 6)); + $this->cal->build($selection); + $i = 1; + while ($Child = $this->cal->fetch()) { + if ($i == 2) { + break; //06-10-2003 is the 2nd day of the week + } + $i++; + } + $this->assertTrue($Child->isSelected()); + } + function testSelectionCornerCase() { + require_once(CALENDAR_ROOT . 'Day.php'); + $selectedDays = array( + Calendar_Factory::create('Day', 2003, 12, 28), + Calendar_Factory::create('Day', 2003, 12, 29), + Calendar_Factory::create('Day', 2003, 12, 30), + Calendar_Factory::create('Day', 2003, 12, 31), + Calendar_Factory::create('Day', 2004, 01, 01), + Calendar_Factory::create('Day', 2004, 01, 02), + Calendar_Factory::create('Day', 2004, 01, 03) + ); + $this->cal = Calendar_Factory::create('Week', 2003, 12, 31, 0); + $this->cal->build($selectedDays); + while ($Day = $this->cal->fetch()) { + $this->assertTrue($Day->isSelected()); + } + $this->cal = Calendar_Factory::create('Week', 2004, 1, 1, 0); + $this->cal->build($selectedDays); + while ($Day = $this->cal->fetch()) { + $this->assertTrue($Day->isSelected()); + } + } +} +if (!defined('TEST_RUNNING')) { + define('TEST_RUNNING', true); + $test = &new TestOfWeek_firstday_0(); + $test->run(new HtmlReporter()); + $test = &new TestOfWeek_firstday_0_Build(); + $test->run(new HtmlReporter()); +} +?> \ No newline at end of file diff --git a/library/pear/Calendar/tests/week_test.php b/library/pear/Calendar/tests/week_test.php new file mode 100644 index 000000000..64db83071 --- /dev/null +++ b/library/pear/Calendar/tests/week_test.php @@ -0,0 +1,276 @@ +UnitTestCase('Test of Week'); + } + function setUp() { + $this->cal = Calendar_Factory::create('Week', 2003, 10, 9); + //print_r($this->cal); + } + function testThisYear () { + $this->assertEqual(2003, $this->cal->thisYear()); + + $stamp = mktime(0,0,0,1,1,2003); + $this->cal->setTimestamp($stamp); + $this->assertEqual(2003, $this->cal->thisYear()); + + $stamp = mktime(0,0,0,12,31,2003); + $this->cal->setTimestamp($stamp); + $this->assertEqual(2004, $this->cal->thisYear()); + + $stamp = mktime(0,0,0,1,1,2005); + $this->cal->setTimestamp($stamp); + $this->assertEqual(2004, $this->cal->thisYear()); + + $stamp = mktime(0,0,0,12,31,2004); + $this->cal->setTimestamp($stamp); + $this->assertEqual(2004, $this->cal->thisYear()); + + $stamp = mktime(0,0,0,1,1,2005); + $this->cal->setTimestamp($stamp); + $this->assertEqual(2004, $this->cal->thisYear()); + + $stamp = mktime(0,0,0,12,31,2005); + $this->cal->setTimestamp($stamp); + $this->assertEqual(2005, $this->cal->thisYear()); + + $stamp = mktime(0,0,0,1,1,2006); + $this->cal->setTimestamp($stamp); + $this->assertEqual(2005, $this->cal->thisYear()); + + $stamp = mktime(0,0,0,12,31,2006); + $this->cal->setTimestamp($stamp); + $this->assertEqual(2006, $this->cal->thisYear()); + } + function testPrevDay () { + $this->assertEqual(8, $this->cal->prevDay()); + } + function testPrevDay_Array () { + $this->assertEqual( + array( + 'year' => 2003, + 'month' => 10, + 'day' => 8, + 'hour' => 0, + 'minute' => 0, + 'second' => 0), + $this->cal->prevDay('array')); + } + function testThisDay () { + $this->assertEqual(9, $this->cal->thisDay()); + } + function testNextDay () { + $this->assertEqual(10, $this->cal->nextDay()); + } + function testPrevHour () { + $this->assertEqual(23, $this->cal->prevHour()); + } + function testThisHour () { + $this->assertEqual(0, $this->cal->thisHour()); + } + function testNextHour () { + $this->assertEqual(1, $this->cal->nextHour()); + } + function testPrevMinute () { + $this->assertEqual(59, $this->cal->prevMinute()); + } + function testThisMinute () { + $this->assertEqual(0, $this->cal->thisMinute()); + } + function testNextMinute () { + $this->assertEqual(1, $this->cal->nextMinute()); + } + function testPrevSecond () { + $this->assertEqual(59, $this->cal->prevSecond()); + } + function testThisSecond () { + $this->assertEqual(0, $this->cal->thisSecond()); + } + function testNextSecond () { + $this->assertEqual(1, $this->cal->nextSecond()); + } + function testGetTimeStamp() { + $stamp = mktime(0,0,0,10,9,2003); + $this->assertEqual($stamp,$this->cal->getTimeStamp()); + } + function testNewTimeStamp() { + $stamp = mktime(0,0,0,7,28,2004); + $this->cal->setTimestamp($stamp); + $this->assertEqual('30 2004', date('W Y', $this->cal->prevWeek(true))); + $this->assertEqual('31 2004', date('W Y', $this->cal->thisWeek(true))); + $this->assertEqual('32 2004', date('W Y', $this->cal->nextWeek(true))); + } + function testPrevWeekInMonth() { + $this->assertEqual(1, $this->cal->prevWeek()); + $stamp = mktime(0,0,0,2,3,2005); + $this->cal->setTimestamp($stamp); + $this->assertEqual(0, $this->cal->prevWeek()); + } + function testThisWeekInMonth() { + $this->assertEqual(2, $this->cal->thisWeek()); + $stamp = mktime(0,0,0,2,3,2005); + $this->cal->setTimestamp($stamp); + $this->assertEqual(1, $this->cal->thisWeek()); + $stamp = mktime(0,0,0,1,1,2005); + $this->cal->setTimestamp($stamp); + $this->assertEqual(1, $this->cal->thisWeek()); + $stamp = mktime(0,0,0,1,3,2005); + $this->cal->setTimestamp($stamp); + $this->assertEqual(2, $this->cal->thisWeek()); + } + function testNextWeekInMonth() { + $this->assertEqual(3, $this->cal->nextWeek()); + $stamp = mktime(0,0,0,2,3,2005); + $this->cal->setTimestamp($stamp); + $this->assertEqual(2, $this->cal->nextWeek()); + } + function testPrevWeekInYear() { + $this->assertEqual(date('W', $this->cal->prevWeek('timestamp')), $this->cal->prevWeek('n_in_year')); + $stamp = mktime(0,0,0,1,1,2004); + $this->cal->setTimestamp($stamp); + $this->assertEqual(date('W', $this->cal->nextWeek('timestamp')), $this->cal->nextWeek('n_in_year')); + } + function testThisWeekInYear() { + $this->assertEqual(date('W', $this->cal->thisWeek('timestamp')), $this->cal->thisWeek('n_in_year')); + $stamp = mktime(0,0,0,1,1,2004); + $this->cal->setTimestamp($stamp); + $this->assertEqual(date('W', $this->cal->thisWeek('timestamp')), $this->cal->thisWeek('n_in_year')); + } + function testFirstWeekInYear() { + $stamp = mktime(0,0,0,1,4,2004); + $this->cal->setTimestamp($stamp); + $this->assertEqual(1, $this->cal->thisWeek('n_in_year')); + } + function testNextWeekInYear() { + $this->assertEqual(date('W', $this->cal->nextWeek('timestamp')), $this->cal->nextWeek('n_in_year')); + } + function testPrevWeekArray() { + $testArray = array( + 'year'=>2003, + 'month'=>9, + 'day'=>29, + 'hour'=>0, + 'minute'=>0, + 'second'=>0 + ); + $this->assertEqual($testArray, $this->cal->prevWeek('array')); + } + function testThisWeekArray() { + $testArray = array( + 'year'=>2003, + 'month'=>10, + 'day'=>6, + 'hour'=>0, + 'minute'=>0, + 'second'=>0 + ); + $this->assertEqual($testArray, $this->cal->thisWeek('array')); + } + function testNextWeekArray() { + $testArray = array( + 'year'=>2003, + 'month'=>10, + 'day'=>13, + 'hour'=>0, + 'minute'=>0, + 'second'=>0 + ); + $this->assertEqual($testArray, $this->cal->nextWeek('array')); + } + function testPrevWeekObject() { + $testWeek = Calendar_Factory::create('Week', 2003, 9, 29); //week starts on monday + $Week = $this->cal->prevWeek('object'); + $this->assertEqual($testWeek->getTimeStamp(), $Week->getTimeStamp()); + } + function testThisWeekObject() { + $testWeek = Calendar_Factory::create('Week', 2003, 10, 6); //week starts on monday + $Week = $this->cal->thisWeek('object'); + $this->assertEqual($testWeek->getTimeStamp(), $Week->getTimeStamp()); + } + function testNextWeekObject() { + $testWeek = Calendar_Factory::create('Week', 2003, 10, 13); //week starts on monday + $Week = $this->cal->nextWeek('object'); + $this->assertEqual($testWeek->getTimeStamp(), $Week->getTimeStamp()); + } +} + +class TestOfWeekBuild extends TestOfWeek { + function TestOfWeekBuild() { + $this->UnitTestCase('Test of Week::build()'); + } + function testSize() { + $this->cal->build(); + $this->assertEqual(7, $this->cal->size()); + } + + function testFetch() { + $this->cal->build(); + $i=0; + while ($Child = $this->cal->fetch()) { + $i++; + } + $this->assertEqual(7, $i); + } + function testFetchAll() { + $this->cal->build(); + $children = array(); + $i = 1; + while ( $Child = $this->cal->fetch() ) { + $children[$i]=$Child; + $i++; + } + $this->assertEqual($children,$this->cal->fetchAll()); + } + + function testSelection() { + require_once(CALENDAR_ROOT . 'Day.php'); + $selection = array(Calendar_Factory::create('Day', 2003, 10, 7)); + $this->cal->build($selection); + $i = 1; + while ($Child = $this->cal->fetch()) { + if ($i == 2) { + break; //07-10-2003 is the 2nd day of the week (starting on monday) + } + $i++; + } + $this->assertTrue($Child->isSelected()); + } + function testSelectionCornerCase() { + require_once(CALENDAR_ROOT . 'Day.php'); + $selectedDays = array( + Calendar_Factory::create('Day', 2003, 12, 29), + Calendar_Factory::create('Day', 2003, 12, 30), + Calendar_Factory::create('Day', 2003, 12, 31), + Calendar_Factory::create('Day', 2004, 01, 01), + Calendar_Factory::create('Day', 2004, 01, 02), + Calendar_Factory::create('Day', 2004, 01, 03), + Calendar_Factory::create('Day', 2004, 01, 04) + ); + $this->cal = Calendar_Factory::create('Week', 2003, 12, 31, 0); + $this->cal->build($selectedDays); + while ($Day = $this->cal->fetch()) { + $this->assertTrue($Day->isSelected()); + } + $this->cal = Calendar_Factory::create('Week', 2004, 1, 1, 0); + $this->cal->build($selectedDays); + while ($Day = $this->cal->fetch()) { + $this->assertTrue($Day->isSelected()); + } + } +} +if (!defined('TEST_RUNNING')) { + define('TEST_RUNNING', true); + $test = &new TestOfWeek(); + $test->run(new HtmlReporter()); + $test = &new TestOfWeekBuild(); + $test->run(new HtmlReporter()); +} +?> \ No newline at end of file diff --git a/library/pear/Calendar/tests/year_test.php b/library/pear/Calendar/tests/year_test.php new file mode 100644 index 000000000..bacd1ec8d --- /dev/null +++ b/library/pear/Calendar/tests/year_test.php @@ -0,0 +1,142 @@ +UnitTestCase('Test of Year'); + } + function setUp() { + $this->cal = new Calendar_Year(2003); + } + function testPrevYear_Object() { + $this->assertEqual(new Calendar_Year(2002), $this->cal->prevYear('object')); + } + function testThisYear_Object() { + $this->assertEqual(new Calendar_Year(2003), $this->cal->thisYear('object')); + } + function testPrevMonth () { + $this->assertEqual(12,$this->cal->prevMonth()); + } + function testPrevMonth_Array () { + $this->assertEqual( + array( + 'year' => 2002, + 'month' => 12, + 'day' => 1, + 'hour' => 0, + 'minute' => 0, + 'second' => 0), + $this->cal->prevMonth('array')); + } + function testThisMonth () { + $this->assertEqual(1,$this->cal->thisMonth()); + } + function testNextMonth () { + $this->assertEqual(2,$this->cal->nextMonth()); + } + function testPrevDay () { + $this->assertEqual(31,$this->cal->prevDay()); + } + function testPrevDay_Array () { + $this->assertEqual( + array( + 'year' => 2002, + 'month' => 12, + 'day' => 31, + 'hour' => 0, + 'minute' => 0, + 'second' => 0), + $this->cal->prevDay('array')); + } + function testThisDay () { + $this->assertEqual(1,$this->cal->thisDay()); + } + function testNextDay () { + $this->assertEqual(2,$this->cal->nextDay()); + } + function testPrevHour () { + $this->assertEqual(23,$this->cal->prevHour()); + } + function testThisHour () { + $this->assertEqual(0,$this->cal->thisHour()); + } + function testNextHour () { + $this->assertEqual(1,$this->cal->nextHour()); + } + function testPrevMinute () { + $this->assertEqual(59,$this->cal->prevMinute()); + } + function testThisMinute () { + $this->assertEqual(0,$this->cal->thisMinute()); + } + function testNextMinute () { + $this->assertEqual(1,$this->cal->nextMinute()); + } + function testPrevSecond () { + $this->assertEqual(59,$this->cal->prevSecond()); + } + function testThisSecond () { + $this->assertEqual(0,$this->cal->thisSecond()); + } + function testNextSecond () { + $this->assertEqual(1,$this->cal->nextSecond()); + } + function testGetTimeStamp() { + $stamp = mktime(0,0,0,1,1,2003); + $this->assertEqual($stamp,$this->cal->getTimeStamp()); + } +} + +class TestOfYearBuild extends TestOfYear { + function TestOfYearBuild() { + $this->UnitTestCase('Test of Year::build()'); + } + function testSize() { + $this->cal->build(); + $this->assertEqual(12,$this->cal->size()); + } + function testFetch() { + $this->cal->build(); + $i=0; + while ( $Child = $this->cal->fetch() ) { + $i++; + } + $this->assertEqual(12,$i); + } + function testFetchAll() { + $this->cal->build(); + $children = array(); + $i = 1; + while ( $Child = $this->cal->fetch() ) { + $children[$i]=$Child; + $i++; + } + $this->assertEqual($children,$this->cal->fetchAll()); + } + function testSelection() { + require_once(CALENDAR_ROOT . 'Month.php'); + $selection = array(new Calendar_Month(2003,10)); + $this->cal->build($selection); + $i = 1; + while ( $Child = $this->cal->fetch() ) { + if ( $i == 10 ) + break; + $i++; + } + $this->assertTrue($Child->isSelected()); + } +} + +if (!defined('TEST_RUNNING')) { + define('TEST_RUNNING', true); + $test = &new TestOfYear(); + $test->run(new HtmlReporter()); + $test = &new TestOfYearBuild(); + $test->run(new HtmlReporter()); +} +?> \ No newline at end of file diff --git a/library/pear/Console/Getopt.php b/library/pear/Console/Getopt.php new file mode 100644 index 000000000..bb9d69ca2 --- /dev/null +++ b/library/pear/Console/Getopt.php @@ -0,0 +1,290 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id: Getopt.php,v 1.4 2007/06/12 14:58:56 cellog Exp $ + +require_once 'PEAR.php'; + +/** + * Command-line options parsing class. + * + * @author Andrei Zmievski + * + */ +class Console_Getopt { + /** + * Parses the command-line options. + * + * The first parameter to this function should be the list of command-line + * arguments without the leading reference to the running program. + * + * The second parameter is a string of allowed short options. Each of the + * option letters can be followed by a colon ':' to specify that the option + * requires an argument, or a double colon '::' to specify that the option + * takes an optional argument. + * + * The third argument is an optional array of allowed long options. The + * leading '--' should not be included in the option name. Options that + * require an argument should be followed by '=', and options that take an + * option argument should be followed by '=='. + * + * The return value is an array of two elements: the list of parsed + * options and the list of non-option command-line arguments. Each entry in + * the list of parsed options is a pair of elements - the first one + * specifies the option, and the second one specifies the option argument, + * if there was one. + * + * Long and short options can be mixed. + * + * Most of the semantics of this function are based on GNU getopt_long(). + * + * @param array $args an array of command-line arguments + * @param string $short_options specifies the list of allowed short options + * @param array $long_options specifies the list of allowed long options + * + * @return array two-element array containing the list of parsed options and + * the non-option arguments + * + * @access public + * + */ + function getopt2($args, $short_options, $long_options = null) + { + return Console_Getopt::doGetopt(2, $args, $short_options, $long_options); + } + + /** + * This function expects $args to start with the script name (POSIX-style). + * Preserved for backwards compatibility. + * @see getopt2() + */ + function getopt($args, $short_options, $long_options = null) + { + return Console_Getopt::doGetopt(1, $args, $short_options, $long_options); + } + + /** + * The actual implementation of the argument parsing code. + */ + function doGetopt($version, $args, $short_options, $long_options = null) + { + // in case you pass directly readPHPArgv() as the first arg + if (PEAR::isError($args)) { + return $args; + } + if (empty($args)) { + return array(array(), array()); + } + $opts = array(); + $non_opts = array(); + + settype($args, 'array'); + + if ($long_options) { + sort($long_options); + } + + /* + * Preserve backwards compatibility with callers that relied on + * erroneous POSIX fix. + */ + if ($version < 2) { + if (isset($args[0]{0}) && $args[0]{0} != '-') { + array_shift($args); + } + } + + reset($args); + while (list($i, $arg) = each($args)) { + + /* The special element '--' means explicit end of + options. Treat the rest of the arguments as non-options + and end the loop. */ + if ($arg == '--') { + $non_opts = array_merge($non_opts, array_slice($args, $i + 1)); + break; + } + + if ($arg{0} != '-' || (strlen($arg) > 1 && $arg{1} == '-' && !$long_options)) { + $non_opts = array_merge($non_opts, array_slice($args, $i)); + break; + } elseif (strlen($arg) > 1 && $arg{1} == '-') { + $error = Console_Getopt::_parseLongOption(substr($arg, 2), $long_options, $opts, $args); + if (PEAR::isError($error)) + return $error; + } elseif ($arg == '-') { + // - is stdin + $non_opts = array_merge($non_opts, array_slice($args, $i)); + break; + } else { + $error = Console_Getopt::_parseShortOption(substr($arg, 1), $short_options, $opts, $args); + if (PEAR::isError($error)) + return $error; + } + } + + return array($opts, $non_opts); + } + + /** + * @access private + * + */ + function _parseShortOption($arg, $short_options, &$opts, &$args) + { + for ($i = 0; $i < strlen($arg); $i++) { + $opt = $arg{$i}; + $opt_arg = null; + + /* Try to find the short option in the specifier string. */ + if (($spec = strstr($short_options, $opt)) === false || $arg{$i} == ':') + { + return PEAR::raiseError("Console_Getopt: unrecognized option -- $opt"); + } + + if (strlen($spec) > 1 && $spec{1} == ':') { + if (strlen($spec) > 2 && $spec{2} == ':') { + if ($i + 1 < strlen($arg)) { + /* Option takes an optional argument. Use the remainder of + the arg string if there is anything left. */ + $opts[] = array($opt, substr($arg, $i + 1)); + break; + } + } else { + /* Option requires an argument. Use the remainder of the arg + string if there is anything left. */ + if ($i + 1 < strlen($arg)) { + $opts[] = array($opt, substr($arg, $i + 1)); + break; + } else if (list(, $opt_arg) = each($args)) { + /* Else use the next argument. */; + if (Console_Getopt::_isShortOpt($opt_arg) || Console_Getopt::_isLongOpt($opt_arg)) { + return PEAR::raiseError("Console_Getopt: option requires an argument -- $opt"); + } + } else { + return PEAR::raiseError("Console_Getopt: option requires an argument -- $opt"); + } + } + } + + $opts[] = array($opt, $opt_arg); + } + } + + /** + * @access private + * + */ + function _isShortOpt($arg) + { + return strlen($arg) == 2 && $arg[0] == '-' && preg_match('/[a-zA-Z]/', $arg[1]); + } + + /** + * @access private + * + */ + function _isLongOpt($arg) + { + return strlen($arg) > 2 && $arg[0] == '-' && $arg[1] == '-' && + preg_match('/[a-zA-Z]+$/', substr($arg, 2)); + } + + /** + * @access private + * + */ + function _parseLongOption($arg, $long_options, &$opts, &$args) + { + @list($opt, $opt_arg) = explode('=', $arg, 2); + $opt_len = strlen($opt); + + for ($i = 0; $i < count($long_options); $i++) { + $long_opt = $long_options[$i]; + $opt_start = substr($long_opt, 0, $opt_len); + $long_opt_name = str_replace('=', '', $long_opt); + + /* Option doesn't match. Go on to the next one. */ + if ($long_opt_name != $opt) { + continue; + } + + $opt_rest = substr($long_opt, $opt_len); + + /* Check that the options uniquely matches one of the allowed + options. */ + if ($i + 1 < count($long_options)) { + $next_option_rest = substr($long_options[$i + 1], $opt_len); + } else { + $next_option_rest = ''; + } + if ($opt_rest != '' && $opt{0} != '=' && + $i + 1 < count($long_options) && + $opt == substr($long_options[$i+1], 0, $opt_len) && + $next_option_rest != '' && + $next_option_rest{0} != '=') { + return PEAR::raiseError("Console_Getopt: option --$opt is ambiguous"); + } + + if (substr($long_opt, -1) == '=') { + if (substr($long_opt, -2) != '==') { + /* Long option requires an argument. + Take the next argument if one wasn't specified. */; + if (!strlen($opt_arg) && !(list(, $opt_arg) = each($args))) { + return PEAR::raiseError("Console_Getopt: option --$opt requires an argument"); + } + if (Console_Getopt::_isShortOpt($opt_arg) || Console_Getopt::_isLongOpt($opt_arg)) { + return PEAR::raiseError("Console_Getopt: option requires an argument --$opt"); + } + } + } else if ($opt_arg) { + return PEAR::raiseError("Console_Getopt: option --$opt doesn't allow an argument"); + } + + $opts[] = array('--' . $opt, $opt_arg); + return; + } + + return PEAR::raiseError("Console_Getopt: unrecognized option --$opt"); + } + + /** + * Safely read the $argv PHP array across different PHP configurations. + * Will take care on register_globals and register_argc_argv ini directives + * + * @access public + * @return mixed the $argv PHP array or PEAR error if not registered + */ + function readPHPArgv() + { + global $argv; + if (!is_array($argv)) { + if (!@is_array($_SERVER['argv'])) { + if (!@is_array($GLOBALS['HTTP_SERVER_VARS']['argv'])) { + return PEAR::raiseError("Console_Getopt: Could not read cmd args (register_argc_argv=Off?)"); + } + return $GLOBALS['HTTP_SERVER_VARS']['argv']; + } + return $_SERVER['argv']; + } + return $argv; + } + +} + +?> diff --git a/library/pear/DB.php b/library/pear/DB.php new file mode 100644 index 000000000..a511979e6 --- /dev/null +++ b/library/pear/DB.php @@ -0,0 +1,1489 @@ + + * @author Tomas V.V.Cox + * @author Daniel Convissor + * @copyright 1997-2007 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version CVS: $Id: DB.php,v 1.88 2007/08/12 05:27:25 aharvey Exp $ + * @link http://pear.php.net/package/DB + */ + +/** + * Obtain the PEAR class so it can be extended from + */ +require_once 'PEAR.php'; + + +// {{{ constants +// {{{ error codes + +/**#@+ + * One of PEAR DB's portable error codes. + * @see DB_common::errorCode(), DB::errorMessage() + * + * {@internal If you add an error code here, make sure you also add a textual + * version of it in DB::errorMessage().}} + */ + +/** + * The code returned by many methods upon success + */ +define('DB_OK', 1); + +/** + * Unkown error + */ +define('DB_ERROR', -1); + +/** + * Syntax error + */ +define('DB_ERROR_SYNTAX', -2); + +/** + * Tried to insert a duplicate value into a primary or unique index + */ +define('DB_ERROR_CONSTRAINT', -3); + +/** + * An identifier in the query refers to a non-existant object + */ +define('DB_ERROR_NOT_FOUND', -4); + +/** + * Tried to create a duplicate object + */ +define('DB_ERROR_ALREADY_EXISTS', -5); + +/** + * The current driver does not support the action you attempted + */ +define('DB_ERROR_UNSUPPORTED', -6); + +/** + * The number of parameters does not match the number of placeholders + */ +define('DB_ERROR_MISMATCH', -7); + +/** + * A literal submitted did not match the data type expected + */ +define('DB_ERROR_INVALID', -8); + +/** + * The current DBMS does not support the action you attempted + */ +define('DB_ERROR_NOT_CAPABLE', -9); + +/** + * A literal submitted was too long so the end of it was removed + */ +define('DB_ERROR_TRUNCATED', -10); + +/** + * A literal number submitted did not match the data type expected + */ +define('DB_ERROR_INVALID_NUMBER', -11); + +/** + * A literal date submitted did not match the data type expected + */ +define('DB_ERROR_INVALID_DATE', -12); + +/** + * Attempt to divide something by zero + */ +define('DB_ERROR_DIVZERO', -13); + +/** + * A database needs to be selected + */ +define('DB_ERROR_NODBSELECTED', -14); + +/** + * Could not create the object requested + */ +define('DB_ERROR_CANNOT_CREATE', -15); + +/** + * Could not drop the database requested because it does not exist + */ +define('DB_ERROR_CANNOT_DROP', -17); + +/** + * An identifier in the query refers to a non-existant table + */ +define('DB_ERROR_NOSUCHTABLE', -18); + +/** + * An identifier in the query refers to a non-existant column + */ +define('DB_ERROR_NOSUCHFIELD', -19); + +/** + * The data submitted to the method was inappropriate + */ +define('DB_ERROR_NEED_MORE_DATA', -20); + +/** + * The attempt to lock the table failed + */ +define('DB_ERROR_NOT_LOCKED', -21); + +/** + * The number of columns doesn't match the number of values + */ +define('DB_ERROR_VALUE_COUNT_ON_ROW', -22); + +/** + * The DSN submitted has problems + */ +define('DB_ERROR_INVALID_DSN', -23); + +/** + * Could not connect to the database + */ +define('DB_ERROR_CONNECT_FAILED', -24); + +/** + * The PHP extension needed for this DBMS could not be found + */ +define('DB_ERROR_EXTENSION_NOT_FOUND',-25); + +/** + * The present user has inadequate permissions to perform the task requestd + */ +define('DB_ERROR_ACCESS_VIOLATION', -26); + +/** + * The database requested does not exist + */ +define('DB_ERROR_NOSUCHDB', -27); + +/** + * Tried to insert a null value into a column that doesn't allow nulls + */ +define('DB_ERROR_CONSTRAINT_NOT_NULL',-29); +/**#@-*/ + + +// }}} +// {{{ prepared statement-related + + +/**#@+ + * Identifiers for the placeholders used in prepared statements. + * @see DB_common::prepare() + */ + +/** + * Indicates a scalar (?) placeholder was used + * + * Quote and escape the value as necessary. + */ +define('DB_PARAM_SCALAR', 1); + +/** + * Indicates an opaque (&) placeholder was used + * + * The value presented is a file name. Extract the contents of that file + * and place them in this column. + */ +define('DB_PARAM_OPAQUE', 2); + +/** + * Indicates a misc (!) placeholder was used + * + * The value should not be quoted or escaped. + */ +define('DB_PARAM_MISC', 3); +/**#@-*/ + + +// }}} +// {{{ binary data-related + + +/**#@+ + * The different ways of returning binary data from queries. + */ + +/** + * Sends the fetched data straight through to output + */ +define('DB_BINMODE_PASSTHRU', 1); + +/** + * Lets you return data as usual + */ +define('DB_BINMODE_RETURN', 2); + +/** + * Converts the data to hex format before returning it + * + * For example the string "123" would become "313233". + */ +define('DB_BINMODE_CONVERT', 3); +/**#@-*/ + + +// }}} +// {{{ fetch modes + + +/**#@+ + * Fetch Modes. + * @see DB_common::setFetchMode() + */ + +/** + * Indicates the current default fetch mode should be used + * @see DB_common::$fetchmode + */ +define('DB_FETCHMODE_DEFAULT', 0); + +/** + * Column data indexed by numbers, ordered from 0 and up + */ +define('DB_FETCHMODE_ORDERED', 1); + +/** + * Column data indexed by column names + */ +define('DB_FETCHMODE_ASSOC', 2); + +/** + * Column data as object properties + */ +define('DB_FETCHMODE_OBJECT', 3); + +/** + * For multi-dimensional results, make the column name the first level + * of the array and put the row number in the second level of the array + * + * This is flipped from the normal behavior, which puts the row numbers + * in the first level of the array and the column names in the second level. + */ +define('DB_FETCHMODE_FLIPPED', 4); +/**#@-*/ + +/**#@+ + * Old fetch modes. Left here for compatibility. + */ +define('DB_GETMODE_ORDERED', DB_FETCHMODE_ORDERED); +define('DB_GETMODE_ASSOC', DB_FETCHMODE_ASSOC); +define('DB_GETMODE_FLIPPED', DB_FETCHMODE_FLIPPED); +/**#@-*/ + + +// }}} +// {{{ tableInfo() && autoPrepare()-related + + +/**#@+ + * The type of information to return from the tableInfo() method. + * + * Bitwised constants, so they can be combined using | + * and removed using ^. + * + * @see DB_common::tableInfo() + * + * {@internal Since the TABLEINFO constants are bitwised, if more of them are + * added in the future, make sure to adjust DB_TABLEINFO_FULL accordingly.}} + */ +define('DB_TABLEINFO_ORDER', 1); +define('DB_TABLEINFO_ORDERTABLE', 2); +define('DB_TABLEINFO_FULL', 3); +/**#@-*/ + + +/**#@+ + * The type of query to create with the automatic query building methods. + * @see DB_common::autoPrepare(), DB_common::autoExecute() + */ +define('DB_AUTOQUERY_INSERT', 1); +define('DB_AUTOQUERY_UPDATE', 2); +/**#@-*/ + + +// }}} +// {{{ portability modes + + +/**#@+ + * Portability Modes. + * + * Bitwised constants, so they can be combined using | + * and removed using ^. + * + * @see DB_common::setOption() + * + * {@internal Since the PORTABILITY constants are bitwised, if more of them are + * added in the future, make sure to adjust DB_PORTABILITY_ALL accordingly.}} + */ + +/** + * Turn off all portability features + */ +define('DB_PORTABILITY_NONE', 0); + +/** + * Convert names of tables and fields to lower case + * when using the get*(), fetch*() and tableInfo() methods + */ +define('DB_PORTABILITY_LOWERCASE', 1); + +/** + * Right trim the data output by get*() and fetch*() + */ +define('DB_PORTABILITY_RTRIM', 2); + +/** + * Force reporting the number of rows deleted + */ +define('DB_PORTABILITY_DELETE_COUNT', 4); + +/** + * Enable hack that makes numRows() work in Oracle + */ +define('DB_PORTABILITY_NUMROWS', 8); + +/** + * Makes certain error messages in certain drivers compatible + * with those from other DBMS's + * + * + mysql, mysqli: change unique/primary key constraints + * DB_ERROR_ALREADY_EXISTS -> DB_ERROR_CONSTRAINT + * + * + odbc(access): MS's ODBC driver reports 'no such field' as code + * 07001, which means 'too few parameters.' When this option is on + * that code gets mapped to DB_ERROR_NOSUCHFIELD. + */ +define('DB_PORTABILITY_ERRORS', 16); + +/** + * Convert null values to empty strings in data output by + * get*() and fetch*() + */ +define('DB_PORTABILITY_NULL_TO_EMPTY', 32); + +/** + * Turn on all portability features + */ +define('DB_PORTABILITY_ALL', 63); +/**#@-*/ + +// }}} + + +// }}} +// {{{ class DB + +/** + * Database independent query interface + * + * The main "DB" class is simply a container class with some static + * methods for creating DB objects as well as some utility functions + * common to all parts of DB. + * + * The object model of DB is as follows (indentation means inheritance): + *
    + * DB           The main DB class.  This is simply a utility class
    + *              with some "static" methods for creating DB objects as
    + *              well as common utility functions for other DB classes.
    + *
    + * DB_common    The base for each DB implementation.  Provides default
    + * |            implementations (in OO lingo virtual methods) for
    + * |            the actual DB implementations as well as a bunch of
    + * |            query utility functions.
    + * |
    + * +-DB_mysql   The DB implementation for MySQL.  Inherits DB_common.
    + *              When calling DB::factory or DB::connect for MySQL
    + *              connections, the object returned is an instance of this
    + *              class.
    + * 
    + * + * @category Database + * @package DB + * @author Stig Bakken + * @author Tomas V.V.Cox + * @author Daniel Convissor + * @copyright 1997-2007 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version Release: 1.7.13 + * @link http://pear.php.net/package/DB + */ +class DB +{ + // {{{ &factory() + + /** + * Create a new DB object for the specified database type but don't + * connect to the database + * + * @param string $type the database type (eg "mysql") + * @param array $options an associative array of option names and values + * + * @return object a new DB object. A DB_Error object on failure. + * + * @see DB_common::setOption() + */ + function &factory($type, $options = false) + { + if (!is_array($options)) { + $options = array('persistent' => $options); + } + + if (isset($options['debug']) && $options['debug'] >= 2) { + // expose php errors with sufficient debug level + include_once "DB/{$type}.php"; + } else { + @include_once "DB/{$type}.php"; + } + + $classname = "DB_${type}"; + + if (!class_exists($classname)) { + $tmp = PEAR::raiseError(null, DB_ERROR_NOT_FOUND, null, null, + "Unable to include the DB/{$type}.php" + . " file for '$dsn'", + 'DB_Error', true); + return $tmp; + } + + @$obj = new $classname; + + foreach ($options as $option => $value) { + $test = $obj->setOption($option, $value); + if (DB::isError($test)) { + return $test; + } + } + + return $obj; + } + + // }}} + // {{{ &connect() + + /** + * Create a new DB object including a connection to the specified database + * + * Example 1. + * + * require_once 'DB.php'; + * + * $dsn = 'pgsql://user:password@host/database'; + * $options = array( + * 'debug' => 2, + * 'portability' => DB_PORTABILITY_ALL, + * ); + * + * $db =& DB::connect($dsn, $options); + * if (PEAR::isError($db)) { + * die($db->getMessage()); + * } + * + * + * @param mixed $dsn the string "data source name" or array in the + * format returned by DB::parseDSN() + * @param array $options an associative array of option names and values + * + * @return object a new DB object. A DB_Error object on failure. + * + * @uses DB_dbase::connect(), DB_fbsql::connect(), DB_ibase::connect(), + * DB_ifx::connect(), DB_msql::connect(), DB_mssql::connect(), + * DB_mysql::connect(), DB_mysqli::connect(), DB_oci8::connect(), + * DB_odbc::connect(), DB_pgsql::connect(), DB_sqlite::connect(), + * DB_sybase::connect() + * + * @uses DB::parseDSN(), DB_common::setOption(), PEAR::isError() + */ + function &connect($dsn, $options = array()) + { + $dsninfo = DB::parseDSN($dsn); + $type = $dsninfo['phptype']; + + if (!is_array($options)) { + /* + * For backwards compatibility. $options used to be boolean, + * indicating whether the connection should be persistent. + */ + $options = array('persistent' => $options); + } + + if (isset($options['debug']) && $options['debug'] >= 2) { + // expose php errors with sufficient debug level + include_once "DB/${type}.php"; + } else { + @include_once "DB/${type}.php"; + } + + $classname = "DB_${type}"; + if (!class_exists($classname)) { + $tmp = PEAR::raiseError(null, DB_ERROR_NOT_FOUND, null, null, + "Unable to include the DB/{$type}.php" + . " file for '$dsn'", + 'DB_Error', true); + return $tmp; + } + + @$obj = new $classname; + + foreach ($options as $option => $value) { + $test = $obj->setOption($option, $value); + if (DB::isError($test)) { + return $test; + } + } + + $err = $obj->connect($dsninfo, $obj->getOption('persistent')); + if (DB::isError($err)) { + if (is_array($dsn)) { + $err->addUserInfo(DB::getDSNString($dsn, true)); + } else { + $err->addUserInfo($dsn); + } + return $err; + } + + return $obj; + } + + // }}} + // {{{ apiVersion() + + /** + * Return the DB API version + * + * @return string the DB API version number + */ + function apiVersion() + { + return '1.7.13'; + } + + // }}} + // {{{ isError() + + /** + * Determines if a variable is a DB_Error object + * + * @param mixed $value the variable to check + * + * @return bool whether $value is DB_Error object + */ + function isError($value) + { + return is_a($value, 'DB_Error'); + } + + // }}} + // {{{ isConnection() + + /** + * Determines if a value is a DB_ object + * + * @param mixed $value the value to test + * + * @return bool whether $value is a DB_ object + */ + function isConnection($value) + { + return (is_object($value) && + is_subclass_of($value, 'db_common') && + method_exists($value, 'simpleQuery')); + } + + // }}} + // {{{ isManip() + + /** + * Tell whether a query is a data manipulation or data definition query + * + * Examples of data manipulation queries are INSERT, UPDATE and DELETE. + * Examples of data definition queries are CREATE, DROP, ALTER, GRANT, + * REVOKE. + * + * @param string $query the query + * + * @return boolean whether $query is a data manipulation query + */ + function isManip($query) + { + $manips = 'INSERT|UPDATE|DELETE|REPLACE|' + . 'CREATE|DROP|' + . 'LOAD DATA|SELECT .* INTO .* FROM|COPY|' + . 'ALTER|GRANT|REVOKE|' + . 'LOCK|UNLOCK'; + if (preg_match('/^\s*"?(' . $manips . ')\s+/i', $query)) { + return true; + } + return false; + } + + // }}} + // {{{ errorMessage() + + /** + * Return a textual error message for a DB error code + * + * @param integer $value the DB error code + * + * @return string the error message or false if the error code was + * not recognized + */ + function errorMessage($value) + { + static $errorMessages; + if (!isset($errorMessages)) { + $errorMessages = array( + DB_ERROR => 'unknown error', + DB_ERROR_ACCESS_VIOLATION => 'insufficient permissions', + DB_ERROR_ALREADY_EXISTS => 'already exists', + DB_ERROR_CANNOT_CREATE => 'can not create', + DB_ERROR_CANNOT_DROP => 'can not drop', + DB_ERROR_CONNECT_FAILED => 'connect failed', + DB_ERROR_CONSTRAINT => 'constraint violation', + DB_ERROR_CONSTRAINT_NOT_NULL=> 'null value violates not-null constraint', + DB_ERROR_DIVZERO => 'division by zero', + DB_ERROR_EXTENSION_NOT_FOUND=> 'extension not found', + DB_ERROR_INVALID => 'invalid', + DB_ERROR_INVALID_DATE => 'invalid date or time', + DB_ERROR_INVALID_DSN => 'invalid DSN', + DB_ERROR_INVALID_NUMBER => 'invalid number', + DB_ERROR_MISMATCH => 'mismatch', + DB_ERROR_NEED_MORE_DATA => 'insufficient data supplied', + DB_ERROR_NODBSELECTED => 'no database selected', + DB_ERROR_NOSUCHDB => 'no such database', + DB_ERROR_NOSUCHFIELD => 'no such field', + DB_ERROR_NOSUCHTABLE => 'no such table', + DB_ERROR_NOT_CAPABLE => 'DB backend not capable', + DB_ERROR_NOT_FOUND => 'not found', + DB_ERROR_NOT_LOCKED => 'not locked', + DB_ERROR_SYNTAX => 'syntax error', + DB_ERROR_UNSUPPORTED => 'not supported', + DB_ERROR_TRUNCATED => 'truncated', + DB_ERROR_VALUE_COUNT_ON_ROW => 'value count on row', + DB_OK => 'no error', + ); + } + + if (DB::isError($value)) { + $value = $value->getCode(); + } + + return isset($errorMessages[$value]) ? $errorMessages[$value] + : $errorMessages[DB_ERROR]; + } + + // }}} + // {{{ parseDSN() + + /** + * Parse a data source name + * + * Additional keys can be added by appending a URI query string to the + * end of the DSN. + * + * The format of the supplied DSN is in its fullest form: + * + * phptype(dbsyntax)://username:password@protocol+hostspec/database?option=8&another=true + * + * + * Most variations are allowed: + * + * phptype://username:password@protocol+hostspec:110//usr/db_file.db?mode=0644 + * phptype://username:password@hostspec/database_name + * phptype://username:password@hostspec + * phptype://username@hostspec + * phptype://hostspec/database + * phptype://hostspec + * phptype(dbsyntax) + * phptype + * + * + * @param string $dsn Data Source Name to be parsed + * + * @return array an associative array with the following keys: + * + phptype: Database backend used in PHP (mysql, odbc etc.) + * + dbsyntax: Database used with regards to SQL syntax etc. + * + protocol: Communication protocol to use (tcp, unix etc.) + * + hostspec: Host specification (hostname[:port]) + * + database: Database to use on the DBMS server + * + username: User name for login + * + password: Password for login + */ + function parseDSN($dsn) + { + $parsed = array( + 'phptype' => false, + 'dbsyntax' => false, + 'username' => false, + 'password' => false, + 'protocol' => false, + 'hostspec' => false, + 'port' => false, + 'socket' => false, + 'database' => false, + ); + + if (is_array($dsn)) { + $dsn = array_merge($parsed, $dsn); + if (!$dsn['dbsyntax']) { + $dsn['dbsyntax'] = $dsn['phptype']; + } + return $dsn; + } + + // Find phptype and dbsyntax + if (($pos = strpos($dsn, '://')) !== false) { + $str = substr($dsn, 0, $pos); + $dsn = substr($dsn, $pos + 3); + } else { + $str = $dsn; + $dsn = null; + } + + // Get phptype and dbsyntax + // $str => phptype(dbsyntax) + if (preg_match('|^(.+?)\((.*?)\)$|', $str, $arr)) { + $parsed['phptype'] = $arr[1]; + $parsed['dbsyntax'] = !$arr[2] ? $arr[1] : $arr[2]; + } else { + $parsed['phptype'] = $str; + $parsed['dbsyntax'] = $str; + } + + if (!count($dsn)) { + return $parsed; + } + + // Get (if found): username and password + // $dsn => username:password@protocol+hostspec/database + if (($at = strrpos($dsn,'@')) !== false) { + $str = substr($dsn, 0, $at); + $dsn = substr($dsn, $at + 1); + if (($pos = strpos($str, ':')) !== false) { + $parsed['username'] = rawurldecode(substr($str, 0, $pos)); + $parsed['password'] = rawurldecode(substr($str, $pos + 1)); + } else { + $parsed['username'] = rawurldecode($str); + } + } + + // Find protocol and hostspec + + if (preg_match('|^([^(]+)\((.*?)\)/?(.*?)$|', $dsn, $match)) { + // $dsn => proto(proto_opts)/database + $proto = $match[1]; + $proto_opts = $match[2] ? $match[2] : false; + $dsn = $match[3]; + + } else { + // $dsn => protocol+hostspec/database (old format) + if (strpos($dsn, '+') !== false) { + list($proto, $dsn) = explode('+', $dsn, 2); + } + if (strpos($dsn, '/') !== false) { + list($proto_opts, $dsn) = explode('/', $dsn, 2); + } else { + $proto_opts = $dsn; + $dsn = null; + } + } + + // process the different protocol options + $parsed['protocol'] = (!empty($proto)) ? $proto : 'tcp'; + $proto_opts = rawurldecode($proto_opts); + if (strpos($proto_opts, ':') !== false) { + list($proto_opts, $parsed['port']) = explode(':', $proto_opts); + } + if ($parsed['protocol'] == 'tcp') { + $parsed['hostspec'] = $proto_opts; + } elseif ($parsed['protocol'] == 'unix') { + $parsed['socket'] = $proto_opts; + } + + // Get dabase if any + // $dsn => database + if ($dsn) { + if (($pos = strpos($dsn, '?')) === false) { + // /database + $parsed['database'] = rawurldecode($dsn); + } else { + // /database?param1=value1¶m2=value2 + $parsed['database'] = rawurldecode(substr($dsn, 0, $pos)); + $dsn = substr($dsn, $pos + 1); + if (strpos($dsn, '&') !== false) { + $opts = explode('&', $dsn); + } else { // database?param1=value1 + $opts = array($dsn); + } + foreach ($opts as $opt) { + list($key, $value) = explode('=', $opt); + if (!isset($parsed[$key])) { + // don't allow params overwrite + $parsed[$key] = rawurldecode($value); + } + } + } + } + + return $parsed; + } + + // }}} + // {{{ getDSNString() + + /** + * Returns the given DSN in a string format suitable for output. + * + * @param array|string the DSN to parse and format + * @param boolean true to hide the password, false to include it + * @return string + */ + function getDSNString($dsn, $hidePassword) { + /* Calling parseDSN will ensure that we have all the array elements + * defined, and means that we deal with strings and array in the same + * manner. */ + $dsnArray = DB::parseDSN($dsn); + + if ($hidePassword) { + $dsnArray['password'] = 'PASSWORD'; + } + + /* Protocol is special-cased, as using the default "tcp" along with an + * Oracle TNS connection string fails. */ + if (is_string($dsn) && strpos($dsn, 'tcp') === false && $dsnArray['protocol'] == 'tcp') { + $dsnArray['protocol'] = false; + } + + // Now we just have to construct the actual string. This is ugly. + $dsnString = $dsnArray['phptype']; + if ($dsnArray['dbsyntax']) { + $dsnString .= '('.$dsnArray['dbsyntax'].')'; + } + $dsnString .= '://' + .$dsnArray['username'] + .':' + .$dsnArray['password'] + .'@' + .$dsnArray['protocol']; + if ($dsnArray['socket']) { + $dsnString .= '('.$dsnArray['socket'].')'; + } + if ($dsnArray['protocol'] && $dsnArray['hostspec']) { + $dsnString .= '+'; + } + $dsnString .= $dsnArray['hostspec']; + if ($dsnArray['port']) { + $dsnString .= ':'.$dsnArray['port']; + } + $dsnString .= '/'.$dsnArray['database']; + + /* Option handling. Unfortunately, parseDSN simply places options into + * the top-level array, so we'll first get rid of the fields defined by + * DB and see what's left. */ + unset($dsnArray['phptype'], + $dsnArray['dbsyntax'], + $dsnArray['username'], + $dsnArray['password'], + $dsnArray['protocol'], + $dsnArray['socket'], + $dsnArray['hostspec'], + $dsnArray['port'], + $dsnArray['database'] + ); + if (count($dsnArray) > 0) { + $dsnString .= '?'; + $i = 0; + foreach ($dsnArray as $key => $value) { + if (++$i > 1) { + $dsnString .= '&'; + } + $dsnString .= $key.'='.$value; + } + } + + return $dsnString; + } + + // }}} +} + +// }}} +// {{{ class DB_Error + +/** + * DB_Error implements a class for reporting portable database error + * messages + * + * @category Database + * @package DB + * @author Stig Bakken + * @copyright 1997-2007 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version Release: 1.7.13 + * @link http://pear.php.net/package/DB + */ +class DB_Error extends PEAR_Error +{ + // {{{ constructor + + /** + * DB_Error constructor + * + * @param mixed $code DB error code, or string with error message + * @param int $mode what "error mode" to operate in + * @param int $level what error level to use for $mode & + * PEAR_ERROR_TRIGGER + * @param mixed $debuginfo additional debug info, such as the last query + * + * @see PEAR_Error + */ + function DB_Error($code = DB_ERROR, $mode = PEAR_ERROR_RETURN, + $level = E_USER_NOTICE, $debuginfo = null) + { + if (is_int($code)) { + $this->PEAR_Error('DB Error: ' . DB::errorMessage($code), $code, + $mode, $level, $debuginfo); + } else { + $this->PEAR_Error("DB Error: $code", DB_ERROR, + $mode, $level, $debuginfo); + } + } + + // }}} +} + +// }}} +// {{{ class DB_result + +/** + * This class implements a wrapper for a DB result set + * + * A new instance of this class will be returned by the DB implementation + * after processing a query that returns data. + * + * @category Database + * @package DB + * @author Stig Bakken + * @copyright 1997-2007 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version Release: 1.7.13 + * @link http://pear.php.net/package/DB + */ +class DB_result +{ + // {{{ properties + + /** + * Should results be freed automatically when there are no more rows? + * @var boolean + * @see DB_common::$options + */ + var $autofree; + + /** + * A reference to the DB_ object + * @var object + */ + var $dbh; + + /** + * The current default fetch mode + * @var integer + * @see DB_common::$fetchmode + */ + var $fetchmode; + + /** + * The name of the class into which results should be fetched when + * DB_FETCHMODE_OBJECT is in effect + * + * @var string + * @see DB_common::$fetchmode_object_class + */ + var $fetchmode_object_class; + + /** + * The number of rows to fetch from a limit query + * @var integer + */ + var $limit_count = null; + + /** + * The row to start fetching from in limit queries + * @var integer + */ + var $limit_from = null; + + /** + * The execute parameters that created this result + * @var array + * @since Property available since Release 1.7.0 + */ + var $parameters; + + /** + * The query string that created this result + * + * Copied here incase it changes in $dbh, which is referenced + * + * @var string + * @since Property available since Release 1.7.0 + */ + var $query; + + /** + * The query result resource id created by PHP + * @var resource + */ + var $result; + + /** + * The present row being dealt with + * @var integer + */ + var $row_counter = null; + + /** + * The prepared statement resource id created by PHP in $dbh + * + * This resource is only available when the result set was created using + * a driver's native execute() method, not PEAR DB's emulated one. + * + * Copied here incase it changes in $dbh, which is referenced + * + * {@internal Mainly here because the InterBase/Firebird API is only + * able to retrieve data from result sets if the statemnt handle is + * still in scope.}} + * + * @var resource + * @since Property available since Release 1.7.0 + */ + var $statement; + + + // }}} + // {{{ constructor + + /** + * This constructor sets the object's properties + * + * @param object &$dbh the DB object reference + * @param resource $result the result resource id + * @param array $options an associative array with result options + * + * @return void + */ + function DB_result(&$dbh, $result, $options = array()) + { + $this->autofree = $dbh->options['autofree']; + $this->dbh = &$dbh; + $this->fetchmode = $dbh->fetchmode; + $this->fetchmode_object_class = $dbh->fetchmode_object_class; + $this->parameters = $dbh->last_parameters; + $this->query = $dbh->last_query; + $this->result = $result; + $this->statement = empty($dbh->last_stmt) ? null : $dbh->last_stmt; + foreach ($options as $key => $value) { + $this->setOption($key, $value); + } + } + + /** + * Set options for the DB_result object + * + * @param string $key the option to set + * @param mixed $value the value to set the option to + * + * @return void + */ + function setOption($key, $value = null) + { + switch ($key) { + case 'limit_from': + $this->limit_from = $value; + break; + case 'limit_count': + $this->limit_count = $value; + } + } + + // }}} + // {{{ fetchRow() + + /** + * Fetch a row of data and return it by reference into an array + * + * The type of array returned can be controlled either by setting this + * method's $fetchmode parameter or by changing the default + * fetch mode setFetchMode() before calling this method. + * + * There are two options for standardizing the information returned + * from databases, ensuring their values are consistent when changing + * DBMS's. These portability options can be turned on when creating a + * new DB object or by using setOption(). + * + * + DB_PORTABILITY_LOWERCASE + * convert names of fields to lower case + * + * + DB_PORTABILITY_RTRIM + * right trim the data + * + * @param int $fetchmode the constant indicating how to format the data + * @param int $rownum the row number to fetch (index starts at 0) + * + * @return mixed an array or object containing the row's data, + * NULL when the end of the result set is reached + * or a DB_Error object on failure. + * + * @see DB_common::setOption(), DB_common::setFetchMode() + */ + function &fetchRow($fetchmode = DB_FETCHMODE_DEFAULT, $rownum = null) + { + if ($fetchmode === DB_FETCHMODE_DEFAULT) { + $fetchmode = $this->fetchmode; + } + if ($fetchmode === DB_FETCHMODE_OBJECT) { + $fetchmode = DB_FETCHMODE_ASSOC; + $object_class = $this->fetchmode_object_class; + } + if (is_null($rownum) && $this->limit_from !== null) { + if ($this->row_counter === null) { + $this->row_counter = $this->limit_from; + // Skip rows + if ($this->dbh->features['limit'] === false) { + $i = 0; + while ($i++ < $this->limit_from) { + $this->dbh->fetchInto($this->result, $arr, $fetchmode); + } + } + } + if ($this->row_counter >= ($this->limit_from + $this->limit_count)) + { + if ($this->autofree) { + $this->free(); + } + $tmp = null; + return $tmp; + } + if ($this->dbh->features['limit'] === 'emulate') { + $rownum = $this->row_counter; + } + $this->row_counter++; + } + $res = $this->dbh->fetchInto($this->result, $arr, $fetchmode, $rownum); + if ($res === DB_OK) { + if (isset($object_class)) { + // The default mode is specified in the + // DB_common::fetchmode_object_class property + if ($object_class == 'stdClass') { + $arr = (object) $arr; + } else { + $arr = new $object_class($arr); + } + } + return $arr; + } + if ($res == null && $this->autofree) { + $this->free(); + } + return $res; + } + + // }}} + // {{{ fetchInto() + + /** + * Fetch a row of data into an array which is passed by reference + * + * The type of array returned can be controlled either by setting this + * method's $fetchmode parameter or by changing the default + * fetch mode setFetchMode() before calling this method. + * + * There are two options for standardizing the information returned + * from databases, ensuring their values are consistent when changing + * DBMS's. These portability options can be turned on when creating a + * new DB object or by using setOption(). + * + * + DB_PORTABILITY_LOWERCASE + * convert names of fields to lower case + * + * + DB_PORTABILITY_RTRIM + * right trim the data + * + * @param array &$arr the variable where the data should be placed + * @param int $fetchmode the constant indicating how to format the data + * @param int $rownum the row number to fetch (index starts at 0) + * + * @return mixed DB_OK if a row is processed, NULL when the end of the + * result set is reached or a DB_Error object on failure + * + * @see DB_common::setOption(), DB_common::setFetchMode() + */ + function fetchInto(&$arr, $fetchmode = DB_FETCHMODE_DEFAULT, $rownum = null) + { + if ($fetchmode === DB_FETCHMODE_DEFAULT) { + $fetchmode = $this->fetchmode; + } + if ($fetchmode === DB_FETCHMODE_OBJECT) { + $fetchmode = DB_FETCHMODE_ASSOC; + $object_class = $this->fetchmode_object_class; + } + if (is_null($rownum) && $this->limit_from !== null) { + if ($this->row_counter === null) { + $this->row_counter = $this->limit_from; + // Skip rows + if ($this->dbh->features['limit'] === false) { + $i = 0; + while ($i++ < $this->limit_from) { + $this->dbh->fetchInto($this->result, $arr, $fetchmode); + } + } + } + if ($this->row_counter >= ( + $this->limit_from + $this->limit_count)) + { + if ($this->autofree) { + $this->free(); + } + return null; + } + if ($this->dbh->features['limit'] === 'emulate') { + $rownum = $this->row_counter; + } + + $this->row_counter++; + } + $res = $this->dbh->fetchInto($this->result, $arr, $fetchmode, $rownum); + if ($res === DB_OK) { + if (isset($object_class)) { + // default mode specified in the + // DB_common::fetchmode_object_class property + if ($object_class == 'stdClass') { + $arr = (object) $arr; + } else { + $arr = new $object_class($arr); + } + } + return DB_OK; + } + if ($res == null && $this->autofree) { + $this->free(); + } + return $res; + } + + // }}} + // {{{ numCols() + + /** + * Get the the number of columns in a result set + * + * @return int the number of columns. A DB_Error object on failure. + */ + function numCols() + { + return $this->dbh->numCols($this->result); + } + + // }}} + // {{{ numRows() + + /** + * Get the number of rows in a result set + * + * @return int the number of rows. A DB_Error object on failure. + */ + function numRows() + { + if ($this->dbh->features['numrows'] === 'emulate' + && $this->dbh->options['portability'] & DB_PORTABILITY_NUMROWS) + { + if ($this->dbh->features['prepare']) { + $res = $this->dbh->query($this->query, $this->parameters); + } else { + $res = $this->dbh->query($this->query); + } + if (DB::isError($res)) { + return $res; + } + $i = 0; + while ($res->fetchInto($tmp, DB_FETCHMODE_ORDERED)) { + $i++; + } + $count = $i; + } else { + $count = $this->dbh->numRows($this->result); + } + + /* fbsql is checked for here because limit queries are implemented + * using a TOP() function, which results in fbsql_num_rows still + * returning the total number of rows that would have been returned, + * rather than the real number. As a result, we'll just do the limit + * calculations for fbsql in the same way as a database with emulated + * limits. Unfortunately, we can't just do this in DB_fbsql::numRows() + * because that only gets the result resource, rather than the full + * DB_Result object. */ + if (($this->dbh->features['limit'] === 'emulate' + && $this->limit_from !== null) + || $this->dbh->phptype == 'fbsql') { + $limit_count = is_null($this->limit_count) ? $count : $this->limit_count; + if ($count < $this->limit_from) { + $count = 0; + } elseif ($count < ($this->limit_from + $limit_count)) { + $count -= $this->limit_from; + } else { + $count = $limit_count; + } + } + + return $count; + } + + // }}} + // {{{ nextResult() + + /** + * Get the next result if a batch of queries was executed + * + * @return bool true if a new result is available or false if not + */ + function nextResult() + { + return $this->dbh->nextResult($this->result); + } + + // }}} + // {{{ free() + + /** + * Frees the resources allocated for this result set + * + * @return bool true on success. A DB_Error object on failure. + */ + function free() + { + $err = $this->dbh->freeResult($this->result); + if (DB::isError($err)) { + return $err; + } + $this->result = false; + $this->statement = false; + return true; + } + + // }}} + // {{{ tableInfo() + + /** + * @see DB_common::tableInfo() + * @deprecated Method deprecated some time before Release 1.2 + */ + function tableInfo($mode = null) + { + if (is_string($mode)) { + return $this->dbh->raiseError(DB_ERROR_NEED_MORE_DATA); + } + return $this->dbh->tableInfo($this, $mode); + } + + // }}} + // {{{ getQuery() + + /** + * Determine the query string that created this result + * + * @return string the query string + * + * @since Method available since Release 1.7.0 + */ + function getQuery() + { + return $this->query; + } + + // }}} + // {{{ getRowCounter() + + /** + * Tells which row number is currently being processed + * + * @return integer the current row being looked at. Starts at 1. + */ + function getRowCounter() + { + return $this->row_counter; + } + + // }}} +} + +// }}} +// {{{ class DB_row + +/** + * PEAR DB Row Object + * + * The object contains a row of data from a result set. Each column's data + * is placed in a property named for the column. + * + * @category Database + * @package DB + * @author Stig Bakken + * @copyright 1997-2007 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version Release: 1.7.13 + * @link http://pear.php.net/package/DB + * @see DB_common::setFetchMode() + */ +class DB_row +{ + // {{{ constructor + + /** + * The constructor places a row's data into properties of this object + * + * @param array the array containing the row's data + * + * @return void + */ + function DB_row(&$arr) + { + foreach ($arr as $key => $value) { + $this->$key = &$arr[$key]; + } + } + + // }}} +} + +// }}} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ + +?> diff --git a/library/pear/DB/common.php b/library/pear/DB/common.php new file mode 100644 index 000000000..8d403126f --- /dev/null +++ b/library/pear/DB/common.php @@ -0,0 +1,2257 @@ + + * @author Tomas V.V. Cox + * @author Daniel Convissor + * @copyright 1997-2007 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version CVS: $Id: common.php,v 1.143 2007/09/21 13:40:41 aharvey Exp $ + * @link http://pear.php.net/package/DB + */ + +/** + * Obtain the PEAR class so it can be extended from + */ +require_once 'PEAR.php'; + +/** + * DB_common is the base class from which each database driver class extends + * + * All common methods are declared here. If a given DBMS driver contains + * a particular method, that method will overload the one here. + * + * @category Database + * @package DB + * @author Stig Bakken + * @author Tomas V.V. Cox + * @author Daniel Convissor + * @copyright 1997-2007 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version Release: 1.7.13 + * @link http://pear.php.net/package/DB + */ +class DB_common extends PEAR +{ + // {{{ properties + + /** + * The current default fetch mode + * @var integer + */ + var $fetchmode = DB_FETCHMODE_ORDERED; + + /** + * The name of the class into which results should be fetched when + * DB_FETCHMODE_OBJECT is in effect + * + * @var string + */ + var $fetchmode_object_class = 'stdClass'; + + /** + * Was a connection present when the object was serialized()? + * @var bool + * @see DB_common::__sleep(), DB_common::__wake() + */ + var $was_connected = null; + + /** + * The most recently executed query + * @var string + */ + var $last_query = ''; + + /** + * Run-time configuration options + * + * The 'optimize' option has been deprecated. Use the 'portability' + * option instead. + * + * @var array + * @see DB_common::setOption() + */ + var $options = array( + 'result_buffering' => 500, + 'persistent' => false, + 'ssl' => false, + 'debug' => 0, + 'seqname_format' => '%s_seq', + 'autofree' => false, + 'portability' => DB_PORTABILITY_NONE, + 'optimize' => 'performance', // Deprecated. Use 'portability'. + ); + + /** + * The parameters from the most recently executed query + * @var array + * @since Property available since Release 1.7.0 + */ + var $last_parameters = array(); + + /** + * The elements from each prepared statement + * @var array + */ + var $prepare_tokens = array(); + + /** + * The data types of the various elements in each prepared statement + * @var array + */ + var $prepare_types = array(); + + /** + * The prepared queries + * @var array + */ + var $prepared_queries = array(); + + /** + * Flag indicating that the last query was a manipulation query. + * @access protected + * @var boolean + */ + var $_last_query_manip = false; + + /** + * Flag indicating that the next query must be a manipulation + * query. + * @access protected + * @var boolean + */ + var $_next_query_manip = false; + + + // }}} + // {{{ DB_common + + /** + * This constructor calls $this->PEAR('DB_Error') + * + * @return void + */ + function DB_common() + { + $this->PEAR('DB_Error'); + } + + // }}} + // {{{ __sleep() + + /** + * Automatically indicates which properties should be saved + * when PHP's serialize() function is called + * + * @return array the array of properties names that should be saved + */ + function __sleep() + { + if ($this->connection) { + // Don't disconnect(), people use serialize() for many reasons + $this->was_connected = true; + } else { + $this->was_connected = false; + } + if (isset($this->autocommit)) { + return array('autocommit', + 'dbsyntax', + 'dsn', + 'features', + 'fetchmode', + 'fetchmode_object_class', + 'options', + 'was_connected', + ); + } else { + return array('dbsyntax', + 'dsn', + 'features', + 'fetchmode', + 'fetchmode_object_class', + 'options', + 'was_connected', + ); + } + } + + // }}} + // {{{ __wakeup() + + /** + * Automatically reconnects to the database when PHP's unserialize() + * function is called + * + * The reconnection attempt is only performed if the object was connected + * at the time PHP's serialize() function was run. + * + * @return void + */ + function __wakeup() + { + if ($this->was_connected) { + $this->connect($this->dsn, $this->options); + } + } + + // }}} + // {{{ __toString() + + /** + * Automatic string conversion for PHP 5 + * + * @return string a string describing the current PEAR DB object + * + * @since Method available since Release 1.7.0 + */ + function __toString() + { + $info = strtolower(get_class($this)); + $info .= ': (phptype=' . $this->phptype . + ', dbsyntax=' . $this->dbsyntax . + ')'; + if ($this->connection) { + $info .= ' [connected]'; + } + return $info; + } + + // }}} + // {{{ toString() + + /** + * DEPRECATED: String conversion method + * + * @return string a string describing the current PEAR DB object + * + * @deprecated Method deprecated in Release 1.7.0 + */ + function toString() + { + return $this->__toString(); + } + + // }}} + // {{{ quoteString() + + /** + * DEPRECATED: Quotes a string so it can be safely used within string + * delimiters in a query + * + * @param string $string the string to be quoted + * + * @return string the quoted string + * + * @see DB_common::quoteSmart(), DB_common::escapeSimple() + * @deprecated Method deprecated some time before Release 1.2 + */ + function quoteString($string) + { + $string = $this->quote($string); + if ($string{0} == "'") { + return substr($string, 1, -1); + } + return $string; + } + + // }}} + // {{{ quote() + + /** + * DEPRECATED: Quotes a string so it can be safely used in a query + * + * @param string $string the string to quote + * + * @return string the quoted string or the string NULL + * if the value submitted is null. + * + * @see DB_common::quoteSmart(), DB_common::escapeSimple() + * @deprecated Deprecated in release 1.6.0 + */ + function quote($string = null) + { + return ($string === null) ? 'NULL' + : "'" . str_replace("'", "''", $string) . "'"; + } + + // }}} + // {{{ quoteIdentifier() + + /** + * Quotes a string so it can be safely used as a table or column name + * + * Delimiting style depends on which database driver is being used. + * + * NOTE: just because you CAN use delimited identifiers doesn't mean + * you SHOULD use them. In general, they end up causing way more + * problems than they solve. + * + * Portability is broken by using the following characters inside + * delimited identifiers: + * + backtick (`) -- due to MySQL + * + double quote (") -- due to Oracle + * + brackets ([ or ]) -- due to Access + * + * Delimited identifiers are known to generally work correctly under + * the following drivers: + * + mssql + * + mysql + * + mysqli + * + oci8 + * + odbc(access) + * + odbc(db2) + * + pgsql + * + sqlite + * + sybase (must execute set quoted_identifier on sometime + * prior to use) + * + * InterBase doesn't seem to be able to use delimited identifiers + * via PHP 4. They work fine under PHP 5. + * + * @param string $str the identifier name to be quoted + * + * @return string the quoted identifier + * + * @since Method available since Release 1.6.0 + */ + function quoteIdentifier($str) + { + return '"' . str_replace('"', '""', $str) . '"'; + } + + // }}} + // {{{ quoteSmart() + + /** + * Formats input so it can be safely used in a query + * + * The output depends on the PHP data type of input and the database + * type being used. + * + * @param mixed $in the data to be formatted + * + * @return mixed the formatted data. The format depends on the input's + * PHP type: + *
      + *
    • + * input -> returns + *
    • + *
    • + * null -> the string NULL + *
    • + *
    • + * integer or double -> the unquoted number + *
    • + *
    • + * bool -> output depends on the driver in use + * Most drivers return integers: 1 if + * true or 0 if + * false. + * Some return strings: TRUE if + * true or FALSE if + * false. + * Finally one returns strings: T if + * true or F if + * false. Here is a list of each DBMS, + * the values returned and the suggested column type: + *
        + *
      • + * dbase -> T/F + * (Logical) + *
      • + *
      • + * fbase -> TRUE/FALSE + * (BOOLEAN) + *
      • + *
      • + * ibase -> 1/0 + * (SMALLINT) [1] + *
      • + *
      • + * ifx -> 1/0 + * (SMALLINT) [1] + *
      • + *
      • + * msql -> 1/0 + * (INTEGER) + *
      • + *
      • + * mssql -> 1/0 + * (BIT) + *
      • + *
      • + * mysql -> 1/0 + * (TINYINT(1)) + *
      • + *
      • + * mysqli -> 1/0 + * (TINYINT(1)) + *
      • + *
      • + * oci8 -> 1/0 + * (NUMBER(1)) + *
      • + *
      • + * odbc -> 1/0 + * (SMALLINT) [1] + *
      • + *
      • + * pgsql -> TRUE/FALSE + * (BOOLEAN) + *
      • + *
      • + * sqlite -> 1/0 + * (INTEGER) + *
      • + *
      • + * sybase -> 1/0 + * (TINYINT(1)) + *
      • + *
      + * [1] Accommodate the lowest common denominator because not all + * versions of have BOOLEAN. + *
    • + *
    • + * other (including strings and numeric strings) -> + * the data with single quotes escaped by preceeding + * single quotes, backslashes are escaped by preceeding + * backslashes, then the whole string is encapsulated + * between single quotes + *
    • + *
    + * + * @see DB_common::escapeSimple() + * @since Method available since Release 1.6.0 + */ + function quoteSmart($in) + { + if (is_int($in)) { + return $in; + } elseif (is_float($in)) { + return $this->quoteFloat($in); + } elseif (is_bool($in)) { + return $this->quoteBoolean($in); + } elseif (is_null($in)) { + return 'NULL'; + } else { + if ($this->dbsyntax == 'access' + && preg_match('/^#.+#$/', $in)) + { + return $this->escapeSimple($in); + } + return "'" . $this->escapeSimple($in) . "'"; + } + } + + // }}} + // {{{ quoteBoolean() + + /** + * Formats a boolean value for use within a query in a locale-independent + * manner. + * + * @param boolean the boolean value to be quoted. + * @return string the quoted string. + * @see DB_common::quoteSmart() + * @since Method available since release 1.7.8. + */ + function quoteBoolean($boolean) { + return $boolean ? '1' : '0'; + } + + // }}} + // {{{ quoteFloat() + + /** + * Formats a float value for use within a query in a locale-independent + * manner. + * + * @param float the float value to be quoted. + * @return string the quoted string. + * @see DB_common::quoteSmart() + * @since Method available since release 1.7.8. + */ + function quoteFloat($float) { + return "'".$this->escapeSimple(str_replace(',', '.', strval(floatval($float))))."'"; + } + + // }}} + // {{{ escapeSimple() + + /** + * Escapes a string according to the current DBMS's standards + * + * In SQLite, this makes things safe for inserts/updates, but may + * cause problems when performing text comparisons against columns + * containing binary data. See the + * {@link http://php.net/sqlite_escape_string PHP manual} for more info. + * + * @param string $str the string to be escaped + * + * @return string the escaped string + * + * @see DB_common::quoteSmart() + * @since Method available since Release 1.6.0 + */ + function escapeSimple($str) + { + return str_replace("'", "''", $str); + } + + // }}} + // {{{ provides() + + /** + * Tells whether the present driver supports a given feature + * + * @param string $feature the feature you're curious about + * + * @return bool whether this driver supports $feature + */ + function provides($feature) + { + return $this->features[$feature]; + } + + // }}} + // {{{ setFetchMode() + + /** + * Sets the fetch mode that should be used by default for query results + * + * @param integer $fetchmode DB_FETCHMODE_ORDERED, DB_FETCHMODE_ASSOC + * or DB_FETCHMODE_OBJECT + * @param string $object_class the class name of the object to be returned + * by the fetch methods when the + * DB_FETCHMODE_OBJECT mode is selected. + * If no class is specified by default a cast + * to object from the assoc array row will be + * done. There is also the posibility to use + * and extend the 'DB_row' class. + * + * @see DB_FETCHMODE_ORDERED, DB_FETCHMODE_ASSOC, DB_FETCHMODE_OBJECT + */ + function setFetchMode($fetchmode, $object_class = 'stdClass') + { + switch ($fetchmode) { + case DB_FETCHMODE_OBJECT: + $this->fetchmode_object_class = $object_class; + case DB_FETCHMODE_ORDERED: + case DB_FETCHMODE_ASSOC: + $this->fetchmode = $fetchmode; + break; + default: + return $this->raiseError('invalid fetchmode mode'); + } + } + + // }}} + // {{{ setOption() + + /** + * Sets run-time configuration options for PEAR DB + * + * Options, their data types, default values and description: + *
      + *
    • + * autofree boolean = false + *
      should results be freed automatically when there are no + * more rows? + *
    • + * result_buffering integer = 500 + *
      how many rows of the result set should be buffered? + *
      In mysql: mysql_unbuffered_query() is used instead of + * mysql_query() if this value is 0. (Release 1.7.0) + *
      In oci8: this value is passed to ocisetprefetch(). + * (Release 1.7.0) + *
    • + * debug integer = 0 + *
      debug level + *
    • + * persistent boolean = false + *
      should the connection be persistent? + *
    • + * portability integer = DB_PORTABILITY_NONE + *
      portability mode constant (see below) + *
    • + * seqname_format string = %s_seq + *
      the sprintf() format string used on sequence names. This + * format is applied to sequence names passed to + * createSequence(), nextID() and dropSequence(). + *
    • + * ssl boolean = false + *
      use ssl to connect? + *
    • + *
    + * + * ----------------------------------------- + * + * PORTABILITY MODES + * + * These modes are bitwised, so they can be combined using | + * and removed using ^. See the examples section below on how + * to do this. + * + * DB_PORTABILITY_NONE + * turn off all portability features + * + * This mode gets automatically turned on if the deprecated + * optimize option gets set to performance. + * + * + * DB_PORTABILITY_LOWERCASE + * convert names of tables and fields to lower case when using + * get*(), fetch*() and tableInfo() + * + * This mode gets automatically turned on in the following databases + * if the deprecated option optimize gets set to + * portability: + * + oci8 + * + * + * DB_PORTABILITY_RTRIM + * right trim the data output by get*() fetch*() + * + * + * DB_PORTABILITY_DELETE_COUNT + * force reporting the number of rows deleted + * + * Some DBMS's don't count the number of rows deleted when performing + * simple DELETE FROM tablename queries. This portability + * mode tricks such DBMS's into telling the count by adding + * WHERE 1=1 to the end of DELETE queries. + * + * This mode gets automatically turned on in the following databases + * if the deprecated option optimize gets set to + * portability: + * + fbsql + * + mysql + * + mysqli + * + sqlite + * + * + * DB_PORTABILITY_NUMROWS + * enable hack that makes numRows() work in Oracle + * + * This mode gets automatically turned on in the following databases + * if the deprecated option optimize gets set to + * portability: + * + oci8 + * + * + * DB_PORTABILITY_ERRORS + * makes certain error messages in certain drivers compatible + * with those from other DBMS's + * + * + mysql, mysqli: change unique/primary key constraints + * DB_ERROR_ALREADY_EXISTS -> DB_ERROR_CONSTRAINT + * + * + odbc(access): MS's ODBC driver reports 'no such field' as code + * 07001, which means 'too few parameters.' When this option is on + * that code gets mapped to DB_ERROR_NOSUCHFIELD. + * DB_ERROR_MISMATCH -> DB_ERROR_NOSUCHFIELD + * + * DB_PORTABILITY_NULL_TO_EMPTY + * convert null values to empty strings in data output by get*() and + * fetch*(). Needed because Oracle considers empty strings to be null, + * while most other DBMS's know the difference between empty and null. + * + * + * DB_PORTABILITY_ALL + * turn on all portability features + * + * ----------------------------------------- + * + * Example 1. Simple setOption() example + * + * $db->setOption('autofree', true); + * + * + * Example 2. Portability for lowercasing and trimming + * + * $db->setOption('portability', + * DB_PORTABILITY_LOWERCASE | DB_PORTABILITY_RTRIM); + * + * + * Example 3. All portability options except trimming + * + * $db->setOption('portability', + * DB_PORTABILITY_ALL ^ DB_PORTABILITY_RTRIM); + * + * + * @param string $option option name + * @param mixed $value value for the option + * + * @return int DB_OK on success. A DB_Error object on failure. + * + * @see DB_common::$options + */ + function setOption($option, $value) + { + if (isset($this->options[$option])) { + $this->options[$option] = $value; + + /* + * Backwards compatibility check for the deprecated 'optimize' + * option. Done here in case settings change after connecting. + */ + if ($option == 'optimize') { + if ($value == 'portability') { + switch ($this->phptype) { + case 'oci8': + $this->options['portability'] = + DB_PORTABILITY_LOWERCASE | + DB_PORTABILITY_NUMROWS; + break; + case 'fbsql': + case 'mysql': + case 'mysqli': + case 'sqlite': + $this->options['portability'] = + DB_PORTABILITY_DELETE_COUNT; + break; + } + } else { + $this->options['portability'] = DB_PORTABILITY_NONE; + } + } + + return DB_OK; + } + return $this->raiseError("unknown option $option"); + } + + // }}} + // {{{ getOption() + + /** + * Returns the value of an option + * + * @param string $option the option name you're curious about + * + * @return mixed the option's value + */ + function getOption($option) + { + if (isset($this->options[$option])) { + return $this->options[$option]; + } + return $this->raiseError("unknown option $option"); + } + + // }}} + // {{{ prepare() + + /** + * Prepares a query for multiple execution with execute() + * + * Creates a query that can be run multiple times. Each time it is run, + * the placeholders, if any, will be replaced by the contents of + * execute()'s $data argument. + * + * Three types of placeholders can be used: + * + ? scalar value (i.e. strings, integers). The system + * will automatically quote and escape the data. + * + ! value is inserted 'as is' + * + & requires a file name. The file's contents get + * inserted into the query (i.e. saving binary + * data in a db) + * + * Example 1. + * + * $sth = $db->prepare('INSERT INTO tbl (a, b, c) VALUES (?, !, &)'); + * $data = array( + * "John's text", + * "'it''s good'", + * 'filename.txt' + * ); + * $res = $db->execute($sth, $data); + * + * + * Use backslashes to escape placeholder characters if you don't want + * them to be interpreted as placeholders: + *
    +     *    "UPDATE foo SET col=? WHERE col='over \& under'"
    +     * 
    + * + * With some database backends, this is emulated. + * + * {@internal ibase and oci8 have their own prepare() methods.}} + * + * @param string $query the query to be prepared + * + * @return mixed DB statement resource on success. A DB_Error object + * on failure. + * + * @see DB_common::execute() + */ + function prepare($query) + { + $tokens = preg_split('/((?prepare_tokens[] = &$newtokens; + end($this->prepare_tokens); + + $k = key($this->prepare_tokens); + $this->prepare_types[$k] = $types; + $this->prepared_queries[$k] = implode(' ', $newtokens); + + return $k; + } + + // }}} + // {{{ autoPrepare() + + /** + * Automaticaly generates an insert or update query and pass it to prepare() + * + * @param string $table the table name + * @param array $table_fields the array of field names + * @param int $mode a type of query to make: + * DB_AUTOQUERY_INSERT or DB_AUTOQUERY_UPDATE + * @param string $where for update queries: the WHERE clause to + * append to the SQL statement. Don't + * include the "WHERE" keyword. + * + * @return resource the query handle + * + * @uses DB_common::prepare(), DB_common::buildManipSQL() + */ + function autoPrepare($table, $table_fields, $mode = DB_AUTOQUERY_INSERT, + $where = false) + { + $query = $this->buildManipSQL($table, $table_fields, $mode, $where); + if (DB::isError($query)) { + return $query; + } + return $this->prepare($query); + } + + // }}} + // {{{ autoExecute() + + /** + * Automaticaly generates an insert or update query and call prepare() + * and execute() with it + * + * @param string $table the table name + * @param array $fields_values the associative array where $key is a + * field name and $value its value + * @param int $mode a type of query to make: + * DB_AUTOQUERY_INSERT or DB_AUTOQUERY_UPDATE + * @param string $where for update queries: the WHERE clause to + * append to the SQL statement. Don't + * include the "WHERE" keyword. + * + * @return mixed a new DB_result object for successful SELECT queries + * or DB_OK for successul data manipulation queries. + * A DB_Error object on failure. + * + * @uses DB_common::autoPrepare(), DB_common::execute() + */ + function autoExecute($table, $fields_values, $mode = DB_AUTOQUERY_INSERT, + $where = false) + { + $sth = $this->autoPrepare($table, array_keys($fields_values), $mode, + $where); + if (DB::isError($sth)) { + return $sth; + } + $ret = $this->execute($sth, array_values($fields_values)); + $this->freePrepared($sth); + return $ret; + + } + + // }}} + // {{{ buildManipSQL() + + /** + * Produces an SQL query string for autoPrepare() + * + * Example: + *
    +     * buildManipSQL('table_sql', array('field1', 'field2', 'field3'),
    +     *               DB_AUTOQUERY_INSERT);
    +     * 
    + * + * That returns + * + * INSERT INTO table_sql (field1,field2,field3) VALUES (?,?,?) + * + * + * NOTES: + * - This belongs more to a SQL Builder class, but this is a simple + * facility. + * - Be carefull! If you don't give a $where param with an UPDATE + * query, all the records of the table will be updated! + * + * @param string $table the table name + * @param array $table_fields the array of field names + * @param int $mode a type of query to make: + * DB_AUTOQUERY_INSERT or DB_AUTOQUERY_UPDATE + * @param string $where for update queries: the WHERE clause to + * append to the SQL statement. Don't + * include the "WHERE" keyword. + * + * @return string the sql query for autoPrepare() + */ + function buildManipSQL($table, $table_fields, $mode, $where = false) + { + if (count($table_fields) == 0) { + return $this->raiseError(DB_ERROR_NEED_MORE_DATA); + } + $first = true; + switch ($mode) { + case DB_AUTOQUERY_INSERT: + $values = ''; + $names = ''; + foreach ($table_fields as $value) { + if ($first) { + $first = false; + } else { + $names .= ','; + $values .= ','; + } + $names .= $value; + $values .= '?'; + } + return "INSERT INTO $table ($names) VALUES ($values)"; + case DB_AUTOQUERY_UPDATE: + $set = ''; + foreach ($table_fields as $value) { + if ($first) { + $first = false; + } else { + $set .= ','; + } + $set .= "$value = ?"; + } + $sql = "UPDATE $table SET $set"; + if ($where) { + $sql .= " WHERE $where"; + } + return $sql; + default: + return $this->raiseError(DB_ERROR_SYNTAX); + } + } + + // }}} + // {{{ execute() + + /** + * Executes a DB statement prepared with prepare() + * + * Example 1. + * + * $sth = $db->prepare('INSERT INTO tbl (a, b, c) VALUES (?, !, &)'); + * $data = array( + * "John's text", + * "'it''s good'", + * 'filename.txt' + * ); + * $res = $db->execute($sth, $data); + * + * + * @param resource $stmt a DB statement resource returned from prepare() + * @param mixed $data array, string or numeric data to be used in + * execution of the statement. Quantity of items + * passed must match quantity of placeholders in + * query: meaning 1 placeholder for non-array + * parameters or 1 placeholder per array element. + * + * @return mixed a new DB_result object for successful SELECT queries + * or DB_OK for successul data manipulation queries. + * A DB_Error object on failure. + * + * {@internal ibase and oci8 have their own execute() methods.}} + * + * @see DB_common::prepare() + */ + function &execute($stmt, $data = array()) + { + $realquery = $this->executeEmulateQuery($stmt, $data); + if (DB::isError($realquery)) { + return $realquery; + } + $result = $this->simpleQuery($realquery); + + if ($result === DB_OK || DB::isError($result)) { + return $result; + } else { + $tmp = new DB_result($this, $result); + return $tmp; + } + } + + // }}} + // {{{ executeEmulateQuery() + + /** + * Emulates executing prepared statements if the DBMS not support them + * + * @param resource $stmt a DB statement resource returned from execute() + * @param mixed $data array, string or numeric data to be used in + * execution of the statement. Quantity of items + * passed must match quantity of placeholders in + * query: meaning 1 placeholder for non-array + * parameters or 1 placeholder per array element. + * + * @return mixed a string containing the real query run when emulating + * prepare/execute. A DB_Error object on failure. + * + * @access protected + * @see DB_common::execute() + */ + function executeEmulateQuery($stmt, $data = array()) + { + $stmt = (int)$stmt; + $data = (array)$data; + $this->last_parameters = $data; + + if (count($this->prepare_types[$stmt]) != count($data)) { + $this->last_query = $this->prepared_queries[$stmt]; + return $this->raiseError(DB_ERROR_MISMATCH); + } + + $realquery = $this->prepare_tokens[$stmt][0]; + + $i = 0; + foreach ($data as $value) { + if ($this->prepare_types[$stmt][$i] == DB_PARAM_SCALAR) { + $realquery .= $this->quoteSmart($value); + } elseif ($this->prepare_types[$stmt][$i] == DB_PARAM_OPAQUE) { + $fp = @fopen($value, 'rb'); + if (!$fp) { + return $this->raiseError(DB_ERROR_ACCESS_VIOLATION); + } + $realquery .= $this->quoteSmart(fread($fp, filesize($value))); + fclose($fp); + } else { + $realquery .= $value; + } + + $realquery .= $this->prepare_tokens[$stmt][++$i]; + } + + return $realquery; + } + + // }}} + // {{{ executeMultiple() + + /** + * Performs several execute() calls on the same statement handle + * + * $data must be an array indexed numerically + * from 0, one execute call is done for every "row" in the array. + * + * If an error occurs during execute(), executeMultiple() does not + * execute the unfinished rows, but rather returns that error. + * + * @param resource $stmt query handle from prepare() + * @param array $data numeric array containing the + * data to insert into the query + * + * @return int DB_OK on success. A DB_Error object on failure. + * + * @see DB_common::prepare(), DB_common::execute() + */ + function executeMultiple($stmt, $data) + { + foreach ($data as $value) { + $res = $this->execute($stmt, $value); + if (DB::isError($res)) { + return $res; + } + } + return DB_OK; + } + + // }}} + // {{{ freePrepared() + + /** + * Frees the internal resources associated with a prepared query + * + * @param resource $stmt the prepared statement's PHP resource + * @param bool $free_resource should the PHP resource be freed too? + * Use false if you need to get data + * from the result set later. + * + * @return bool TRUE on success, FALSE if $result is invalid + * + * @see DB_common::prepare() + */ + function freePrepared($stmt, $free_resource = true) + { + $stmt = (int)$stmt; + if (isset($this->prepare_tokens[$stmt])) { + unset($this->prepare_tokens[$stmt]); + unset($this->prepare_types[$stmt]); + unset($this->prepared_queries[$stmt]); + return true; + } + return false; + } + + // }}} + // {{{ modifyQuery() + + /** + * Changes a query string for various DBMS specific reasons + * + * It is defined here to ensure all drivers have this method available. + * + * @param string $query the query string to modify + * + * @return string the modified query string + * + * @access protected + * @see DB_mysql::modifyQuery(), DB_oci8::modifyQuery(), + * DB_sqlite::modifyQuery() + */ + function modifyQuery($query) + { + return $query; + } + + // }}} + // {{{ modifyLimitQuery() + + /** + * Adds LIMIT clauses to a query string according to current DBMS standards + * + * It is defined here to assure that all implementations + * have this method defined. + * + * @param string $query the query to modify + * @param int $from the row to start to fetching (0 = the first row) + * @param int $count the numbers of rows to fetch + * @param mixed $params array, string or numeric data to be used in + * execution of the statement. Quantity of items + * passed must match quantity of placeholders in + * query: meaning 1 placeholder for non-array + * parameters or 1 placeholder per array element. + * + * @return string the query string with LIMIT clauses added + * + * @access protected + */ + function modifyLimitQuery($query, $from, $count, $params = array()) + { + return $query; + } + + // }}} + // {{{ query() + + /** + * Sends a query to the database server + * + * The query string can be either a normal statement to be sent directly + * to the server OR if $params are passed the query can have + * placeholders and it will be passed through prepare() and execute(). + * + * @param string $query the SQL query or the statement to prepare + * @param mixed $params array, string or numeric data to be used in + * execution of the statement. Quantity of items + * passed must match quantity of placeholders in + * query: meaning 1 placeholder for non-array + * parameters or 1 placeholder per array element. + * + * @return mixed a new DB_result object for successful SELECT queries + * or DB_OK for successul data manipulation queries. + * A DB_Error object on failure. + * + * @see DB_result, DB_common::prepare(), DB_common::execute() + */ + function &query($query, $params = array()) + { + if (sizeof($params) > 0) { + $sth = $this->prepare($query); + if (DB::isError($sth)) { + return $sth; + } + $ret = $this->execute($sth, $params); + $this->freePrepared($sth, false); + return $ret; + } else { + $this->last_parameters = array(); + $result = $this->simpleQuery($query); + if ($result === DB_OK || DB::isError($result)) { + return $result; + } else { + $tmp = new DB_result($this, $result); + return $tmp; + } + } + } + + // }}} + // {{{ limitQuery() + + /** + * Generates and executes a LIMIT query + * + * @param string $query the query + * @param intr $from the row to start to fetching (0 = the first row) + * @param int $count the numbers of rows to fetch + * @param mixed $params array, string or numeric data to be used in + * execution of the statement. Quantity of items + * passed must match quantity of placeholders in + * query: meaning 1 placeholder for non-array + * parameters or 1 placeholder per array element. + * + * @return mixed a new DB_result object for successful SELECT queries + * or DB_OK for successul data manipulation queries. + * A DB_Error object on failure. + */ + function &limitQuery($query, $from, $count, $params = array()) + { + $query = $this->modifyLimitQuery($query, $from, $count, $params); + if (DB::isError($query)){ + return $query; + } + $result = $this->query($query, $params); + if (is_a($result, 'DB_result')) { + $result->setOption('limit_from', $from); + $result->setOption('limit_count', $count); + } + return $result; + } + + // }}} + // {{{ getOne() + + /** + * Fetches the first column of the first row from a query result + * + * Takes care of doing the query and freeing the results when finished. + * + * @param string $query the SQL query + * @param mixed $params array, string or numeric data to be used in + * execution of the statement. Quantity of items + * passed must match quantity of placeholders in + * query: meaning 1 placeholder for non-array + * parameters or 1 placeholder per array element. + * + * @return mixed the returned value of the query. + * A DB_Error object on failure. + */ + function &getOne($query, $params = array()) + { + $params = (array)$params; + // modifyLimitQuery() would be nice here, but it causes BC issues + if (sizeof($params) > 0) { + $sth = $this->prepare($query); + if (DB::isError($sth)) { + return $sth; + } + $res = $this->execute($sth, $params); + $this->freePrepared($sth); + } else { + $res = $this->query($query); + } + + if (DB::isError($res)) { + return $res; + } + + $err = $res->fetchInto($row, DB_FETCHMODE_ORDERED); + $res->free(); + + if ($err !== DB_OK) { + return $err; + } + + return $row[0]; + } + + // }}} + // {{{ getRow() + + /** + * Fetches the first row of data returned from a query result + * + * Takes care of doing the query and freeing the results when finished. + * + * @param string $query the SQL query + * @param mixed $params array, string or numeric data to be used in + * execution of the statement. Quantity of items + * passed must match quantity of placeholders in + * query: meaning 1 placeholder for non-array + * parameters or 1 placeholder per array element. + * @param int $fetchmode the fetch mode to use + * + * @return array the first row of results as an array. + * A DB_Error object on failure. + */ + function &getRow($query, $params = array(), + $fetchmode = DB_FETCHMODE_DEFAULT) + { + // compat check, the params and fetchmode parameters used to + // have the opposite order + if (!is_array($params)) { + if (is_array($fetchmode)) { + if ($params === null) { + $tmp = DB_FETCHMODE_DEFAULT; + } else { + $tmp = $params; + } + $params = $fetchmode; + $fetchmode = $tmp; + } elseif ($params !== null) { + $fetchmode = $params; + $params = array(); + } + } + // modifyLimitQuery() would be nice here, but it causes BC issues + if (sizeof($params) > 0) { + $sth = $this->prepare($query); + if (DB::isError($sth)) { + return $sth; + } + $res = $this->execute($sth, $params); + $this->freePrepared($sth); + } else { + $res = $this->query($query); + } + + if (DB::isError($res)) { + return $res; + } + + $err = $res->fetchInto($row, $fetchmode); + + $res->free(); + + if ($err !== DB_OK) { + return $err; + } + + return $row; + } + + // }}} + // {{{ getCol() + + /** + * Fetches a single column from a query result and returns it as an + * indexed array + * + * @param string $query the SQL query + * @param mixed $col which column to return (integer [column number, + * starting at 0] or string [column name]) + * @param mixed $params array, string or numeric data to be used in + * execution of the statement. Quantity of items + * passed must match quantity of placeholders in + * query: meaning 1 placeholder for non-array + * parameters or 1 placeholder per array element. + * + * @return array the results as an array. A DB_Error object on failure. + * + * @see DB_common::query() + */ + function &getCol($query, $col = 0, $params = array()) + { + $params = (array)$params; + if (sizeof($params) > 0) { + $sth = $this->prepare($query); + + if (DB::isError($sth)) { + return $sth; + } + + $res = $this->execute($sth, $params); + $this->freePrepared($sth); + } else { + $res = $this->query($query); + } + + if (DB::isError($res)) { + return $res; + } + + $fetchmode = is_int($col) ? DB_FETCHMODE_ORDERED : DB_FETCHMODE_ASSOC; + + if (!is_array($row = $res->fetchRow($fetchmode))) { + $ret = array(); + } else { + if (!array_key_exists($col, $row)) { + $ret = $this->raiseError(DB_ERROR_NOSUCHFIELD); + } else { + $ret = array($row[$col]); + while (is_array($row = $res->fetchRow($fetchmode))) { + $ret[] = $row[$col]; + } + } + } + + $res->free(); + + if (DB::isError($row)) { + $ret = $row; + } + + return $ret; + } + + // }}} + // {{{ getAssoc() + + /** + * Fetches an entire query result and returns it as an + * associative array using the first column as the key + * + * If the result set contains more than two columns, the value + * will be an array of the values from column 2-n. If the result + * set contains only two columns, the returned value will be a + * scalar with the value of the second column (unless forced to an + * array with the $force_array parameter). A DB error code is + * returned on errors. If the result set contains fewer than two + * columns, a DB_ERROR_TRUNCATED error is returned. + * + * For example, if the table "mytable" contains: + * + *
    +     *  ID      TEXT       DATE
    +     * --------------------------------
    +     *  1       'one'      944679408
    +     *  2       'two'      944679408
    +     *  3       'three'    944679408
    +     * 
    + * + * Then the call getAssoc('SELECT id,text FROM mytable') returns: + *
    +     *   array(
    +     *     '1' => 'one',
    +     *     '2' => 'two',
    +     *     '3' => 'three',
    +     *   )
    +     * 
    + * + * ...while the call getAssoc('SELECT id,text,date FROM mytable') returns: + *
    +     *   array(
    +     *     '1' => array('one', '944679408'),
    +     *     '2' => array('two', '944679408'),
    +     *     '3' => array('three', '944679408')
    +     *   )
    +     * 
    + * + * If the more than one row occurs with the same value in the + * first column, the last row overwrites all previous ones by + * default. Use the $group parameter if you don't want to + * overwrite like this. Example: + * + *
    +     * getAssoc('SELECT category,id,name FROM mytable', false, null,
    +     *          DB_FETCHMODE_ASSOC, true) returns:
    +     *
    +     *   array(
    +     *     '1' => array(array('id' => '4', 'name' => 'number four'),
    +     *                  array('id' => '6', 'name' => 'number six')
    +     *            ),
    +     *     '9' => array(array('id' => '4', 'name' => 'number four'),
    +     *                  array('id' => '6', 'name' => 'number six')
    +     *            )
    +     *   )
    +     * 
    + * + * Keep in mind that database functions in PHP usually return string + * values for results regardless of the database's internal type. + * + * @param string $query the SQL query + * @param bool $force_array used only when the query returns + * exactly two columns. If true, the values + * of the returned array will be one-element + * arrays instead of scalars. + * @param mixed $params array, string or numeric data to be used in + * execution of the statement. Quantity of + * items passed must match quantity of + * placeholders in query: meaning 1 + * placeholder for non-array parameters or + * 1 placeholder per array element. + * @param int $fetchmode the fetch mode to use + * @param bool $group if true, the values of the returned array + * is wrapped in another array. If the same + * key value (in the first column) repeats + * itself, the values will be appended to + * this array instead of overwriting the + * existing values. + * + * @return array the associative array containing the query results. + * A DB_Error object on failure. + */ + function &getAssoc($query, $force_array = false, $params = array(), + $fetchmode = DB_FETCHMODE_DEFAULT, $group = false) + { + $params = (array)$params; + if (sizeof($params) > 0) { + $sth = $this->prepare($query); + + if (DB::isError($sth)) { + return $sth; + } + + $res = $this->execute($sth, $params); + $this->freePrepared($sth); + } else { + $res = $this->query($query); + } + + if (DB::isError($res)) { + return $res; + } + if ($fetchmode == DB_FETCHMODE_DEFAULT) { + $fetchmode = $this->fetchmode; + } + $cols = $res->numCols(); + + if ($cols < 2) { + $tmp = $this->raiseError(DB_ERROR_TRUNCATED); + return $tmp; + } + + $results = array(); + + if ($cols > 2 || $force_array) { + // return array values + // XXX this part can be optimized + if ($fetchmode == DB_FETCHMODE_ASSOC) { + while (is_array($row = $res->fetchRow(DB_FETCHMODE_ASSOC))) { + reset($row); + $key = current($row); + unset($row[key($row)]); + if ($group) { + $results[$key][] = $row; + } else { + $results[$key] = $row; + } + } + } elseif ($fetchmode == DB_FETCHMODE_OBJECT) { + while ($row = $res->fetchRow(DB_FETCHMODE_OBJECT)) { + $arr = get_object_vars($row); + $key = current($arr); + if ($group) { + $results[$key][] = $row; + } else { + $results[$key] = $row; + } + } + } else { + while (is_array($row = $res->fetchRow(DB_FETCHMODE_ORDERED))) { + // we shift away the first element to get + // indices running from 0 again + $key = array_shift($row); + if ($group) { + $results[$key][] = $row; + } else { + $results[$key] = $row; + } + } + } + if (DB::isError($row)) { + $results = $row; + } + } else { + // return scalar values + while (is_array($row = $res->fetchRow(DB_FETCHMODE_ORDERED))) { + if ($group) { + $results[$row[0]][] = $row[1]; + } else { + $results[$row[0]] = $row[1]; + } + } + if (DB::isError($row)) { + $results = $row; + } + } + + $res->free(); + + return $results; + } + + // }}} + // {{{ getAll() + + /** + * Fetches all of the rows from a query result + * + * @param string $query the SQL query + * @param mixed $params array, string or numeric data to be used in + * execution of the statement. Quantity of + * items passed must match quantity of + * placeholders in query: meaning 1 + * placeholder for non-array parameters or + * 1 placeholder per array element. + * @param int $fetchmode the fetch mode to use: + * + DB_FETCHMODE_ORDERED + * + DB_FETCHMODE_ASSOC + * + DB_FETCHMODE_ORDERED | DB_FETCHMODE_FLIPPED + * + DB_FETCHMODE_ASSOC | DB_FETCHMODE_FLIPPED + * + * @return array the nested array. A DB_Error object on failure. + */ + function &getAll($query, $params = array(), + $fetchmode = DB_FETCHMODE_DEFAULT) + { + // compat check, the params and fetchmode parameters used to + // have the opposite order + if (!is_array($params)) { + if (is_array($fetchmode)) { + if ($params === null) { + $tmp = DB_FETCHMODE_DEFAULT; + } else { + $tmp = $params; + } + $params = $fetchmode; + $fetchmode = $tmp; + } elseif ($params !== null) { + $fetchmode = $params; + $params = array(); + } + } + + if (sizeof($params) > 0) { + $sth = $this->prepare($query); + + if (DB::isError($sth)) { + return $sth; + } + + $res = $this->execute($sth, $params); + $this->freePrepared($sth); + } else { + $res = $this->query($query); + } + + if ($res === DB_OK || DB::isError($res)) { + return $res; + } + + $results = array(); + while (DB_OK === $res->fetchInto($row, $fetchmode)) { + if ($fetchmode & DB_FETCHMODE_FLIPPED) { + foreach ($row as $key => $val) { + $results[$key][] = $val; + } + } else { + $results[] = $row; + } + } + + $res->free(); + + if (DB::isError($row)) { + $tmp = $this->raiseError($row); + return $tmp; + } + return $results; + } + + // }}} + // {{{ autoCommit() + + /** + * Enables or disables automatic commits + * + * @param bool $onoff true turns it on, false turns it off + * + * @return int DB_OK on success. A DB_Error object if the driver + * doesn't support auto-committing transactions. + */ + function autoCommit($onoff = false) + { + return $this->raiseError(DB_ERROR_NOT_CAPABLE); + } + + // }}} + // {{{ commit() + + /** + * Commits the current transaction + * + * @return int DB_OK on success. A DB_Error object on failure. + */ + function commit() + { + return $this->raiseError(DB_ERROR_NOT_CAPABLE); + } + + // }}} + // {{{ rollback() + + /** + * Reverts the current transaction + * + * @return int DB_OK on success. A DB_Error object on failure. + */ + function rollback() + { + return $this->raiseError(DB_ERROR_NOT_CAPABLE); + } + + // }}} + // {{{ numRows() + + /** + * Determines the number of rows in a query result + * + * @param resource $result the query result idenifier produced by PHP + * + * @return int the number of rows. A DB_Error object on failure. + */ + function numRows($result) + { + return $this->raiseError(DB_ERROR_NOT_CAPABLE); + } + + // }}} + // {{{ affectedRows() + + /** + * Determines the number of rows affected by a data maniuplation query + * + * 0 is returned for queries that don't manipulate data. + * + * @return int the number of rows. A DB_Error object on failure. + */ + function affectedRows() + { + return $this->raiseError(DB_ERROR_NOT_CAPABLE); + } + + // }}} + // {{{ getSequenceName() + + /** + * Generates the name used inside the database for a sequence + * + * The createSequence() docblock contains notes about storing sequence + * names. + * + * @param string $sqn the sequence's public name + * + * @return string the sequence's name in the backend + * + * @access protected + * @see DB_common::createSequence(), DB_common::dropSequence(), + * DB_common::nextID(), DB_common::setOption() + */ + function getSequenceName($sqn) + { + return sprintf($this->getOption('seqname_format'), + preg_replace('/[^a-z0-9_.]/i', '_', $sqn)); + } + + // }}} + // {{{ nextId() + + /** + * Returns the next free id in a sequence + * + * @param string $seq_name name of the sequence + * @param boolean $ondemand when true, the seqence is automatically + * created if it does not exist + * + * @return int the next id number in the sequence. + * A DB_Error object on failure. + * + * @see DB_common::createSequence(), DB_common::dropSequence(), + * DB_common::getSequenceName() + */ + function nextId($seq_name, $ondemand = true) + { + return $this->raiseError(DB_ERROR_NOT_CAPABLE); + } + + // }}} + // {{{ createSequence() + + /** + * Creates a new sequence + * + * The name of a given sequence is determined by passing the string + * provided in the $seq_name argument through PHP's sprintf() + * function using the value from the seqname_format option as + * the sprintf()'s format argument. + * + * seqname_format is set via setOption(). + * + * @param string $seq_name name of the new sequence + * + * @return int DB_OK on success. A DB_Error object on failure. + * + * @see DB_common::dropSequence(), DB_common::getSequenceName(), + * DB_common::nextID() + */ + function createSequence($seq_name) + { + return $this->raiseError(DB_ERROR_NOT_CAPABLE); + } + + // }}} + // {{{ dropSequence() + + /** + * Deletes a sequence + * + * @param string $seq_name name of the sequence to be deleted + * + * @return int DB_OK on success. A DB_Error object on failure. + * + * @see DB_common::createSequence(), DB_common::getSequenceName(), + * DB_common::nextID() + */ + function dropSequence($seq_name) + { + return $this->raiseError(DB_ERROR_NOT_CAPABLE); + } + + // }}} + // {{{ raiseError() + + /** + * Communicates an error and invoke error callbacks, etc + * + * Basically a wrapper for PEAR::raiseError without the message string. + * + * @param mixed integer error code, or a PEAR error object (all + * other parameters are ignored if this parameter is + * an object + * @param int error mode, see PEAR_Error docs + * @param mixed if error mode is PEAR_ERROR_TRIGGER, this is the + * error level (E_USER_NOTICE etc). If error mode is + * PEAR_ERROR_CALLBACK, this is the callback function, + * either as a function name, or as an array of an + * object and method name. For other error modes this + * parameter is ignored. + * @param string extra debug information. Defaults to the last + * query and native error code. + * @param mixed native error code, integer or string depending the + * backend + * + * @return object the PEAR_Error object + * + * @see PEAR_Error + */ + function &raiseError($code = DB_ERROR, $mode = null, $options = null, + $userinfo = null, $nativecode = null) + { + // The error is yet a DB error object + if (is_object($code)) { + // because we the static PEAR::raiseError, our global + // handler should be used if it is set + if ($mode === null && !empty($this->_default_error_mode)) { + $mode = $this->_default_error_mode; + $options = $this->_default_error_options; + } + $tmp = PEAR::raiseError($code, null, $mode, $options, + null, null, true); + return $tmp; + } + + if ($userinfo === null) { + $userinfo = $this->last_query; + } + + if ($nativecode) { + $userinfo .= ' [nativecode=' . trim($nativecode) . ']'; + } else { + $userinfo .= ' [DB Error: ' . DB::errorMessage($code) . ']'; + } + + $tmp = PEAR::raiseError(null, $code, $mode, $options, $userinfo, + 'DB_Error', true); + return $tmp; + } + + // }}} + // {{{ errorNative() + + /** + * Gets the DBMS' native error code produced by the last query + * + * @return mixed the DBMS' error code. A DB_Error object on failure. + */ + function errorNative() + { + return $this->raiseError(DB_ERROR_NOT_CAPABLE); + } + + // }}} + // {{{ errorCode() + + /** + * Maps native error codes to DB's portable ones + * + * Uses the $errorcode_map property defined in each driver. + * + * @param string|int $nativecode the error code returned by the DBMS + * + * @return int the portable DB error code. Return DB_ERROR if the + * current driver doesn't have a mapping for the + * $nativecode submitted. + */ + function errorCode($nativecode) + { + if (isset($this->errorcode_map[$nativecode])) { + return $this->errorcode_map[$nativecode]; + } + // Fall back to DB_ERROR if there was no mapping. + return DB_ERROR; + } + + // }}} + // {{{ errorMessage() + + /** + * Maps a DB error code to a textual message + * + * @param integer $dbcode the DB error code + * + * @return string the error message corresponding to the error code + * submitted. FALSE if the error code is unknown. + * + * @see DB::errorMessage() + */ + function errorMessage($dbcode) + { + return DB::errorMessage($this->errorcode_map[$dbcode]); + } + + // }}} + // {{{ tableInfo() + + /** + * Returns information about a table or a result set + * + * The format of the resulting array depends on which $mode + * you select. The sample output below is based on this query: + *
    +     *    SELECT tblFoo.fldID, tblFoo.fldPhone, tblBar.fldId
    +     *    FROM tblFoo
    +     *    JOIN tblBar ON tblFoo.fldId = tblBar.fldId
    +     * 
    + * + *
      + *
    • + * + * null (default) + *
      +     *   [0] => Array (
      +     *       [table] => tblFoo
      +     *       [name] => fldId
      +     *       [type] => int
      +     *       [len] => 11
      +     *       [flags] => primary_key not_null
      +     *   )
      +     *   [1] => Array (
      +     *       [table] => tblFoo
      +     *       [name] => fldPhone
      +     *       [type] => string
      +     *       [len] => 20
      +     *       [flags] =>
      +     *   )
      +     *   [2] => Array (
      +     *       [table] => tblBar
      +     *       [name] => fldId
      +     *       [type] => int
      +     *       [len] => 11
      +     *       [flags] => primary_key not_null
      +     *   )
      +     *   
      + * + *
    • + * + * DB_TABLEINFO_ORDER + * + *

      In addition to the information found in the default output, + * a notation of the number of columns is provided by the + * num_fields element while the order + * element provides an array with the column names as the keys and + * their location index number (corresponding to the keys in the + * the default output) as the values.

      + * + *

      If a result set has identical field names, the last one is + * used.

      + * + *
      +     *   [num_fields] => 3
      +     *   [order] => Array (
      +     *       [fldId] => 2
      +     *       [fldTrans] => 1
      +     *   )
      +     *   
      + * + *
    • + * + * DB_TABLEINFO_ORDERTABLE + * + *

      Similar to DB_TABLEINFO_ORDER but adds more + * dimensions to the array in which the table names are keys and + * the field names are sub-keys. This is helpful for queries that + * join tables which have identical field names.

      + * + *
      +     *   [num_fields] => 3
      +     *   [ordertable] => Array (
      +     *       [tblFoo] => Array (
      +     *           [fldId] => 0
      +     *           [fldPhone] => 1
      +     *       )
      +     *       [tblBar] => Array (
      +     *           [fldId] => 2
      +     *       )
      +     *   )
      +     *   
      + * + *
    • + *
    + * + * The flags element contains a space separated list + * of extra information about the field. This data is inconsistent + * between DBMS's due to the way each DBMS works. + * + primary_key + * + unique_key + * + multiple_key + * + not_null + * + * Most DBMS's only provide the table and flags + * elements if $result is a table name. The following DBMS's + * provide full information from queries: + * + fbsql + * + mysql + * + * If the 'portability' option has DB_PORTABILITY_LOWERCASE + * turned on, the names of tables and fields will be lowercased. + * + * @param object|string $result DB_result object from a query or a + * string containing the name of a table. + * While this also accepts a query result + * resource identifier, this behavior is + * deprecated. + * @param int $mode either unused or one of the tableInfo modes: + * DB_TABLEINFO_ORDERTABLE, + * DB_TABLEINFO_ORDER or + * DB_TABLEINFO_FULL (which does both). + * These are bitwise, so the first two can be + * combined using |. + * + * @return array an associative array with the information requested. + * A DB_Error object on failure. + * + * @see DB_common::setOption() + */ + function tableInfo($result, $mode = null) + { + /* + * If the DB_ class has a tableInfo() method, that one + * overrides this one. But, if the driver doesn't have one, + * this method runs and tells users about that fact. + */ + return $this->raiseError(DB_ERROR_NOT_CAPABLE); + } + + // }}} + // {{{ getTables() + + /** + * Lists the tables in the current database + * + * @return array the list of tables. A DB_Error object on failure. + * + * @deprecated Method deprecated some time before Release 1.2 + */ + function getTables() + { + return $this->getListOf('tables'); + } + + // }}} + // {{{ getListOf() + + /** + * Lists internal database information + * + * @param string $type type of information being sought. + * Common items being sought are: + * tables, databases, users, views, functions + * Each DBMS's has its own capabilities. + * + * @return array an array listing the items sought. + * A DB DB_Error object on failure. + */ + function getListOf($type) + { + $sql = $this->getSpecialQuery($type); + if ($sql === null) { + $this->last_query = ''; + return $this->raiseError(DB_ERROR_UNSUPPORTED); + } elseif (is_int($sql) || DB::isError($sql)) { + // Previous error + return $this->raiseError($sql); + } elseif (is_array($sql)) { + // Already the result + return $sql; + } + // Launch this query + return $this->getCol($sql); + } + + // }}} + // {{{ getSpecialQuery() + + /** + * Obtains the query string needed for listing a given type of objects + * + * @param string $type the kind of objects you want to retrieve + * + * @return string the SQL query string or null if the driver doesn't + * support the object type requested + * + * @access protected + * @see DB_common::getListOf() + */ + function getSpecialQuery($type) + { + return $this->raiseError(DB_ERROR_UNSUPPORTED); + } + + // }}} + // {{{ nextQueryIsManip() + + /** + * Sets (or unsets) a flag indicating that the next query will be a + * manipulation query, regardless of the usual DB::isManip() heuristics. + * + * @param boolean true to set the flag overriding the isManip() behaviour, + * false to clear it and fall back onto isManip() + * + * @return void + * + * @access public + */ + function nextQueryIsManip($manip) + { + $this->_next_query_manip = $manip; + } + + // }}} + // {{{ _checkManip() + + /** + * Checks if the given query is a manipulation query. This also takes into + * account the _next_query_manip flag and sets the _last_query_manip flag + * (and resets _next_query_manip) according to the result. + * + * @param string The query to check. + * + * @return boolean true if the query is a manipulation query, false + * otherwise + * + * @access protected + */ + function _checkManip($query) + { + if ($this->_next_query_manip || DB::isManip($query)) { + $this->_last_query_manip = true; + } else { + $this->_last_query_manip = false; + } + $this->_next_query_manip = false; + return $this->_last_query_manip; + $manip = $this->_next_query_manip; + } + + // }}} + // {{{ _rtrimArrayValues() + + /** + * Right-trims all strings in an array + * + * @param array $array the array to be trimmed (passed by reference) + * + * @return void + * + * @access protected + */ + function _rtrimArrayValues(&$array) + { + foreach ($array as $key => $value) { + if (is_string($value)) { + $array[$key] = rtrim($value); + } + } + } + + // }}} + // {{{ _convertNullArrayValuesToEmpty() + + /** + * Converts all null values in an array to empty strings + * + * @param array $array the array to be de-nullified (passed by reference) + * + * @return void + * + * @access protected + */ + function _convertNullArrayValuesToEmpty(&$array) + { + foreach ($array as $key => $value) { + if (is_null($value)) { + $array[$key] = ''; + } + } + } + + // }}} +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ + +?> diff --git a/library/pear/DB/dbase.php b/library/pear/DB/dbase.php new file mode 100644 index 000000000..cdd2e59ef --- /dev/null +++ b/library/pear/DB/dbase.php @@ -0,0 +1,510 @@ + + * @author Daniel Convissor + * @copyright 1997-2007 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version CVS: $Id: dbase.php,v 1.45 2007/09/21 13:40:41 aharvey Exp $ + * @link http://pear.php.net/package/DB + */ + +/** + * Obtain the DB_common class so it can be extended from + */ +require_once 'DB/common.php'; + +/** + * The methods PEAR DB uses to interact with PHP's dbase extension + * for interacting with dBase databases + * + * These methods overload the ones declared in DB_common. + * + * @category Database + * @package DB + * @author Tomas V.V. Cox + * @author Daniel Convissor + * @copyright 1997-2007 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version Release: 1.7.13 + * @link http://pear.php.net/package/DB + */ +class DB_dbase extends DB_common +{ + // {{{ properties + + /** + * The DB driver type (mysql, oci8, odbc, etc.) + * @var string + */ + var $phptype = 'dbase'; + + /** + * The database syntax variant to be used (db2, access, etc.), if any + * @var string + */ + var $dbsyntax = 'dbase'; + + /** + * The capabilities of this DB implementation + * + * The 'new_link' element contains the PHP version that first provided + * new_link support for this DBMS. Contains false if it's unsupported. + * + * Meaning of the 'limit' element: + * + 'emulate' = emulate with fetch row by number + * + 'alter' = alter the query + * + false = skip rows + * + * @var array + */ + var $features = array( + 'limit' => false, + 'new_link' => false, + 'numrows' => true, + 'pconnect' => false, + 'prepare' => false, + 'ssl' => false, + 'transactions' => false, + ); + + /** + * A mapping of native error codes to DB error codes + * @var array + */ + var $errorcode_map = array( + ); + + /** + * The raw database connection created by PHP + * @var resource + */ + var $connection; + + /** + * The DSN information for connecting to a database + * @var array + */ + var $dsn = array(); + + + /** + * A means of emulating result resources + * @var array + */ + var $res_row = array(); + + /** + * The quantity of results so far + * + * For emulating result resources. + * + * @var integer + */ + var $result = 0; + + /** + * Maps dbase data type id's to human readable strings + * + * The human readable values are based on the output of PHP's + * dbase_get_header_info() function. + * + * @var array + * @since Property available since Release 1.7.0 + */ + var $types = array( + 'C' => 'character', + 'D' => 'date', + 'L' => 'boolean', + 'M' => 'memo', + 'N' => 'number', + ); + + + // }}} + // {{{ constructor + + /** + * This constructor calls $this->DB_common() + * + * @return void + */ + function DB_dbase() + { + $this->DB_common(); + } + + // }}} + // {{{ connect() + + /** + * Connect to the database and create it if it doesn't exist + * + * Don't call this method directly. Use DB::connect() instead. + * + * PEAR DB's dbase driver supports the following extra DSN options: + * + mode An integer specifying the read/write mode to use + * (0 = read only, 1 = write only, 2 = read/write). + * Available since PEAR DB 1.7.0. + * + fields An array of arrays that PHP's dbase_create() function needs + * to create a new database. This information is used if the + * dBase file specified in the "database" segment of the DSN + * does not exist. For more info, see the PHP manual's + * {@link http://php.net/dbase_create dbase_create()} page. + * Available since PEAR DB 1.7.0. + * + * Example of how to connect and establish a new dBase file if necessary: + * + * require_once 'DB.php'; + * + * $dsn = array( + * 'phptype' => 'dbase', + * 'database' => '/path/and/name/of/dbase/file', + * 'mode' => 2, + * 'fields' => array( + * array('a', 'N', 5, 0), + * array('b', 'C', 40), + * array('c', 'C', 255), + * array('d', 'C', 20), + * ), + * ); + * $options = array( + * 'debug' => 2, + * 'portability' => DB_PORTABILITY_ALL, + * ); + * + * $db = DB::connect($dsn, $options); + * if (PEAR::isError($db)) { + * die($db->getMessage()); + * } + * + * + * @param array $dsn the data source name + * @param bool $persistent should the connection be persistent? + * + * @return int DB_OK on success. A DB_Error object on failure. + */ + function connect($dsn, $persistent = false) + { + if (!PEAR::loadExtension('dbase')) { + return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND); + } + + $this->dsn = $dsn; + if ($dsn['dbsyntax']) { + $this->dbsyntax = $dsn['dbsyntax']; + } + + /* + * Turn track_errors on for entire script since $php_errormsg + * is the only way to find errors from the dbase extension. + */ + @ini_set('track_errors', 1); + $php_errormsg = ''; + + if (!file_exists($dsn['database'])) { + $this->dsn['mode'] = 2; + if (empty($dsn['fields']) || !is_array($dsn['fields'])) { + return $this->raiseError(DB_ERROR_CONNECT_FAILED, + null, null, null, + 'the dbase file does not exist and ' + . 'it could not be created because ' + . 'the "fields" element of the DSN ' + . 'is not properly set'); + } + $this->connection = @dbase_create($dsn['database'], + $dsn['fields']); + if (!$this->connection) { + return $this->raiseError(DB_ERROR_CONNECT_FAILED, + null, null, null, + 'the dbase file does not exist and ' + . 'the attempt to create it failed: ' + . $php_errormsg); + } + } else { + if (!isset($this->dsn['mode'])) { + $this->dsn['mode'] = 0; + } + $this->connection = @dbase_open($dsn['database'], + $this->dsn['mode']); + if (!$this->connection) { + return $this->raiseError(DB_ERROR_CONNECT_FAILED, + null, null, null, + $php_errormsg); + } + } + return DB_OK; + } + + // }}} + // {{{ disconnect() + + /** + * Disconnects from the database server + * + * @return bool TRUE on success, FALSE on failure + */ + function disconnect() + { + $ret = @dbase_close($this->connection); + $this->connection = null; + return $ret; + } + + // }}} + // {{{ &query() + + function &query($query = null) + { + // emulate result resources + $this->res_row[(int)$this->result] = 0; + $tmp = new DB_result($this, $this->result++); + return $tmp; + } + + // }}} + // {{{ fetchInto() + + /** + * Places a row from the result set into the given array + * + * Formating of the array and the data therein are configurable. + * See DB_result::fetchInto() for more information. + * + * This method is not meant to be called directly. Use + * DB_result::fetchInto() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result the query result resource + * @param array $arr the referenced array to put the data in + * @param int $fetchmode how the resulting array should be indexed + * @param int $rownum the row number to fetch (0 = first row) + * + * @return mixed DB_OK on success, NULL when the end of a result set is + * reached or on failure + * + * @see DB_result::fetchInto() + */ + function fetchInto($result, &$arr, $fetchmode, $rownum = null) + { + if ($rownum === null) { + $rownum = $this->res_row[(int)$result]++; + } + if ($fetchmode & DB_FETCHMODE_ASSOC) { + $arr = @dbase_get_record_with_names($this->connection, $rownum); + if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE && $arr) { + $arr = array_change_key_case($arr, CASE_LOWER); + } + } else { + $arr = @dbase_get_record($this->connection, $rownum); + } + if (!$arr) { + return null; + } + if ($this->options['portability'] & DB_PORTABILITY_RTRIM) { + $this->_rtrimArrayValues($arr); + } + if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) { + $this->_convertNullArrayValuesToEmpty($arr); + } + return DB_OK; + } + + // }}} + // {{{ freeResult() + + /** + * Deletes the result set and frees the memory occupied by the result set. + * + * This method is a no-op for dbase, as there aren't result resources in + * the same sense as most other database backends. + * + * @param resource $result PHP's query result resource + * + * @return bool TRUE on success, FALSE if $result is invalid + * + * @see DB_result::free() + */ + function freeResult($result) + { + return true; + } + + // }}} + // {{{ numCols() + + /** + * Gets the number of columns in a result set + * + * This method is not meant to be called directly. Use + * DB_result::numCols() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result PHP's query result resource + * + * @return int the number of columns. A DB_Error object on failure. + * + * @see DB_result::numCols() + */ + function numCols($foo) + { + return @dbase_numfields($this->connection); + } + + // }}} + // {{{ numRows() + + /** + * Gets the number of rows in a result set + * + * This method is not meant to be called directly. Use + * DB_result::numRows() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result PHP's query result resource + * + * @return int the number of rows. A DB_Error object on failure. + * + * @see DB_result::numRows() + */ + function numRows($foo) + { + return @dbase_numrecords($this->connection); + } + + // }}} + // {{{ quoteBoolean() + + /** + * Formats a boolean value for use within a query in a locale-independent + * manner. + * + * @param boolean the boolean value to be quoted. + * @return string the quoted string. + * @see DB_common::quoteSmart() + * @since Method available since release 1.7.8. + */ + function quoteBoolean($boolean) { + return $boolean ? 'T' : 'F'; + } + + // }}} + // {{{ tableInfo() + + /** + * Returns information about the current database + * + * @param mixed $result THIS IS UNUSED IN DBASE. The current database + * is examined regardless of what is provided here. + * @param int $mode a valid tableInfo mode + * + * @return array an associative array with the information requested. + * A DB_Error object on failure. + * + * @see DB_common::tableInfo() + * @since Method available since Release 1.7.0 + */ + function tableInfo($result = null, $mode = null) + { + if (function_exists('dbase_get_header_info')) { + $id = @dbase_get_header_info($this->connection); + if (!$id && $php_errormsg) { + return $this->raiseError(DB_ERROR, + null, null, null, + $php_errormsg); + } + } else { + /* + * This segment for PHP 4 is loosely based on code by + * Hadi Rusiah in the comments on + * the dBase reference page in the PHP manual. + */ + $db = @fopen($this->dsn['database'], 'r'); + if (!$db) { + return $this->raiseError(DB_ERROR_CONNECT_FAILED, + null, null, null, + $php_errormsg); + } + + $id = array(); + $i = 0; + + $line = fread($db, 32); + while (!feof($db)) { + $line = fread($db, 32); + if (substr($line, 0, 1) == chr(13)) { + break; + } else { + $pos = strpos(substr($line, 0, 10), chr(0)); + $pos = ($pos == 0 ? 10 : $pos); + $id[$i] = array( + 'name' => substr($line, 0, $pos), + 'type' => $this->types[substr($line, 11, 1)], + 'length' => ord(substr($line, 16, 1)), + 'precision' => ord(substr($line, 17, 1)), + ); + } + $i++; + } + + fclose($db); + } + + if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) { + $case_func = 'strtolower'; + } else { + $case_func = 'strval'; + } + + $res = array(); + $count = count($id); + + if ($mode) { + $res['num_fields'] = $count; + } + + for ($i = 0; $i < $count; $i++) { + $res[$i] = array( + 'table' => $this->dsn['database'], + 'name' => $case_func($id[$i]['name']), + 'type' => $id[$i]['type'], + 'len' => $id[$i]['length'], + 'flags' => '' + ); + if ($mode & DB_TABLEINFO_ORDER) { + $res['order'][$res[$i]['name']] = $i; + } + if ($mode & DB_TABLEINFO_ORDERTABLE) { + $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i; + } + } + + return $res; + } + + // }}} +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ + +?> diff --git a/library/pear/DB/fbsql.php b/library/pear/DB/fbsql.php new file mode 100644 index 000000000..7749a98fa --- /dev/null +++ b/library/pear/DB/fbsql.php @@ -0,0 +1,769 @@ + + * @author Daniel Convissor + * @copyright 1997-2007 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version CVS: $Id: fbsql.php,v 1.88 2007/07/06 05:19:21 aharvey Exp $ + * @link http://pear.php.net/package/DB + */ + +/** + * Obtain the DB_common class so it can be extended from + */ +require_once 'DB/common.php'; + +/** + * The methods PEAR DB uses to interact with PHP's fbsql extension + * for interacting with FrontBase databases + * + * These methods overload the ones declared in DB_common. + * + * @category Database + * @package DB + * @author Frank M. Kromann + * @author Daniel Convissor + * @copyright 1997-2007 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version Release: 1.7.13 + * @link http://pear.php.net/package/DB + * @since Class functional since Release 1.7.0 + */ +class DB_fbsql extends DB_common +{ + // {{{ properties + + /** + * The DB driver type (mysql, oci8, odbc, etc.) + * @var string + */ + var $phptype = 'fbsql'; + + /** + * The database syntax variant to be used (db2, access, etc.), if any + * @var string + */ + var $dbsyntax = 'fbsql'; + + /** + * The capabilities of this DB implementation + * + * The 'new_link' element contains the PHP version that first provided + * new_link support for this DBMS. Contains false if it's unsupported. + * + * Meaning of the 'limit' element: + * + 'emulate' = emulate with fetch row by number + * + 'alter' = alter the query + * + false = skip rows + * + * @var array + */ + var $features = array( + 'limit' => 'alter', + 'new_link' => false, + 'numrows' => true, + 'pconnect' => true, + 'prepare' => false, + 'ssl' => false, + 'transactions' => true, + ); + + /** + * A mapping of native error codes to DB error codes + * @var array + */ + var $errorcode_map = array( + 22 => DB_ERROR_SYNTAX, + 85 => DB_ERROR_ALREADY_EXISTS, + 108 => DB_ERROR_SYNTAX, + 116 => DB_ERROR_NOSUCHTABLE, + 124 => DB_ERROR_VALUE_COUNT_ON_ROW, + 215 => DB_ERROR_NOSUCHFIELD, + 217 => DB_ERROR_INVALID_NUMBER, + 226 => DB_ERROR_NOSUCHFIELD, + 231 => DB_ERROR_INVALID, + 239 => DB_ERROR_TRUNCATED, + 251 => DB_ERROR_SYNTAX, + 266 => DB_ERROR_NOT_FOUND, + 357 => DB_ERROR_CONSTRAINT_NOT_NULL, + 358 => DB_ERROR_CONSTRAINT, + 360 => DB_ERROR_CONSTRAINT, + 361 => DB_ERROR_CONSTRAINT, + ); + + /** + * The raw database connection created by PHP + * @var resource + */ + var $connection; + + /** + * The DSN information for connecting to a database + * @var array + */ + var $dsn = array(); + + + // }}} + // {{{ constructor + + /** + * This constructor calls $this->DB_common() + * + * @return void + */ + function DB_fbsql() + { + $this->DB_common(); + } + + // }}} + // {{{ connect() + + /** + * Connect to the database server, log in and open the database + * + * Don't call this method directly. Use DB::connect() instead. + * + * @param array $dsn the data source name + * @param bool $persistent should the connection be persistent? + * + * @return int DB_OK on success. A DB_Error object on failure. + */ + function connect($dsn, $persistent = false) + { + if (!PEAR::loadExtension('fbsql')) { + return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND); + } + + $this->dsn = $dsn; + if ($dsn['dbsyntax']) { + $this->dbsyntax = $dsn['dbsyntax']; + } + + $params = array( + $dsn['hostspec'] ? $dsn['hostspec'] : 'localhost', + $dsn['username'] ? $dsn['username'] : null, + $dsn['password'] ? $dsn['password'] : null, + ); + + $connect_function = $persistent ? 'fbsql_pconnect' : 'fbsql_connect'; + + $ini = ini_get('track_errors'); + $php_errormsg = ''; + if ($ini) { + $this->connection = @call_user_func_array($connect_function, + $params); + } else { + @ini_set('track_errors', 1); + $this->connection = @call_user_func_array($connect_function, + $params); + @ini_set('track_errors', $ini); + } + + if (!$this->connection) { + return $this->raiseError(DB_ERROR_CONNECT_FAILED, + null, null, null, + $php_errormsg); + } + + if ($dsn['database']) { + if (!@fbsql_select_db($dsn['database'], $this->connection)) { + return $this->fbsqlRaiseError(); + } + } + + return DB_OK; + } + + // }}} + // {{{ disconnect() + + /** + * Disconnects from the database server + * + * @return bool TRUE on success, FALSE on failure + */ + function disconnect() + { + $ret = @fbsql_close($this->connection); + $this->connection = null; + return $ret; + } + + // }}} + // {{{ simpleQuery() + + /** + * Sends a query to the database server + * + * @param string the SQL query string + * + * @return mixed + a PHP result resrouce for successful SELECT queries + * + the DB_OK constant for other successful queries + * + a DB_Error object on failure + */ + function simpleQuery($query) + { + $this->last_query = $query; + $query = $this->modifyQuery($query); + $result = @fbsql_query("$query;", $this->connection); + if (!$result) { + return $this->fbsqlRaiseError(); + } + // Determine which queries that should return data, and which + // should return an error code only. + if ($this->_checkManip($query)) { + return DB_OK; + } + return $result; + } + + // }}} + // {{{ nextResult() + + /** + * Move the internal fbsql result pointer to the next available result + * + * @param a valid fbsql result resource + * + * @access public + * + * @return true if a result is available otherwise return false + */ + function nextResult($result) + { + return @fbsql_next_result($result); + } + + // }}} + // {{{ fetchInto() + + /** + * Places a row from the result set into the given array + * + * Formating of the array and the data therein are configurable. + * See DB_result::fetchInto() for more information. + * + * This method is not meant to be called directly. Use + * DB_result::fetchInto() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result the query result resource + * @param array $arr the referenced array to put the data in + * @param int $fetchmode how the resulting array should be indexed + * @param int $rownum the row number to fetch (0 = first row) + * + * @return mixed DB_OK on success, NULL when the end of a result set is + * reached or on failure + * + * @see DB_result::fetchInto() + */ + function fetchInto($result, &$arr, $fetchmode, $rownum = null) + { + if ($rownum !== null) { + if (!@fbsql_data_seek($result, $rownum)) { + return null; + } + } + if ($fetchmode & DB_FETCHMODE_ASSOC) { + $arr = @fbsql_fetch_array($result, FBSQL_ASSOC); + if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE && $arr) { + $arr = array_change_key_case($arr, CASE_LOWER); + } + } else { + $arr = @fbsql_fetch_row($result); + } + if (!$arr) { + return null; + } + if ($this->options['portability'] & DB_PORTABILITY_RTRIM) { + $this->_rtrimArrayValues($arr); + } + if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) { + $this->_convertNullArrayValuesToEmpty($arr); + } + return DB_OK; + } + + // }}} + // {{{ freeResult() + + /** + * Deletes the result set and frees the memory occupied by the result set + * + * This method is not meant to be called directly. Use + * DB_result::free() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result PHP's query result resource + * + * @return bool TRUE on success, FALSE if $result is invalid + * + * @see DB_result::free() + */ + function freeResult($result) + { + return is_resource($result) ? fbsql_free_result($result) : false; + } + + // }}} + // {{{ autoCommit() + + /** + * Enables or disables automatic commits + * + * @param bool $onoff true turns it on, false turns it off + * + * @return int DB_OK on success. A DB_Error object if the driver + * doesn't support auto-committing transactions. + */ + function autoCommit($onoff=false) + { + if ($onoff) { + $this->query("SET COMMIT TRUE"); + } else { + $this->query("SET COMMIT FALSE"); + } + } + + // }}} + // {{{ commit() + + /** + * Commits the current transaction + * + * @return int DB_OK on success. A DB_Error object on failure. + */ + function commit() + { + @fbsql_commit($this->connection); + } + + // }}} + // {{{ rollback() + + /** + * Reverts the current transaction + * + * @return int DB_OK on success. A DB_Error object on failure. + */ + function rollback() + { + @fbsql_rollback($this->connection); + } + + // }}} + // {{{ numCols() + + /** + * Gets the number of columns in a result set + * + * This method is not meant to be called directly. Use + * DB_result::numCols() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result PHP's query result resource + * + * @return int the number of columns. A DB_Error object on failure. + * + * @see DB_result::numCols() + */ + function numCols($result) + { + $cols = @fbsql_num_fields($result); + if (!$cols) { + return $this->fbsqlRaiseError(); + } + return $cols; + } + + // }}} + // {{{ numRows() + + /** + * Gets the number of rows in a result set + * + * This method is not meant to be called directly. Use + * DB_result::numRows() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result PHP's query result resource + * + * @return int the number of rows. A DB_Error object on failure. + * + * @see DB_result::numRows() + */ + function numRows($result) + { + $rows = @fbsql_num_rows($result); + if ($rows === null) { + return $this->fbsqlRaiseError(); + } + return $rows; + } + + // }}} + // {{{ affectedRows() + + /** + * Determines the number of rows affected by a data maniuplation query + * + * 0 is returned for queries that don't manipulate data. + * + * @return int the number of rows. A DB_Error object on failure. + */ + function affectedRows() + { + if ($this->_last_query_manip) { + $result = @fbsql_affected_rows($this->connection); + } else { + $result = 0; + } + return $result; + } + + // }}} + // {{{ nextId() + + /** + * Returns the next free id in a sequence + * + * @param string $seq_name name of the sequence + * @param boolean $ondemand when true, the seqence is automatically + * created if it does not exist + * + * @return int the next id number in the sequence. + * A DB_Error object on failure. + * + * @see DB_common::nextID(), DB_common::getSequenceName(), + * DB_fbsql::createSequence(), DB_fbsql::dropSequence() + */ + function nextId($seq_name, $ondemand = true) + { + $seqname = $this->getSequenceName($seq_name); + do { + $repeat = 0; + $this->pushErrorHandling(PEAR_ERROR_RETURN); + $result = $this->query('SELECT UNIQUE FROM ' . $seqname); + $this->popErrorHandling(); + if ($ondemand && DB::isError($result) && + $result->getCode() == DB_ERROR_NOSUCHTABLE) { + $repeat = 1; + $result = $this->createSequence($seq_name); + if (DB::isError($result)) { + return $result; + } + } else { + $repeat = 0; + } + } while ($repeat); + if (DB::isError($result)) { + return $this->fbsqlRaiseError(); + } + $result->fetchInto($tmp, DB_FETCHMODE_ORDERED); + return $tmp[0]; + } + + /** + * Creates a new sequence + * + * @param string $seq_name name of the new sequence + * + * @return int DB_OK on success. A DB_Error object on failure. + * + * @see DB_common::createSequence(), DB_common::getSequenceName(), + * DB_fbsql::nextID(), DB_fbsql::dropSequence() + */ + function createSequence($seq_name) + { + $seqname = $this->getSequenceName($seq_name); + $res = $this->query('CREATE TABLE ' . $seqname + . ' (id INTEGER NOT NULL,' + . ' PRIMARY KEY(id))'); + if ($res) { + $res = $this->query('SET UNIQUE = 0 FOR ' . $seqname); + } + return $res; + } + + // }}} + // {{{ dropSequence() + + /** + * Deletes a sequence + * + * @param string $seq_name name of the sequence to be deleted + * + * @return int DB_OK on success. A DB_Error object on failure. + * + * @see DB_common::dropSequence(), DB_common::getSequenceName(), + * DB_fbsql::nextID(), DB_fbsql::createSequence() + */ + function dropSequence($seq_name) + { + return $this->query('DROP TABLE ' . $this->getSequenceName($seq_name) + . ' RESTRICT'); + } + + // }}} + // {{{ modifyLimitQuery() + + /** + * Adds LIMIT clauses to a query string according to current DBMS standards + * + * @param string $query the query to modify + * @param int $from the row to start to fetching (0 = the first row) + * @param int $count the numbers of rows to fetch + * @param mixed $params array, string or numeric data to be used in + * execution of the statement. Quantity of items + * passed must match quantity of placeholders in + * query: meaning 1 placeholder for non-array + * parameters or 1 placeholder per array element. + * + * @return string the query string with LIMIT clauses added + * + * @access protected + */ + function modifyLimitQuery($query, $from, $count, $params = array()) + { + if (DB::isManip($query) || $this->_next_query_manip) { + return preg_replace('/^([\s(])*SELECT/i', + "\\1SELECT TOP($count)", $query); + } else { + return preg_replace('/([\s(])*SELECT/i', + "\\1SELECT TOP($from, $count)", $query); + } + } + + // }}} + // {{{ quoteBoolean() + + /** + * Formats a boolean value for use within a query in a locale-independent + * manner. + * + * @param boolean the boolean value to be quoted. + * @return string the quoted string. + * @see DB_common::quoteSmart() + * @since Method available since release 1.7.8. + */ + function quoteBoolean($boolean) { + return $boolean ? 'TRUE' : 'FALSE'; + } + + // }}} + // {{{ quoteFloat() + + /** + * Formats a float value for use within a query in a locale-independent + * manner. + * + * @param float the float value to be quoted. + * @return string the quoted string. + * @see DB_common::quoteSmart() + * @since Method available since release 1.7.8. + */ + function quoteFloat($float) { + return $this->escapeSimple(str_replace(',', '.', strval(floatval($float)))); + } + + // }}} + // {{{ fbsqlRaiseError() + + /** + * Produces a DB_Error object regarding the current problem + * + * @param int $errno if the error is being manually raised pass a + * DB_ERROR* constant here. If this isn't passed + * the error information gathered from the DBMS. + * + * @return object the DB_Error object + * + * @see DB_common::raiseError(), + * DB_fbsql::errorNative(), DB_common::errorCode() + */ + function fbsqlRaiseError($errno = null) + { + if ($errno === null) { + $errno = $this->errorCode(fbsql_errno($this->connection)); + } + return $this->raiseError($errno, null, null, null, + @fbsql_error($this->connection)); + } + + // }}} + // {{{ errorNative() + + /** + * Gets the DBMS' native error code produced by the last query + * + * @return int the DBMS' error code + */ + function errorNative() + { + return @fbsql_errno($this->connection); + } + + // }}} + // {{{ tableInfo() + + /** + * Returns information about a table or a result set + * + * @param object|string $result DB_result object from a query or a + * string containing the name of a table. + * While this also accepts a query result + * resource identifier, this behavior is + * deprecated. + * @param int $mode a valid tableInfo mode + * + * @return array an associative array with the information requested. + * A DB_Error object on failure. + * + * @see DB_common::tableInfo() + */ + function tableInfo($result, $mode = null) + { + if (is_string($result)) { + /* + * Probably received a table name. + * Create a result resource identifier. + */ + $id = @fbsql_list_fields($this->dsn['database'], + $result, $this->connection); + $got_string = true; + } elseif (isset($result->result)) { + /* + * Probably received a result object. + * Extract the result resource identifier. + */ + $id = $result->result; + $got_string = false; + } else { + /* + * Probably received a result resource identifier. + * Copy it. + * Deprecated. Here for compatibility only. + */ + $id = $result; + $got_string = false; + } + + if (!is_resource($id)) { + return $this->fbsqlRaiseError(DB_ERROR_NEED_MORE_DATA); + } + + if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) { + $case_func = 'strtolower'; + } else { + $case_func = 'strval'; + } + + $count = @fbsql_num_fields($id); + $res = array(); + + if ($mode) { + $res['num_fields'] = $count; + } + + for ($i = 0; $i < $count; $i++) { + $res[$i] = array( + 'table' => $case_func(@fbsql_field_table($id, $i)), + 'name' => $case_func(@fbsql_field_name($id, $i)), + 'type' => @fbsql_field_type($id, $i), + 'len' => @fbsql_field_len($id, $i), + 'flags' => @fbsql_field_flags($id, $i), + ); + if ($mode & DB_TABLEINFO_ORDER) { + $res['order'][$res[$i]['name']] = $i; + } + if ($mode & DB_TABLEINFO_ORDERTABLE) { + $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i; + } + } + + // free the result only if we were called on a table + if ($got_string) { + @fbsql_free_result($id); + } + return $res; + } + + // }}} + // {{{ getSpecialQuery() + + /** + * Obtains the query string needed for listing a given type of objects + * + * @param string $type the kind of objects you want to retrieve + * + * @return string the SQL query string or null if the driver doesn't + * support the object type requested + * + * @access protected + * @see DB_common::getListOf() + */ + function getSpecialQuery($type) + { + switch ($type) { + case 'tables': + return 'SELECT "table_name" FROM information_schema.tables' + . ' t0, information_schema.schemata t1' + . ' WHERE t0.schema_pk=t1.schema_pk AND' + . ' "table_type" = \'BASE TABLE\'' + . ' AND "schema_name" = current_schema'; + case 'views': + return 'SELECT "table_name" FROM information_schema.tables' + . ' t0, information_schema.schemata t1' + . ' WHERE t0.schema_pk=t1.schema_pk AND' + . ' "table_type" = \'VIEW\'' + . ' AND "schema_name" = current_schema'; + case 'users': + return 'SELECT "user_name" from information_schema.users'; + case 'functions': + return 'SELECT "routine_name" FROM' + . ' information_schema.psm_routines' + . ' t0, information_schema.schemata t1' + . ' WHERE t0.schema_pk=t1.schema_pk' + . ' AND "routine_kind"=\'FUNCTION\'' + . ' AND "schema_name" = current_schema'; + case 'procedures': + return 'SELECT "routine_name" FROM' + . ' information_schema.psm_routines' + . ' t0, information_schema.schemata t1' + . ' WHERE t0.schema_pk=t1.schema_pk' + . ' AND "routine_kind"=\'PROCEDURE\'' + . ' AND "schema_name" = current_schema'; + default: + return null; + } + } + + // }}} +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ + +?> diff --git a/library/pear/DB/ibase.php b/library/pear/DB/ibase.php new file mode 100644 index 000000000..783606425 --- /dev/null +++ b/library/pear/DB/ibase.php @@ -0,0 +1,1082 @@ + + * @author Daniel Convissor + * @copyright 1997-2007 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version CVS: $Id: ibase.php,v 1.116 2007/09/21 13:40:41 aharvey Exp $ + * @link http://pear.php.net/package/DB + */ + +/** + * Obtain the DB_common class so it can be extended from + */ +require_once 'DB/common.php'; + +/** + * The methods PEAR DB uses to interact with PHP's interbase extension + * for interacting with Interbase and Firebird databases + * + * These methods overload the ones declared in DB_common. + * + * While this class works with PHP 4, PHP's InterBase extension is + * unstable in PHP 4. Use PHP 5. + * + * NOTICE: limitQuery() only works for Firebird. + * + * @category Database + * @package DB + * @author Sterling Hughes + * @author Daniel Convissor + * @copyright 1997-2007 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version Release: 1.7.13 + * @link http://pear.php.net/package/DB + * @since Class became stable in Release 1.7.0 + */ +class DB_ibase extends DB_common +{ + // {{{ properties + + /** + * The DB driver type (mysql, oci8, odbc, etc.) + * @var string + */ + var $phptype = 'ibase'; + + /** + * The database syntax variant to be used (db2, access, etc.), if any + * @var string + */ + var $dbsyntax = 'ibase'; + + /** + * The capabilities of this DB implementation + * + * The 'new_link' element contains the PHP version that first provided + * new_link support for this DBMS. Contains false if it's unsupported. + * + * Meaning of the 'limit' element: + * + 'emulate' = emulate with fetch row by number + * + 'alter' = alter the query + * + false = skip rows + * + * NOTE: only firebird supports limit. + * + * @var array + */ + var $features = array( + 'limit' => false, + 'new_link' => false, + 'numrows' => 'emulate', + 'pconnect' => true, + 'prepare' => true, + 'ssl' => false, + 'transactions' => true, + ); + + /** + * A mapping of native error codes to DB error codes + * @var array + */ + var $errorcode_map = array( + -104 => DB_ERROR_SYNTAX, + -150 => DB_ERROR_ACCESS_VIOLATION, + -151 => DB_ERROR_ACCESS_VIOLATION, + -155 => DB_ERROR_NOSUCHTABLE, + -157 => DB_ERROR_NOSUCHFIELD, + -158 => DB_ERROR_VALUE_COUNT_ON_ROW, + -170 => DB_ERROR_MISMATCH, + -171 => DB_ERROR_MISMATCH, + -172 => DB_ERROR_INVALID, + // -204 => // Covers too many errors, need to use regex on msg + -205 => DB_ERROR_NOSUCHFIELD, + -206 => DB_ERROR_NOSUCHFIELD, + -208 => DB_ERROR_INVALID, + -219 => DB_ERROR_NOSUCHTABLE, + -297 => DB_ERROR_CONSTRAINT, + -303 => DB_ERROR_INVALID, + -413 => DB_ERROR_INVALID_NUMBER, + -530 => DB_ERROR_CONSTRAINT, + -551 => DB_ERROR_ACCESS_VIOLATION, + -552 => DB_ERROR_ACCESS_VIOLATION, + // -607 => // Covers too many errors, need to use regex on msg + -625 => DB_ERROR_CONSTRAINT_NOT_NULL, + -803 => DB_ERROR_CONSTRAINT, + -804 => DB_ERROR_VALUE_COUNT_ON_ROW, + // -902 => // Covers too many errors, need to use regex on msg + -904 => DB_ERROR_CONNECT_FAILED, + -922 => DB_ERROR_NOSUCHDB, + -923 => DB_ERROR_CONNECT_FAILED, + -924 => DB_ERROR_CONNECT_FAILED + ); + + /** + * The raw database connection created by PHP + * @var resource + */ + var $connection; + + /** + * The DSN information for connecting to a database + * @var array + */ + var $dsn = array(); + + + /** + * The number of rows affected by a data manipulation query + * @var integer + * @access private + */ + var $affected = 0; + + /** + * Should data manipulation queries be committed automatically? + * @var bool + * @access private + */ + var $autocommit = true; + + /** + * The prepared statement handle from the most recently executed statement + * + * {@internal Mainly here because the InterBase/Firebird API is only + * able to retrieve data from result sets if the statemnt handle is + * still in scope.}} + * + * @var resource + */ + var $last_stmt; + + /** + * Is the given prepared statement a data manipulation query? + * @var array + * @access private + */ + var $manip_query = array(); + + + // }}} + // {{{ constructor + + /** + * This constructor calls $this->DB_common() + * + * @return void + */ + function DB_ibase() + { + $this->DB_common(); + } + + // }}} + // {{{ connect() + + /** + * Connect to the database server, log in and open the database + * + * Don't call this method directly. Use DB::connect() instead. + * + * PEAR DB's ibase driver supports the following extra DSN options: + * + buffers The number of database buffers to allocate for the + * server-side cache. + * + charset The default character set for a database. + * + dialect The default SQL dialect for any statement + * executed within a connection. Defaults to the + * highest one supported by client libraries. + * Functional only with InterBase 6 and up. + * + role Functional only with InterBase 5 and up. + * + * @param array $dsn the data source name + * @param bool $persistent should the connection be persistent? + * + * @return int DB_OK on success. A DB_Error object on failure. + */ + function connect($dsn, $persistent = false) + { + if (!PEAR::loadExtension('interbase')) { + return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND); + } + + $this->dsn = $dsn; + if ($dsn['dbsyntax']) { + $this->dbsyntax = $dsn['dbsyntax']; + } + if ($this->dbsyntax == 'firebird') { + $this->features['limit'] = 'alter'; + } + + $params = array( + $dsn['hostspec'] + ? ($dsn['hostspec'] . ':' . $dsn['database']) + : $dsn['database'], + $dsn['username'] ? $dsn['username'] : null, + $dsn['password'] ? $dsn['password'] : null, + isset($dsn['charset']) ? $dsn['charset'] : null, + isset($dsn['buffers']) ? $dsn['buffers'] : null, + isset($dsn['dialect']) ? $dsn['dialect'] : null, + isset($dsn['role']) ? $dsn['role'] : null, + ); + + $connect_function = $persistent ? 'ibase_pconnect' : 'ibase_connect'; + + $this->connection = @call_user_func_array($connect_function, $params); + if (!$this->connection) { + return $this->ibaseRaiseError(DB_ERROR_CONNECT_FAILED); + } + return DB_OK; + } + + // }}} + // {{{ disconnect() + + /** + * Disconnects from the database server + * + * @return bool TRUE on success, FALSE on failure + */ + function disconnect() + { + $ret = @ibase_close($this->connection); + $this->connection = null; + return $ret; + } + + // }}} + // {{{ simpleQuery() + + /** + * Sends a query to the database server + * + * @param string the SQL query string + * + * @return mixed + a PHP result resrouce for successful SELECT queries + * + the DB_OK constant for other successful queries + * + a DB_Error object on failure + */ + function simpleQuery($query) + { + $ismanip = $this->_checkManip($query); + $this->last_query = $query; + $query = $this->modifyQuery($query); + $result = @ibase_query($this->connection, $query); + + if (!$result) { + return $this->ibaseRaiseError(); + } + if ($this->autocommit && $ismanip) { + @ibase_commit($this->connection); + } + if ($ismanip) { + $this->affected = $result; + return DB_OK; + } else { + $this->affected = 0; + return $result; + } + } + + // }}} + // {{{ modifyLimitQuery() + + /** + * Adds LIMIT clauses to a query string according to current DBMS standards + * + * Only works with Firebird. + * + * @param string $query the query to modify + * @param int $from the row to start to fetching (0 = the first row) + * @param int $count the numbers of rows to fetch + * @param mixed $params array, string or numeric data to be used in + * execution of the statement. Quantity of items + * passed must match quantity of placeholders in + * query: meaning 1 placeholder for non-array + * parameters or 1 placeholder per array element. + * + * @return string the query string with LIMIT clauses added + * + * @access protected + */ + function modifyLimitQuery($query, $from, $count, $params = array()) + { + if ($this->dsn['dbsyntax'] == 'firebird') { + $query = preg_replace('/^([\s(])*SELECT/i', + "SELECT FIRST $count SKIP $from", $query); + } + return $query; + } + + // }}} + // {{{ nextResult() + + /** + * Move the internal ibase result pointer to the next available result + * + * @param a valid fbsql result resource + * + * @access public + * + * @return true if a result is available otherwise return false + */ + function nextResult($result) + { + return false; + } + + // }}} + // {{{ fetchInto() + + /** + * Places a row from the result set into the given array + * + * Formating of the array and the data therein are configurable. + * See DB_result::fetchInto() for more information. + * + * This method is not meant to be called directly. Use + * DB_result::fetchInto() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result the query result resource + * @param array $arr the referenced array to put the data in + * @param int $fetchmode how the resulting array should be indexed + * @param int $rownum the row number to fetch (0 = first row) + * + * @return mixed DB_OK on success, NULL when the end of a result set is + * reached or on failure + * + * @see DB_result::fetchInto() + */ + function fetchInto($result, &$arr, $fetchmode, $rownum = null) + { + if ($rownum !== null) { + return $this->ibaseRaiseError(DB_ERROR_NOT_CAPABLE); + } + if ($fetchmode & DB_FETCHMODE_ASSOC) { + if (function_exists('ibase_fetch_assoc')) { + $arr = @ibase_fetch_assoc($result); + } else { + $arr = get_object_vars(ibase_fetch_object($result)); + } + if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE && $arr) { + $arr = array_change_key_case($arr, CASE_LOWER); + } + } else { + $arr = @ibase_fetch_row($result); + } + if (!$arr) { + return null; + } + if ($this->options['portability'] & DB_PORTABILITY_RTRIM) { + $this->_rtrimArrayValues($arr); + } + if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) { + $this->_convertNullArrayValuesToEmpty($arr); + } + return DB_OK; + } + + // }}} + // {{{ freeResult() + + /** + * Deletes the result set and frees the memory occupied by the result set + * + * This method is not meant to be called directly. Use + * DB_result::free() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result PHP's query result resource + * + * @return bool TRUE on success, FALSE if $result is invalid + * + * @see DB_result::free() + */ + function freeResult($result) + { + return is_resource($result) ? ibase_free_result($result) : false; + } + + // }}} + // {{{ freeQuery() + + function freeQuery($query) + { + return is_resource($query) ? ibase_free_query($query) : false; + } + + // }}} + // {{{ affectedRows() + + /** + * Determines the number of rows affected by a data maniuplation query + * + * 0 is returned for queries that don't manipulate data. + * + * @return int the number of rows. A DB_Error object on failure. + */ + function affectedRows() + { + if (is_integer($this->affected)) { + return $this->affected; + } + return $this->ibaseRaiseError(DB_ERROR_NOT_CAPABLE); + } + + // }}} + // {{{ numCols() + + /** + * Gets the number of columns in a result set + * + * This method is not meant to be called directly. Use + * DB_result::numCols() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result PHP's query result resource + * + * @return int the number of columns. A DB_Error object on failure. + * + * @see DB_result::numCols() + */ + function numCols($result) + { + $cols = @ibase_num_fields($result); + if (!$cols) { + return $this->ibaseRaiseError(); + } + return $cols; + } + + // }}} + // {{{ prepare() + + /** + * Prepares a query for multiple execution with execute(). + * + * prepare() requires a generic query as string like + * INSERT INTO numbers VALUES (?, ?, ?) + * . The ? characters are placeholders. + * + * Three types of placeholders can be used: + * + ? a quoted scalar value, i.e. strings, integers + * + ! value is inserted 'as is' + * + & requires a file name. The file's contents get + * inserted into the query (i.e. saving binary + * data in a db) + * + * Use backslashes to escape placeholder characters if you don't want + * them to be interpreted as placeholders. Example: + * "UPDATE foo SET col=? WHERE col='over \& under'" + * + * + * @param string $query query to be prepared + * @return mixed DB statement resource on success. DB_Error on failure. + */ + function prepare($query) + { + $tokens = preg_split('/((? $val) { + switch ($val) { + case '?': + $types[$token++] = DB_PARAM_SCALAR; + break; + case '&': + $types[$token++] = DB_PARAM_OPAQUE; + break; + case '!': + $types[$token++] = DB_PARAM_MISC; + break; + default: + $tokens[$key] = preg_replace('/\\\([&?!])/', "\\1", $val); + $newquery .= $tokens[$key] . '?'; + } + } + + $newquery = substr($newquery, 0, -1); + $this->last_query = $query; + $newquery = $this->modifyQuery($newquery); + $stmt = @ibase_prepare($this->connection, $newquery); + + if ($stmt === false) { + $stmt = $this->ibaseRaiseError(); + } else { + $this->prepare_types[(int)$stmt] = $types; + $this->manip_query[(int)$stmt] = DB::isManip($query); + } + + return $stmt; + } + + // }}} + // {{{ execute() + + /** + * Executes a DB statement prepared with prepare(). + * + * @param resource $stmt a DB statement resource returned from prepare() + * @param mixed $data array, string or numeric data to be used in + * execution of the statement. Quantity of items + * passed must match quantity of placeholders in + * query: meaning 1 for non-array items or the + * quantity of elements in the array. + * @return object a new DB_Result or a DB_Error when fail + * @see DB_ibase::prepare() + * @access public + */ + function &execute($stmt, $data = array()) + { + $data = (array)$data; + $this->last_parameters = $data; + + $types = $this->prepare_types[(int)$stmt]; + if (count($types) != count($data)) { + $tmp = $this->raiseError(DB_ERROR_MISMATCH); + return $tmp; + } + + $i = 0; + foreach ($data as $key => $value) { + if ($types[$i] == DB_PARAM_MISC) { + /* + * ibase doesn't seem to have the ability to pass a + * parameter along unchanged, so strip off quotes from start + * and end, plus turn two single quotes to one single quote, + * in order to avoid the quotes getting escaped by + * ibase and ending up in the database. + */ + $data[$key] = preg_replace("/^'(.*)'$/", "\\1", $data[$key]); + $data[$key] = str_replace("''", "'", $data[$key]); + } elseif ($types[$i] == DB_PARAM_OPAQUE) { + $fp = @fopen($data[$key], 'rb'); + if (!$fp) { + $tmp = $this->raiseError(DB_ERROR_ACCESS_VIOLATION); + return $tmp; + } + $data[$key] = fread($fp, filesize($data[$key])); + fclose($fp); + } + $i++; + } + + array_unshift($data, $stmt); + + $res = call_user_func_array('ibase_execute', $data); + if (!$res) { + $tmp = $this->ibaseRaiseError(); + return $tmp; + } + /* XXX need this? + if ($this->autocommit && $this->manip_query[(int)$stmt]) { + @ibase_commit($this->connection); + }*/ + $this->last_stmt = $stmt; + if ($this->manip_query[(int)$stmt] || $this->_next_query_manip) { + $this->_last_query_manip = true; + $this->_next_query_manip = false; + $tmp = DB_OK; + } else { + $this->_last_query_manip = false; + $tmp = new DB_result($this, $res); + } + return $tmp; + } + + /** + * Frees the internal resources associated with a prepared query + * + * @param resource $stmt the prepared statement's PHP resource + * @param bool $free_resource should the PHP resource be freed too? + * Use false if you need to get data + * from the result set later. + * + * @return bool TRUE on success, FALSE if $result is invalid + * + * @see DB_ibase::prepare() + */ + function freePrepared($stmt, $free_resource = true) + { + if (!is_resource($stmt)) { + return false; + } + if ($free_resource) { + @ibase_free_query($stmt); + } + unset($this->prepare_tokens[(int)$stmt]); + unset($this->prepare_types[(int)$stmt]); + unset($this->manip_query[(int)$stmt]); + return true; + } + + // }}} + // {{{ autoCommit() + + /** + * Enables or disables automatic commits + * + * @param bool $onoff true turns it on, false turns it off + * + * @return int DB_OK on success. A DB_Error object if the driver + * doesn't support auto-committing transactions. + */ + function autoCommit($onoff = false) + { + $this->autocommit = $onoff ? 1 : 0; + return DB_OK; + } + + // }}} + // {{{ commit() + + /** + * Commits the current transaction + * + * @return int DB_OK on success. A DB_Error object on failure. + */ + function commit() + { + return @ibase_commit($this->connection); + } + + // }}} + // {{{ rollback() + + /** + * Reverts the current transaction + * + * @return int DB_OK on success. A DB_Error object on failure. + */ + function rollback() + { + return @ibase_rollback($this->connection); + } + + // }}} + // {{{ transactionInit() + + function transactionInit($trans_args = 0) + { + return $trans_args + ? @ibase_trans($trans_args, $this->connection) + : @ibase_trans(); + } + + // }}} + // {{{ nextId() + + /** + * Returns the next free id in a sequence + * + * @param string $seq_name name of the sequence + * @param boolean $ondemand when true, the seqence is automatically + * created if it does not exist + * + * @return int the next id number in the sequence. + * A DB_Error object on failure. + * + * @see DB_common::nextID(), DB_common::getSequenceName(), + * DB_ibase::createSequence(), DB_ibase::dropSequence() + */ + function nextId($seq_name, $ondemand = true) + { + $sqn = strtoupper($this->getSequenceName($seq_name)); + $repeat = 0; + do { + $this->pushErrorHandling(PEAR_ERROR_RETURN); + $result = $this->query("SELECT GEN_ID(${sqn}, 1) " + . 'FROM RDB$GENERATORS ' + . "WHERE RDB\$GENERATOR_NAME='${sqn}'"); + $this->popErrorHandling(); + if ($ondemand && DB::isError($result)) { + $repeat = 1; + $result = $this->createSequence($seq_name); + if (DB::isError($result)) { + return $result; + } + } else { + $repeat = 0; + } + } while ($repeat); + if (DB::isError($result)) { + return $this->raiseError($result); + } + $arr = $result->fetchRow(DB_FETCHMODE_ORDERED); + $result->free(); + return $arr[0]; + } + + // }}} + // {{{ createSequence() + + /** + * Creates a new sequence + * + * @param string $seq_name name of the new sequence + * + * @return int DB_OK on success. A DB_Error object on failure. + * + * @see DB_common::createSequence(), DB_common::getSequenceName(), + * DB_ibase::nextID(), DB_ibase::dropSequence() + */ + function createSequence($seq_name) + { + $sqn = strtoupper($this->getSequenceName($seq_name)); + $this->pushErrorHandling(PEAR_ERROR_RETURN); + $result = $this->query("CREATE GENERATOR ${sqn}"); + $this->popErrorHandling(); + + return $result; + } + + // }}} + // {{{ dropSequence() + + /** + * Deletes a sequence + * + * @param string $seq_name name of the sequence to be deleted + * + * @return int DB_OK on success. A DB_Error object on failure. + * + * @see DB_common::dropSequence(), DB_common::getSequenceName(), + * DB_ibase::nextID(), DB_ibase::createSequence() + */ + function dropSequence($seq_name) + { + return $this->query('DELETE FROM RDB$GENERATORS ' + . "WHERE RDB\$GENERATOR_NAME='" + . strtoupper($this->getSequenceName($seq_name)) + . "'"); + } + + // }}} + // {{{ _ibaseFieldFlags() + + /** + * Get the column's flags + * + * Supports "primary_key", "unique_key", "not_null", "default", + * "computed" and "blob". + * + * @param string $field_name the name of the field + * @param string $table_name the name of the table + * + * @return string the flags + * + * @access private + */ + function _ibaseFieldFlags($field_name, $table_name) + { + $sql = 'SELECT R.RDB$CONSTRAINT_TYPE CTYPE' + .' FROM RDB$INDEX_SEGMENTS I' + .' JOIN RDB$RELATION_CONSTRAINTS R ON I.RDB$INDEX_NAME=R.RDB$INDEX_NAME' + .' WHERE I.RDB$FIELD_NAME=\'' . $field_name . '\'' + .' AND UPPER(R.RDB$RELATION_NAME)=\'' . strtoupper($table_name) . '\''; + + $result = @ibase_query($this->connection, $sql); + if (!$result) { + return $this->ibaseRaiseError(); + } + + $flags = ''; + if ($obj = @ibase_fetch_object($result)) { + @ibase_free_result($result); + if (isset($obj->CTYPE) && trim($obj->CTYPE) == 'PRIMARY KEY') { + $flags .= 'primary_key '; + } + if (isset($obj->CTYPE) && trim($obj->CTYPE) == 'UNIQUE') { + $flags .= 'unique_key '; + } + } + + $sql = 'SELECT R.RDB$NULL_FLAG AS NFLAG,' + .' R.RDB$DEFAULT_SOURCE AS DSOURCE,' + .' F.RDB$FIELD_TYPE AS FTYPE,' + .' F.RDB$COMPUTED_SOURCE AS CSOURCE' + .' FROM RDB$RELATION_FIELDS R ' + .' JOIN RDB$FIELDS F ON R.RDB$FIELD_SOURCE=F.RDB$FIELD_NAME' + .' WHERE UPPER(R.RDB$RELATION_NAME)=\'' . strtoupper($table_name) . '\'' + .' AND R.RDB$FIELD_NAME=\'' . $field_name . '\''; + + $result = @ibase_query($this->connection, $sql); + if (!$result) { + return $this->ibaseRaiseError(); + } + if ($obj = @ibase_fetch_object($result)) { + @ibase_free_result($result); + if (isset($obj->NFLAG)) { + $flags .= 'not_null '; + } + if (isset($obj->DSOURCE)) { + $flags .= 'default '; + } + if (isset($obj->CSOURCE)) { + $flags .= 'computed '; + } + if (isset($obj->FTYPE) && $obj->FTYPE == 261) { + $flags .= 'blob '; + } + } + + return trim($flags); + } + + // }}} + // {{{ ibaseRaiseError() + + /** + * Produces a DB_Error object regarding the current problem + * + * @param int $errno if the error is being manually raised pass a + * DB_ERROR* constant here. If this isn't passed + * the error information gathered from the DBMS. + * + * @return object the DB_Error object + * + * @see DB_common::raiseError(), + * DB_ibase::errorNative(), DB_ibase::errorCode() + */ + function &ibaseRaiseError($errno = null) + { + if ($errno === null) { + $errno = $this->errorCode($this->errorNative()); + } + $tmp = $this->raiseError($errno, null, null, null, @ibase_errmsg()); + return $tmp; + } + + // }}} + // {{{ errorNative() + + /** + * Gets the DBMS' native error code produced by the last query + * + * @return int the DBMS' error code. NULL if there is no error code. + * + * @since Method available since Release 1.7.0 + */ + function errorNative() + { + if (function_exists('ibase_errcode')) { + return @ibase_errcode(); + } + if (preg_match('/^Dynamic SQL Error SQL error code = ([0-9-]+)/i', + @ibase_errmsg(), $m)) { + return (int)$m[1]; + } + return null; + } + + // }}} + // {{{ errorCode() + + /** + * Maps native error codes to DB's portable ones + * + * @param int $nativecode the error code returned by the DBMS + * + * @return int the portable DB error code. Return DB_ERROR if the + * current driver doesn't have a mapping for the + * $nativecode submitted. + * + * @since Method available since Release 1.7.0 + */ + function errorCode($nativecode = null) + { + if (isset($this->errorcode_map[$nativecode])) { + return $this->errorcode_map[$nativecode]; + } + + static $error_regexps; + if (!isset($error_regexps)) { + $error_regexps = array( + '/generator .* is not defined/' + => DB_ERROR_SYNTAX, // for compat. w ibase_errcode() + '/table.*(not exist|not found|unknown)/i' + => DB_ERROR_NOSUCHTABLE, + '/table .* already exists/i' + => DB_ERROR_ALREADY_EXISTS, + '/unsuccessful metadata update .* failed attempt to store duplicate value/i' + => DB_ERROR_ALREADY_EXISTS, + '/unsuccessful metadata update .* not found/i' + => DB_ERROR_NOT_FOUND, + '/validation error for column .* value "\*\*\* null/i' + => DB_ERROR_CONSTRAINT_NOT_NULL, + '/violation of [\w ]+ constraint/i' + => DB_ERROR_CONSTRAINT, + '/conversion error from string/i' + => DB_ERROR_INVALID_NUMBER, + '/no permission for/i' + => DB_ERROR_ACCESS_VIOLATION, + '/arithmetic exception, numeric overflow, or string truncation/i' + => DB_ERROR_INVALID, + '/feature is not supported/i' + => DB_ERROR_NOT_CAPABLE, + ); + } + + $errormsg = @ibase_errmsg(); + foreach ($error_regexps as $regexp => $code) { + if (preg_match($regexp, $errormsg)) { + return $code; + } + } + return DB_ERROR; + } + + // }}} + // {{{ tableInfo() + + /** + * Returns information about a table or a result set + * + * NOTE: only supports 'table' and 'flags' if $result + * is a table name. + * + * @param object|string $result DB_result object from a query or a + * string containing the name of a table. + * While this also accepts a query result + * resource identifier, this behavior is + * deprecated. + * @param int $mode a valid tableInfo mode + * + * @return array an associative array with the information requested. + * A DB_Error object on failure. + * + * @see DB_common::tableInfo() + */ + function tableInfo($result, $mode = null) + { + if (is_string($result)) { + /* + * Probably received a table name. + * Create a result resource identifier. + */ + $id = @ibase_query($this->connection, + "SELECT * FROM $result WHERE 1=0"); + $got_string = true; + } elseif (isset($result->result)) { + /* + * Probably received a result object. + * Extract the result resource identifier. + */ + $id = $result->result; + $got_string = false; + } else { + /* + * Probably received a result resource identifier. + * Copy it. + * Deprecated. Here for compatibility only. + */ + $id = $result; + $got_string = false; + } + + if (!is_resource($id)) { + return $this->ibaseRaiseError(DB_ERROR_NEED_MORE_DATA); + } + + if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) { + $case_func = 'strtolower'; + } else { + $case_func = 'strval'; + } + + $count = @ibase_num_fields($id); + $res = array(); + + if ($mode) { + $res['num_fields'] = $count; + } + + for ($i = 0; $i < $count; $i++) { + $info = @ibase_field_info($id, $i); + $res[$i] = array( + 'table' => $got_string ? $case_func($result) : '', + 'name' => $case_func($info['name']), + 'type' => $info['type'], + 'len' => $info['length'], + 'flags' => ($got_string) + ? $this->_ibaseFieldFlags($info['name'], $result) + : '', + ); + if ($mode & DB_TABLEINFO_ORDER) { + $res['order'][$res[$i]['name']] = $i; + } + if ($mode & DB_TABLEINFO_ORDERTABLE) { + $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i; + } + } + + // free the result only if we were called on a table + if ($got_string) { + @ibase_free_result($id); + } + return $res; + } + + // }}} + // {{{ getSpecialQuery() + + /** + * Obtains the query string needed for listing a given type of objects + * + * @param string $type the kind of objects you want to retrieve + * + * @return string the SQL query string or null if the driver doesn't + * support the object type requested + * + * @access protected + * @see DB_common::getListOf() + */ + function getSpecialQuery($type) + { + switch ($type) { + case 'tables': + return 'SELECT DISTINCT R.RDB$RELATION_NAME FROM ' + . 'RDB$RELATION_FIELDS R WHERE R.RDB$SYSTEM_FLAG=0'; + case 'views': + return 'SELECT DISTINCT RDB$VIEW_NAME from RDB$VIEW_RELATIONS'; + case 'users': + return 'SELECT DISTINCT RDB$USER FROM RDB$USER_PRIVILEGES'; + default: + return null; + } + } + + // }}} + +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ + +?> diff --git a/library/pear/DB/ifx.php b/library/pear/DB/ifx.php new file mode 100644 index 000000000..98ebecb2d --- /dev/null +++ b/library/pear/DB/ifx.php @@ -0,0 +1,683 @@ + + * @author Daniel Convissor + * @copyright 1997-2007 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version CVS: $Id: ifx.php,v 1.75 2007/07/06 05:19:21 aharvey Exp $ + * @link http://pear.php.net/package/DB + */ + +/** + * Obtain the DB_common class so it can be extended from + */ +require_once 'DB/common.php'; + +/** + * The methods PEAR DB uses to interact with PHP's ifx extension + * for interacting with Informix databases + * + * These methods overload the ones declared in DB_common. + * + * More info on Informix errors can be found at: + * http://www.informix.com/answers/english/ierrors.htm + * + * TODO: + * - set needed env Informix vars on connect + * - implement native prepare/execute + * + * @category Database + * @package DB + * @author Tomas V.V.Cox + * @author Daniel Convissor + * @copyright 1997-2007 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version Release: 1.7.13 + * @link http://pear.php.net/package/DB + */ +class DB_ifx extends DB_common +{ + // {{{ properties + + /** + * The DB driver type (mysql, oci8, odbc, etc.) + * @var string + */ + var $phptype = 'ifx'; + + /** + * The database syntax variant to be used (db2, access, etc.), if any + * @var string + */ + var $dbsyntax = 'ifx'; + + /** + * The capabilities of this DB implementation + * + * The 'new_link' element contains the PHP version that first provided + * new_link support for this DBMS. Contains false if it's unsupported. + * + * Meaning of the 'limit' element: + * + 'emulate' = emulate with fetch row by number + * + 'alter' = alter the query + * + false = skip rows + * + * @var array + */ + var $features = array( + 'limit' => 'emulate', + 'new_link' => false, + 'numrows' => 'emulate', + 'pconnect' => true, + 'prepare' => false, + 'ssl' => false, + 'transactions' => true, + ); + + /** + * A mapping of native error codes to DB error codes + * @var array + */ + var $errorcode_map = array( + '-201' => DB_ERROR_SYNTAX, + '-206' => DB_ERROR_NOSUCHTABLE, + '-217' => DB_ERROR_NOSUCHFIELD, + '-236' => DB_ERROR_VALUE_COUNT_ON_ROW, + '-239' => DB_ERROR_CONSTRAINT, + '-253' => DB_ERROR_SYNTAX, + '-268' => DB_ERROR_CONSTRAINT, + '-292' => DB_ERROR_CONSTRAINT_NOT_NULL, + '-310' => DB_ERROR_ALREADY_EXISTS, + '-316' => DB_ERROR_ALREADY_EXISTS, + '-319' => DB_ERROR_NOT_FOUND, + '-329' => DB_ERROR_NODBSELECTED, + '-346' => DB_ERROR_CONSTRAINT, + '-386' => DB_ERROR_CONSTRAINT_NOT_NULL, + '-391' => DB_ERROR_CONSTRAINT_NOT_NULL, + '-554' => DB_ERROR_SYNTAX, + '-691' => DB_ERROR_CONSTRAINT, + '-692' => DB_ERROR_CONSTRAINT, + '-703' => DB_ERROR_CONSTRAINT_NOT_NULL, + '-1202' => DB_ERROR_DIVZERO, + '-1204' => DB_ERROR_INVALID_DATE, + '-1205' => DB_ERROR_INVALID_DATE, + '-1206' => DB_ERROR_INVALID_DATE, + '-1209' => DB_ERROR_INVALID_DATE, + '-1210' => DB_ERROR_INVALID_DATE, + '-1212' => DB_ERROR_INVALID_DATE, + '-1213' => DB_ERROR_INVALID_NUMBER, + ); + + /** + * The raw database connection created by PHP + * @var resource + */ + var $connection; + + /** + * The DSN information for connecting to a database + * @var array + */ + var $dsn = array(); + + + /** + * Should data manipulation queries be committed automatically? + * @var bool + * @access private + */ + var $autocommit = true; + + /** + * The quantity of transactions begun + * + * {@internal While this is private, it can't actually be designated + * private in PHP 5 because it is directly accessed in the test suite.}} + * + * @var integer + * @access private + */ + var $transaction_opcount = 0; + + /** + * The number of rows affected by a data manipulation query + * @var integer + * @access private + */ + var $affected = 0; + + + // }}} + // {{{ constructor + + /** + * This constructor calls $this->DB_common() + * + * @return void + */ + function DB_ifx() + { + $this->DB_common(); + } + + // }}} + // {{{ connect() + + /** + * Connect to the database server, log in and open the database + * + * Don't call this method directly. Use DB::connect() instead. + * + * @param array $dsn the data source name + * @param bool $persistent should the connection be persistent? + * + * @return int DB_OK on success. A DB_Error object on failure. + */ + function connect($dsn, $persistent = false) + { + if (!PEAR::loadExtension('informix') && + !PEAR::loadExtension('Informix')) + { + return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND); + } + + $this->dsn = $dsn; + if ($dsn['dbsyntax']) { + $this->dbsyntax = $dsn['dbsyntax']; + } + + $dbhost = $dsn['hostspec'] ? '@' . $dsn['hostspec'] : ''; + $dbname = $dsn['database'] ? $dsn['database'] . $dbhost : ''; + $user = $dsn['username'] ? $dsn['username'] : ''; + $pw = $dsn['password'] ? $dsn['password'] : ''; + + $connect_function = $persistent ? 'ifx_pconnect' : 'ifx_connect'; + + $this->connection = @$connect_function($dbname, $user, $pw); + if (!is_resource($this->connection)) { + return $this->ifxRaiseError(DB_ERROR_CONNECT_FAILED); + } + return DB_OK; + } + + // }}} + // {{{ disconnect() + + /** + * Disconnects from the database server + * + * @return bool TRUE on success, FALSE on failure + */ + function disconnect() + { + $ret = @ifx_close($this->connection); + $this->connection = null; + return $ret; + } + + // }}} + // {{{ simpleQuery() + + /** + * Sends a query to the database server + * + * @param string the SQL query string + * + * @return mixed + a PHP result resrouce for successful SELECT queries + * + the DB_OK constant for other successful queries + * + a DB_Error object on failure + */ + function simpleQuery($query) + { + $ismanip = $this->_checkManip($query); + $this->last_query = $query; + $this->affected = null; + if (preg_match('/(SELECT|EXECUTE)/i', $query)) { //TESTME: Use !DB::isManip()? + // the scroll is needed for fetching absolute row numbers + // in a select query result + $result = @ifx_query($query, $this->connection, IFX_SCROLL); + } else { + if (!$this->autocommit && $ismanip) { + if ($this->transaction_opcount == 0) { + $result = @ifx_query('BEGIN WORK', $this->connection); + if (!$result) { + return $this->ifxRaiseError(); + } + } + $this->transaction_opcount++; + } + $result = @ifx_query($query, $this->connection); + } + if (!$result) { + return $this->ifxRaiseError(); + } + $this->affected = @ifx_affected_rows($result); + // Determine which queries should return data, and which + // should return an error code only. + if (preg_match('/(SELECT|EXECUTE)/i', $query)) { + return $result; + } + // XXX Testme: free results inside a transaction + // may cause to stop it and commit the work? + + // Result has to be freed even with a insert or update + @ifx_free_result($result); + + return DB_OK; + } + + // }}} + // {{{ nextResult() + + /** + * Move the internal ifx result pointer to the next available result + * + * @param a valid fbsql result resource + * + * @access public + * + * @return true if a result is available otherwise return false + */ + function nextResult($result) + { + return false; + } + + // }}} + // {{{ affectedRows() + + /** + * Determines the number of rows affected by a data maniuplation query + * + * 0 is returned for queries that don't manipulate data. + * + * @return int the number of rows. A DB_Error object on failure. + */ + function affectedRows() + { + if ($this->_last_query_manip) { + return $this->affected; + } else { + return 0; + } + } + + // }}} + // {{{ fetchInto() + + /** + * Places a row from the result set into the given array + * + * Formating of the array and the data therein are configurable. + * See DB_result::fetchInto() for more information. + * + * This method is not meant to be called directly. Use + * DB_result::fetchInto() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result the query result resource + * @param array $arr the referenced array to put the data in + * @param int $fetchmode how the resulting array should be indexed + * @param int $rownum the row number to fetch (0 = first row) + * + * @return mixed DB_OK on success, NULL when the end of a result set is + * reached or on failure + * + * @see DB_result::fetchInto() + */ + function fetchInto($result, &$arr, $fetchmode, $rownum = null) + { + if (($rownum !== null) && ($rownum < 0)) { + return null; + } + if ($rownum === null) { + /* + * Even though fetch_row() should return the next row if + * $rownum is null, it doesn't in all cases. Bug 598. + */ + $rownum = 'NEXT'; + } else { + // Index starts at row 1, unlike most DBMS's starting at 0. + $rownum++; + } + if (!$arr = @ifx_fetch_row($result, $rownum)) { + return null; + } + if ($fetchmode !== DB_FETCHMODE_ASSOC) { + $i=0; + $order = array(); + foreach ($arr as $val) { + $order[$i++] = $val; + } + $arr = $order; + } elseif ($fetchmode == DB_FETCHMODE_ASSOC && + $this->options['portability'] & DB_PORTABILITY_LOWERCASE) + { + $arr = array_change_key_case($arr, CASE_LOWER); + } + if ($this->options['portability'] & DB_PORTABILITY_RTRIM) { + $this->_rtrimArrayValues($arr); + } + if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) { + $this->_convertNullArrayValuesToEmpty($arr); + } + return DB_OK; + } + + // }}} + // {{{ numCols() + + /** + * Gets the number of columns in a result set + * + * This method is not meant to be called directly. Use + * DB_result::numCols() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result PHP's query result resource + * + * @return int the number of columns. A DB_Error object on failure. + * + * @see DB_result::numCols() + */ + function numCols($result) + { + if (!$cols = @ifx_num_fields($result)) { + return $this->ifxRaiseError(); + } + return $cols; + } + + // }}} + // {{{ freeResult() + + /** + * Deletes the result set and frees the memory occupied by the result set + * + * This method is not meant to be called directly. Use + * DB_result::free() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result PHP's query result resource + * + * @return bool TRUE on success, FALSE if $result is invalid + * + * @see DB_result::free() + */ + function freeResult($result) + { + return is_resource($result) ? ifx_free_result($result) : false; + } + + // }}} + // {{{ autoCommit() + + /** + * Enables or disables automatic commits + * + * @param bool $onoff true turns it on, false turns it off + * + * @return int DB_OK on success. A DB_Error object if the driver + * doesn't support auto-committing transactions. + */ + function autoCommit($onoff = true) + { + // XXX if $this->transaction_opcount > 0, we should probably + // issue a warning here. + $this->autocommit = $onoff ? true : false; + return DB_OK; + } + + // }}} + // {{{ commit() + + /** + * Commits the current transaction + * + * @return int DB_OK on success. A DB_Error object on failure. + */ + function commit() + { + if ($this->transaction_opcount > 0) { + $result = @ifx_query('COMMIT WORK', $this->connection); + $this->transaction_opcount = 0; + if (!$result) { + return $this->ifxRaiseError(); + } + } + return DB_OK; + } + + // }}} + // {{{ rollback() + + /** + * Reverts the current transaction + * + * @return int DB_OK on success. A DB_Error object on failure. + */ + function rollback() + { + if ($this->transaction_opcount > 0) { + $result = @ifx_query('ROLLBACK WORK', $this->connection); + $this->transaction_opcount = 0; + if (!$result) { + return $this->ifxRaiseError(); + } + } + return DB_OK; + } + + // }}} + // {{{ ifxRaiseError() + + /** + * Produces a DB_Error object regarding the current problem + * + * @param int $errno if the error is being manually raised pass a + * DB_ERROR* constant here. If this isn't passed + * the error information gathered from the DBMS. + * + * @return object the DB_Error object + * + * @see DB_common::raiseError(), + * DB_ifx::errorNative(), DB_ifx::errorCode() + */ + function ifxRaiseError($errno = null) + { + if ($errno === null) { + $errno = $this->errorCode(ifx_error()); + } + return $this->raiseError($errno, null, null, null, + $this->errorNative()); + } + + // }}} + // {{{ errorNative() + + /** + * Gets the DBMS' native error code and message produced by the last query + * + * @return string the DBMS' error code and message + */ + function errorNative() + { + return @ifx_error() . ' ' . @ifx_errormsg(); + } + + // }}} + // {{{ errorCode() + + /** + * Maps native error codes to DB's portable ones. + * + * Requires that the DB implementation's constructor fills + * in the $errorcode_map property. + * + * @param string $nativecode error code returned by the database + * @return int a portable DB error code, or DB_ERROR if this DB + * implementation has no mapping for the given error code. + */ + function errorCode($nativecode) + { + if (ereg('SQLCODE=(.*)]', $nativecode, $match)) { + $code = $match[1]; + if (isset($this->errorcode_map[$code])) { + return $this->errorcode_map[$code]; + } + } + return DB_ERROR; + } + + // }}} + // {{{ tableInfo() + + /** + * Returns information about a table or a result set + * + * NOTE: only supports 'table' if $result is a table name. + * + * If analyzing a query result and the result has duplicate field names, + * an error will be raised saying + * can't distinguish duplicate field names. + * + * @param object|string $result DB_result object from a query or a + * string containing the name of a table. + * While this also accepts a query result + * resource identifier, this behavior is + * deprecated. + * @param int $mode a valid tableInfo mode + * + * @return array an associative array with the information requested. + * A DB_Error object on failure. + * + * @see DB_common::tableInfo() + * @since Method available since Release 1.6.0 + */ + function tableInfo($result, $mode = null) + { + if (is_string($result)) { + /* + * Probably received a table name. + * Create a result resource identifier. + */ + $id = @ifx_query("SELECT * FROM $result WHERE 1=0", + $this->connection); + $got_string = true; + } elseif (isset($result->result)) { + /* + * Probably received a result object. + * Extract the result resource identifier. + */ + $id = $result->result; + $got_string = false; + } else { + /* + * Probably received a result resource identifier. + * Copy it. + */ + $id = $result; + $got_string = false; + } + + if (!is_resource($id)) { + return $this->ifxRaiseError(DB_ERROR_NEED_MORE_DATA); + } + + $flds = @ifx_fieldproperties($id); + $count = @ifx_num_fields($id); + + if (count($flds) != $count) { + return $this->raiseError("can't distinguish duplicate field names"); + } + + if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) { + $case_func = 'strtolower'; + } else { + $case_func = 'strval'; + } + + $i = 0; + $res = array(); + + if ($mode) { + $res['num_fields'] = $count; + } + + foreach ($flds as $key => $value) { + $props = explode(';', $value); + $res[$i] = array( + 'table' => $got_string ? $case_func($result) : '', + 'name' => $case_func($key), + 'type' => $props[0], + 'len' => $props[1], + 'flags' => $props[4] == 'N' ? 'not_null' : '', + ); + if ($mode & DB_TABLEINFO_ORDER) { + $res['order'][$res[$i]['name']] = $i; + } + if ($mode & DB_TABLEINFO_ORDERTABLE) { + $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i; + } + $i++; + } + + // free the result only if we were called on a table + if ($got_string) { + @ifx_free_result($id); + } + return $res; + } + + // }}} + // {{{ getSpecialQuery() + + /** + * Obtains the query string needed for listing a given type of objects + * + * @param string $type the kind of objects you want to retrieve + * + * @return string the SQL query string or null if the driver doesn't + * support the object type requested + * + * @access protected + * @see DB_common::getListOf() + */ + function getSpecialQuery($type) + { + switch ($type) { + case 'tables': + return 'SELECT tabname FROM systables WHERE tabid >= 100'; + default: + return null; + } + } + + // }}} + +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ + +?> diff --git a/library/pear/DB/msql.php b/library/pear/DB/msql.php new file mode 100644 index 000000000..f9c107d34 --- /dev/null +++ b/library/pear/DB/msql.php @@ -0,0 +1,831 @@ + + * @copyright 1997-2007 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version CVS: $Id: msql.php,v 1.64 2007/09/21 13:40:41 aharvey Exp $ + * @link http://pear.php.net/package/DB + */ + +/** + * Obtain the DB_common class so it can be extended from + */ +require_once 'DB/common.php'; + +/** + * The methods PEAR DB uses to interact with PHP's msql extension + * for interacting with Mini SQL databases + * + * These methods overload the ones declared in DB_common. + * + * PHP's mSQL extension did weird things with NULL values prior to PHP + * 4.3.11 and 5.0.4. Make sure your version of PHP meets or exceeds + * those versions. + * + * @category Database + * @package DB + * @author Daniel Convissor + * @copyright 1997-2007 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version Release: 1.7.13 + * @link http://pear.php.net/package/DB + * @since Class not functional until Release 1.7.0 + */ +class DB_msql extends DB_common +{ + // {{{ properties + + /** + * The DB driver type (mysql, oci8, odbc, etc.) + * @var string + */ + var $phptype = 'msql'; + + /** + * The database syntax variant to be used (db2, access, etc.), if any + * @var string + */ + var $dbsyntax = 'msql'; + + /** + * The capabilities of this DB implementation + * + * The 'new_link' element contains the PHP version that first provided + * new_link support for this DBMS. Contains false if it's unsupported. + * + * Meaning of the 'limit' element: + * + 'emulate' = emulate with fetch row by number + * + 'alter' = alter the query + * + false = skip rows + * + * @var array + */ + var $features = array( + 'limit' => 'emulate', + 'new_link' => false, + 'numrows' => true, + 'pconnect' => true, + 'prepare' => false, + 'ssl' => false, + 'transactions' => false, + ); + + /** + * A mapping of native error codes to DB error codes + * @var array + */ + var $errorcode_map = array( + ); + + /** + * The raw database connection created by PHP + * @var resource + */ + var $connection; + + /** + * The DSN information for connecting to a database + * @var array + */ + var $dsn = array(); + + + /** + * The query result resource created by PHP + * + * Used to make affectedRows() work. Only contains the result for + * data manipulation queries. Contains false for other queries. + * + * @var resource + * @access private + */ + var $_result; + + + // }}} + // {{{ constructor + + /** + * This constructor calls $this->DB_common() + * + * @return void + */ + function DB_msql() + { + $this->DB_common(); + } + + // }}} + // {{{ connect() + + /** + * Connect to the database server, log in and open the database + * + * Don't call this method directly. Use DB::connect() instead. + * + * Example of how to connect: + * + * require_once 'DB.php'; + * + * // $dsn = 'msql://hostname/dbname'; // use a TCP connection + * $dsn = 'msql:///dbname'; // use a socket + * $options = array( + * 'portability' => DB_PORTABILITY_ALL, + * ); + * + * $db = DB::connect($dsn, $options); + * if (PEAR::isError($db)) { + * die($db->getMessage()); + * } + * + * + * @param array $dsn the data source name + * @param bool $persistent should the connection be persistent? + * + * @return int DB_OK on success. A DB_Error object on failure. + */ + function connect($dsn, $persistent = false) + { + if (!PEAR::loadExtension('msql')) { + return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND); + } + + $this->dsn = $dsn; + if ($dsn['dbsyntax']) { + $this->dbsyntax = $dsn['dbsyntax']; + } + + $params = array(); + if ($dsn['hostspec']) { + $params[] = $dsn['port'] + ? $dsn['hostspec'] . ',' . $dsn['port'] + : $dsn['hostspec']; + } + + $connect_function = $persistent ? 'msql_pconnect' : 'msql_connect'; + + $ini = ini_get('track_errors'); + $php_errormsg = ''; + if ($ini) { + $this->connection = @call_user_func_array($connect_function, + $params); + } else { + @ini_set('track_errors', 1); + $this->connection = @call_user_func_array($connect_function, + $params); + @ini_set('track_errors', $ini); + } + + if (!$this->connection) { + if (($err = @msql_error()) != '') { + return $this->raiseError(DB_ERROR_CONNECT_FAILED, + null, null, null, + $err); + } else { + return $this->raiseError(DB_ERROR_CONNECT_FAILED, + null, null, null, + $php_errormsg); + } + } + + if (!@msql_select_db($dsn['database'], $this->connection)) { + return $this->msqlRaiseError(); + } + return DB_OK; + } + + // }}} + // {{{ disconnect() + + /** + * Disconnects from the database server + * + * @return bool TRUE on success, FALSE on failure + */ + function disconnect() + { + $ret = @msql_close($this->connection); + $this->connection = null; + return $ret; + } + + // }}} + // {{{ simpleQuery() + + /** + * Sends a query to the database server + * + * @param string the SQL query string + * + * @return mixed + a PHP result resrouce for successful SELECT queries + * + the DB_OK constant for other successful queries + * + a DB_Error object on failure + */ + function simpleQuery($query) + { + $this->last_query = $query; + $query = $this->modifyQuery($query); + $result = @msql_query($query, $this->connection); + if (!$result) { + return $this->msqlRaiseError(); + } + // Determine which queries that should return data, and which + // should return an error code only. + if ($this->_checkManip($query)) { + $this->_result = $result; + return DB_OK; + } else { + $this->_result = false; + return $result; + } + } + + + // }}} + // {{{ nextResult() + + /** + * Move the internal msql result pointer to the next available result + * + * @param a valid fbsql result resource + * + * @access public + * + * @return true if a result is available otherwise return false + */ + function nextResult($result) + { + return false; + } + + // }}} + // {{{ fetchInto() + + /** + * Places a row from the result set into the given array + * + * Formating of the array and the data therein are configurable. + * See DB_result::fetchInto() for more information. + * + * This method is not meant to be called directly. Use + * DB_result::fetchInto() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * PHP's mSQL extension did weird things with NULL values prior to PHP + * 4.3.11 and 5.0.4. Make sure your version of PHP meets or exceeds + * those versions. + * + * @param resource $result the query result resource + * @param array $arr the referenced array to put the data in + * @param int $fetchmode how the resulting array should be indexed + * @param int $rownum the row number to fetch (0 = first row) + * + * @return mixed DB_OK on success, NULL when the end of a result set is + * reached or on failure + * + * @see DB_result::fetchInto() + */ + function fetchInto($result, &$arr, $fetchmode, $rownum = null) + { + if ($rownum !== null) { + if (!@msql_data_seek($result, $rownum)) { + return null; + } + } + if ($fetchmode & DB_FETCHMODE_ASSOC) { + $arr = @msql_fetch_array($result, MSQL_ASSOC); + if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE && $arr) { + $arr = array_change_key_case($arr, CASE_LOWER); + } + } else { + $arr = @msql_fetch_row($result); + } + if (!$arr) { + return null; + } + if ($this->options['portability'] & DB_PORTABILITY_RTRIM) { + $this->_rtrimArrayValues($arr); + } + if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) { + $this->_convertNullArrayValuesToEmpty($arr); + } + return DB_OK; + } + + // }}} + // {{{ freeResult() + + /** + * Deletes the result set and frees the memory occupied by the result set + * + * This method is not meant to be called directly. Use + * DB_result::free() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result PHP's query result resource + * + * @return bool TRUE on success, FALSE if $result is invalid + * + * @see DB_result::free() + */ + function freeResult($result) + { + return is_resource($result) ? msql_free_result($result) : false; + } + + // }}} + // {{{ numCols() + + /** + * Gets the number of columns in a result set + * + * This method is not meant to be called directly. Use + * DB_result::numCols() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result PHP's query result resource + * + * @return int the number of columns. A DB_Error object on failure. + * + * @see DB_result::numCols() + */ + function numCols($result) + { + $cols = @msql_num_fields($result); + if (!$cols) { + return $this->msqlRaiseError(); + } + return $cols; + } + + // }}} + // {{{ numRows() + + /** + * Gets the number of rows in a result set + * + * This method is not meant to be called directly. Use + * DB_result::numRows() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result PHP's query result resource + * + * @return int the number of rows. A DB_Error object on failure. + * + * @see DB_result::numRows() + */ + function numRows($result) + { + $rows = @msql_num_rows($result); + if ($rows === false) { + return $this->msqlRaiseError(); + } + return $rows; + } + + // }}} + // {{{ affected() + + /** + * Determines the number of rows affected by a data maniuplation query + * + * 0 is returned for queries that don't manipulate data. + * + * @return int the number of rows. A DB_Error object on failure. + */ + function affectedRows() + { + if (!$this->_result) { + return 0; + } + return msql_affected_rows($this->_result); + } + + // }}} + // {{{ nextId() + + /** + * Returns the next free id in a sequence + * + * @param string $seq_name name of the sequence + * @param boolean $ondemand when true, the seqence is automatically + * created if it does not exist + * + * @return int the next id number in the sequence. + * A DB_Error object on failure. + * + * @see DB_common::nextID(), DB_common::getSequenceName(), + * DB_msql::createSequence(), DB_msql::dropSequence() + */ + function nextId($seq_name, $ondemand = true) + { + $seqname = $this->getSequenceName($seq_name); + $repeat = false; + do { + $this->pushErrorHandling(PEAR_ERROR_RETURN); + $result = $this->query("SELECT _seq FROM ${seqname}"); + $this->popErrorHandling(); + if ($ondemand && DB::isError($result) && + $result->getCode() == DB_ERROR_NOSUCHTABLE) { + $repeat = true; + $this->pushErrorHandling(PEAR_ERROR_RETURN); + $result = $this->createSequence($seq_name); + $this->popErrorHandling(); + if (DB::isError($result)) { + return $this->raiseError($result); + } + } else { + $repeat = false; + } + } while ($repeat); + if (DB::isError($result)) { + return $this->raiseError($result); + } + $arr = $result->fetchRow(DB_FETCHMODE_ORDERED); + $result->free(); + return $arr[0]; + } + + // }}} + // {{{ createSequence() + + /** + * Creates a new sequence + * + * Also creates a new table to associate the sequence with. Uses + * a separate table to ensure portability with other drivers. + * + * @param string $seq_name name of the new sequence + * + * @return int DB_OK on success. A DB_Error object on failure. + * + * @see DB_common::createSequence(), DB_common::getSequenceName(), + * DB_msql::nextID(), DB_msql::dropSequence() + */ + function createSequence($seq_name) + { + $seqname = $this->getSequenceName($seq_name); + $res = $this->query('CREATE TABLE ' . $seqname + . ' (id INTEGER NOT NULL)'); + if (DB::isError($res)) { + return $res; + } + $res = $this->query("CREATE SEQUENCE ON ${seqname}"); + return $res; + } + + // }}} + // {{{ dropSequence() + + /** + * Deletes a sequence + * + * @param string $seq_name name of the sequence to be deleted + * + * @return int DB_OK on success. A DB_Error object on failure. + * + * @see DB_common::dropSequence(), DB_common::getSequenceName(), + * DB_msql::nextID(), DB_msql::createSequence() + */ + function dropSequence($seq_name) + { + return $this->query('DROP TABLE ' . $this->getSequenceName($seq_name)); + } + + // }}} + // {{{ quoteIdentifier() + + /** + * mSQL does not support delimited identifiers + * + * @param string $str the identifier name to be quoted + * + * @return object a DB_Error object + * + * @see DB_common::quoteIdentifier() + * @since Method available since Release 1.7.0 + */ + function quoteIdentifier($str) + { + return $this->raiseError(DB_ERROR_UNSUPPORTED); + } + + // }}} + // {{{ quoteFloat() + + /** + * Formats a float value for use within a query in a locale-independent + * manner. + * + * @param float the float value to be quoted. + * @return string the quoted string. + * @see DB_common::quoteSmart() + * @since Method available since release 1.7.8. + */ + function quoteFloat($float) { + return $this->escapeSimple(str_replace(',', '.', strval(floatval($float)))); + } + + // }}} + // {{{ escapeSimple() + + /** + * Escapes a string according to the current DBMS's standards + * + * @param string $str the string to be escaped + * + * @return string the escaped string + * + * @see DB_common::quoteSmart() + * @since Method available since Release 1.7.0 + */ + function escapeSimple($str) + { + return addslashes($str); + } + + // }}} + // {{{ msqlRaiseError() + + /** + * Produces a DB_Error object regarding the current problem + * + * @param int $errno if the error is being manually raised pass a + * DB_ERROR* constant here. If this isn't passed + * the error information gathered from the DBMS. + * + * @return object the DB_Error object + * + * @see DB_common::raiseError(), + * DB_msql::errorNative(), DB_msql::errorCode() + */ + function msqlRaiseError($errno = null) + { + $native = $this->errorNative(); + if ($errno === null) { + $errno = $this->errorCode($native); + } + return $this->raiseError($errno, null, null, null, $native); + } + + // }}} + // {{{ errorNative() + + /** + * Gets the DBMS' native error message produced by the last query + * + * @return string the DBMS' error message + */ + function errorNative() + { + return @msql_error(); + } + + // }}} + // {{{ errorCode() + + /** + * Determines PEAR::DB error code from the database's text error message + * + * @param string $errormsg the error message returned from the database + * + * @return integer the error number from a DB_ERROR* constant + */ + function errorCode($errormsg) + { + static $error_regexps; + + // PHP 5.2+ prepends the function name to $php_errormsg, so we need + // this hack to work around it, per bug #9599. + $errormsg = preg_replace('/^msql[a-z_]+\(\): /', '', $errormsg); + + if (!isset($error_regexps)) { + $error_regexps = array( + '/^Access to database denied/i' + => DB_ERROR_ACCESS_VIOLATION, + '/^Bad index name/i' + => DB_ERROR_ALREADY_EXISTS, + '/^Bad order field/i' + => DB_ERROR_SYNTAX, + '/^Bad type for comparison/i' + => DB_ERROR_SYNTAX, + '/^Can\'t perform LIKE on/i' + => DB_ERROR_SYNTAX, + '/^Can\'t use TEXT fields in LIKE comparison/i' + => DB_ERROR_SYNTAX, + '/^Couldn\'t create temporary table/i' + => DB_ERROR_CANNOT_CREATE, + '/^Error creating table file/i' + => DB_ERROR_CANNOT_CREATE, + '/^Field .* cannot be null$/i' + => DB_ERROR_CONSTRAINT_NOT_NULL, + '/^Index (field|condition) .* cannot be null$/i' + => DB_ERROR_SYNTAX, + '/^Invalid date format/i' + => DB_ERROR_INVALID_DATE, + '/^Invalid time format/i' + => DB_ERROR_INVALID, + '/^Literal value for .* is wrong type$/i' + => DB_ERROR_INVALID_NUMBER, + '/^No Database Selected/i' + => DB_ERROR_NODBSELECTED, + '/^No value specified for field/i' + => DB_ERROR_VALUE_COUNT_ON_ROW, + '/^Non unique value for unique index/i' + => DB_ERROR_CONSTRAINT, + '/^Out of memory for temporary table/i' + => DB_ERROR_CANNOT_CREATE, + '/^Permission denied/i' + => DB_ERROR_ACCESS_VIOLATION, + '/^Reference to un-selected table/i' + => DB_ERROR_SYNTAX, + '/^syntax error/i' + => DB_ERROR_SYNTAX, + '/^Table .* exists$/i' + => DB_ERROR_ALREADY_EXISTS, + '/^Unknown database/i' + => DB_ERROR_NOSUCHDB, + '/^Unknown field/i' + => DB_ERROR_NOSUCHFIELD, + '/^Unknown (index|system variable)/i' + => DB_ERROR_NOT_FOUND, + '/^Unknown table/i' + => DB_ERROR_NOSUCHTABLE, + '/^Unqualified field/i' + => DB_ERROR_SYNTAX, + ); + } + + foreach ($error_regexps as $regexp => $code) { + if (preg_match($regexp, $errormsg)) { + return $code; + } + } + return DB_ERROR; + } + + // }}} + // {{{ tableInfo() + + /** + * Returns information about a table or a result set + * + * @param object|string $result DB_result object from a query or a + * string containing the name of a table. + * While this also accepts a query result + * resource identifier, this behavior is + * deprecated. + * @param int $mode a valid tableInfo mode + * + * @return array an associative array with the information requested. + * A DB_Error object on failure. + * + * @see DB_common::setOption() + */ + function tableInfo($result, $mode = null) + { + if (is_string($result)) { + /* + * Probably received a table name. + * Create a result resource identifier. + */ + $id = @msql_query("SELECT * FROM $result", + $this->connection); + $got_string = true; + } elseif (isset($result->result)) { + /* + * Probably received a result object. + * Extract the result resource identifier. + */ + $id = $result->result; + $got_string = false; + } else { + /* + * Probably received a result resource identifier. + * Copy it. + * Deprecated. Here for compatibility only. + */ + $id = $result; + $got_string = false; + } + + if (!is_resource($id)) { + return $this->raiseError(DB_ERROR_NEED_MORE_DATA); + } + + if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) { + $case_func = 'strtolower'; + } else { + $case_func = 'strval'; + } + + $count = @msql_num_fields($id); + $res = array(); + + if ($mode) { + $res['num_fields'] = $count; + } + + for ($i = 0; $i < $count; $i++) { + $tmp = @msql_fetch_field($id); + + $flags = ''; + if ($tmp->not_null) { + $flags .= 'not_null '; + } + if ($tmp->unique) { + $flags .= 'unique_key '; + } + $flags = trim($flags); + + $res[$i] = array( + 'table' => $case_func($tmp->table), + 'name' => $case_func($tmp->name), + 'type' => $tmp->type, + 'len' => msql_field_len($id, $i), + 'flags' => $flags, + ); + + if ($mode & DB_TABLEINFO_ORDER) { + $res['order'][$res[$i]['name']] = $i; + } + if ($mode & DB_TABLEINFO_ORDERTABLE) { + $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i; + } + } + + // free the result only if we were called on a table + if ($got_string) { + @msql_free_result($id); + } + return $res; + } + + // }}} + // {{{ getSpecialQuery() + + /** + * Obtain a list of a given type of objects + * + * @param string $type the kind of objects you want to retrieve + * + * @return array the array containing the list of objects requested + * + * @access protected + * @see DB_common::getListOf() + */ + function getSpecialQuery($type) + { + switch ($type) { + case 'databases': + $id = @msql_list_dbs($this->connection); + break; + case 'tables': + $id = @msql_list_tables($this->dsn['database'], + $this->connection); + break; + default: + return null; + } + if (!$id) { + return $this->msqlRaiseError(); + } + $out = array(); + while ($row = @msql_fetch_row($id)) { + $out[] = $row[0]; + } + return $out; + } + + // }}} + +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ + +?> diff --git a/library/pear/DB/mssql.php b/library/pear/DB/mssql.php new file mode 100644 index 000000000..57e19b0fd --- /dev/null +++ b/library/pear/DB/mssql.php @@ -0,0 +1,963 @@ + + * @author Daniel Convissor + * @copyright 1997-2007 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version CVS: $Id: mssql.php,v 1.92 2007/09/21 13:40:41 aharvey Exp $ + * @link http://pear.php.net/package/DB + */ + +/** + * Obtain the DB_common class so it can be extended from + */ +require_once 'DB/common.php'; + +/** + * The methods PEAR DB uses to interact with PHP's mssql extension + * for interacting with Microsoft SQL Server databases + * + * These methods overload the ones declared in DB_common. + * + * DB's mssql driver is only for Microsfoft SQL Server databases. + * + * If you're connecting to a Sybase database, you MUST specify "sybase" + * as the "phptype" in the DSN. + * + * This class only works correctly if you have compiled PHP using + * --with-mssql=[dir_to_FreeTDS]. + * + * @category Database + * @package DB + * @author Sterling Hughes + * @author Daniel Convissor + * @copyright 1997-2007 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version Release: 1.7.13 + * @link http://pear.php.net/package/DB + */ +class DB_mssql extends DB_common +{ + // {{{ properties + + /** + * The DB driver type (mysql, oci8, odbc, etc.) + * @var string + */ + var $phptype = 'mssql'; + + /** + * The database syntax variant to be used (db2, access, etc.), if any + * @var string + */ + var $dbsyntax = 'mssql'; + + /** + * The capabilities of this DB implementation + * + * The 'new_link' element contains the PHP version that first provided + * new_link support for this DBMS. Contains false if it's unsupported. + * + * Meaning of the 'limit' element: + * + 'emulate' = emulate with fetch row by number + * + 'alter' = alter the query + * + false = skip rows + * + * @var array + */ + var $features = array( + 'limit' => 'emulate', + 'new_link' => false, + 'numrows' => true, + 'pconnect' => true, + 'prepare' => false, + 'ssl' => false, + 'transactions' => true, + ); + + /** + * A mapping of native error codes to DB error codes + * @var array + */ + // XXX Add here error codes ie: 'S100E' => DB_ERROR_SYNTAX + var $errorcode_map = array( + 102 => DB_ERROR_SYNTAX, + 110 => DB_ERROR_VALUE_COUNT_ON_ROW, + 155 => DB_ERROR_NOSUCHFIELD, + 156 => DB_ERROR_SYNTAX, + 170 => DB_ERROR_SYNTAX, + 207 => DB_ERROR_NOSUCHFIELD, + 208 => DB_ERROR_NOSUCHTABLE, + 245 => DB_ERROR_INVALID_NUMBER, + 319 => DB_ERROR_SYNTAX, + 321 => DB_ERROR_NOSUCHFIELD, + 325 => DB_ERROR_SYNTAX, + 336 => DB_ERROR_SYNTAX, + 515 => DB_ERROR_CONSTRAINT_NOT_NULL, + 547 => DB_ERROR_CONSTRAINT, + 1018 => DB_ERROR_SYNTAX, + 1035 => DB_ERROR_SYNTAX, + 1913 => DB_ERROR_ALREADY_EXISTS, + 2209 => DB_ERROR_SYNTAX, + 2223 => DB_ERROR_SYNTAX, + 2248 => DB_ERROR_SYNTAX, + 2256 => DB_ERROR_SYNTAX, + 2257 => DB_ERROR_SYNTAX, + 2627 => DB_ERROR_CONSTRAINT, + 2714 => DB_ERROR_ALREADY_EXISTS, + 3607 => DB_ERROR_DIVZERO, + 3701 => DB_ERROR_NOSUCHTABLE, + 7630 => DB_ERROR_SYNTAX, + 8134 => DB_ERROR_DIVZERO, + 9303 => DB_ERROR_SYNTAX, + 9317 => DB_ERROR_SYNTAX, + 9318 => DB_ERROR_SYNTAX, + 9331 => DB_ERROR_SYNTAX, + 9332 => DB_ERROR_SYNTAX, + 15253 => DB_ERROR_SYNTAX, + ); + + /** + * The raw database connection created by PHP + * @var resource + */ + var $connection; + + /** + * The DSN information for connecting to a database + * @var array + */ + var $dsn = array(); + + + /** + * Should data manipulation queries be committed automatically? + * @var bool + * @access private + */ + var $autocommit = true; + + /** + * The quantity of transactions begun + * + * {@internal While this is private, it can't actually be designated + * private in PHP 5 because it is directly accessed in the test suite.}} + * + * @var integer + * @access private + */ + var $transaction_opcount = 0; + + /** + * The database specified in the DSN + * + * It's a fix to allow calls to different databases in the same script. + * + * @var string + * @access private + */ + var $_db = null; + + + // }}} + // {{{ constructor + + /** + * This constructor calls $this->DB_common() + * + * @return void + */ + function DB_mssql() + { + $this->DB_common(); + } + + // }}} + // {{{ connect() + + /** + * Connect to the database server, log in and open the database + * + * Don't call this method directly. Use DB::connect() instead. + * + * @param array $dsn the data source name + * @param bool $persistent should the connection be persistent? + * + * @return int DB_OK on success. A DB_Error object on failure. + */ + function connect($dsn, $persistent = false) + { + if (!PEAR::loadExtension('mssql') && !PEAR::loadExtension('sybase') + && !PEAR::loadExtension('sybase_ct')) + { + return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND); + } + + $this->dsn = $dsn; + if ($dsn['dbsyntax']) { + $this->dbsyntax = $dsn['dbsyntax']; + } + + $params = array( + $dsn['hostspec'] ? $dsn['hostspec'] : 'localhost', + $dsn['username'] ? $dsn['username'] : null, + $dsn['password'] ? $dsn['password'] : null, + ); + if ($dsn['port']) { + $params[0] .= ((substr(PHP_OS, 0, 3) == 'WIN') ? ',' : ':') + . $dsn['port']; + } + + $connect_function = $persistent ? 'mssql_pconnect' : 'mssql_connect'; + + $this->connection = @call_user_func_array($connect_function, $params); + + if (!$this->connection) { + return $this->raiseError(DB_ERROR_CONNECT_FAILED, + null, null, null, + @mssql_get_last_message()); + } + if ($dsn['database']) { + if (!@mssql_select_db($dsn['database'], $this->connection)) { + return $this->raiseError(DB_ERROR_NODBSELECTED, + null, null, null, + @mssql_get_last_message()); + } + $this->_db = $dsn['database']; + } + return DB_OK; + } + + // }}} + // {{{ disconnect() + + /** + * Disconnects from the database server + * + * @return bool TRUE on success, FALSE on failure + */ + function disconnect() + { + $ret = @mssql_close($this->connection); + $this->connection = null; + return $ret; + } + + // }}} + // {{{ simpleQuery() + + /** + * Sends a query to the database server + * + * @param string the SQL query string + * + * @return mixed + a PHP result resrouce for successful SELECT queries + * + the DB_OK constant for other successful queries + * + a DB_Error object on failure + */ + function simpleQuery($query) + { + $ismanip = $this->_checkManip($query); + $this->last_query = $query; + if (!@mssql_select_db($this->_db, $this->connection)) { + return $this->mssqlRaiseError(DB_ERROR_NODBSELECTED); + } + $query = $this->modifyQuery($query); + if (!$this->autocommit && $ismanip) { + if ($this->transaction_opcount == 0) { + $result = @mssql_query('BEGIN TRAN', $this->connection); + if (!$result) { + return $this->mssqlRaiseError(); + } + } + $this->transaction_opcount++; + } + $result = @mssql_query($query, $this->connection); + if (!$result) { + return $this->mssqlRaiseError(); + } + // Determine which queries that should return data, and which + // should return an error code only. + return $ismanip ? DB_OK : $result; + } + + // }}} + // {{{ nextResult() + + /** + * Move the internal mssql result pointer to the next available result + * + * @param a valid fbsql result resource + * + * @access public + * + * @return true if a result is available otherwise return false + */ + function nextResult($result) + { + return @mssql_next_result($result); + } + + // }}} + // {{{ fetchInto() + + /** + * Places a row from the result set into the given array + * + * Formating of the array and the data therein are configurable. + * See DB_result::fetchInto() for more information. + * + * This method is not meant to be called directly. Use + * DB_result::fetchInto() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result the query result resource + * @param array $arr the referenced array to put the data in + * @param int $fetchmode how the resulting array should be indexed + * @param int $rownum the row number to fetch (0 = first row) + * + * @return mixed DB_OK on success, NULL when the end of a result set is + * reached or on failure + * + * @see DB_result::fetchInto() + */ + function fetchInto($result, &$arr, $fetchmode, $rownum = null) + { + if ($rownum !== null) { + if (!@mssql_data_seek($result, $rownum)) { + return null; + } + } + if ($fetchmode & DB_FETCHMODE_ASSOC) { + $arr = @mssql_fetch_assoc($result); + if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE && $arr) { + $arr = array_change_key_case($arr, CASE_LOWER); + } + } else { + $arr = @mssql_fetch_row($result); + } + if (!$arr) { + return null; + } + if ($this->options['portability'] & DB_PORTABILITY_RTRIM) { + $this->_rtrimArrayValues($arr); + } + if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) { + $this->_convertNullArrayValuesToEmpty($arr); + } + return DB_OK; + } + + // }}} + // {{{ freeResult() + + /** + * Deletes the result set and frees the memory occupied by the result set + * + * This method is not meant to be called directly. Use + * DB_result::free() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result PHP's query result resource + * + * @return bool TRUE on success, FALSE if $result is invalid + * + * @see DB_result::free() + */ + function freeResult($result) + { + return is_resource($result) ? mssql_free_result($result) : false; + } + + // }}} + // {{{ numCols() + + /** + * Gets the number of columns in a result set + * + * This method is not meant to be called directly. Use + * DB_result::numCols() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result PHP's query result resource + * + * @return int the number of columns. A DB_Error object on failure. + * + * @see DB_result::numCols() + */ + function numCols($result) + { + $cols = @mssql_num_fields($result); + if (!$cols) { + return $this->mssqlRaiseError(); + } + return $cols; + } + + // }}} + // {{{ numRows() + + /** + * Gets the number of rows in a result set + * + * This method is not meant to be called directly. Use + * DB_result::numRows() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result PHP's query result resource + * + * @return int the number of rows. A DB_Error object on failure. + * + * @see DB_result::numRows() + */ + function numRows($result) + { + $rows = @mssql_num_rows($result); + if ($rows === false) { + return $this->mssqlRaiseError(); + } + return $rows; + } + + // }}} + // {{{ autoCommit() + + /** + * Enables or disables automatic commits + * + * @param bool $onoff true turns it on, false turns it off + * + * @return int DB_OK on success. A DB_Error object if the driver + * doesn't support auto-committing transactions. + */ + function autoCommit($onoff = false) + { + // XXX if $this->transaction_opcount > 0, we should probably + // issue a warning here. + $this->autocommit = $onoff ? true : false; + return DB_OK; + } + + // }}} + // {{{ commit() + + /** + * Commits the current transaction + * + * @return int DB_OK on success. A DB_Error object on failure. + */ + function commit() + { + if ($this->transaction_opcount > 0) { + if (!@mssql_select_db($this->_db, $this->connection)) { + return $this->mssqlRaiseError(DB_ERROR_NODBSELECTED); + } + $result = @mssql_query('COMMIT TRAN', $this->connection); + $this->transaction_opcount = 0; + if (!$result) { + return $this->mssqlRaiseError(); + } + } + return DB_OK; + } + + // }}} + // {{{ rollback() + + /** + * Reverts the current transaction + * + * @return int DB_OK on success. A DB_Error object on failure. + */ + function rollback() + { + if ($this->transaction_opcount > 0) { + if (!@mssql_select_db($this->_db, $this->connection)) { + return $this->mssqlRaiseError(DB_ERROR_NODBSELECTED); + } + $result = @mssql_query('ROLLBACK TRAN', $this->connection); + $this->transaction_opcount = 0; + if (!$result) { + return $this->mssqlRaiseError(); + } + } + return DB_OK; + } + + // }}} + // {{{ affectedRows() + + /** + * Determines the number of rows affected by a data maniuplation query + * + * 0 is returned for queries that don't manipulate data. + * + * @return int the number of rows. A DB_Error object on failure. + */ + function affectedRows() + { + if ($this->_last_query_manip) { + $res = @mssql_query('select @@rowcount', $this->connection); + if (!$res) { + return $this->mssqlRaiseError(); + } + $ar = @mssql_fetch_row($res); + if (!$ar) { + $result = 0; + } else { + @mssql_free_result($res); + $result = $ar[0]; + } + } else { + $result = 0; + } + return $result; + } + + // }}} + // {{{ nextId() + + /** + * Returns the next free id in a sequence + * + * @param string $seq_name name of the sequence + * @param boolean $ondemand when true, the seqence is automatically + * created if it does not exist + * + * @return int the next id number in the sequence. + * A DB_Error object on failure. + * + * @see DB_common::nextID(), DB_common::getSequenceName(), + * DB_mssql::createSequence(), DB_mssql::dropSequence() + */ + function nextId($seq_name, $ondemand = true) + { + $seqname = $this->getSequenceName($seq_name); + if (!@mssql_select_db($this->_db, $this->connection)) { + return $this->mssqlRaiseError(DB_ERROR_NODBSELECTED); + } + $repeat = 0; + do { + $this->pushErrorHandling(PEAR_ERROR_RETURN); + $result = $this->query("INSERT INTO $seqname (vapor) VALUES (0)"); + $this->popErrorHandling(); + if ($ondemand && DB::isError($result) && + ($result->getCode() == DB_ERROR || $result->getCode() == DB_ERROR_NOSUCHTABLE)) + { + $repeat = 1; + $result = $this->createSequence($seq_name); + if (DB::isError($result)) { + return $this->raiseError($result); + } + } elseif (!DB::isError($result)) { + $result = $this->query("SELECT IDENT_CURRENT('$seqname')"); + if (DB::isError($result)) { + /* Fallback code for MS SQL Server 7.0, which doesn't have + * IDENT_CURRENT. This is *not* safe for concurrent + * requests, and really, if you're using it, you're in a + * world of hurt. Nevertheless, it's here to ensure BC. See + * bug #181 for the gory details.*/ + $result = $this->query("SELECT @@IDENTITY FROM $seqname"); + } + $repeat = 0; + } else { + $repeat = false; + } + } while ($repeat); + if (DB::isError($result)) { + return $this->raiseError($result); + } + $result = $result->fetchRow(DB_FETCHMODE_ORDERED); + return $result[0]; + } + + /** + * Creates a new sequence + * + * @param string $seq_name name of the new sequence + * + * @return int DB_OK on success. A DB_Error object on failure. + * + * @see DB_common::createSequence(), DB_common::getSequenceName(), + * DB_mssql::nextID(), DB_mssql::dropSequence() + */ + function createSequence($seq_name) + { + return $this->query('CREATE TABLE ' + . $this->getSequenceName($seq_name) + . ' ([id] [int] IDENTITY (1, 1) NOT NULL,' + . ' [vapor] [int] NULL)'); + } + + // }}} + // {{{ dropSequence() + + /** + * Deletes a sequence + * + * @param string $seq_name name of the sequence to be deleted + * + * @return int DB_OK on success. A DB_Error object on failure. + * + * @see DB_common::dropSequence(), DB_common::getSequenceName(), + * DB_mssql::nextID(), DB_mssql::createSequence() + */ + function dropSequence($seq_name) + { + return $this->query('DROP TABLE ' . $this->getSequenceName($seq_name)); + } + + // }}} + // {{{ quoteIdentifier() + + /** + * Quotes a string so it can be safely used as a table or column name + * + * @param string $str identifier name to be quoted + * + * @return string quoted identifier string + * + * @see DB_common::quoteIdentifier() + * @since Method available since Release 1.6.0 + */ + function quoteIdentifier($str) + { + return '[' . str_replace(']', ']]', $str) . ']'; + } + + // }}} + // {{{ mssqlRaiseError() + + /** + * Produces a DB_Error object regarding the current problem + * + * @param int $errno if the error is being manually raised pass a + * DB_ERROR* constant here. If this isn't passed + * the error information gathered from the DBMS. + * + * @return object the DB_Error object + * + * @see DB_common::raiseError(), + * DB_mssql::errorNative(), DB_mssql::errorCode() + */ + function mssqlRaiseError($code = null) + { + $message = @mssql_get_last_message(); + if (!$code) { + $code = $this->errorNative(); + } + return $this->raiseError($this->errorCode($code, $message), + null, null, null, "$code - $message"); + } + + // }}} + // {{{ errorNative() + + /** + * Gets the DBMS' native error code produced by the last query + * + * @return int the DBMS' error code + */ + function errorNative() + { + $res = @mssql_query('select @@ERROR as ErrorCode', $this->connection); + if (!$res) { + return DB_ERROR; + } + $row = @mssql_fetch_row($res); + return $row[0]; + } + + // }}} + // {{{ errorCode() + + /** + * Determines PEAR::DB error code from mssql's native codes. + * + * If $nativecode isn't known yet, it will be looked up. + * + * @param mixed $nativecode mssql error code, if known + * @return integer an error number from a DB error constant + * @see errorNative() + */ + function errorCode($nativecode = null, $msg = '') + { + if (!$nativecode) { + $nativecode = $this->errorNative(); + } + if (isset($this->errorcode_map[$nativecode])) { + if ($nativecode == 3701 + && preg_match('/Cannot drop the index/i', $msg)) + { + return DB_ERROR_NOT_FOUND; + } + return $this->errorcode_map[$nativecode]; + } else { + return DB_ERROR; + } + } + + // }}} + // {{{ tableInfo() + + /** + * Returns information about a table or a result set + * + * NOTE: only supports 'table' and 'flags' if $result + * is a table name. + * + * @param object|string $result DB_result object from a query or a + * string containing the name of a table. + * While this also accepts a query result + * resource identifier, this behavior is + * deprecated. + * @param int $mode a valid tableInfo mode + * + * @return array an associative array with the information requested. + * A DB_Error object on failure. + * + * @see DB_common::tableInfo() + */ + function tableInfo($result, $mode = null) + { + if (is_string($result)) { + /* + * Probably received a table name. + * Create a result resource identifier. + */ + if (!@mssql_select_db($this->_db, $this->connection)) { + return $this->mssqlRaiseError(DB_ERROR_NODBSELECTED); + } + $id = @mssql_query("SELECT * FROM $result WHERE 1=0", + $this->connection); + $got_string = true; + } elseif (isset($result->result)) { + /* + * Probably received a result object. + * Extract the result resource identifier. + */ + $id = $result->result; + $got_string = false; + } else { + /* + * Probably received a result resource identifier. + * Copy it. + * Deprecated. Here for compatibility only. + */ + $id = $result; + $got_string = false; + } + + if (!is_resource($id)) { + return $this->mssqlRaiseError(DB_ERROR_NEED_MORE_DATA); + } + + if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) { + $case_func = 'strtolower'; + } else { + $case_func = 'strval'; + } + + $count = @mssql_num_fields($id); + $res = array(); + + if ($mode) { + $res['num_fields'] = $count; + } + + for ($i = 0; $i < $count; $i++) { + if ($got_string) { + $flags = $this->_mssql_field_flags($result, + @mssql_field_name($id, $i)); + if (DB::isError($flags)) { + return $flags; + } + } else { + $flags = ''; + } + + $res[$i] = array( + 'table' => $got_string ? $case_func($result) : '', + 'name' => $case_func(@mssql_field_name($id, $i)), + 'type' => @mssql_field_type($id, $i), + 'len' => @mssql_field_length($id, $i), + 'flags' => $flags, + ); + if ($mode & DB_TABLEINFO_ORDER) { + $res['order'][$res[$i]['name']] = $i; + } + if ($mode & DB_TABLEINFO_ORDERTABLE) { + $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i; + } + } + + // free the result only if we were called on a table + if ($got_string) { + @mssql_free_result($id); + } + return $res; + } + + // }}} + // {{{ _mssql_field_flags() + + /** + * Get a column's flags + * + * Supports "not_null", "primary_key", + * "auto_increment" (mssql identity), "timestamp" (mssql timestamp), + * "unique_key" (mssql unique index, unique check or primary_key) and + * "multiple_key" (multikey index) + * + * mssql timestamp is NOT similar to the mysql timestamp so this is maybe + * not useful at all - is the behaviour of mysql_field_flags that primary + * keys are alway unique? is the interpretation of multiple_key correct? + * + * @param string $table the table name + * @param string $column the field name + * + * @return string the flags + * + * @access private + * @author Joern Barthel + */ + function _mssql_field_flags($table, $column) + { + static $tableName = null; + static $flags = array(); + + if ($table != $tableName) { + + $flags = array(); + $tableName = $table; + + // get unique and primary keys + $res = $this->getAll("EXEC SP_HELPINDEX $table", DB_FETCHMODE_ASSOC); + if (DB::isError($res)) { + return $res; + } + + foreach ($res as $val) { + $keys = explode(', ', $val['index_keys']); + + if (sizeof($keys) > 1) { + foreach ($keys as $key) { + $this->_add_flag($flags[$key], 'multiple_key'); + } + } + + if (strpos($val['index_description'], 'primary key')) { + foreach ($keys as $key) { + $this->_add_flag($flags[$key], 'primary_key'); + } + } elseif (strpos($val['index_description'], 'unique')) { + foreach ($keys as $key) { + $this->_add_flag($flags[$key], 'unique_key'); + } + } + } + + // get auto_increment, not_null and timestamp + $res = $this->getAll("EXEC SP_COLUMNS $table", DB_FETCHMODE_ASSOC); + if (DB::isError($res)) { + return $res; + } + + foreach ($res as $val) { + $val = array_change_key_case($val, CASE_LOWER); + if ($val['nullable'] == '0') { + $this->_add_flag($flags[$val['column_name']], 'not_null'); + } + if (strpos($val['type_name'], 'identity')) { + $this->_add_flag($flags[$val['column_name']], 'auto_increment'); + } + if (strpos($val['type_name'], 'timestamp')) { + $this->_add_flag($flags[$val['column_name']], 'timestamp'); + } + } + } + + if (array_key_exists($column, $flags)) { + return(implode(' ', $flags[$column])); + } + return ''; + } + + // }}} + // {{{ _add_flag() + + /** + * Adds a string to the flags array if the flag is not yet in there + * - if there is no flag present the array is created + * + * @param array &$array the reference to the flag-array + * @param string $value the flag value + * + * @return void + * + * @access private + * @author Joern Barthel + */ + function _add_flag(&$array, $value) + { + if (!is_array($array)) { + $array = array($value); + } elseif (!in_array($value, $array)) { + array_push($array, $value); + } + } + + // }}} + // {{{ getSpecialQuery() + + /** + * Obtains the query string needed for listing a given type of objects + * + * @param string $type the kind of objects you want to retrieve + * + * @return string the SQL query string or null if the driver doesn't + * support the object type requested + * + * @access protected + * @see DB_common::getListOf() + */ + function getSpecialQuery($type) + { + switch ($type) { + case 'tables': + return "SELECT name FROM sysobjects WHERE type = 'U'" + . ' ORDER BY name'; + case 'views': + return "SELECT name FROM sysobjects WHERE type = 'V'"; + default: + return null; + } + } + + // }}} +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ + +?> diff --git a/library/pear/DB/mysql.php b/library/pear/DB/mysql.php new file mode 100644 index 000000000..e9b5e70c9 --- /dev/null +++ b/library/pear/DB/mysql.php @@ -0,0 +1,1045 @@ + + * @author Daniel Convissor + * @copyright 1997-2007 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version CVS: $Id: mysql.php,v 1.126 2007/09/21 13:32:52 aharvey Exp $ + * @link http://pear.php.net/package/DB + */ + +/** + * Obtain the DB_common class so it can be extended from + */ +require_once 'DB/common.php'; + +/** + * The methods PEAR DB uses to interact with PHP's mysql extension + * for interacting with MySQL databases + * + * These methods overload the ones declared in DB_common. + * + * @category Database + * @package DB + * @author Stig Bakken + * @author Daniel Convissor + * @copyright 1997-2007 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version Release: 1.7.13 + * @link http://pear.php.net/package/DB + */ +class DB_mysql extends DB_common +{ + // {{{ properties + + /** + * The DB driver type (mysql, oci8, odbc, etc.) + * @var string + */ + var $phptype = 'mysql'; + + /** + * The database syntax variant to be used (db2, access, etc.), if any + * @var string + */ + var $dbsyntax = 'mysql'; + + /** + * The capabilities of this DB implementation + * + * The 'new_link' element contains the PHP version that first provided + * new_link support for this DBMS. Contains false if it's unsupported. + * + * Meaning of the 'limit' element: + * + 'emulate' = emulate with fetch row by number + * + 'alter' = alter the query + * + false = skip rows + * + * @var array + */ + var $features = array( + 'limit' => 'alter', + 'new_link' => '4.2.0', + 'numrows' => true, + 'pconnect' => true, + 'prepare' => false, + 'ssl' => false, + 'transactions' => true, + ); + + /** + * A mapping of native error codes to DB error codes + * @var array + */ + var $errorcode_map = array( + 1004 => DB_ERROR_CANNOT_CREATE, + 1005 => DB_ERROR_CANNOT_CREATE, + 1006 => DB_ERROR_CANNOT_CREATE, + 1007 => DB_ERROR_ALREADY_EXISTS, + 1008 => DB_ERROR_CANNOT_DROP, + 1022 => DB_ERROR_ALREADY_EXISTS, + 1044 => DB_ERROR_ACCESS_VIOLATION, + 1046 => DB_ERROR_NODBSELECTED, + 1048 => DB_ERROR_CONSTRAINT, + 1049 => DB_ERROR_NOSUCHDB, + 1050 => DB_ERROR_ALREADY_EXISTS, + 1051 => DB_ERROR_NOSUCHTABLE, + 1054 => DB_ERROR_NOSUCHFIELD, + 1061 => DB_ERROR_ALREADY_EXISTS, + 1062 => DB_ERROR_ALREADY_EXISTS, + 1064 => DB_ERROR_SYNTAX, + 1091 => DB_ERROR_NOT_FOUND, + 1100 => DB_ERROR_NOT_LOCKED, + 1136 => DB_ERROR_VALUE_COUNT_ON_ROW, + 1142 => DB_ERROR_ACCESS_VIOLATION, + 1146 => DB_ERROR_NOSUCHTABLE, + 1216 => DB_ERROR_CONSTRAINT, + 1217 => DB_ERROR_CONSTRAINT, + 1356 => DB_ERROR_DIVZERO, + 1451 => DB_ERROR_CONSTRAINT, + 1452 => DB_ERROR_CONSTRAINT, + ); + + /** + * The raw database connection created by PHP + * @var resource + */ + var $connection; + + /** + * The DSN information for connecting to a database + * @var array + */ + var $dsn = array(); + + + /** + * Should data manipulation queries be committed automatically? + * @var bool + * @access private + */ + var $autocommit = true; + + /** + * The quantity of transactions begun + * + * {@internal While this is private, it can't actually be designated + * private in PHP 5 because it is directly accessed in the test suite.}} + * + * @var integer + * @access private + */ + var $transaction_opcount = 0; + + /** + * The database specified in the DSN + * + * It's a fix to allow calls to different databases in the same script. + * + * @var string + * @access private + */ + var $_db = ''; + + + // }}} + // {{{ constructor + + /** + * This constructor calls $this->DB_common() + * + * @return void + */ + function DB_mysql() + { + $this->DB_common(); + } + + // }}} + // {{{ connect() + + /** + * Connect to the database server, log in and open the database + * + * Don't call this method directly. Use DB::connect() instead. + * + * PEAR DB's mysql driver supports the following extra DSN options: + * + new_link If set to true, causes subsequent calls to connect() + * to return a new connection link instead of the + * existing one. WARNING: this is not portable to + * other DBMS's. Available since PEAR DB 1.7.0. + * + client_flags Any combination of MYSQL_CLIENT_* constants. + * Only used if PHP is at version 4.3.0 or greater. + * Available since PEAR DB 1.7.0. + * + * @param array $dsn the data source name + * @param bool $persistent should the connection be persistent? + * + * @return int DB_OK on success. A DB_Error object on failure. + */ + function connect($dsn, $persistent = false) + { + if (!PEAR::loadExtension('mysql')) { + return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND); + } + + $this->dsn = $dsn; + if ($dsn['dbsyntax']) { + $this->dbsyntax = $dsn['dbsyntax']; + } + + $params = array(); + if ($dsn['protocol'] && $dsn['protocol'] == 'unix') { + $params[0] = ':' . $dsn['socket']; + } else { + $params[0] = $dsn['hostspec'] ? $dsn['hostspec'] + : 'localhost'; + if ($dsn['port']) { + $params[0] .= ':' . $dsn['port']; + } + } + $params[] = $dsn['username'] ? $dsn['username'] : null; + $params[] = $dsn['password'] ? $dsn['password'] : null; + + if (!$persistent) { + if (isset($dsn['new_link']) + && ($dsn['new_link'] == 'true' || $dsn['new_link'] === true)) + { + $params[] = true; + } else { + $params[] = false; + } + } + if (version_compare(phpversion(), '4.3.0', '>=')) { + $params[] = isset($dsn['client_flags']) + ? $dsn['client_flags'] : null; + } + + $connect_function = $persistent ? 'mysql_pconnect' : 'mysql_connect'; + + $ini = ini_get('track_errors'); + $php_errormsg = ''; + if ($ini) { + $this->connection = @call_user_func_array($connect_function, + $params); + } else { + @ini_set('track_errors', 1); + $this->connection = @call_user_func_array($connect_function, + $params); + @ini_set('track_errors', $ini); + } + + if (!$this->connection) { + if (($err = @mysql_error()) != '') { + return $this->raiseError(DB_ERROR_CONNECT_FAILED, + null, null, null, + $err); + } else { + return $this->raiseError(DB_ERROR_CONNECT_FAILED, + null, null, null, + $php_errormsg); + } + } + + if ($dsn['database']) { + if (!@mysql_select_db($dsn['database'], $this->connection)) { + return $this->mysqlRaiseError(); + } + $this->_db = $dsn['database']; + } + + return DB_OK; + } + + // }}} + // {{{ disconnect() + + /** + * Disconnects from the database server + * + * @return bool TRUE on success, FALSE on failure + */ + function disconnect() + { + $ret = @mysql_close($this->connection); + $this->connection = null; + return $ret; + } + + // }}} + // {{{ simpleQuery() + + /** + * Sends a query to the database server + * + * Generally uses mysql_query(). If you want to use + * mysql_unbuffered_query() set the "result_buffering" option to 0 using + * setOptions(). This option was added in Release 1.7.0. + * + * @param string the SQL query string + * + * @return mixed + a PHP result resrouce for successful SELECT queries + * + the DB_OK constant for other successful queries + * + a DB_Error object on failure + */ + function simpleQuery($query) + { + $ismanip = $this->_checkManip($query); + $this->last_query = $query; + $query = $this->modifyQuery($query); + if ($this->_db) { + if (!@mysql_select_db($this->_db, $this->connection)) { + return $this->mysqlRaiseError(DB_ERROR_NODBSELECTED); + } + } + if (!$this->autocommit && $ismanip) { + if ($this->transaction_opcount == 0) { + $result = @mysql_query('SET AUTOCOMMIT=0', $this->connection); + $result = @mysql_query('BEGIN', $this->connection); + if (!$result) { + return $this->mysqlRaiseError(); + } + } + $this->transaction_opcount++; + } + if (!$this->options['result_buffering']) { + $result = @mysql_unbuffered_query($query, $this->connection); + } else { + $result = @mysql_query($query, $this->connection); + } + if (!$result) { + return $this->mysqlRaiseError(); + } + if (is_resource($result)) { + return $result; + } + return DB_OK; + } + + // }}} + // {{{ nextResult() + + /** + * Move the internal mysql result pointer to the next available result + * + * This method has not been implemented yet. + * + * @param a valid sql result resource + * + * @return false + */ + function nextResult($result) + { + return false; + } + + // }}} + // {{{ fetchInto() + + /** + * Places a row from the result set into the given array + * + * Formating of the array and the data therein are configurable. + * See DB_result::fetchInto() for more information. + * + * This method is not meant to be called directly. Use + * DB_result::fetchInto() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result the query result resource + * @param array $arr the referenced array to put the data in + * @param int $fetchmode how the resulting array should be indexed + * @param int $rownum the row number to fetch (0 = first row) + * + * @return mixed DB_OK on success, NULL when the end of a result set is + * reached or on failure + * + * @see DB_result::fetchInto() + */ + function fetchInto($result, &$arr, $fetchmode, $rownum = null) + { + if ($rownum !== null) { + if (!@mysql_data_seek($result, $rownum)) { + return null; + } + } + if ($fetchmode & DB_FETCHMODE_ASSOC) { + $arr = @mysql_fetch_array($result, MYSQL_ASSOC); + if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE && $arr) { + $arr = array_change_key_case($arr, CASE_LOWER); + } + } else { + $arr = @mysql_fetch_row($result); + } + if (!$arr) { + return null; + } + if ($this->options['portability'] & DB_PORTABILITY_RTRIM) { + /* + * Even though this DBMS already trims output, we do this because + * a field might have intentional whitespace at the end that + * gets removed by DB_PORTABILITY_RTRIM under another driver. + */ + $this->_rtrimArrayValues($arr); + } + if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) { + $this->_convertNullArrayValuesToEmpty($arr); + } + return DB_OK; + } + + // }}} + // {{{ freeResult() + + /** + * Deletes the result set and frees the memory occupied by the result set + * + * This method is not meant to be called directly. Use + * DB_result::free() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result PHP's query result resource + * + * @return bool TRUE on success, FALSE if $result is invalid + * + * @see DB_result::free() + */ + function freeResult($result) + { + return is_resource($result) ? mysql_free_result($result) : false; + } + + // }}} + // {{{ numCols() + + /** + * Gets the number of columns in a result set + * + * This method is not meant to be called directly. Use + * DB_result::numCols() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result PHP's query result resource + * + * @return int the number of columns. A DB_Error object on failure. + * + * @see DB_result::numCols() + */ + function numCols($result) + { + $cols = @mysql_num_fields($result); + if (!$cols) { + return $this->mysqlRaiseError(); + } + return $cols; + } + + // }}} + // {{{ numRows() + + /** + * Gets the number of rows in a result set + * + * This method is not meant to be called directly. Use + * DB_result::numRows() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result PHP's query result resource + * + * @return int the number of rows. A DB_Error object on failure. + * + * @see DB_result::numRows() + */ + function numRows($result) + { + $rows = @mysql_num_rows($result); + if ($rows === null) { + return $this->mysqlRaiseError(); + } + return $rows; + } + + // }}} + // {{{ autoCommit() + + /** + * Enables or disables automatic commits + * + * @param bool $onoff true turns it on, false turns it off + * + * @return int DB_OK on success. A DB_Error object if the driver + * doesn't support auto-committing transactions. + */ + function autoCommit($onoff = false) + { + // XXX if $this->transaction_opcount > 0, we should probably + // issue a warning here. + $this->autocommit = $onoff ? true : false; + return DB_OK; + } + + // }}} + // {{{ commit() + + /** + * Commits the current transaction + * + * @return int DB_OK on success. A DB_Error object on failure. + */ + function commit() + { + if ($this->transaction_opcount > 0) { + if ($this->_db) { + if (!@mysql_select_db($this->_db, $this->connection)) { + return $this->mysqlRaiseError(DB_ERROR_NODBSELECTED); + } + } + $result = @mysql_query('COMMIT', $this->connection); + $result = @mysql_query('SET AUTOCOMMIT=1', $this->connection); + $this->transaction_opcount = 0; + if (!$result) { + return $this->mysqlRaiseError(); + } + } + return DB_OK; + } + + // }}} + // {{{ rollback() + + /** + * Reverts the current transaction + * + * @return int DB_OK on success. A DB_Error object on failure. + */ + function rollback() + { + if ($this->transaction_opcount > 0) { + if ($this->_db) { + if (!@mysql_select_db($this->_db, $this->connection)) { + return $this->mysqlRaiseError(DB_ERROR_NODBSELECTED); + } + } + $result = @mysql_query('ROLLBACK', $this->connection); + $result = @mysql_query('SET AUTOCOMMIT=1', $this->connection); + $this->transaction_opcount = 0; + if (!$result) { + return $this->mysqlRaiseError(); + } + } + return DB_OK; + } + + // }}} + // {{{ affectedRows() + + /** + * Determines the number of rows affected by a data maniuplation query + * + * 0 is returned for queries that don't manipulate data. + * + * @return int the number of rows. A DB_Error object on failure. + */ + function affectedRows() + { + if ($this->_last_query_manip) { + return @mysql_affected_rows($this->connection); + } else { + return 0; + } + } + + // }}} + // {{{ nextId() + + /** + * Returns the next free id in a sequence + * + * @param string $seq_name name of the sequence + * @param boolean $ondemand when true, the seqence is automatically + * created if it does not exist + * + * @return int the next id number in the sequence. + * A DB_Error object on failure. + * + * @see DB_common::nextID(), DB_common::getSequenceName(), + * DB_mysql::createSequence(), DB_mysql::dropSequence() + */ + function nextId($seq_name, $ondemand = true) + { + $seqname = $this->getSequenceName($seq_name); + do { + $repeat = 0; + $this->pushErrorHandling(PEAR_ERROR_RETURN); + $result = $this->query("UPDATE ${seqname} ". + 'SET id=LAST_INSERT_ID(id+1)'); + $this->popErrorHandling(); + if ($result === DB_OK) { + // COMMON CASE + $id = @mysql_insert_id($this->connection); + if ($id != 0) { + return $id; + } + // EMPTY SEQ TABLE + // Sequence table must be empty for some reason, so fill + // it and return 1 and obtain a user-level lock + $result = $this->getOne("SELECT GET_LOCK('${seqname}_lock',10)"); + if (DB::isError($result)) { + return $this->raiseError($result); + } + if ($result == 0) { + // Failed to get the lock + return $this->mysqlRaiseError(DB_ERROR_NOT_LOCKED); + } + + // add the default value + $result = $this->query("REPLACE INTO ${seqname} (id) VALUES (0)"); + if (DB::isError($result)) { + return $this->raiseError($result); + } + + // Release the lock + $result = $this->getOne('SELECT RELEASE_LOCK(' + . "'${seqname}_lock')"); + if (DB::isError($result)) { + return $this->raiseError($result); + } + // We know what the result will be, so no need to try again + return 1; + + } elseif ($ondemand && DB::isError($result) && + $result->getCode() == DB_ERROR_NOSUCHTABLE) + { + // ONDEMAND TABLE CREATION + $result = $this->createSequence($seq_name); + if (DB::isError($result)) { + return $this->raiseError($result); + } else { + $repeat = 1; + } + + } elseif (DB::isError($result) && + $result->getCode() == DB_ERROR_ALREADY_EXISTS) + { + // BACKWARDS COMPAT + // see _BCsequence() comment + $result = $this->_BCsequence($seqname); + if (DB::isError($result)) { + return $this->raiseError($result); + } + $repeat = 1; + } + } while ($repeat); + + return $this->raiseError($result); + } + + // }}} + // {{{ createSequence() + + /** + * Creates a new sequence + * + * @param string $seq_name name of the new sequence + * + * @return int DB_OK on success. A DB_Error object on failure. + * + * @see DB_common::createSequence(), DB_common::getSequenceName(), + * DB_mysql::nextID(), DB_mysql::dropSequence() + */ + function createSequence($seq_name) + { + $seqname = $this->getSequenceName($seq_name); + $res = $this->query('CREATE TABLE ' . $seqname + . ' (id INTEGER UNSIGNED AUTO_INCREMENT NOT NULL,' + . ' PRIMARY KEY(id))'); + if (DB::isError($res)) { + return $res; + } + // insert yields value 1, nextId call will generate ID 2 + $res = $this->query("INSERT INTO ${seqname} (id) VALUES (0)"); + if (DB::isError($res)) { + return $res; + } + // so reset to zero + return $this->query("UPDATE ${seqname} SET id = 0"); + } + + // }}} + // {{{ dropSequence() + + /** + * Deletes a sequence + * + * @param string $seq_name name of the sequence to be deleted + * + * @return int DB_OK on success. A DB_Error object on failure. + * + * @see DB_common::dropSequence(), DB_common::getSequenceName(), + * DB_mysql::nextID(), DB_mysql::createSequence() + */ + function dropSequence($seq_name) + { + return $this->query('DROP TABLE ' . $this->getSequenceName($seq_name)); + } + + // }}} + // {{{ _BCsequence() + + /** + * Backwards compatibility with old sequence emulation implementation + * (clean up the dupes) + * + * @param string $seqname the sequence name to clean up + * + * @return bool true on success. A DB_Error object on failure. + * + * @access private + */ + function _BCsequence($seqname) + { + // Obtain a user-level lock... this will release any previous + // application locks, but unlike LOCK TABLES, it does not abort + // the current transaction and is much less frequently used. + $result = $this->getOne("SELECT GET_LOCK('${seqname}_lock',10)"); + if (DB::isError($result)) { + return $result; + } + if ($result == 0) { + // Failed to get the lock, can't do the conversion, bail + // with a DB_ERROR_NOT_LOCKED error + return $this->mysqlRaiseError(DB_ERROR_NOT_LOCKED); + } + + $highest_id = $this->getOne("SELECT MAX(id) FROM ${seqname}"); + if (DB::isError($highest_id)) { + return $highest_id; + } + // This should kill all rows except the highest + // We should probably do something if $highest_id isn't + // numeric, but I'm at a loss as how to handle that... + $result = $this->query('DELETE FROM ' . $seqname + . " WHERE id <> $highest_id"); + if (DB::isError($result)) { + return $result; + } + + // If another thread has been waiting for this lock, + // it will go thru the above procedure, but will have no + // real effect + $result = $this->getOne("SELECT RELEASE_LOCK('${seqname}_lock')"); + if (DB::isError($result)) { + return $result; + } + return true; + } + + // }}} + // {{{ quoteIdentifier() + + /** + * Quotes a string so it can be safely used as a table or column name + * (WARNING: using names that require this is a REALLY BAD IDEA) + * + * WARNING: Older versions of MySQL can't handle the backtick + * character (`) in table or column names. + * + * @param string $str identifier name to be quoted + * + * @return string quoted identifier string + * + * @see DB_common::quoteIdentifier() + * @since Method available since Release 1.6.0 + */ + function quoteIdentifier($str) + { + return '`' . str_replace('`', '``', $str) . '`'; + } + + // }}} + // {{{ quote() + + /** + * @deprecated Deprecated in release 1.6.0 + */ + function quote($str) + { + return $this->quoteSmart($str); + } + + // }}} + // {{{ escapeSimple() + + /** + * Escapes a string according to the current DBMS's standards + * + * @param string $str the string to be escaped + * + * @return string the escaped string + * + * @see DB_common::quoteSmart() + * @since Method available since Release 1.6.0 + */ + function escapeSimple($str) + { + if (function_exists('mysql_real_escape_string')) { + return @mysql_real_escape_string($str, $this->connection); + } else { + return @mysql_escape_string($str); + } + } + + // }}} + // {{{ modifyQuery() + + /** + * Changes a query string for various DBMS specific reasons + * + * This little hack lets you know how many rows were deleted + * when running a "DELETE FROM table" query. Only implemented + * if the DB_PORTABILITY_DELETE_COUNT portability option is on. + * + * @param string $query the query string to modify + * + * @return string the modified query string + * + * @access protected + * @see DB_common::setOption() + */ + function modifyQuery($query) + { + if ($this->options['portability'] & DB_PORTABILITY_DELETE_COUNT) { + // "DELETE FROM table" gives 0 affected rows in MySQL. + // This little hack lets you know how many rows were deleted. + if (preg_match('/^\s*DELETE\s+FROM\s+(\S+)\s*$/i', $query)) { + $query = preg_replace('/^\s*DELETE\s+FROM\s+(\S+)\s*$/', + 'DELETE FROM \1 WHERE 1=1', $query); + } + } + return $query; + } + + // }}} + // {{{ modifyLimitQuery() + + /** + * Adds LIMIT clauses to a query string according to current DBMS standards + * + * @param string $query the query to modify + * @param int $from the row to start to fetching (0 = the first row) + * @param int $count the numbers of rows to fetch + * @param mixed $params array, string or numeric data to be used in + * execution of the statement. Quantity of items + * passed must match quantity of placeholders in + * query: meaning 1 placeholder for non-array + * parameters or 1 placeholder per array element. + * + * @return string the query string with LIMIT clauses added + * + * @access protected + */ + function modifyLimitQuery($query, $from, $count, $params = array()) + { + if (DB::isManip($query) || $this->_next_query_manip) { + return $query . " LIMIT $count"; + } else { + return $query . " LIMIT $from, $count"; + } + } + + // }}} + // {{{ mysqlRaiseError() + + /** + * Produces a DB_Error object regarding the current problem + * + * @param int $errno if the error is being manually raised pass a + * DB_ERROR* constant here. If this isn't passed + * the error information gathered from the DBMS. + * + * @return object the DB_Error object + * + * @see DB_common::raiseError(), + * DB_mysql::errorNative(), DB_common::errorCode() + */ + function mysqlRaiseError($errno = null) + { + if ($errno === null) { + if ($this->options['portability'] & DB_PORTABILITY_ERRORS) { + $this->errorcode_map[1022] = DB_ERROR_CONSTRAINT; + $this->errorcode_map[1048] = DB_ERROR_CONSTRAINT_NOT_NULL; + $this->errorcode_map[1062] = DB_ERROR_CONSTRAINT; + } else { + // Doing this in case mode changes during runtime. + $this->errorcode_map[1022] = DB_ERROR_ALREADY_EXISTS; + $this->errorcode_map[1048] = DB_ERROR_CONSTRAINT; + $this->errorcode_map[1062] = DB_ERROR_ALREADY_EXISTS; + } + $errno = $this->errorCode(mysql_errno($this->connection)); + } + return $this->raiseError($errno, null, null, null, + @mysql_errno($this->connection) . ' ** ' . + @mysql_error($this->connection)); + } + + // }}} + // {{{ errorNative() + + /** + * Gets the DBMS' native error code produced by the last query + * + * @return int the DBMS' error code + */ + function errorNative() + { + return @mysql_errno($this->connection); + } + + // }}} + // {{{ tableInfo() + + /** + * Returns information about a table or a result set + * + * @param object|string $result DB_result object from a query or a + * string containing the name of a table. + * While this also accepts a query result + * resource identifier, this behavior is + * deprecated. + * @param int $mode a valid tableInfo mode + * + * @return array an associative array with the information requested. + * A DB_Error object on failure. + * + * @see DB_common::tableInfo() + */ + function tableInfo($result, $mode = null) + { + if (is_string($result)) { + // Fix for bug #11580. + if ($this->_db) { + if (!@mysql_select_db($this->_db, $this->connection)) { + return $this->mysqlRaiseError(DB_ERROR_NODBSELECTED); + } + } + + /* + * Probably received a table name. + * Create a result resource identifier. + */ + $id = @mysql_query("SELECT * FROM $result LIMIT 0", + $this->connection); + $got_string = true; + } elseif (isset($result->result)) { + /* + * Probably received a result object. + * Extract the result resource identifier. + */ + $id = $result->result; + $got_string = false; + } else { + /* + * Probably received a result resource identifier. + * Copy it. + * Deprecated. Here for compatibility only. + */ + $id = $result; + $got_string = false; + } + + if (!is_resource($id)) { + return $this->mysqlRaiseError(DB_ERROR_NEED_MORE_DATA); + } + + if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) { + $case_func = 'strtolower'; + } else { + $case_func = 'strval'; + } + + $count = @mysql_num_fields($id); + $res = array(); + + if ($mode) { + $res['num_fields'] = $count; + } + + for ($i = 0; $i < $count; $i++) { + $res[$i] = array( + 'table' => $case_func(@mysql_field_table($id, $i)), + 'name' => $case_func(@mysql_field_name($id, $i)), + 'type' => @mysql_field_type($id, $i), + 'len' => @mysql_field_len($id, $i), + 'flags' => @mysql_field_flags($id, $i), + ); + if ($mode & DB_TABLEINFO_ORDER) { + $res['order'][$res[$i]['name']] = $i; + } + if ($mode & DB_TABLEINFO_ORDERTABLE) { + $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i; + } + } + + // free the result only if we were called on a table + if ($got_string) { + @mysql_free_result($id); + } + return $res; + } + + // }}} + // {{{ getSpecialQuery() + + /** + * Obtains the query string needed for listing a given type of objects + * + * @param string $type the kind of objects you want to retrieve + * + * @return string the SQL query string or null if the driver doesn't + * support the object type requested + * + * @access protected + * @see DB_common::getListOf() + */ + function getSpecialQuery($type) + { + switch ($type) { + case 'tables': + return 'SHOW TABLES'; + case 'users': + return 'SELECT DISTINCT User FROM mysql.user'; + case 'databases': + return 'SHOW DATABASES'; + default: + return null; + } + } + + // }}} + +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ + +?> diff --git a/library/pear/DB/mysqli.php b/library/pear/DB/mysqli.php new file mode 100644 index 000000000..4449484d7 --- /dev/null +++ b/library/pear/DB/mysqli.php @@ -0,0 +1,1092 @@ + + * @copyright 1997-2007 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version CVS: $Id: mysqli.php,v 1.82 2007/09/21 13:40:41 aharvey Exp $ + * @link http://pear.php.net/package/DB + */ + +/** + * Obtain the DB_common class so it can be extended from + */ +require_once 'DB/common.php'; + +/** + * The methods PEAR DB uses to interact with PHP's mysqli extension + * for interacting with MySQL databases + * + * This is for MySQL versions 4.1 and above. Requires PHP 5. + * + * Note that persistent connections no longer exist. + * + * These methods overload the ones declared in DB_common. + * + * @category Database + * @package DB + * @author Daniel Convissor + * @copyright 1997-2007 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version Release: 1.7.13 + * @link http://pear.php.net/package/DB + * @since Class functional since Release 1.6.3 + */ +class DB_mysqli extends DB_common +{ + // {{{ properties + + /** + * The DB driver type (mysql, oci8, odbc, etc.) + * @var string + */ + var $phptype = 'mysqli'; + + /** + * The database syntax variant to be used (db2, access, etc.), if any + * @var string + */ + var $dbsyntax = 'mysqli'; + + /** + * The capabilities of this DB implementation + * + * The 'new_link' element contains the PHP version that first provided + * new_link support for this DBMS. Contains false if it's unsupported. + * + * Meaning of the 'limit' element: + * + 'emulate' = emulate with fetch row by number + * + 'alter' = alter the query + * + false = skip rows + * + * @var array + */ + var $features = array( + 'limit' => 'alter', + 'new_link' => false, + 'numrows' => true, + 'pconnect' => false, + 'prepare' => false, + 'ssl' => true, + 'transactions' => true, + ); + + /** + * A mapping of native error codes to DB error codes + * @var array + */ + var $errorcode_map = array( + 1004 => DB_ERROR_CANNOT_CREATE, + 1005 => DB_ERROR_CANNOT_CREATE, + 1006 => DB_ERROR_CANNOT_CREATE, + 1007 => DB_ERROR_ALREADY_EXISTS, + 1008 => DB_ERROR_CANNOT_DROP, + 1022 => DB_ERROR_ALREADY_EXISTS, + 1044 => DB_ERROR_ACCESS_VIOLATION, + 1046 => DB_ERROR_NODBSELECTED, + 1048 => DB_ERROR_CONSTRAINT, + 1049 => DB_ERROR_NOSUCHDB, + 1050 => DB_ERROR_ALREADY_EXISTS, + 1051 => DB_ERROR_NOSUCHTABLE, + 1054 => DB_ERROR_NOSUCHFIELD, + 1061 => DB_ERROR_ALREADY_EXISTS, + 1062 => DB_ERROR_ALREADY_EXISTS, + 1064 => DB_ERROR_SYNTAX, + 1091 => DB_ERROR_NOT_FOUND, + 1100 => DB_ERROR_NOT_LOCKED, + 1136 => DB_ERROR_VALUE_COUNT_ON_ROW, + 1142 => DB_ERROR_ACCESS_VIOLATION, + 1146 => DB_ERROR_NOSUCHTABLE, + 1216 => DB_ERROR_CONSTRAINT, + 1217 => DB_ERROR_CONSTRAINT, + 1356 => DB_ERROR_DIVZERO, + 1451 => DB_ERROR_CONSTRAINT, + 1452 => DB_ERROR_CONSTRAINT, + ); + + /** + * The raw database connection created by PHP + * @var resource + */ + var $connection; + + /** + * The DSN information for connecting to a database + * @var array + */ + var $dsn = array(); + + + /** + * Should data manipulation queries be committed automatically? + * @var bool + * @access private + */ + var $autocommit = true; + + /** + * The quantity of transactions begun + * + * {@internal While this is private, it can't actually be designated + * private in PHP 5 because it is directly accessed in the test suite.}} + * + * @var integer + * @access private + */ + var $transaction_opcount = 0; + + /** + * The database specified in the DSN + * + * It's a fix to allow calls to different databases in the same script. + * + * @var string + * @access private + */ + var $_db = ''; + + /** + * Array for converting MYSQLI_*_FLAG constants to text values + * @var array + * @access public + * @since Property available since Release 1.6.5 + */ + var $mysqli_flags = array( + MYSQLI_NOT_NULL_FLAG => 'not_null', + MYSQLI_PRI_KEY_FLAG => 'primary_key', + MYSQLI_UNIQUE_KEY_FLAG => 'unique_key', + MYSQLI_MULTIPLE_KEY_FLAG => 'multiple_key', + MYSQLI_BLOB_FLAG => 'blob', + MYSQLI_UNSIGNED_FLAG => 'unsigned', + MYSQLI_ZEROFILL_FLAG => 'zerofill', + MYSQLI_AUTO_INCREMENT_FLAG => 'auto_increment', + MYSQLI_TIMESTAMP_FLAG => 'timestamp', + MYSQLI_SET_FLAG => 'set', + // MYSQLI_NUM_FLAG => 'numeric', // unnecessary + // MYSQLI_PART_KEY_FLAG => 'multiple_key', // duplicatvie + MYSQLI_GROUP_FLAG => 'group_by' + ); + + /** + * Array for converting MYSQLI_TYPE_* constants to text values + * @var array + * @access public + * @since Property available since Release 1.6.5 + */ + var $mysqli_types = array( + MYSQLI_TYPE_DECIMAL => 'decimal', + MYSQLI_TYPE_TINY => 'tinyint', + MYSQLI_TYPE_SHORT => 'int', + MYSQLI_TYPE_LONG => 'int', + MYSQLI_TYPE_FLOAT => 'float', + MYSQLI_TYPE_DOUBLE => 'double', + // MYSQLI_TYPE_NULL => 'DEFAULT NULL', // let flags handle it + MYSQLI_TYPE_TIMESTAMP => 'timestamp', + MYSQLI_TYPE_LONGLONG => 'bigint', + MYSQLI_TYPE_INT24 => 'mediumint', + MYSQLI_TYPE_DATE => 'date', + MYSQLI_TYPE_TIME => 'time', + MYSQLI_TYPE_DATETIME => 'datetime', + MYSQLI_TYPE_YEAR => 'year', + MYSQLI_TYPE_NEWDATE => 'date', + MYSQLI_TYPE_ENUM => 'enum', + MYSQLI_TYPE_SET => 'set', + MYSQLI_TYPE_TINY_BLOB => 'tinyblob', + MYSQLI_TYPE_MEDIUM_BLOB => 'mediumblob', + MYSQLI_TYPE_LONG_BLOB => 'longblob', + MYSQLI_TYPE_BLOB => 'blob', + MYSQLI_TYPE_VAR_STRING => 'varchar', + MYSQLI_TYPE_STRING => 'char', + MYSQLI_TYPE_GEOMETRY => 'geometry', + /* These constants are conditionally compiled in ext/mysqli, so we'll + * define them by number rather than constant. */ + 16 => 'bit', + 246 => 'decimal', + ); + + + // }}} + // {{{ constructor + + /** + * This constructor calls $this->DB_common() + * + * @return void + */ + function DB_mysqli() + { + $this->DB_common(); + } + + // }}} + // {{{ connect() + + /** + * Connect to the database server, log in and open the database + * + * Don't call this method directly. Use DB::connect() instead. + * + * PEAR DB's mysqli driver supports the following extra DSN options: + * + When the 'ssl' $option passed to DB::connect() is true: + * + key The path to the key file. + * + cert The path to the certificate file. + * + ca The path to the certificate authority file. + * + capath The path to a directory that contains trusted SSL + * CA certificates in pem format. + * + cipher The list of allowable ciphers for SSL encryption. + * + * Example of how to connect using SSL: + * + * require_once 'DB.php'; + * + * $dsn = array( + * 'phptype' => 'mysqli', + * 'username' => 'someuser', + * 'password' => 'apasswd', + * 'hostspec' => 'localhost', + * 'database' => 'thedb', + * 'key' => 'client-key.pem', + * 'cert' => 'client-cert.pem', + * 'ca' => 'cacert.pem', + * 'capath' => '/path/to/ca/dir', + * 'cipher' => 'AES', + * ); + * + * $options = array( + * 'ssl' => true, + * ); + * + * $db = DB::connect($dsn, $options); + * if (PEAR::isError($db)) { + * die($db->getMessage()); + * } + * + * + * @param array $dsn the data source name + * @param bool $persistent should the connection be persistent? + * + * @return int DB_OK on success. A DB_Error object on failure. + */ + function connect($dsn, $persistent = false) + { + if (!PEAR::loadExtension('mysqli')) { + return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND); + } + + $this->dsn = $dsn; + if ($dsn['dbsyntax']) { + $this->dbsyntax = $dsn['dbsyntax']; + } + + $ini = ini_get('track_errors'); + @ini_set('track_errors', 1); + $php_errormsg = ''; + + if (((int) $this->getOption('ssl')) === 1) { + $init = mysqli_init(); + mysqli_ssl_set( + $init, + empty($dsn['key']) ? null : $dsn['key'], + empty($dsn['cert']) ? null : $dsn['cert'], + empty($dsn['ca']) ? null : $dsn['ca'], + empty($dsn['capath']) ? null : $dsn['capath'], + empty($dsn['cipher']) ? null : $dsn['cipher'] + ); + if ($this->connection = @mysqli_real_connect( + $init, + $dsn['hostspec'], + $dsn['username'], + $dsn['password'], + $dsn['database'], + $dsn['port'], + $dsn['socket'])) + { + $this->connection = $init; + } + } else { + $this->connection = @mysqli_connect( + $dsn['hostspec'], + $dsn['username'], + $dsn['password'], + $dsn['database'], + $dsn['port'], + $dsn['socket'] + ); + } + + @ini_set('track_errors', $ini); + + if (!$this->connection) { + if (($err = @mysqli_connect_error()) != '') { + return $this->raiseError(DB_ERROR_CONNECT_FAILED, + null, null, null, + $err); + } else { + return $this->raiseError(DB_ERROR_CONNECT_FAILED, + null, null, null, + $php_errormsg); + } + } + + if ($dsn['database']) { + $this->_db = $dsn['database']; + } + + return DB_OK; + } + + // }}} + // {{{ disconnect() + + /** + * Disconnects from the database server + * + * @return bool TRUE on success, FALSE on failure + */ + function disconnect() + { + $ret = @mysqli_close($this->connection); + $this->connection = null; + return $ret; + } + + // }}} + // {{{ simpleQuery() + + /** + * Sends a query to the database server + * + * @param string the SQL query string + * + * @return mixed + a PHP result resrouce for successful SELECT queries + * + the DB_OK constant for other successful queries + * + a DB_Error object on failure + */ + function simpleQuery($query) + { + $ismanip = $this->_checkManip($query); + $this->last_query = $query; + $query = $this->modifyQuery($query); + if ($this->_db) { + if (!@mysqli_select_db($this->connection, $this->_db)) { + return $this->mysqliRaiseError(DB_ERROR_NODBSELECTED); + } + } + if (!$this->autocommit && $ismanip) { + if ($this->transaction_opcount == 0) { + $result = @mysqli_query($this->connection, 'SET AUTOCOMMIT=0'); + $result = @mysqli_query($this->connection, 'BEGIN'); + if (!$result) { + return $this->mysqliRaiseError(); + } + } + $this->transaction_opcount++; + } + $result = @mysqli_query($this->connection, $query); + if (!$result) { + return $this->mysqliRaiseError(); + } + if (is_object($result)) { + return $result; + } + return DB_OK; + } + + // }}} + // {{{ nextResult() + + /** + * Move the internal mysql result pointer to the next available result. + * + * This method has not been implemented yet. + * + * @param resource $result a valid sql result resource + * @return false + * @access public + */ + function nextResult($result) + { + return false; + } + + // }}} + // {{{ fetchInto() + + /** + * Places a row from the result set into the given array + * + * Formating of the array and the data therein are configurable. + * See DB_result::fetchInto() for more information. + * + * This method is not meant to be called directly. Use + * DB_result::fetchInto() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result the query result resource + * @param array $arr the referenced array to put the data in + * @param int $fetchmode how the resulting array should be indexed + * @param int $rownum the row number to fetch (0 = first row) + * + * @return mixed DB_OK on success, NULL when the end of a result set is + * reached or on failure + * + * @see DB_result::fetchInto() + */ + function fetchInto($result, &$arr, $fetchmode, $rownum = null) + { + if ($rownum !== null) { + if (!@mysqli_data_seek($result, $rownum)) { + return null; + } + } + if ($fetchmode & DB_FETCHMODE_ASSOC) { + $arr = @mysqli_fetch_array($result, MYSQLI_ASSOC); + if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE && $arr) { + $arr = array_change_key_case($arr, CASE_LOWER); + } + } else { + $arr = @mysqli_fetch_row($result); + } + if (!$arr) { + return null; + } + if ($this->options['portability'] & DB_PORTABILITY_RTRIM) { + /* + * Even though this DBMS already trims output, we do this because + * a field might have intentional whitespace at the end that + * gets removed by DB_PORTABILITY_RTRIM under another driver. + */ + $this->_rtrimArrayValues($arr); + } + if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) { + $this->_convertNullArrayValuesToEmpty($arr); + } + return DB_OK; + } + + // }}} + // {{{ freeResult() + + /** + * Deletes the result set and frees the memory occupied by the result set + * + * This method is not meant to be called directly. Use + * DB_result::free() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result PHP's query result resource + * + * @return bool TRUE on success, FALSE if $result is invalid + * + * @see DB_result::free() + */ + function freeResult($result) + { + return is_resource($result) ? mysqli_free_result($result) : false; + } + + // }}} + // {{{ numCols() + + /** + * Gets the number of columns in a result set + * + * This method is not meant to be called directly. Use + * DB_result::numCols() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result PHP's query result resource + * + * @return int the number of columns. A DB_Error object on failure. + * + * @see DB_result::numCols() + */ + function numCols($result) + { + $cols = @mysqli_num_fields($result); + if (!$cols) { + return $this->mysqliRaiseError(); + } + return $cols; + } + + // }}} + // {{{ numRows() + + /** + * Gets the number of rows in a result set + * + * This method is not meant to be called directly. Use + * DB_result::numRows() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result PHP's query result resource + * + * @return int the number of rows. A DB_Error object on failure. + * + * @see DB_result::numRows() + */ + function numRows($result) + { + $rows = @mysqli_num_rows($result); + if ($rows === null) { + return $this->mysqliRaiseError(); + } + return $rows; + } + + // }}} + // {{{ autoCommit() + + /** + * Enables or disables automatic commits + * + * @param bool $onoff true turns it on, false turns it off + * + * @return int DB_OK on success. A DB_Error object if the driver + * doesn't support auto-committing transactions. + */ + function autoCommit($onoff = false) + { + // XXX if $this->transaction_opcount > 0, we should probably + // issue a warning here. + $this->autocommit = $onoff ? true : false; + return DB_OK; + } + + // }}} + // {{{ commit() + + /** + * Commits the current transaction + * + * @return int DB_OK on success. A DB_Error object on failure. + */ + function commit() + { + if ($this->transaction_opcount > 0) { + if ($this->_db) { + if (!@mysqli_select_db($this->connection, $this->_db)) { + return $this->mysqliRaiseError(DB_ERROR_NODBSELECTED); + } + } + $result = @mysqli_query($this->connection, 'COMMIT'); + $result = @mysqli_query($this->connection, 'SET AUTOCOMMIT=1'); + $this->transaction_opcount = 0; + if (!$result) { + return $this->mysqliRaiseError(); + } + } + return DB_OK; + } + + // }}} + // {{{ rollback() + + /** + * Reverts the current transaction + * + * @return int DB_OK on success. A DB_Error object on failure. + */ + function rollback() + { + if ($this->transaction_opcount > 0) { + if ($this->_db) { + if (!@mysqli_select_db($this->connection, $this->_db)) { + return $this->mysqliRaiseError(DB_ERROR_NODBSELECTED); + } + } + $result = @mysqli_query($this->connection, 'ROLLBACK'); + $result = @mysqli_query($this->connection, 'SET AUTOCOMMIT=1'); + $this->transaction_opcount = 0; + if (!$result) { + return $this->mysqliRaiseError(); + } + } + return DB_OK; + } + + // }}} + // {{{ affectedRows() + + /** + * Determines the number of rows affected by a data maniuplation query + * + * 0 is returned for queries that don't manipulate data. + * + * @return int the number of rows. A DB_Error object on failure. + */ + function affectedRows() + { + if ($this->_last_query_manip) { + return @mysqli_affected_rows($this->connection); + } else { + return 0; + } + } + + // }}} + // {{{ nextId() + + /** + * Returns the next free id in a sequence + * + * @param string $seq_name name of the sequence + * @param boolean $ondemand when true, the seqence is automatically + * created if it does not exist + * + * @return int the next id number in the sequence. + * A DB_Error object on failure. + * + * @see DB_common::nextID(), DB_common::getSequenceName(), + * DB_mysqli::createSequence(), DB_mysqli::dropSequence() + */ + function nextId($seq_name, $ondemand = true) + { + $seqname = $this->getSequenceName($seq_name); + do { + $repeat = 0; + $this->pushErrorHandling(PEAR_ERROR_RETURN); + $result = $this->query('UPDATE ' . $seqname + . ' SET id = LAST_INSERT_ID(id + 1)'); + $this->popErrorHandling(); + if ($result === DB_OK) { + // COMMON CASE + $id = @mysqli_insert_id($this->connection); + if ($id != 0) { + return $id; + } + + // EMPTY SEQ TABLE + // Sequence table must be empty for some reason, + // so fill it and return 1 + // Obtain a user-level lock + $result = $this->getOne('SELECT GET_LOCK(' + . "'${seqname}_lock', 10)"); + if (DB::isError($result)) { + return $this->raiseError($result); + } + if ($result == 0) { + return $this->mysqliRaiseError(DB_ERROR_NOT_LOCKED); + } + + // add the default value + $result = $this->query('REPLACE INTO ' . $seqname + . ' (id) VALUES (0)'); + if (DB::isError($result)) { + return $this->raiseError($result); + } + + // Release the lock + $result = $this->getOne('SELECT RELEASE_LOCK(' + . "'${seqname}_lock')"); + if (DB::isError($result)) { + return $this->raiseError($result); + } + // We know what the result will be, so no need to try again + return 1; + + } elseif ($ondemand && DB::isError($result) && + $result->getCode() == DB_ERROR_NOSUCHTABLE) + { + // ONDEMAND TABLE CREATION + $result = $this->createSequence($seq_name); + + // Since createSequence initializes the ID to be 1, + // we do not need to retrieve the ID again (or we will get 2) + if (DB::isError($result)) { + return $this->raiseError($result); + } else { + // First ID of a newly created sequence is 1 + return 1; + } + + } elseif (DB::isError($result) && + $result->getCode() == DB_ERROR_ALREADY_EXISTS) + { + // BACKWARDS COMPAT + // see _BCsequence() comment + $result = $this->_BCsequence($seqname); + if (DB::isError($result)) { + return $this->raiseError($result); + } + $repeat = 1; + } + } while ($repeat); + + return $this->raiseError($result); + } + + /** + * Creates a new sequence + * + * @param string $seq_name name of the new sequence + * + * @return int DB_OK on success. A DB_Error object on failure. + * + * @see DB_common::createSequence(), DB_common::getSequenceName(), + * DB_mysqli::nextID(), DB_mysqli::dropSequence() + */ + function createSequence($seq_name) + { + $seqname = $this->getSequenceName($seq_name); + $res = $this->query('CREATE TABLE ' . $seqname + . ' (id INTEGER UNSIGNED AUTO_INCREMENT NOT NULL,' + . ' PRIMARY KEY(id))'); + if (DB::isError($res)) { + return $res; + } + // insert yields value 1, nextId call will generate ID 2 + return $this->query("INSERT INTO ${seqname} (id) VALUES (0)"); + } + + // }}} + // {{{ dropSequence() + + /** + * Deletes a sequence + * + * @param string $seq_name name of the sequence to be deleted + * + * @return int DB_OK on success. A DB_Error object on failure. + * + * @see DB_common::dropSequence(), DB_common::getSequenceName(), + * DB_mysql::nextID(), DB_mysql::createSequence() + */ + function dropSequence($seq_name) + { + return $this->query('DROP TABLE ' . $this->getSequenceName($seq_name)); + } + + // }}} + // {{{ _BCsequence() + + /** + * Backwards compatibility with old sequence emulation implementation + * (clean up the dupes) + * + * @param string $seqname the sequence name to clean up + * + * @return bool true on success. A DB_Error object on failure. + * + * @access private + */ + function _BCsequence($seqname) + { + // Obtain a user-level lock... this will release any previous + // application locks, but unlike LOCK TABLES, it does not abort + // the current transaction and is much less frequently used. + $result = $this->getOne("SELECT GET_LOCK('${seqname}_lock',10)"); + if (DB::isError($result)) { + return $result; + } + if ($result == 0) { + // Failed to get the lock, can't do the conversion, bail + // with a DB_ERROR_NOT_LOCKED error + return $this->mysqliRaiseError(DB_ERROR_NOT_LOCKED); + } + + $highest_id = $this->getOne("SELECT MAX(id) FROM ${seqname}"); + if (DB::isError($highest_id)) { + return $highest_id; + } + + // This should kill all rows except the highest + // We should probably do something if $highest_id isn't + // numeric, but I'm at a loss as how to handle that... + $result = $this->query('DELETE FROM ' . $seqname + . " WHERE id <> $highest_id"); + if (DB::isError($result)) { + return $result; + } + + // If another thread has been waiting for this lock, + // it will go thru the above procedure, but will have no + // real effect + $result = $this->getOne("SELECT RELEASE_LOCK('${seqname}_lock')"); + if (DB::isError($result)) { + return $result; + } + return true; + } + + // }}} + // {{{ quoteIdentifier() + + /** + * Quotes a string so it can be safely used as a table or column name + * (WARNING: using names that require this is a REALLY BAD IDEA) + * + * WARNING: Older versions of MySQL can't handle the backtick + * character (`) in table or column names. + * + * @param string $str identifier name to be quoted + * + * @return string quoted identifier string + * + * @see DB_common::quoteIdentifier() + * @since Method available since Release 1.6.0 + */ + function quoteIdentifier($str) + { + return '`' . str_replace('`', '``', $str) . '`'; + } + + // }}} + // {{{ escapeSimple() + + /** + * Escapes a string according to the current DBMS's standards + * + * @param string $str the string to be escaped + * + * @return string the escaped string + * + * @see DB_common::quoteSmart() + * @since Method available since Release 1.6.0 + */ + function escapeSimple($str) + { + return @mysqli_real_escape_string($this->connection, $str); + } + + // }}} + // {{{ modifyLimitQuery() + + /** + * Adds LIMIT clauses to a query string according to current DBMS standards + * + * @param string $query the query to modify + * @param int $from the row to start to fetching (0 = the first row) + * @param int $count the numbers of rows to fetch + * @param mixed $params array, string or numeric data to be used in + * execution of the statement. Quantity of items + * passed must match quantity of placeholders in + * query: meaning 1 placeholder for non-array + * parameters or 1 placeholder per array element. + * + * @return string the query string with LIMIT clauses added + * + * @access protected + */ + function modifyLimitQuery($query, $from, $count, $params = array()) + { + if (DB::isManip($query) || $this->_next_query_manip) { + return $query . " LIMIT $count"; + } else { + return $query . " LIMIT $from, $count"; + } + } + + // }}} + // {{{ mysqliRaiseError() + + /** + * Produces a DB_Error object regarding the current problem + * + * @param int $errno if the error is being manually raised pass a + * DB_ERROR* constant here. If this isn't passed + * the error information gathered from the DBMS. + * + * @return object the DB_Error object + * + * @see DB_common::raiseError(), + * DB_mysqli::errorNative(), DB_common::errorCode() + */ + function mysqliRaiseError($errno = null) + { + if ($errno === null) { + if ($this->options['portability'] & DB_PORTABILITY_ERRORS) { + $this->errorcode_map[1022] = DB_ERROR_CONSTRAINT; + $this->errorcode_map[1048] = DB_ERROR_CONSTRAINT_NOT_NULL; + $this->errorcode_map[1062] = DB_ERROR_CONSTRAINT; + } else { + // Doing this in case mode changes during runtime. + $this->errorcode_map[1022] = DB_ERROR_ALREADY_EXISTS; + $this->errorcode_map[1048] = DB_ERROR_CONSTRAINT; + $this->errorcode_map[1062] = DB_ERROR_ALREADY_EXISTS; + } + $errno = $this->errorCode(mysqli_errno($this->connection)); + } + return $this->raiseError($errno, null, null, null, + @mysqli_errno($this->connection) . ' ** ' . + @mysqli_error($this->connection)); + } + + // }}} + // {{{ errorNative() + + /** + * Gets the DBMS' native error code produced by the last query + * + * @return int the DBMS' error code + */ + function errorNative() + { + return @mysqli_errno($this->connection); + } + + // }}} + // {{{ tableInfo() + + /** + * Returns information about a table or a result set + * + * @param object|string $result DB_result object from a query or a + * string containing the name of a table. + * While this also accepts a query result + * resource identifier, this behavior is + * deprecated. + * @param int $mode a valid tableInfo mode + * + * @return array an associative array with the information requested. + * A DB_Error object on failure. + * + * @see DB_common::setOption() + */ + function tableInfo($result, $mode = null) + { + if (is_string($result)) { + // Fix for bug #11580. + if ($this->_db) { + if (!@mysqli_select_db($this->connection, $this->_db)) { + return $this->mysqliRaiseError(DB_ERROR_NODBSELECTED); + } + } + + /* + * Probably received a table name. + * Create a result resource identifier. + */ + $id = @mysqli_query($this->connection, + "SELECT * FROM $result LIMIT 0"); + $got_string = true; + } elseif (isset($result->result)) { + /* + * Probably received a result object. + * Extract the result resource identifier. + */ + $id = $result->result; + $got_string = false; + } else { + /* + * Probably received a result resource identifier. + * Copy it. + * Deprecated. Here for compatibility only. + */ + $id = $result; + $got_string = false; + } + + if (!is_a($id, 'mysqli_result')) { + return $this->mysqliRaiseError(DB_ERROR_NEED_MORE_DATA); + } + + if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) { + $case_func = 'strtolower'; + } else { + $case_func = 'strval'; + } + + $count = @mysqli_num_fields($id); + $res = array(); + + if ($mode) { + $res['num_fields'] = $count; + } + + for ($i = 0; $i < $count; $i++) { + $tmp = @mysqli_fetch_field($id); + + $flags = ''; + foreach ($this->mysqli_flags as $const => $means) { + if ($tmp->flags & $const) { + $flags .= $means . ' '; + } + } + if ($tmp->def) { + $flags .= 'default_' . rawurlencode($tmp->def); + } + $flags = trim($flags); + + $res[$i] = array( + 'table' => $case_func($tmp->table), + 'name' => $case_func($tmp->name), + 'type' => isset($this->mysqli_types[$tmp->type]) + ? $this->mysqli_types[$tmp->type] + : 'unknown', + // http://bugs.php.net/?id=36579 + 'len' => $tmp->length, + 'flags' => $flags, + ); + + if ($mode & DB_TABLEINFO_ORDER) { + $res['order'][$res[$i]['name']] = $i; + } + if ($mode & DB_TABLEINFO_ORDERTABLE) { + $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i; + } + } + + // free the result only if we were called on a table + if ($got_string) { + @mysqli_free_result($id); + } + return $res; + } + + // }}} + // {{{ getSpecialQuery() + + /** + * Obtains the query string needed for listing a given type of objects + * + * @param string $type the kind of objects you want to retrieve + * + * @return string the SQL query string or null if the driver doesn't + * support the object type requested + * + * @access protected + * @see DB_common::getListOf() + */ + function getSpecialQuery($type) + { + switch ($type) { + case 'tables': + return 'SHOW TABLES'; + case 'users': + return 'SELECT DISTINCT User FROM mysql.user'; + case 'databases': + return 'SHOW DATABASES'; + default: + return null; + } + } + + // }}} + +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ + +?> diff --git a/library/pear/DB/oci8.php b/library/pear/DB/oci8.php new file mode 100644 index 000000000..3dfee116e --- /dev/null +++ b/library/pear/DB/oci8.php @@ -0,0 +1,1156 @@ + + * @author Daniel Convissor + * @copyright 1997-2007 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version CVS: $Id: oci8.php,v 1.115 2007/09/21 13:40:41 aharvey Exp $ + * @link http://pear.php.net/package/DB + */ + +/** + * Obtain the DB_common class so it can be extended from + */ +require_once 'DB/common.php'; + +/** + * The methods PEAR DB uses to interact with PHP's oci8 extension + * for interacting with Oracle databases + * + * Definitely works with versions 8 and 9 of Oracle. + * + * These methods overload the ones declared in DB_common. + * + * Be aware... OCIError() only appears to return anything when given a + * statement, so functions return the generic DB_ERROR instead of more + * useful errors that have to do with feedback from the database. + * + * @category Database + * @package DB + * @author James L. Pine + * @author Daniel Convissor + * @copyright 1997-2007 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version Release: 1.7.13 + * @link http://pear.php.net/package/DB + */ +class DB_oci8 extends DB_common +{ + // {{{ properties + + /** + * The DB driver type (mysql, oci8, odbc, etc.) + * @var string + */ + var $phptype = 'oci8'; + + /** + * The database syntax variant to be used (db2, access, etc.), if any + * @var string + */ + var $dbsyntax = 'oci8'; + + /** + * The capabilities of this DB implementation + * + * The 'new_link' element contains the PHP version that first provided + * new_link support for this DBMS. Contains false if it's unsupported. + * + * Meaning of the 'limit' element: + * + 'emulate' = emulate with fetch row by number + * + 'alter' = alter the query + * + false = skip rows + * + * @var array + */ + var $features = array( + 'limit' => 'alter', + 'new_link' => '5.0.0', + 'numrows' => 'subquery', + 'pconnect' => true, + 'prepare' => true, + 'ssl' => false, + 'transactions' => true, + ); + + /** + * A mapping of native error codes to DB error codes + * @var array + */ + var $errorcode_map = array( + 1 => DB_ERROR_CONSTRAINT, + 900 => DB_ERROR_SYNTAX, + 904 => DB_ERROR_NOSUCHFIELD, + 913 => DB_ERROR_VALUE_COUNT_ON_ROW, + 921 => DB_ERROR_SYNTAX, + 923 => DB_ERROR_SYNTAX, + 942 => DB_ERROR_NOSUCHTABLE, + 955 => DB_ERROR_ALREADY_EXISTS, + 1400 => DB_ERROR_CONSTRAINT_NOT_NULL, + 1401 => DB_ERROR_INVALID, + 1407 => DB_ERROR_CONSTRAINT_NOT_NULL, + 1418 => DB_ERROR_NOT_FOUND, + 1476 => DB_ERROR_DIVZERO, + 1722 => DB_ERROR_INVALID_NUMBER, + 2289 => DB_ERROR_NOSUCHTABLE, + 2291 => DB_ERROR_CONSTRAINT, + 2292 => DB_ERROR_CONSTRAINT, + 2449 => DB_ERROR_CONSTRAINT, + 12899 => DB_ERROR_INVALID, + ); + + /** + * The raw database connection created by PHP + * @var resource + */ + var $connection; + + /** + * The DSN information for connecting to a database + * @var array + */ + var $dsn = array(); + + + /** + * Should data manipulation queries be committed automatically? + * @var bool + * @access private + */ + var $autocommit = true; + + /** + * Stores the $data passed to execute() in the oci8 driver + * + * Gets reset to array() when simpleQuery() is run. + * + * Needed in case user wants to call numRows() after prepare/execute + * was used. + * + * @var array + * @access private + */ + var $_data = array(); + + /** + * The result or statement handle from the most recently executed query + * @var resource + */ + var $last_stmt; + + /** + * Is the given prepared statement a data manipulation query? + * @var array + * @access private + */ + var $manip_query = array(); + + /** + * Store of prepared SQL queries. + * @var array + * @access private + */ + var $_prepared_queries = array(); + + + // }}} + // {{{ constructor + + /** + * This constructor calls $this->DB_common() + * + * @return void + */ + function DB_oci8() + { + $this->DB_common(); + } + + // }}} + // {{{ connect() + + /** + * Connect to the database server, log in and open the database + * + * Don't call this method directly. Use DB::connect() instead. + * + * If PHP is at version 5.0.0 or greater: + * + Generally, oci_connect() or oci_pconnect() are used. + * + But if the new_link DSN option is set to true, oci_new_connect() + * is used. + * + * When using PHP version 4.x, OCILogon() or OCIPLogon() are used. + * + * PEAR DB's oci8 driver supports the following extra DSN options: + * + charset The character set to be used on the connection. + * Only used if PHP is at version 5.0.0 or greater + * and the Oracle server is at 9.2 or greater. + * Available since PEAR DB 1.7.0. + * + new_link If set to true, causes subsequent calls to + * connect() to return a new connection link + * instead of the existing one. WARNING: this is + * not portable to other DBMS's. + * Available since PEAR DB 1.7.0. + * + * @param array $dsn the data source name + * @param bool $persistent should the connection be persistent? + * + * @return int DB_OK on success. A DB_Error object on failure. + */ + function connect($dsn, $persistent = false) + { + if (!PEAR::loadExtension('oci8')) { + return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND); + } + + $this->dsn = $dsn; + if ($dsn['dbsyntax']) { + $this->dbsyntax = $dsn['dbsyntax']; + } + + // Backwards compatibility with DB < 1.7.0 + if (empty($dsn['database']) && !empty($dsn['hostspec'])) { + $db = $dsn['hostspec']; + } else { + $db = $dsn['database']; + } + + if (function_exists('oci_connect')) { + if (isset($dsn['new_link']) + && ($dsn['new_link'] == 'true' || $dsn['new_link'] === true)) + { + $connect_function = 'oci_new_connect'; + } else { + $connect_function = $persistent ? 'oci_pconnect' + : 'oci_connect'; + } + if (isset($this->dsn['port']) && $this->dsn['port']) { + $db = '//'.$db.':'.$this->dsn['port']; + } + + $char = empty($dsn['charset']) ? null : $dsn['charset']; + $this->connection = @$connect_function($dsn['username'], + $dsn['password'], + $db, + $char); + $error = OCIError(); + if (!empty($error) && $error['code'] == 12541) { + // Couldn't find TNS listener. Try direct connection. + $this->connection = @$connect_function($dsn['username'], + $dsn['password'], + null, + $char); + } + } else { + $connect_function = $persistent ? 'OCIPLogon' : 'OCILogon'; + if ($db) { + $this->connection = @$connect_function($dsn['username'], + $dsn['password'], + $db); + } elseif ($dsn['username'] || $dsn['password']) { + $this->connection = @$connect_function($dsn['username'], + $dsn['password']); + } + } + + if (!$this->connection) { + $error = OCIError(); + $error = (is_array($error)) ? $error['message'] : null; + return $this->raiseError(DB_ERROR_CONNECT_FAILED, + null, null, null, + $error); + } + return DB_OK; + } + + // }}} + // {{{ disconnect() + + /** + * Disconnects from the database server + * + * @return bool TRUE on success, FALSE on failure + */ + function disconnect() + { + if (function_exists('oci_close')) { + $ret = @oci_close($this->connection); + } else { + $ret = @OCILogOff($this->connection); + } + $this->connection = null; + return $ret; + } + + // }}} + // {{{ simpleQuery() + + /** + * Sends a query to the database server + * + * To determine how many rows of a result set get buffered using + * ocisetprefetch(), see the "result_buffering" option in setOptions(). + * This option was added in Release 1.7.0. + * + * @param string the SQL query string + * + * @return mixed + a PHP result resrouce for successful SELECT queries + * + the DB_OK constant for other successful queries + * + a DB_Error object on failure + */ + function simpleQuery($query) + { + $this->_data = array(); + $this->last_parameters = array(); + $this->last_query = $query; + $query = $this->modifyQuery($query); + $result = @OCIParse($this->connection, $query); + if (!$result) { + return $this->oci8RaiseError(); + } + if ($this->autocommit) { + $success = @OCIExecute($result,OCI_COMMIT_ON_SUCCESS); + } else { + $success = @OCIExecute($result,OCI_DEFAULT); + } + if (!$success) { + return $this->oci8RaiseError($result); + } + $this->last_stmt = $result; + if ($this->_checkManip($query)) { + return DB_OK; + } else { + @ocisetprefetch($result, $this->options['result_buffering']); + return $result; + } + } + + // }}} + // {{{ nextResult() + + /** + * Move the internal oracle result pointer to the next available result + * + * @param a valid oci8 result resource + * + * @access public + * + * @return true if a result is available otherwise return false + */ + function nextResult($result) + { + return false; + } + + // }}} + // {{{ fetchInto() + + /** + * Places a row from the result set into the given array + * + * Formating of the array and the data therein are configurable. + * See DB_result::fetchInto() for more information. + * + * This method is not meant to be called directly. Use + * DB_result::fetchInto() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result the query result resource + * @param array $arr the referenced array to put the data in + * @param int $fetchmode how the resulting array should be indexed + * @param int $rownum the row number to fetch (0 = first row) + * + * @return mixed DB_OK on success, NULL when the end of a result set is + * reached or on failure + * + * @see DB_result::fetchInto() + */ + function fetchInto($result, &$arr, $fetchmode, $rownum = null) + { + if ($rownum !== null) { + return $this->raiseError(DB_ERROR_NOT_CAPABLE); + } + if ($fetchmode & DB_FETCHMODE_ASSOC) { + $moredata = @OCIFetchInto($result,$arr,OCI_ASSOC+OCI_RETURN_NULLS+OCI_RETURN_LOBS); + if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE && + $moredata) + { + $arr = array_change_key_case($arr, CASE_LOWER); + } + } else { + $moredata = OCIFetchInto($result,$arr,OCI_RETURN_NULLS+OCI_RETURN_LOBS); + } + if (!$moredata) { + return null; + } + if ($this->options['portability'] & DB_PORTABILITY_RTRIM) { + $this->_rtrimArrayValues($arr); + } + if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) { + $this->_convertNullArrayValuesToEmpty($arr); + } + return DB_OK; + } + + // }}} + // {{{ freeResult() + + /** + * Deletes the result set and frees the memory occupied by the result set + * + * This method is not meant to be called directly. Use + * DB_result::free() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result PHP's query result resource + * + * @return bool TRUE on success, FALSE if $result is invalid + * + * @see DB_result::free() + */ + function freeResult($result) + { + return is_resource($result) ? OCIFreeStatement($result) : false; + } + + /** + * Frees the internal resources associated with a prepared query + * + * @param resource $stmt the prepared statement's resource + * @param bool $free_resource should the PHP resource be freed too? + * Use false if you need to get data + * from the result set later. + * + * @return bool TRUE on success, FALSE if $result is invalid + * + * @see DB_oci8::prepare() + */ + function freePrepared($stmt, $free_resource = true) + { + if (!is_resource($stmt)) { + return false; + } + if ($free_resource) { + @ocifreestatement($stmt); + } + if (isset($this->prepare_types[(int)$stmt])) { + unset($this->prepare_types[(int)$stmt]); + unset($this->manip_query[(int)$stmt]); + } else { + return false; + } + return true; + } + + // }}} + // {{{ numRows() + + /** + * Gets the number of rows in a result set + * + * Only works if the DB_PORTABILITY_NUMROWS portability option + * is turned on. + * + * This method is not meant to be called directly. Use + * DB_result::numRows() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result PHP's query result resource + * + * @return int the number of rows. A DB_Error object on failure. + * + * @see DB_result::numRows(), DB_common::setOption() + */ + function numRows($result) + { + // emulate numRows for Oracle. yuck. + if ($this->options['portability'] & DB_PORTABILITY_NUMROWS && + $result === $this->last_stmt) + { + $countquery = 'SELECT COUNT(*) FROM ('.$this->last_query.')'; + $save_query = $this->last_query; + $save_stmt = $this->last_stmt; + + $count = $this->query($countquery); + + // Restore the last query and statement. + $this->last_query = $save_query; + $this->last_stmt = $save_stmt; + + if (DB::isError($count) || + DB::isError($row = $count->fetchRow(DB_FETCHMODE_ORDERED))) + { + return $this->raiseError(DB_ERROR_NOT_CAPABLE); + } + + return $row[0]; + } + return $this->raiseError(DB_ERROR_NOT_CAPABLE); + } + + // }}} + // {{{ numCols() + + /** + * Gets the number of columns in a result set + * + * This method is not meant to be called directly. Use + * DB_result::numCols() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result PHP's query result resource + * + * @return int the number of columns. A DB_Error object on failure. + * + * @see DB_result::numCols() + */ + function numCols($result) + { + $cols = @OCINumCols($result); + if (!$cols) { + return $this->oci8RaiseError($result); + } + return $cols; + } + + // }}} + // {{{ prepare() + + /** + * Prepares a query for multiple execution with execute(). + * + * With oci8, this is emulated. + * + * prepare() requires a generic query as string like + * INSERT INTO numbers VALUES (?, ?, ?) + * . The ? characters are placeholders. + * + * Three types of placeholders can be used: + * + ? a quoted scalar value, i.e. strings, integers + * + ! value is inserted 'as is' + * + & requires a file name. The file's contents get + * inserted into the query (i.e. saving binary + * data in a db) + * + * Use backslashes to escape placeholder characters if you don't want + * them to be interpreted as placeholders. Example: + * "UPDATE foo SET col=? WHERE col='over \& under'" + * + * + * @param string $query the query to be prepared + * + * @return mixed DB statement resource on success. DB_Error on failure. + * + * @see DB_oci8::execute() + */ + function prepare($query) + { + $tokens = preg_split('/((? $val) { + switch ($val) { + case '?': + $types[$token++] = DB_PARAM_SCALAR; + unset($tokens[$key]); + break; + case '&': + $types[$token++] = DB_PARAM_OPAQUE; + unset($tokens[$key]); + break; + case '!': + $types[$token++] = DB_PARAM_MISC; + unset($tokens[$key]); + break; + default: + $tokens[$key] = preg_replace('/\\\([&?!])/', "\\1", $val); + if ($key != $binds) { + $newquery .= $tokens[$key] . ':bind' . $token; + } else { + $newquery .= $tokens[$key]; + } + } + } + + $this->last_query = $query; + $newquery = $this->modifyQuery($newquery); + if (!$stmt = @OCIParse($this->connection, $newquery)) { + return $this->oci8RaiseError(); + } + $this->prepare_types[(int)$stmt] = $types; + $this->manip_query[(int)$stmt] = DB::isManip($query); + $this->_prepared_queries[(int)$stmt] = $newquery; + return $stmt; + } + + // }}} + // {{{ execute() + + /** + * Executes a DB statement prepared with prepare(). + * + * To determine how many rows of a result set get buffered using + * ocisetprefetch(), see the "result_buffering" option in setOptions(). + * This option was added in Release 1.7.0. + * + * @param resource $stmt a DB statement resource returned from prepare() + * @param mixed $data array, string or numeric data to be used in + * execution of the statement. Quantity of items + * passed must match quantity of placeholders in + * query: meaning 1 for non-array items or the + * quantity of elements in the array. + * + * @return mixed returns an oic8 result resource for successful SELECT + * queries, DB_OK for other successful queries. + * A DB error object is returned on failure. + * + * @see DB_oci8::prepare() + */ + function &execute($stmt, $data = array()) + { + $data = (array)$data; + $this->last_parameters = $data; + $this->last_query = $this->_prepared_queries[(int)$stmt]; + $this->_data = $data; + + $types = $this->prepare_types[(int)$stmt]; + if (count($types) != count($data)) { + $tmp = $this->raiseError(DB_ERROR_MISMATCH); + return $tmp; + } + + $i = 0; + foreach ($data as $key => $value) { + if ($types[$i] == DB_PARAM_MISC) { + /* + * Oracle doesn't seem to have the ability to pass a + * parameter along unchanged, so strip off quotes from start + * and end, plus turn two single quotes to one single quote, + * in order to avoid the quotes getting escaped by + * Oracle and ending up in the database. + */ + $data[$key] = preg_replace("/^'(.*)'$/", "\\1", $data[$key]); + $data[$key] = str_replace("''", "'", $data[$key]); + } elseif ($types[$i] == DB_PARAM_OPAQUE) { + $fp = @fopen($data[$key], 'rb'); + if (!$fp) { + $tmp = $this->raiseError(DB_ERROR_ACCESS_VIOLATION); + return $tmp; + } + $data[$key] = fread($fp, filesize($data[$key])); + fclose($fp); + } elseif ($types[$i] == DB_PARAM_SCALAR) { + // Floats have to be converted to a locale-neutral + // representation. + if (is_float($data[$key])) { + $data[$key] = $this->quoteFloat($data[$key]); + } + } + if (!@OCIBindByName($stmt, ':bind' . $i, $data[$key], -1)) { + $tmp = $this->oci8RaiseError($stmt); + return $tmp; + } + $this->last_query = str_replace(':bind'.$i, $this->quoteSmart($data[$key]), $this->last_query); + $i++; + } + if ($this->autocommit) { + $success = @OCIExecute($stmt, OCI_COMMIT_ON_SUCCESS); + } else { + $success = @OCIExecute($stmt, OCI_DEFAULT); + } + if (!$success) { + $tmp = $this->oci8RaiseError($stmt); + return $tmp; + } + $this->last_stmt = $stmt; + if ($this->manip_query[(int)$stmt] || $this->_next_query_manip) { + $this->_last_query_manip = true; + $this->_next_query_manip = false; + $tmp = DB_OK; + } else { + $this->_last_query_manip = false; + @ocisetprefetch($stmt, $this->options['result_buffering']); + $tmp = new DB_result($this, $stmt); + } + return $tmp; + } + + // }}} + // {{{ autoCommit() + + /** + * Enables or disables automatic commits + * + * @param bool $onoff true turns it on, false turns it off + * + * @return int DB_OK on success. A DB_Error object if the driver + * doesn't support auto-committing transactions. + */ + function autoCommit($onoff = false) + { + $this->autocommit = (bool)$onoff;; + return DB_OK; + } + + // }}} + // {{{ commit() + + /** + * Commits the current transaction + * + * @return int DB_OK on success. A DB_Error object on failure. + */ + function commit() + { + $result = @OCICommit($this->connection); + if (!$result) { + return $this->oci8RaiseError(); + } + return DB_OK; + } + + // }}} + // {{{ rollback() + + /** + * Reverts the current transaction + * + * @return int DB_OK on success. A DB_Error object on failure. + */ + function rollback() + { + $result = @OCIRollback($this->connection); + if (!$result) { + return $this->oci8RaiseError(); + } + return DB_OK; + } + + // }}} + // {{{ affectedRows() + + /** + * Determines the number of rows affected by a data maniuplation query + * + * 0 is returned for queries that don't manipulate data. + * + * @return int the number of rows. A DB_Error object on failure. + */ + function affectedRows() + { + if ($this->last_stmt === false) { + return $this->oci8RaiseError(); + } + $result = @OCIRowCount($this->last_stmt); + if ($result === false) { + return $this->oci8RaiseError($this->last_stmt); + } + return $result; + } + + // }}} + // {{{ modifyQuery() + + /** + * Changes a query string for various DBMS specific reasons + * + * "SELECT 2+2" must be "SELECT 2+2 FROM dual" in Oracle. + * + * @param string $query the query string to modify + * + * @return string the modified query string + * + * @access protected + */ + function modifyQuery($query) + { + if (preg_match('/^\s*SELECT/i', $query) && + !preg_match('/\sFROM\s/i', $query)) { + $query .= ' FROM dual'; + } + return $query; + } + + // }}} + // {{{ modifyLimitQuery() + + /** + * Adds LIMIT clauses to a query string according to current DBMS standards + * + * @param string $query the query to modify + * @param int $from the row to start to fetching (0 = the first row) + * @param int $count the numbers of rows to fetch + * @param mixed $params array, string or numeric data to be used in + * execution of the statement. Quantity of items + * passed must match quantity of placeholders in + * query: meaning 1 placeholder for non-array + * parameters or 1 placeholder per array element. + * + * @return string the query string with LIMIT clauses added + * + * @access protected + */ + function modifyLimitQuery($query, $from, $count, $params = array()) + { + // Let Oracle return the name of the columns instead of + // coding a "home" SQL parser + + if (count($params)) { + $result = $this->prepare("SELECT * FROM ($query) " + . 'WHERE NULL = NULL'); + $tmp = $this->execute($result, $params); + } else { + $q_fields = "SELECT * FROM ($query) WHERE NULL = NULL"; + + if (!$result = @OCIParse($this->connection, $q_fields)) { + $this->last_query = $q_fields; + return $this->oci8RaiseError(); + } + if (!@OCIExecute($result, OCI_DEFAULT)) { + $this->last_query = $q_fields; + return $this->oci8RaiseError($result); + } + } + + $ncols = OCINumCols($result); + $cols = array(); + for ( $i = 1; $i <= $ncols; $i++ ) { + $cols[] = '"' . OCIColumnName($result, $i) . '"'; + } + $fields = implode(', ', $cols); + // XXX Test that (tip by John Lim) + //if (preg_match('/^\s*SELECT\s+/is', $query, $match)) { + // // Introduce the FIRST_ROWS Oracle query optimizer + // $query = substr($query, strlen($match[0]), strlen($query)); + // $query = "SELECT /* +FIRST_ROWS */ " . $query; + //} + + // Construct the query + // more at: http://marc.theaimsgroup.com/?l=php-db&m=99831958101212&w=2 + // Perhaps this could be optimized with the use of Unions + $query = "SELECT $fields FROM". + " (SELECT rownum as linenum, $fields FROM". + " ($query)". + ' WHERE rownum <= '. ($from + $count) . + ') WHERE linenum >= ' . ++$from; + return $query; + } + + // }}} + // {{{ nextId() + + /** + * Returns the next free id in a sequence + * + * @param string $seq_name name of the sequence + * @param boolean $ondemand when true, the seqence is automatically + * created if it does not exist + * + * @return int the next id number in the sequence. + * A DB_Error object on failure. + * + * @see DB_common::nextID(), DB_common::getSequenceName(), + * DB_oci8::createSequence(), DB_oci8::dropSequence() + */ + function nextId($seq_name, $ondemand = true) + { + $seqname = $this->getSequenceName($seq_name); + $repeat = 0; + do { + $this->expectError(DB_ERROR_NOSUCHTABLE); + $result = $this->query("SELECT ${seqname}.nextval FROM dual"); + $this->popExpect(); + if ($ondemand && DB::isError($result) && + $result->getCode() == DB_ERROR_NOSUCHTABLE) { + $repeat = 1; + $result = $this->createSequence($seq_name); + if (DB::isError($result)) { + return $this->raiseError($result); + } + } else { + $repeat = 0; + } + } while ($repeat); + if (DB::isError($result)) { + return $this->raiseError($result); + } + $arr = $result->fetchRow(DB_FETCHMODE_ORDERED); + return $arr[0]; + } + + /** + * Creates a new sequence + * + * @param string $seq_name name of the new sequence + * + * @return int DB_OK on success. A DB_Error object on failure. + * + * @see DB_common::createSequence(), DB_common::getSequenceName(), + * DB_oci8::nextID(), DB_oci8::dropSequence() + */ + function createSequence($seq_name) + { + return $this->query('CREATE SEQUENCE ' + . $this->getSequenceName($seq_name)); + } + + // }}} + // {{{ dropSequence() + + /** + * Deletes a sequence + * + * @param string $seq_name name of the sequence to be deleted + * + * @return int DB_OK on success. A DB_Error object on failure. + * + * @see DB_common::dropSequence(), DB_common::getSequenceName(), + * DB_oci8::nextID(), DB_oci8::createSequence() + */ + function dropSequence($seq_name) + { + return $this->query('DROP SEQUENCE ' + . $this->getSequenceName($seq_name)); + } + + // }}} + // {{{ oci8RaiseError() + + /** + * Produces a DB_Error object regarding the current problem + * + * @param int $errno if the error is being manually raised pass a + * DB_ERROR* constant here. If this isn't passed + * the error information gathered from the DBMS. + * + * @return object the DB_Error object + * + * @see DB_common::raiseError(), + * DB_oci8::errorNative(), DB_oci8::errorCode() + */ + function oci8RaiseError($errno = null) + { + if ($errno === null) { + $error = @OCIError($this->connection); + return $this->raiseError($this->errorCode($error['code']), + null, null, null, $error['message']); + } elseif (is_resource($errno)) { + $error = @OCIError($errno); + return $this->raiseError($this->errorCode($error['code']), + null, null, null, $error['message']); + } + return $this->raiseError($this->errorCode($errno)); + } + + // }}} + // {{{ errorNative() + + /** + * Gets the DBMS' native error code produced by the last query + * + * @return int the DBMS' error code. FALSE if the code could not be + * determined + */ + function errorNative() + { + if (is_resource($this->last_stmt)) { + $error = @OCIError($this->last_stmt); + } else { + $error = @OCIError($this->connection); + } + if (is_array($error)) { + return $error['code']; + } + return false; + } + + // }}} + // {{{ tableInfo() + + /** + * Returns information about a table or a result set + * + * NOTE: only supports 'table' and 'flags' if $result + * is a table name. + * + * NOTE: flags won't contain index information. + * + * @param object|string $result DB_result object from a query or a + * string containing the name of a table. + * While this also accepts a query result + * resource identifier, this behavior is + * deprecated. + * @param int $mode a valid tableInfo mode + * + * @return array an associative array with the information requested. + * A DB_Error object on failure. + * + * @see DB_common::tableInfo() + */ + function tableInfo($result, $mode = null) + { + if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) { + $case_func = 'strtolower'; + } else { + $case_func = 'strval'; + } + + $res = array(); + + if (is_string($result)) { + /* + * Probably received a table name. + * Create a result resource identifier. + */ + $result = strtoupper($result); + $q_fields = 'SELECT column_name, data_type, data_length, ' + . 'nullable ' + . 'FROM user_tab_columns ' + . "WHERE table_name='$result' ORDER BY column_id"; + + $this->last_query = $q_fields; + + if (!$stmt = @OCIParse($this->connection, $q_fields)) { + return $this->oci8RaiseError(DB_ERROR_NEED_MORE_DATA); + } + if (!@OCIExecute($stmt, OCI_DEFAULT)) { + return $this->oci8RaiseError($stmt); + } + + $i = 0; + while (@OCIFetch($stmt)) { + $res[$i] = array( + 'table' => $case_func($result), + 'name' => $case_func(@OCIResult($stmt, 1)), + 'type' => @OCIResult($stmt, 2), + 'len' => @OCIResult($stmt, 3), + 'flags' => (@OCIResult($stmt, 4) == 'N') ? 'not_null' : '', + ); + if ($mode & DB_TABLEINFO_ORDER) { + $res['order'][$res[$i]['name']] = $i; + } + if ($mode & DB_TABLEINFO_ORDERTABLE) { + $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i; + } + $i++; + } + + if ($mode) { + $res['num_fields'] = $i; + } + @OCIFreeStatement($stmt); + + } else { + if (isset($result->result)) { + /* + * Probably received a result object. + * Extract the result resource identifier. + */ + $result = $result->result; + } + + $res = array(); + + if ($result === $this->last_stmt) { + $count = @OCINumCols($result); + if ($mode) { + $res['num_fields'] = $count; + } + for ($i = 0; $i < $count; $i++) { + $res[$i] = array( + 'table' => '', + 'name' => $case_func(@OCIColumnName($result, $i+1)), + 'type' => @OCIColumnType($result, $i+1), + 'len' => @OCIColumnSize($result, $i+1), + 'flags' => '', + ); + if ($mode & DB_TABLEINFO_ORDER) { + $res['order'][$res[$i]['name']] = $i; + } + if ($mode & DB_TABLEINFO_ORDERTABLE) { + $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i; + } + } + } else { + return $this->raiseError(DB_ERROR_NOT_CAPABLE); + } + } + return $res; + } + + // }}} + // {{{ getSpecialQuery() + + /** + * Obtains the query string needed for listing a given type of objects + * + * @param string $type the kind of objects you want to retrieve + * + * @return string the SQL query string or null if the driver doesn't + * support the object type requested + * + * @access protected + * @see DB_common::getListOf() + */ + function getSpecialQuery($type) + { + switch ($type) { + case 'tables': + return 'SELECT table_name FROM user_tables'; + case 'synonyms': + return 'SELECT synonym_name FROM user_synonyms'; + case 'views': + return 'SELECT view_name FROM user_views'; + default: + return null; + } + } + + // }}} + // {{{ quoteFloat() + + /** + * Formats a float value for use within a query in a locale-independent + * manner. + * + * @param float the float value to be quoted. + * @return string the quoted string. + * @see DB_common::quoteSmart() + * @since Method available since release 1.7.8. + */ + function quoteFloat($float) { + return $this->escapeSimple(str_replace(',', '.', strval(floatval($float)))); + } + + // }}} + +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ + +?> diff --git a/library/pear/DB/odbc.php b/library/pear/DB/odbc.php new file mode 100644 index 000000000..fecc548d8 --- /dev/null +++ b/library/pear/DB/odbc.php @@ -0,0 +1,883 @@ + + * @author Daniel Convissor + * @copyright 1997-2007 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version CVS: $Id: odbc.php,v 1.81 2007/07/06 05:19:21 aharvey Exp $ + * @link http://pear.php.net/package/DB + */ + +/** + * Obtain the DB_common class so it can be extended from + */ +require_once 'DB/common.php'; + +/** + * The methods PEAR DB uses to interact with PHP's odbc extension + * for interacting with databases via ODBC connections + * + * These methods overload the ones declared in DB_common. + * + * More info on ODBC errors could be found here: + * http://msdn.microsoft.com/library/default.asp?url=/library/en-us/trblsql/tr_err_odbc_5stz.asp + * + * @category Database + * @package DB + * @author Stig Bakken + * @author Daniel Convissor + * @copyright 1997-2007 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version Release: 1.7.13 + * @link http://pear.php.net/package/DB + */ +class DB_odbc extends DB_common +{ + // {{{ properties + + /** + * The DB driver type (mysql, oci8, odbc, etc.) + * @var string + */ + var $phptype = 'odbc'; + + /** + * The database syntax variant to be used (db2, access, etc.), if any + * @var string + */ + var $dbsyntax = 'sql92'; + + /** + * The capabilities of this DB implementation + * + * The 'new_link' element contains the PHP version that first provided + * new_link support for this DBMS. Contains false if it's unsupported. + * + * Meaning of the 'limit' element: + * + 'emulate' = emulate with fetch row by number + * + 'alter' = alter the query + * + false = skip rows + * + * NOTE: The feature set of the following drivers are different than + * the default: + * + solid: 'transactions' = true + * + navision: 'limit' = false + * + * @var array + */ + var $features = array( + 'limit' => 'emulate', + 'new_link' => false, + 'numrows' => true, + 'pconnect' => true, + 'prepare' => false, + 'ssl' => false, + 'transactions' => false, + ); + + /** + * A mapping of native error codes to DB error codes + * @var array + */ + var $errorcode_map = array( + '01004' => DB_ERROR_TRUNCATED, + '07001' => DB_ERROR_MISMATCH, + '21S01' => DB_ERROR_VALUE_COUNT_ON_ROW, + '21S02' => DB_ERROR_MISMATCH, + '22001' => DB_ERROR_INVALID, + '22003' => DB_ERROR_INVALID_NUMBER, + '22005' => DB_ERROR_INVALID_NUMBER, + '22008' => DB_ERROR_INVALID_DATE, + '22012' => DB_ERROR_DIVZERO, + '23000' => DB_ERROR_CONSTRAINT, + '23502' => DB_ERROR_CONSTRAINT_NOT_NULL, + '23503' => DB_ERROR_CONSTRAINT, + '23504' => DB_ERROR_CONSTRAINT, + '23505' => DB_ERROR_CONSTRAINT, + '24000' => DB_ERROR_INVALID, + '34000' => DB_ERROR_INVALID, + '37000' => DB_ERROR_SYNTAX, + '42000' => DB_ERROR_SYNTAX, + '42601' => DB_ERROR_SYNTAX, + 'IM001' => DB_ERROR_UNSUPPORTED, + 'S0000' => DB_ERROR_NOSUCHTABLE, + 'S0001' => DB_ERROR_ALREADY_EXISTS, + 'S0002' => DB_ERROR_NOSUCHTABLE, + 'S0011' => DB_ERROR_ALREADY_EXISTS, + 'S0012' => DB_ERROR_NOT_FOUND, + 'S0021' => DB_ERROR_ALREADY_EXISTS, + 'S0022' => DB_ERROR_NOSUCHFIELD, + 'S1009' => DB_ERROR_INVALID, + 'S1090' => DB_ERROR_INVALID, + 'S1C00' => DB_ERROR_NOT_CAPABLE, + ); + + /** + * The raw database connection created by PHP + * @var resource + */ + var $connection; + + /** + * The DSN information for connecting to a database + * @var array + */ + var $dsn = array(); + + + /** + * The number of rows affected by a data manipulation query + * @var integer + * @access private + */ + var $affected = 0; + + + // }}} + // {{{ constructor + + /** + * This constructor calls $this->DB_common() + * + * @return void + */ + function DB_odbc() + { + $this->DB_common(); + } + + // }}} + // {{{ connect() + + /** + * Connect to the database server, log in and open the database + * + * Don't call this method directly. Use DB::connect() instead. + * + * PEAR DB's odbc driver supports the following extra DSN options: + * + cursor The type of cursor to be used for this connection. + * + * @param array $dsn the data source name + * @param bool $persistent should the connection be persistent? + * + * @return int DB_OK on success. A DB_Error object on failure. + */ + function connect($dsn, $persistent = false) + { + if (!PEAR::loadExtension('odbc')) { + return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND); + } + + $this->dsn = $dsn; + if ($dsn['dbsyntax']) { + $this->dbsyntax = $dsn['dbsyntax']; + } + switch ($this->dbsyntax) { + case 'access': + case 'db2': + case 'solid': + $this->features['transactions'] = true; + break; + case 'navision': + $this->features['limit'] = false; + } + + /* + * This is hear for backwards compatibility. Should have been using + * 'database' all along, but prior to 1.6.0RC3 'hostspec' was used. + */ + if ($dsn['database']) { + $odbcdsn = $dsn['database']; + } elseif ($dsn['hostspec']) { + $odbcdsn = $dsn['hostspec']; + } else { + $odbcdsn = 'localhost'; + } + + $connect_function = $persistent ? 'odbc_pconnect' : 'odbc_connect'; + + if (empty($dsn['cursor'])) { + $this->connection = @$connect_function($odbcdsn, $dsn['username'], + $dsn['password']); + } else { + $this->connection = @$connect_function($odbcdsn, $dsn['username'], + $dsn['password'], + $dsn['cursor']); + } + + if (!is_resource($this->connection)) { + return $this->raiseError(DB_ERROR_CONNECT_FAILED, + null, null, null, + $this->errorNative()); + } + return DB_OK; + } + + // }}} + // {{{ disconnect() + + /** + * Disconnects from the database server + * + * @return bool TRUE on success, FALSE on failure + */ + function disconnect() + { + $err = @odbc_close($this->connection); + $this->connection = null; + return $err; + } + + // }}} + // {{{ simpleQuery() + + /** + * Sends a query to the database server + * + * @param string the SQL query string + * + * @return mixed + a PHP result resrouce for successful SELECT queries + * + the DB_OK constant for other successful queries + * + a DB_Error object on failure + */ + function simpleQuery($query) + { + $this->last_query = $query; + $query = $this->modifyQuery($query); + $result = @odbc_exec($this->connection, $query); + if (!$result) { + return $this->odbcRaiseError(); // XXX ERRORMSG + } + // Determine which queries that should return data, and which + // should return an error code only. + if ($this->_checkManip($query)) { + $this->affected = $result; // For affectedRows() + return DB_OK; + } + $this->affected = 0; + return $result; + } + + // }}} + // {{{ nextResult() + + /** + * Move the internal odbc result pointer to the next available result + * + * @param a valid fbsql result resource + * + * @access public + * + * @return true if a result is available otherwise return false + */ + function nextResult($result) + { + return @odbc_next_result($result); + } + + // }}} + // {{{ fetchInto() + + /** + * Places a row from the result set into the given array + * + * Formating of the array and the data therein are configurable. + * See DB_result::fetchInto() for more information. + * + * This method is not meant to be called directly. Use + * DB_result::fetchInto() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result the query result resource + * @param array $arr the referenced array to put the data in + * @param int $fetchmode how the resulting array should be indexed + * @param int $rownum the row number to fetch (0 = first row) + * + * @return mixed DB_OK on success, NULL when the end of a result set is + * reached or on failure + * + * @see DB_result::fetchInto() + */ + function fetchInto($result, &$arr, $fetchmode, $rownum = null) + { + $arr = array(); + if ($rownum !== null) { + $rownum++; // ODBC first row is 1 + if (version_compare(phpversion(), '4.2.0', 'ge')) { + $cols = @odbc_fetch_into($result, $arr, $rownum); + } else { + $cols = @odbc_fetch_into($result, $rownum, $arr); + } + } else { + $cols = @odbc_fetch_into($result, $arr); + } + if (!$cols) { + return null; + } + if ($fetchmode !== DB_FETCHMODE_ORDERED) { + for ($i = 0; $i < count($arr); $i++) { + $colName = @odbc_field_name($result, $i+1); + $a[$colName] = $arr[$i]; + } + if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) { + $a = array_change_key_case($a, CASE_LOWER); + } + $arr = $a; + } + if ($this->options['portability'] & DB_PORTABILITY_RTRIM) { + $this->_rtrimArrayValues($arr); + } + if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) { + $this->_convertNullArrayValuesToEmpty($arr); + } + return DB_OK; + } + + // }}} + // {{{ freeResult() + + /** + * Deletes the result set and frees the memory occupied by the result set + * + * This method is not meant to be called directly. Use + * DB_result::free() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result PHP's query result resource + * + * @return bool TRUE on success, FALSE if $result is invalid + * + * @see DB_result::free() + */ + function freeResult($result) + { + return is_resource($result) ? odbc_free_result($result) : false; + } + + // }}} + // {{{ numCols() + + /** + * Gets the number of columns in a result set + * + * This method is not meant to be called directly. Use + * DB_result::numCols() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result PHP's query result resource + * + * @return int the number of columns. A DB_Error object on failure. + * + * @see DB_result::numCols() + */ + function numCols($result) + { + $cols = @odbc_num_fields($result); + if (!$cols) { + return $this->odbcRaiseError(); + } + return $cols; + } + + // }}} + // {{{ affectedRows() + + /** + * Determines the number of rows affected by a data maniuplation query + * + * 0 is returned for queries that don't manipulate data. + * + * @return int the number of rows. A DB_Error object on failure. + */ + function affectedRows() + { + if (empty($this->affected)) { // In case of SELECT stms + return 0; + } + $nrows = @odbc_num_rows($this->affected); + if ($nrows == -1) { + return $this->odbcRaiseError(); + } + return $nrows; + } + + // }}} + // {{{ numRows() + + /** + * Gets the number of rows in a result set + * + * Not all ODBC drivers support this functionality. If they don't + * a DB_Error object for DB_ERROR_UNSUPPORTED is returned. + * + * This method is not meant to be called directly. Use + * DB_result::numRows() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result PHP's query result resource + * + * @return int the number of rows. A DB_Error object on failure. + * + * @see DB_result::numRows() + */ + function numRows($result) + { + $nrows = @odbc_num_rows($result); + if ($nrows == -1) { + return $this->odbcRaiseError(DB_ERROR_UNSUPPORTED); + } + if ($nrows === false) { + return $this->odbcRaiseError(); + } + return $nrows; + } + + // }}} + // {{{ quoteIdentifier() + + /** + * Quotes a string so it can be safely used as a table or column name + * + * Use 'mssql' as the dbsyntax in the DB DSN only if you've unchecked + * "Use ANSI quoted identifiers" when setting up the ODBC data source. + * + * @param string $str identifier name to be quoted + * + * @return string quoted identifier string + * + * @see DB_common::quoteIdentifier() + * @since Method available since Release 1.6.0 + */ + function quoteIdentifier($str) + { + switch ($this->dsn['dbsyntax']) { + case 'access': + return '[' . $str . ']'; + case 'mssql': + case 'sybase': + return '[' . str_replace(']', ']]', $str) . ']'; + case 'mysql': + case 'mysqli': + return '`' . $str . '`'; + default: + return '"' . str_replace('"', '""', $str) . '"'; + } + } + + // }}} + // {{{ quote() + + /** + * @deprecated Deprecated in release 1.6.0 + * @internal + */ + function quote($str) + { + return $this->quoteSmart($str); + } + + // }}} + // {{{ nextId() + + /** + * Returns the next free id in a sequence + * + * @param string $seq_name name of the sequence + * @param boolean $ondemand when true, the seqence is automatically + * created if it does not exist + * + * @return int the next id number in the sequence. + * A DB_Error object on failure. + * + * @see DB_common::nextID(), DB_common::getSequenceName(), + * DB_odbc::createSequence(), DB_odbc::dropSequence() + */ + function nextId($seq_name, $ondemand = true) + { + $seqname = $this->getSequenceName($seq_name); + $repeat = 0; + do { + $this->pushErrorHandling(PEAR_ERROR_RETURN); + $result = $this->query("update ${seqname} set id = id + 1"); + $this->popErrorHandling(); + if ($ondemand && DB::isError($result) && + $result->getCode() == DB_ERROR_NOSUCHTABLE) { + $repeat = 1; + $this->pushErrorHandling(PEAR_ERROR_RETURN); + $result = $this->createSequence($seq_name); + $this->popErrorHandling(); + if (DB::isError($result)) { + return $this->raiseError($result); + } + $result = $this->query("insert into ${seqname} (id) values(0)"); + } else { + $repeat = 0; + } + } while ($repeat); + + if (DB::isError($result)) { + return $this->raiseError($result); + } + + $result = $this->query("select id from ${seqname}"); + if (DB::isError($result)) { + return $result; + } + + $row = $result->fetchRow(DB_FETCHMODE_ORDERED); + if (DB::isError($row || !$row)) { + return $row; + } + + return $row[0]; + } + + /** + * Creates a new sequence + * + * @param string $seq_name name of the new sequence + * + * @return int DB_OK on success. A DB_Error object on failure. + * + * @see DB_common::createSequence(), DB_common::getSequenceName(), + * DB_odbc::nextID(), DB_odbc::dropSequence() + */ + function createSequence($seq_name) + { + return $this->query('CREATE TABLE ' + . $this->getSequenceName($seq_name) + . ' (id integer NOT NULL,' + . ' PRIMARY KEY(id))'); + } + + // }}} + // {{{ dropSequence() + + /** + * Deletes a sequence + * + * @param string $seq_name name of the sequence to be deleted + * + * @return int DB_OK on success. A DB_Error object on failure. + * + * @see DB_common::dropSequence(), DB_common::getSequenceName(), + * DB_odbc::nextID(), DB_odbc::createSequence() + */ + function dropSequence($seq_name) + { + return $this->query('DROP TABLE ' . $this->getSequenceName($seq_name)); + } + + // }}} + // {{{ autoCommit() + + /** + * Enables or disables automatic commits + * + * @param bool $onoff true turns it on, false turns it off + * + * @return int DB_OK on success. A DB_Error object if the driver + * doesn't support auto-committing transactions. + */ + function autoCommit($onoff = false) + { + if (!@odbc_autocommit($this->connection, $onoff)) { + return $this->odbcRaiseError(); + } + return DB_OK; + } + + // }}} + // {{{ commit() + + /** + * Commits the current transaction + * + * @return int DB_OK on success. A DB_Error object on failure. + */ + function commit() + { + if (!@odbc_commit($this->connection)) { + return $this->odbcRaiseError(); + } + return DB_OK; + } + + // }}} + // {{{ rollback() + + /** + * Reverts the current transaction + * + * @return int DB_OK on success. A DB_Error object on failure. + */ + function rollback() + { + if (!@odbc_rollback($this->connection)) { + return $this->odbcRaiseError(); + } + return DB_OK; + } + + // }}} + // {{{ odbcRaiseError() + + /** + * Produces a DB_Error object regarding the current problem + * + * @param int $errno if the error is being manually raised pass a + * DB_ERROR* constant here. If this isn't passed + * the error information gathered from the DBMS. + * + * @return object the DB_Error object + * + * @see DB_common::raiseError(), + * DB_odbc::errorNative(), DB_common::errorCode() + */ + function odbcRaiseError($errno = null) + { + if ($errno === null) { + switch ($this->dbsyntax) { + case 'access': + if ($this->options['portability'] & DB_PORTABILITY_ERRORS) { + $this->errorcode_map['07001'] = DB_ERROR_NOSUCHFIELD; + } else { + // Doing this in case mode changes during runtime. + $this->errorcode_map['07001'] = DB_ERROR_MISMATCH; + } + + $native_code = odbc_error($this->connection); + + // S1000 is for "General Error." Let's be more specific. + if ($native_code == 'S1000') { + $errormsg = odbc_errormsg($this->connection); + static $error_regexps; + if (!isset($error_regexps)) { + $error_regexps = array( + '/includes related records.$/i' => DB_ERROR_CONSTRAINT, + '/cannot contain a Null value/i' => DB_ERROR_CONSTRAINT_NOT_NULL, + ); + } + foreach ($error_regexps as $regexp => $code) { + if (preg_match($regexp, $errormsg)) { + return $this->raiseError($code, + null, null, null, + $native_code . ' ' . $errormsg); + } + } + $errno = DB_ERROR; + } else { + $errno = $this->errorCode($native_code); + } + break; + default: + $errno = $this->errorCode(odbc_error($this->connection)); + } + } + return $this->raiseError($errno, null, null, null, + $this->errorNative()); + } + + // }}} + // {{{ errorNative() + + /** + * Gets the DBMS' native error code and message produced by the last query + * + * @return string the DBMS' error code and message + */ + function errorNative() + { + if (!is_resource($this->connection)) { + return @odbc_error() . ' ' . @odbc_errormsg(); + } + return @odbc_error($this->connection) . ' ' . @odbc_errormsg($this->connection); + } + + // }}} + // {{{ tableInfo() + + /** + * Returns information about a table or a result set + * + * @param object|string $result DB_result object from a query or a + * string containing the name of a table. + * While this also accepts a query result + * resource identifier, this behavior is + * deprecated. + * @param int $mode a valid tableInfo mode + * + * @return array an associative array with the information requested. + * A DB_Error object on failure. + * + * @see DB_common::tableInfo() + * @since Method available since Release 1.7.0 + */ + function tableInfo($result, $mode = null) + { + if (is_string($result)) { + /* + * Probably received a table name. + * Create a result resource identifier. + */ + $id = @odbc_exec($this->connection, "SELECT * FROM $result"); + if (!$id) { + return $this->odbcRaiseError(); + } + $got_string = true; + } elseif (isset($result->result)) { + /* + * Probably received a result object. + * Extract the result resource identifier. + */ + $id = $result->result; + $got_string = false; + } else { + /* + * Probably received a result resource identifier. + * Copy it. + * Deprecated. Here for compatibility only. + */ + $id = $result; + $got_string = false; + } + + if (!is_resource($id)) { + return $this->odbcRaiseError(DB_ERROR_NEED_MORE_DATA); + } + + if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) { + $case_func = 'strtolower'; + } else { + $case_func = 'strval'; + } + + $count = @odbc_num_fields($id); + $res = array(); + + if ($mode) { + $res['num_fields'] = $count; + } + + for ($i = 0; $i < $count; $i++) { + $col = $i + 1; + $res[$i] = array( + 'table' => $got_string ? $case_func($result) : '', + 'name' => $case_func(@odbc_field_name($id, $col)), + 'type' => @odbc_field_type($id, $col), + 'len' => @odbc_field_len($id, $col), + 'flags' => '', + ); + if ($mode & DB_TABLEINFO_ORDER) { + $res['order'][$res[$i]['name']] = $i; + } + if ($mode & DB_TABLEINFO_ORDERTABLE) { + $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i; + } + } + + // free the result only if we were called on a table + if ($got_string) { + @odbc_free_result($id); + } + return $res; + } + + // }}} + // {{{ getSpecialQuery() + + /** + * Obtains the query string needed for listing a given type of objects + * + * Thanks to symbol1@gmail.com and Philippe.Jausions@11abacus.com. + * + * @param string $type the kind of objects you want to retrieve + * + * @return string the list of objects requested + * + * @access protected + * @see DB_common::getListOf() + * @since Method available since Release 1.7.0 + */ + function getSpecialQuery($type) + { + switch ($type) { + case 'databases': + if (!function_exists('odbc_data_source')) { + return null; + } + $res = @odbc_data_source($this->connection, SQL_FETCH_FIRST); + if (is_array($res)) { + $out = array($res['server']); + while($res = @odbc_data_source($this->connection, + SQL_FETCH_NEXT)) + { + $out[] = $res['server']; + } + return $out; + } else { + return $this->odbcRaiseError(); + } + break; + case 'tables': + case 'schema.tables': + $keep = 'TABLE'; + break; + case 'views': + $keep = 'VIEW'; + break; + default: + return null; + } + + /* + * Removing non-conforming items in the while loop rather than + * in the odbc_tables() call because some backends choke on this: + * odbc_tables($this->connection, '', '', '', 'TABLE') + */ + $res = @odbc_tables($this->connection); + if (!$res) { + return $this->odbcRaiseError(); + } + $out = array(); + while ($row = odbc_fetch_array($res)) { + if ($row['TABLE_TYPE'] != $keep) { + continue; + } + if ($type == 'schema.tables') { + $out[] = $row['TABLE_SCHEM'] . '.' . $row['TABLE_NAME']; + } else { + $out[] = $row['TABLE_NAME']; + } + } + return $out; + } + + // }}} + +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ + +?> diff --git a/library/pear/DB/pgsql.php b/library/pear/DB/pgsql.php new file mode 100644 index 000000000..039888faf --- /dev/null +++ b/library/pear/DB/pgsql.php @@ -0,0 +1,1116 @@ + + * @author Stig Bakken + * @author Daniel Convissor + * @copyright 1997-2007 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version CVS: $Id: pgsql.php,v 1.138 2007/09/21 13:40:41 aharvey Exp $ + * @link http://pear.php.net/package/DB + */ + +/** + * Obtain the DB_common class so it can be extended from + */ +require_once 'DB/common.php'; + +/** + * The methods PEAR DB uses to interact with PHP's pgsql extension + * for interacting with PostgreSQL databases + * + * These methods overload the ones declared in DB_common. + * + * @category Database + * @package DB + * @author Rui Hirokawa + * @author Stig Bakken + * @author Daniel Convissor + * @copyright 1997-2007 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version Release: 1.7.13 + * @link http://pear.php.net/package/DB + */ +class DB_pgsql extends DB_common +{ + // {{{ properties + + /** + * The DB driver type (mysql, oci8, odbc, etc.) + * @var string + */ + var $phptype = 'pgsql'; + + /** + * The database syntax variant to be used (db2, access, etc.), if any + * @var string + */ + var $dbsyntax = 'pgsql'; + + /** + * The capabilities of this DB implementation + * + * The 'new_link' element contains the PHP version that first provided + * new_link support for this DBMS. Contains false if it's unsupported. + * + * Meaning of the 'limit' element: + * + 'emulate' = emulate with fetch row by number + * + 'alter' = alter the query + * + false = skip rows + * + * @var array + */ + var $features = array( + 'limit' => 'alter', + 'new_link' => '4.3.0', + 'numrows' => true, + 'pconnect' => true, + 'prepare' => false, + 'ssl' => true, + 'transactions' => true, + ); + + /** + * A mapping of native error codes to DB error codes + * @var array + */ + var $errorcode_map = array( + ); + + /** + * The raw database connection created by PHP + * @var resource + */ + var $connection; + + /** + * The DSN information for connecting to a database + * @var array + */ + var $dsn = array(); + + + /** + * Should data manipulation queries be committed automatically? + * @var bool + * @access private + */ + var $autocommit = true; + + /** + * The quantity of transactions begun + * + * {@internal While this is private, it can't actually be designated + * private in PHP 5 because it is directly accessed in the test suite.}} + * + * @var integer + * @access private + */ + var $transaction_opcount = 0; + + /** + * The number of rows affected by a data manipulation query + * @var integer + */ + var $affected = 0; + + /** + * The current row being looked at in fetchInto() + * @var array + * @access private + */ + var $row = array(); + + /** + * The number of rows in a given result set + * @var array + * @access private + */ + var $_num_rows = array(); + + + // }}} + // {{{ constructor + + /** + * This constructor calls $this->DB_common() + * + * @return void + */ + function DB_pgsql() + { + $this->DB_common(); + } + + // }}} + // {{{ connect() + + /** + * Connect to the database server, log in and open the database + * + * Don't call this method directly. Use DB::connect() instead. + * + * PEAR DB's pgsql driver supports the following extra DSN options: + * + connect_timeout How many seconds to wait for a connection to + * be established. Available since PEAR DB 1.7.0. + * + new_link If set to true, causes subsequent calls to + * connect() to return a new connection link + * instead of the existing one. WARNING: this is + * not portable to other DBMS's. Available only + * if PHP is >= 4.3.0 and PEAR DB is >= 1.7.0. + * + options Command line options to be sent to the server. + * Available since PEAR DB 1.6.4. + * + service Specifies a service name in pg_service.conf that + * holds additional connection parameters. + * Available since PEAR DB 1.7.0. + * + sslmode How should SSL be used when connecting? Values: + * disable, allow, prefer or require. + * Available since PEAR DB 1.7.0. + * + tty This was used to specify where to send server + * debug output. Available since PEAR DB 1.6.4. + * + * Example of connecting to a new link via a socket: + * + * require_once 'DB.php'; + * + * $dsn = 'pgsql://user:pass@unix(/tmp)/dbname?new_link=true'; + * $options = array( + * 'portability' => DB_PORTABILITY_ALL, + * ); + * + * $db = DB::connect($dsn, $options); + * if (PEAR::isError($db)) { + * die($db->getMessage()); + * } + * + * + * @param array $dsn the data source name + * @param bool $persistent should the connection be persistent? + * + * @return int DB_OK on success. A DB_Error object on failure. + * + * @link http://www.postgresql.org/docs/current/static/libpq.html#LIBPQ-CONNECT + */ + function connect($dsn, $persistent = false) + { + if (!PEAR::loadExtension('pgsql')) { + return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND); + } + + $this->dsn = $dsn; + if ($dsn['dbsyntax']) { + $this->dbsyntax = $dsn['dbsyntax']; + } + + $protocol = $dsn['protocol'] ? $dsn['protocol'] : 'tcp'; + + $params = array(''); + if ($protocol == 'tcp') { + if ($dsn['hostspec']) { + $params[0] .= 'host=' . $dsn['hostspec']; + } + if ($dsn['port']) { + $params[0] .= ' port=' . $dsn['port']; + } + } elseif ($protocol == 'unix') { + // Allow for pg socket in non-standard locations. + if ($dsn['socket']) { + $params[0] .= 'host=' . $dsn['socket']; + } + if ($dsn['port']) { + $params[0] .= ' port=' . $dsn['port']; + } + } + if ($dsn['database']) { + $params[0] .= ' dbname=\'' . addslashes($dsn['database']) . '\''; + } + if ($dsn['username']) { + $params[0] .= ' user=\'' . addslashes($dsn['username']) . '\''; + } + if ($dsn['password']) { + $params[0] .= ' password=\'' . addslashes($dsn['password']) . '\''; + } + if (!empty($dsn['options'])) { + $params[0] .= ' options=' . $dsn['options']; + } + if (!empty($dsn['tty'])) { + $params[0] .= ' tty=' . $dsn['tty']; + } + if (!empty($dsn['connect_timeout'])) { + $params[0] .= ' connect_timeout=' . $dsn['connect_timeout']; + } + if (!empty($dsn['sslmode'])) { + $params[0] .= ' sslmode=' . $dsn['sslmode']; + } + if (!empty($dsn['service'])) { + $params[0] .= ' service=' . $dsn['service']; + } + + if (isset($dsn['new_link']) + && ($dsn['new_link'] == 'true' || $dsn['new_link'] === true)) + { + if (version_compare(phpversion(), '4.3.0', '>=')) { + $params[] = PGSQL_CONNECT_FORCE_NEW; + } + } + + $connect_function = $persistent ? 'pg_pconnect' : 'pg_connect'; + + $ini = ini_get('track_errors'); + $php_errormsg = ''; + if ($ini) { + $this->connection = @call_user_func_array($connect_function, + $params); + } else { + @ini_set('track_errors', 1); + $this->connection = @call_user_func_array($connect_function, + $params); + @ini_set('track_errors', $ini); + } + + if (!$this->connection) { + return $this->raiseError(DB_ERROR_CONNECT_FAILED, + null, null, null, + $php_errormsg); + } + return DB_OK; + } + + // }}} + // {{{ disconnect() + + /** + * Disconnects from the database server + * + * @return bool TRUE on success, FALSE on failure + */ + function disconnect() + { + $ret = @pg_close($this->connection); + $this->connection = null; + return $ret; + } + + // }}} + // {{{ simpleQuery() + + /** + * Sends a query to the database server + * + * @param string the SQL query string + * + * @return mixed + a PHP result resrouce for successful SELECT queries + * + the DB_OK constant for other successful queries + * + a DB_Error object on failure + */ + function simpleQuery($query) + { + $ismanip = $this->_checkManip($query); + $this->last_query = $query; + $query = $this->modifyQuery($query); + if (!$this->autocommit && $ismanip) { + if ($this->transaction_opcount == 0) { + $result = @pg_exec($this->connection, 'begin;'); + if (!$result) { + return $this->pgsqlRaiseError(); + } + } + $this->transaction_opcount++; + } + $result = @pg_exec($this->connection, $query); + if (!$result) { + return $this->pgsqlRaiseError(); + } + + /* + * Determine whether queries produce affected rows, result or nothing. + * + * This logic was introduced in version 1.1 of the file by ssb, + * though the regex has been modified slightly since then. + * + * PostgreSQL commands: + * ABORT, ALTER, BEGIN, CLOSE, CLUSTER, COMMIT, COPY, + * CREATE, DECLARE, DELETE, DROP TABLE, EXPLAIN, FETCH, + * GRANT, INSERT, LISTEN, LOAD, LOCK, MOVE, NOTIFY, RESET, + * REVOKE, ROLLBACK, SELECT, SELECT INTO, SET, SHOW, + * UNLISTEN, UPDATE, VACUUM + */ + if ($ismanip) { + $this->affected = @pg_affected_rows($result); + return DB_OK; + } elseif (preg_match('/^\s*\(*\s*(SELECT|EXPLAIN|FETCH|SHOW)\s/si', + $query)) + { + $this->row[(int)$result] = 0; // reset the row counter. + $numrows = $this->numRows($result); + if (is_object($numrows)) { + return $numrows; + } + $this->_num_rows[(int)$result] = $numrows; + $this->affected = 0; + return $result; + } else { + $this->affected = 0; + return DB_OK; + } + } + + // }}} + // {{{ nextResult() + + /** + * Move the internal pgsql result pointer to the next available result + * + * @param a valid fbsql result resource + * + * @access public + * + * @return true if a result is available otherwise return false + */ + function nextResult($result) + { + return false; + } + + // }}} + // {{{ fetchInto() + + /** + * Places a row from the result set into the given array + * + * Formating of the array and the data therein are configurable. + * See DB_result::fetchInto() for more information. + * + * This method is not meant to be called directly. Use + * DB_result::fetchInto() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result the query result resource + * @param array $arr the referenced array to put the data in + * @param int $fetchmode how the resulting array should be indexed + * @param int $rownum the row number to fetch (0 = first row) + * + * @return mixed DB_OK on success, NULL when the end of a result set is + * reached or on failure + * + * @see DB_result::fetchInto() + */ + function fetchInto($result, &$arr, $fetchmode, $rownum = null) + { + $result_int = (int)$result; + $rownum = ($rownum !== null) ? $rownum : $this->row[$result_int]; + if ($rownum >= $this->_num_rows[$result_int]) { + return null; + } + if ($fetchmode & DB_FETCHMODE_ASSOC) { + $arr = @pg_fetch_array($result, $rownum, PGSQL_ASSOC); + if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE && $arr) { + $arr = array_change_key_case($arr, CASE_LOWER); + } + } else { + $arr = @pg_fetch_row($result, $rownum); + } + if (!$arr) { + return null; + } + if ($this->options['portability'] & DB_PORTABILITY_RTRIM) { + $this->_rtrimArrayValues($arr); + } + if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) { + $this->_convertNullArrayValuesToEmpty($arr); + } + $this->row[$result_int] = ++$rownum; + return DB_OK; + } + + // }}} + // {{{ freeResult() + + /** + * Deletes the result set and frees the memory occupied by the result set + * + * This method is not meant to be called directly. Use + * DB_result::free() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result PHP's query result resource + * + * @return bool TRUE on success, FALSE if $result is invalid + * + * @see DB_result::free() + */ + function freeResult($result) + { + if (is_resource($result)) { + unset($this->row[(int)$result]); + unset($this->_num_rows[(int)$result]); + $this->affected = 0; + return @pg_freeresult($result); + } + return false; + } + + // }}} + // {{{ quote() + + /** + * @deprecated Deprecated in release 1.6.0 + * @internal + */ + function quote($str) + { + return $this->quoteSmart($str); + } + + // }}} + // {{{ quoteBoolean() + + /** + * Formats a boolean value for use within a query in a locale-independent + * manner. + * + * @param boolean the boolean value to be quoted. + * @return string the quoted string. + * @see DB_common::quoteSmart() + * @since Method available since release 1.7.8. + */ + function quoteBoolean($boolean) { + return $boolean ? 'TRUE' : 'FALSE'; + } + + // }}} + // {{{ escapeSimple() + + /** + * Escapes a string according to the current DBMS's standards + * + * {@internal PostgreSQL treats a backslash as an escape character, + * so they are escaped as well. + * + * @param string $str the string to be escaped + * + * @return string the escaped string + * + * @see DB_common::quoteSmart() + * @since Method available since Release 1.6.0 + */ + function escapeSimple($str) + { + if (function_exists('pg_escape_string')) { + /* This fixes an undocumented BC break in PHP 5.2.0 which changed + * the prototype of pg_escape_string. I'm not thrilled about having + * to sniff the PHP version, quite frankly, but it's the only way + * to deal with the problem. Revision 1.331.2.13.2.10 on + * php-src/ext/pgsql/pgsql.c (PHP_5_2 branch) is to blame, for the + * record. */ + if (version_compare(PHP_VERSION, '5.2.0', '>=')) { + return pg_escape_string($this->connection, $str); + } else { + return pg_escape_string($str); + } + } else { + return str_replace("'", "''", str_replace('\\', '\\\\', $str)); + } + } + + // }}} + // {{{ numCols() + + /** + * Gets the number of columns in a result set + * + * This method is not meant to be called directly. Use + * DB_result::numCols() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result PHP's query result resource + * + * @return int the number of columns. A DB_Error object on failure. + * + * @see DB_result::numCols() + */ + function numCols($result) + { + $cols = @pg_numfields($result); + if (!$cols) { + return $this->pgsqlRaiseError(); + } + return $cols; + } + + // }}} + // {{{ numRows() + + /** + * Gets the number of rows in a result set + * + * This method is not meant to be called directly. Use + * DB_result::numRows() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result PHP's query result resource + * + * @return int the number of rows. A DB_Error object on failure. + * + * @see DB_result::numRows() + */ + function numRows($result) + { + $rows = @pg_numrows($result); + if ($rows === null) { + return $this->pgsqlRaiseError(); + } + return $rows; + } + + // }}} + // {{{ autoCommit() + + /** + * Enables or disables automatic commits + * + * @param bool $onoff true turns it on, false turns it off + * + * @return int DB_OK on success. A DB_Error object if the driver + * doesn't support auto-committing transactions. + */ + function autoCommit($onoff = false) + { + // XXX if $this->transaction_opcount > 0, we should probably + // issue a warning here. + $this->autocommit = $onoff ? true : false; + return DB_OK; + } + + // }}} + // {{{ commit() + + /** + * Commits the current transaction + * + * @return int DB_OK on success. A DB_Error object on failure. + */ + function commit() + { + if ($this->transaction_opcount > 0) { + // (disabled) hack to shut up error messages from libpq.a + //@fclose(@fopen("php://stderr", "w")); + $result = @pg_exec($this->connection, 'end;'); + $this->transaction_opcount = 0; + if (!$result) { + return $this->pgsqlRaiseError(); + } + } + return DB_OK; + } + + // }}} + // {{{ rollback() + + /** + * Reverts the current transaction + * + * @return int DB_OK on success. A DB_Error object on failure. + */ + function rollback() + { + if ($this->transaction_opcount > 0) { + $result = @pg_exec($this->connection, 'abort;'); + $this->transaction_opcount = 0; + if (!$result) { + return $this->pgsqlRaiseError(); + } + } + return DB_OK; + } + + // }}} + // {{{ affectedRows() + + /** + * Determines the number of rows affected by a data maniuplation query + * + * 0 is returned for queries that don't manipulate data. + * + * @return int the number of rows. A DB_Error object on failure. + */ + function affectedRows() + { + return $this->affected; + } + + // }}} + // {{{ nextId() + + /** + * Returns the next free id in a sequence + * + * @param string $seq_name name of the sequence + * @param boolean $ondemand when true, the seqence is automatically + * created if it does not exist + * + * @return int the next id number in the sequence. + * A DB_Error object on failure. + * + * @see DB_common::nextID(), DB_common::getSequenceName(), + * DB_pgsql::createSequence(), DB_pgsql::dropSequence() + */ + function nextId($seq_name, $ondemand = true) + { + $seqname = $this->getSequenceName($seq_name); + $repeat = false; + do { + $this->pushErrorHandling(PEAR_ERROR_RETURN); + $result = $this->query("SELECT NEXTVAL('${seqname}')"); + $this->popErrorHandling(); + if ($ondemand && DB::isError($result) && + $result->getCode() == DB_ERROR_NOSUCHTABLE) { + $repeat = true; + $this->pushErrorHandling(PEAR_ERROR_RETURN); + $result = $this->createSequence($seq_name); + $this->popErrorHandling(); + if (DB::isError($result)) { + return $this->raiseError($result); + } + } else { + $repeat = false; + } + } while ($repeat); + if (DB::isError($result)) { + return $this->raiseError($result); + } + $arr = $result->fetchRow(DB_FETCHMODE_ORDERED); + $result->free(); + return $arr[0]; + } + + // }}} + // {{{ createSequence() + + /** + * Creates a new sequence + * + * @param string $seq_name name of the new sequence + * + * @return int DB_OK on success. A DB_Error object on failure. + * + * @see DB_common::createSequence(), DB_common::getSequenceName(), + * DB_pgsql::nextID(), DB_pgsql::dropSequence() + */ + function createSequence($seq_name) + { + $seqname = $this->getSequenceName($seq_name); + $result = $this->query("CREATE SEQUENCE ${seqname}"); + return $result; + } + + // }}} + // {{{ dropSequence() + + /** + * Deletes a sequence + * + * @param string $seq_name name of the sequence to be deleted + * + * @return int DB_OK on success. A DB_Error object on failure. + * + * @see DB_common::dropSequence(), DB_common::getSequenceName(), + * DB_pgsql::nextID(), DB_pgsql::createSequence() + */ + function dropSequence($seq_name) + { + return $this->query('DROP SEQUENCE ' + . $this->getSequenceName($seq_name)); + } + + // }}} + // {{{ modifyLimitQuery() + + /** + * Adds LIMIT clauses to a query string according to current DBMS standards + * + * @param string $query the query to modify + * @param int $from the row to start to fetching (0 = the first row) + * @param int $count the numbers of rows to fetch + * @param mixed $params array, string or numeric data to be used in + * execution of the statement. Quantity of items + * passed must match quantity of placeholders in + * query: meaning 1 placeholder for non-array + * parameters or 1 placeholder per array element. + * + * @return string the query string with LIMIT clauses added + * + * @access protected + */ + function modifyLimitQuery($query, $from, $count, $params = array()) + { + return "$query LIMIT $count OFFSET $from"; + } + + // }}} + // {{{ pgsqlRaiseError() + + /** + * Produces a DB_Error object regarding the current problem + * + * @param int $errno if the error is being manually raised pass a + * DB_ERROR* constant here. If this isn't passed + * the error information gathered from the DBMS. + * + * @return object the DB_Error object + * + * @see DB_common::raiseError(), + * DB_pgsql::errorNative(), DB_pgsql::errorCode() + */ + function pgsqlRaiseError($errno = null) + { + $native = $this->errorNative(); + if (!$native) { + $native = 'Database connection has been lost.'; + $errno = DB_ERROR_CONNECT_FAILED; + } + if ($errno === null) { + $errno = $this->errorCode($native); + } + return $this->raiseError($errno, null, null, null, $native); + } + + // }}} + // {{{ errorNative() + + /** + * Gets the DBMS' native error message produced by the last query + * + * {@internal Error messages are used instead of error codes + * in order to support older versions of PostgreSQL.}} + * + * @return string the DBMS' error message + */ + function errorNative() + { + return @pg_errormessage($this->connection); + } + + // }}} + // {{{ errorCode() + + /** + * Determines PEAR::DB error code from the database's text error message. + * + * @param string $errormsg error message returned from the database + * @return integer an error number from a DB error constant + */ + function errorCode($errormsg) + { + static $error_regexps; + if (!isset($error_regexps)) { + $error_regexps = array( + '/column .* (of relation .*)?does not exist/i' + => DB_ERROR_NOSUCHFIELD, + '/(relation|sequence|table).*does not exist|class .* not found/i' + => DB_ERROR_NOSUCHTABLE, + '/index .* does not exist/' + => DB_ERROR_NOT_FOUND, + '/relation .* already exists/i' + => DB_ERROR_ALREADY_EXISTS, + '/(divide|division) by zero$/i' + => DB_ERROR_DIVZERO, + '/pg_atoi: error in .*: can\'t parse /i' + => DB_ERROR_INVALID_NUMBER, + '/invalid input syntax for( type)? (integer|numeric)/i' + => DB_ERROR_INVALID_NUMBER, + '/value .* is out of range for type \w*int/i' + => DB_ERROR_INVALID_NUMBER, + '/integer out of range/i' + => DB_ERROR_INVALID_NUMBER, + '/value too long for type character/i' + => DB_ERROR_INVALID, + '/attribute .* not found|relation .* does not have attribute/i' + => DB_ERROR_NOSUCHFIELD, + '/column .* specified in USING clause does not exist in (left|right) table/i' + => DB_ERROR_NOSUCHFIELD, + '/parser: parse error at or near/i' + => DB_ERROR_SYNTAX, + '/syntax error at/' + => DB_ERROR_SYNTAX, + '/column reference .* is ambiguous/i' + => DB_ERROR_SYNTAX, + '/permission denied/' + => DB_ERROR_ACCESS_VIOLATION, + '/violates not-null constraint/' + => DB_ERROR_CONSTRAINT_NOT_NULL, + '/violates [\w ]+ constraint/' + => DB_ERROR_CONSTRAINT, + '/referential integrity violation/' + => DB_ERROR_CONSTRAINT, + '/more expressions than target columns/i' + => DB_ERROR_VALUE_COUNT_ON_ROW, + ); + } + foreach ($error_regexps as $regexp => $code) { + if (preg_match($regexp, $errormsg)) { + return $code; + } + } + // Fall back to DB_ERROR if there was no mapping. + return DB_ERROR; + } + + // }}} + // {{{ tableInfo() + + /** + * Returns information about a table or a result set + * + * NOTE: only supports 'table' and 'flags' if $result + * is a table name. + * + * @param object|string $result DB_result object from a query or a + * string containing the name of a table. + * While this also accepts a query result + * resource identifier, this behavior is + * deprecated. + * @param int $mode a valid tableInfo mode + * + * @return array an associative array with the information requested. + * A DB_Error object on failure. + * + * @see DB_common::tableInfo() + */ + function tableInfo($result, $mode = null) + { + if (is_string($result)) { + /* + * Probably received a table name. + * Create a result resource identifier. + */ + $id = @pg_exec($this->connection, "SELECT * FROM $result LIMIT 0"); + $got_string = true; + } elseif (isset($result->result)) { + /* + * Probably received a result object. + * Extract the result resource identifier. + */ + $id = $result->result; + $got_string = false; + } else { + /* + * Probably received a result resource identifier. + * Copy it. + * Deprecated. Here for compatibility only. + */ + $id = $result; + $got_string = false; + } + + if (!is_resource($id)) { + return $this->pgsqlRaiseError(DB_ERROR_NEED_MORE_DATA); + } + + if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) { + $case_func = 'strtolower'; + } else { + $case_func = 'strval'; + } + + $count = @pg_numfields($id); + $res = array(); + + if ($mode) { + $res['num_fields'] = $count; + } + + for ($i = 0; $i < $count; $i++) { + $res[$i] = array( + 'table' => $got_string ? $case_func($result) : '', + 'name' => $case_func(@pg_fieldname($id, $i)), + 'type' => @pg_fieldtype($id, $i), + 'len' => @pg_fieldsize($id, $i), + 'flags' => $got_string + ? $this->_pgFieldFlags($id, $i, $result) + : '', + ); + if ($mode & DB_TABLEINFO_ORDER) { + $res['order'][$res[$i]['name']] = $i; + } + if ($mode & DB_TABLEINFO_ORDERTABLE) { + $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i; + } + } + + // free the result only if we were called on a table + if ($got_string) { + @pg_freeresult($id); + } + return $res; + } + + // }}} + // {{{ _pgFieldFlags() + + /** + * Get a column's flags + * + * Supports "not_null", "default_value", "primary_key", "unique_key" + * and "multiple_key". The default value is passed through + * rawurlencode() in case there are spaces in it. + * + * @param int $resource the PostgreSQL result identifier + * @param int $num_field the field number + * + * @return string the flags + * + * @access private + */ + function _pgFieldFlags($resource, $num_field, $table_name) + { + $field_name = @pg_fieldname($resource, $num_field); + + // Check if there's a schema in $table_name and update things + // accordingly. + $from = 'pg_attribute f, pg_class tab, pg_type typ'; + if (strpos($table_name, '.') !== false) { + $from .= ', pg_namespace nsp'; + list($schema, $table) = explode('.', $table_name); + $tableWhere = "tab.relname = '$table' AND tab.relnamespace = nsp.oid AND nsp.nspname = '$schema'"; + } else { + $tableWhere = "tab.relname = '$table_name'"; + } + + $result = @pg_exec($this->connection, "SELECT f.attnotnull, f.atthasdef + FROM $from + WHERE tab.relname = typ.typname + AND typ.typrelid = f.attrelid + AND f.attname = '$field_name' + AND $tableWhere"); + if (@pg_numrows($result) > 0) { + $row = @pg_fetch_row($result, 0); + $flags = ($row[0] == 't') ? 'not_null ' : ''; + + if ($row[1] == 't') { + $result = @pg_exec($this->connection, "SELECT a.adsrc + FROM $from, pg_attrdef a + WHERE tab.relname = typ.typname AND typ.typrelid = f.attrelid + AND f.attrelid = a.adrelid AND f.attname = '$field_name' + AND $tableWhere AND f.attnum = a.adnum"); + $row = @pg_fetch_row($result, 0); + $num = preg_replace("/'(.*)'::\w+/", "\\1", $row[0]); + $flags .= 'default_' . rawurlencode($num) . ' '; + } + } else { + $flags = ''; + } + $result = @pg_exec($this->connection, "SELECT i.indisunique, i.indisprimary, i.indkey + FROM $from, pg_index i + WHERE tab.relname = typ.typname + AND typ.typrelid = f.attrelid + AND f.attrelid = i.indrelid + AND f.attname = '$field_name' + AND $tableWhere"); + $count = @pg_numrows($result); + + for ($i = 0; $i < $count ; $i++) { + $row = @pg_fetch_row($result, $i); + $keys = explode(' ', $row[2]); + + if (in_array($num_field + 1, $keys)) { + $flags .= ($row[0] == 't' && $row[1] == 'f') ? 'unique_key ' : ''; + $flags .= ($row[1] == 't') ? 'primary_key ' : ''; + if (count($keys) > 1) + $flags .= 'multiple_key '; + } + } + + return trim($flags); + } + + // }}} + // {{{ getSpecialQuery() + + /** + * Obtains the query string needed for listing a given type of objects + * + * @param string $type the kind of objects you want to retrieve + * + * @return string the SQL query string or null if the driver doesn't + * support the object type requested + * + * @access protected + * @see DB_common::getListOf() + */ + function getSpecialQuery($type) + { + switch ($type) { + case 'tables': + return 'SELECT c.relname AS "Name"' + . ' FROM pg_class c, pg_user u' + . ' WHERE c.relowner = u.usesysid' + . " AND c.relkind = 'r'" + . ' AND NOT EXISTS' + . ' (SELECT 1 FROM pg_views' + . ' WHERE viewname = c.relname)' + . " AND c.relname !~ '^(pg_|sql_)'" + . ' UNION' + . ' SELECT c.relname AS "Name"' + . ' FROM pg_class c' + . " WHERE c.relkind = 'r'" + . ' AND NOT EXISTS' + . ' (SELECT 1 FROM pg_views' + . ' WHERE viewname = c.relname)' + . ' AND NOT EXISTS' + . ' (SELECT 1 FROM pg_user' + . ' WHERE usesysid = c.relowner)' + . " AND c.relname !~ '^pg_'"; + case 'schema.tables': + return "SELECT schemaname || '.' || tablename" + . ' AS "Name"' + . ' FROM pg_catalog.pg_tables' + . ' WHERE schemaname NOT IN' + . " ('pg_catalog', 'information_schema', 'pg_toast')"; + case 'schema.views': + return "SELECT schemaname || '.' || viewname from pg_views WHERE schemaname" + . " NOT IN ('information_schema', 'pg_catalog')"; + case 'views': + // Table cols: viewname | viewowner | definition + return 'SELECT viewname from pg_views WHERE schemaname' + . " NOT IN ('information_schema', 'pg_catalog')"; + case 'users': + // cols: usename |usesysid|usecreatedb|usetrace|usesuper|usecatupd|passwd |valuntil + return 'SELECT usename FROM pg_user'; + case 'databases': + return 'SELECT datname FROM pg_database'; + case 'functions': + case 'procedures': + return 'SELECT proname FROM pg_proc WHERE proowner <> 1'; + default: + return null; + } + } + + // }}} + +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ + +?> diff --git a/library/pear/DB/sqlite.php b/library/pear/DB/sqlite.php new file mode 100644 index 000000000..bf2acec5a --- /dev/null +++ b/library/pear/DB/sqlite.php @@ -0,0 +1,959 @@ + + * @author Mika Tuupola + * @author Daniel Convissor + * @copyright 1997-2007 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 3.0 + * @version CVS: $Id: sqlite.php,v 1.117 2007/09/21 14:23:28 aharvey Exp $ + * @link http://pear.php.net/package/DB + */ + +/** + * Obtain the DB_common class so it can be extended from + */ +require_once 'DB/common.php'; + +/** + * The methods PEAR DB uses to interact with PHP's sqlite extension + * for interacting with SQLite databases + * + * These methods overload the ones declared in DB_common. + * + * NOTICE: This driver needs PHP's track_errors ini setting to be on. + * It is automatically turned on when connecting to the database. + * Make sure your scripts don't turn it off. + * + * @category Database + * @package DB + * @author Urs Gehrig + * @author Mika Tuupola + * @author Daniel Convissor + * @copyright 1997-2007 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 3.0 + * @version Release: 1.7.13 + * @link http://pear.php.net/package/DB + */ +class DB_sqlite extends DB_common +{ + // {{{ properties + + /** + * The DB driver type (mysql, oci8, odbc, etc.) + * @var string + */ + var $phptype = 'sqlite'; + + /** + * The database syntax variant to be used (db2, access, etc.), if any + * @var string + */ + var $dbsyntax = 'sqlite'; + + /** + * The capabilities of this DB implementation + * + * The 'new_link' element contains the PHP version that first provided + * new_link support for this DBMS. Contains false if it's unsupported. + * + * Meaning of the 'limit' element: + * + 'emulate' = emulate with fetch row by number + * + 'alter' = alter the query + * + false = skip rows + * + * @var array + */ + var $features = array( + 'limit' => 'alter', + 'new_link' => false, + 'numrows' => true, + 'pconnect' => true, + 'prepare' => false, + 'ssl' => false, + 'transactions' => false, + ); + + /** + * A mapping of native error codes to DB error codes + * + * {@internal Error codes according to sqlite_exec. See the online + * manual at http://sqlite.org/c_interface.html for info. + * This error handling based on sqlite_exec is not yet implemented.}} + * + * @var array + */ + var $errorcode_map = array( + ); + + /** + * The raw database connection created by PHP + * @var resource + */ + var $connection; + + /** + * The DSN information for connecting to a database + * @var array + */ + var $dsn = array(); + + + /** + * SQLite data types + * + * @link http://www.sqlite.org/datatypes.html + * + * @var array + */ + var $keywords = array ( + 'BLOB' => '', + 'BOOLEAN' => '', + 'CHARACTER' => '', + 'CLOB' => '', + 'FLOAT' => '', + 'INTEGER' => '', + 'KEY' => '', + 'NATIONAL' => '', + 'NUMERIC' => '', + 'NVARCHAR' => '', + 'PRIMARY' => '', + 'TEXT' => '', + 'TIMESTAMP' => '', + 'UNIQUE' => '', + 'VARCHAR' => '', + 'VARYING' => '', + ); + + /** + * The most recent error message from $php_errormsg + * @var string + * @access private + */ + var $_lasterror = ''; + + + // }}} + // {{{ constructor + + /** + * This constructor calls $this->DB_common() + * + * @return void + */ + function DB_sqlite() + { + $this->DB_common(); + } + + // }}} + // {{{ connect() + + /** + * Connect to the database server, log in and open the database + * + * Don't call this method directly. Use DB::connect() instead. + * + * PEAR DB's sqlite driver supports the following extra DSN options: + * + mode The permissions for the database file, in four digit + * chmod octal format (eg "0600"). + * + * Example of connecting to a database in read-only mode: + * + * require_once 'DB.php'; + * + * $dsn = 'sqlite:///path/and/name/of/db/file?mode=0400'; + * $options = array( + * 'portability' => DB_PORTABILITY_ALL, + * ); + * + * $db = DB::connect($dsn, $options); + * if (PEAR::isError($db)) { + * die($db->getMessage()); + * } + * + * + * @param array $dsn the data source name + * @param bool $persistent should the connection be persistent? + * + * @return int DB_OK on success. A DB_Error object on failure. + */ + function connect($dsn, $persistent = false) + { + if (!PEAR::loadExtension('sqlite')) { + return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND); + } + + $this->dsn = $dsn; + if ($dsn['dbsyntax']) { + $this->dbsyntax = $dsn['dbsyntax']; + } + + if (!$dsn['database']) { + return $this->sqliteRaiseError(DB_ERROR_ACCESS_VIOLATION); + } + + if ($dsn['database'] !== ':memory:') { + if (!file_exists($dsn['database'])) { + if (!touch($dsn['database'])) { + return $this->sqliteRaiseError(DB_ERROR_NOT_FOUND); + } + if (!isset($dsn['mode']) || + !is_numeric($dsn['mode'])) + { + $mode = 0644; + } else { + $mode = octdec($dsn['mode']); + } + if (!chmod($dsn['database'], $mode)) { + return $this->sqliteRaiseError(DB_ERROR_NOT_FOUND); + } + if (!file_exists($dsn['database'])) { + return $this->sqliteRaiseError(DB_ERROR_NOT_FOUND); + } + } + if (!is_file($dsn['database'])) { + return $this->sqliteRaiseError(DB_ERROR_INVALID); + } + if (!is_readable($dsn['database'])) { + return $this->sqliteRaiseError(DB_ERROR_ACCESS_VIOLATION); + } + } + + $connect_function = $persistent ? 'sqlite_popen' : 'sqlite_open'; + + // track_errors must remain on for simpleQuery() + @ini_set('track_errors', 1); + $php_errormsg = ''; + + if (!$this->connection = @$connect_function($dsn['database'])) { + return $this->raiseError(DB_ERROR_NODBSELECTED, + null, null, null, + $php_errormsg); + } + return DB_OK; + } + + // }}} + // {{{ disconnect() + + /** + * Disconnects from the database server + * + * @return bool TRUE on success, FALSE on failure + */ + function disconnect() + { + $ret = @sqlite_close($this->connection); + $this->connection = null; + return $ret; + } + + // }}} + // {{{ simpleQuery() + + /** + * Sends a query to the database server + * + * NOTICE: This method needs PHP's track_errors ini setting to be on. + * It is automatically turned on when connecting to the database. + * Make sure your scripts don't turn it off. + * + * @param string the SQL query string + * + * @return mixed + a PHP result resrouce for successful SELECT queries + * + the DB_OK constant for other successful queries + * + a DB_Error object on failure + */ + function simpleQuery($query) + { + $ismanip = $this->_checkManip($query); + $this->last_query = $query; + $query = $this->modifyQuery($query); + + $php_errormsg = ''; + + $result = @sqlite_query($query, $this->connection); + $this->_lasterror = $php_errormsg ? $php_errormsg : ''; + + $this->result = $result; + if (!$this->result) { + return $this->sqliteRaiseError(null); + } + + // sqlite_query() seems to allways return a resource + // so cant use that. Using $ismanip instead + if (!$ismanip) { + $numRows = $this->numRows($result); + if (is_object($numRows)) { + // we've got PEAR_Error + return $numRows; + } + return $result; + } + return DB_OK; + } + + // }}} + // {{{ nextResult() + + /** + * Move the internal sqlite result pointer to the next available result + * + * @param resource $result the valid sqlite result resource + * + * @return bool true if a result is available otherwise return false + */ + function nextResult($result) + { + return false; + } + + // }}} + // {{{ fetchInto() + + /** + * Places a row from the result set into the given array + * + * Formating of the array and the data therein are configurable. + * See DB_result::fetchInto() for more information. + * + * This method is not meant to be called directly. Use + * DB_result::fetchInto() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result the query result resource + * @param array $arr the referenced array to put the data in + * @param int $fetchmode how the resulting array should be indexed + * @param int $rownum the row number to fetch (0 = first row) + * + * @return mixed DB_OK on success, NULL when the end of a result set is + * reached or on failure + * + * @see DB_result::fetchInto() + */ + function fetchInto($result, &$arr, $fetchmode, $rownum = null) + { + if ($rownum !== null) { + if (!@sqlite_seek($this->result, $rownum)) { + return null; + } + } + if ($fetchmode & DB_FETCHMODE_ASSOC) { + $arr = @sqlite_fetch_array($result, SQLITE_ASSOC); + if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE && $arr) { + $arr = array_change_key_case($arr, CASE_LOWER); + } + + /* Remove extraneous " characters from the fields in the result. + * Fixes bug #11716. */ + if (is_array($arr) && count($arr) > 0) { + $strippedArr = array(); + foreach ($arr as $field => $value) { + $strippedArr[trim($field, '"')] = $value; + } + $arr = $strippedArr; + } + } else { + $arr = @sqlite_fetch_array($result, SQLITE_NUM); + } + if (!$arr) { + return null; + } + if ($this->options['portability'] & DB_PORTABILITY_RTRIM) { + /* + * Even though this DBMS already trims output, we do this because + * a field might have intentional whitespace at the end that + * gets removed by DB_PORTABILITY_RTRIM under another driver. + */ + $this->_rtrimArrayValues($arr); + } + if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) { + $this->_convertNullArrayValuesToEmpty($arr); + } + return DB_OK; + } + + // }}} + // {{{ freeResult() + + /** + * Deletes the result set and frees the memory occupied by the result set + * + * This method is not meant to be called directly. Use + * DB_result::free() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result PHP's query result resource + * + * @return bool TRUE on success, FALSE if $result is invalid + * + * @see DB_result::free() + */ + function freeResult(&$result) + { + // XXX No native free? + if (!is_resource($result)) { + return false; + } + $result = null; + return true; + } + + // }}} + // {{{ numCols() + + /** + * Gets the number of columns in a result set + * + * This method is not meant to be called directly. Use + * DB_result::numCols() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result PHP's query result resource + * + * @return int the number of columns. A DB_Error object on failure. + * + * @see DB_result::numCols() + */ + function numCols($result) + { + $cols = @sqlite_num_fields($result); + if (!$cols) { + return $this->sqliteRaiseError(); + } + return $cols; + } + + // }}} + // {{{ numRows() + + /** + * Gets the number of rows in a result set + * + * This method is not meant to be called directly. Use + * DB_result::numRows() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result PHP's query result resource + * + * @return int the number of rows. A DB_Error object on failure. + * + * @see DB_result::numRows() + */ + function numRows($result) + { + $rows = @sqlite_num_rows($result); + if ($rows === null) { + return $this->sqliteRaiseError(); + } + return $rows; + } + + // }}} + // {{{ affected() + + /** + * Determines the number of rows affected by a data maniuplation query + * + * 0 is returned for queries that don't manipulate data. + * + * @return int the number of rows. A DB_Error object on failure. + */ + function affectedRows() + { + return @sqlite_changes($this->connection); + } + + // }}} + // {{{ dropSequence() + + /** + * Deletes a sequence + * + * @param string $seq_name name of the sequence to be deleted + * + * @return int DB_OK on success. A DB_Error object on failure. + * + * @see DB_common::dropSequence(), DB_common::getSequenceName(), + * DB_sqlite::nextID(), DB_sqlite::createSequence() + */ + function dropSequence($seq_name) + { + return $this->query('DROP TABLE ' . $this->getSequenceName($seq_name)); + } + + /** + * Creates a new sequence + * + * @param string $seq_name name of the new sequence + * + * @return int DB_OK on success. A DB_Error object on failure. + * + * @see DB_common::createSequence(), DB_common::getSequenceName(), + * DB_sqlite::nextID(), DB_sqlite::dropSequence() + */ + function createSequence($seq_name) + { + $seqname = $this->getSequenceName($seq_name); + $query = 'CREATE TABLE ' . $seqname . + ' (id INTEGER UNSIGNED PRIMARY KEY) '; + $result = $this->query($query); + if (DB::isError($result)) { + return($result); + } + $query = "CREATE TRIGGER ${seqname}_cleanup AFTER INSERT ON $seqname + BEGIN + DELETE FROM $seqname WHERE idquery($query); + if (DB::isError($result)) { + return($result); + } + } + + // }}} + // {{{ nextId() + + /** + * Returns the next free id in a sequence + * + * @param string $seq_name name of the sequence + * @param boolean $ondemand when true, the seqence is automatically + * created if it does not exist + * + * @return int the next id number in the sequence. + * A DB_Error object on failure. + * + * @see DB_common::nextID(), DB_common::getSequenceName(), + * DB_sqlite::createSequence(), DB_sqlite::dropSequence() + */ + function nextId($seq_name, $ondemand = true) + { + $seqname = $this->getSequenceName($seq_name); + + do { + $repeat = 0; + $this->pushErrorHandling(PEAR_ERROR_RETURN); + $result = $this->query("INSERT INTO $seqname (id) VALUES (NULL)"); + $this->popErrorHandling(); + if ($result === DB_OK) { + $id = @sqlite_last_insert_rowid($this->connection); + if ($id != 0) { + return $id; + } + } elseif ($ondemand && DB::isError($result) && + $result->getCode() == DB_ERROR_NOSUCHTABLE) + { + $result = $this->createSequence($seq_name); + if (DB::isError($result)) { + return $this->raiseError($result); + } else { + $repeat = 1; + } + } + } while ($repeat); + + return $this->raiseError($result); + } + + // }}} + // {{{ getDbFileStats() + + /** + * Get the file stats for the current database + * + * Possible arguments are dev, ino, mode, nlink, uid, gid, rdev, size, + * atime, mtime, ctime, blksize, blocks or a numeric key between + * 0 and 12. + * + * @param string $arg the array key for stats() + * + * @return mixed an array on an unspecified key, integer on a passed + * arg and false at a stats error + */ + function getDbFileStats($arg = '') + { + $stats = stat($this->dsn['database']); + if ($stats == false) { + return false; + } + if (is_array($stats)) { + if (is_numeric($arg)) { + if (((int)$arg <= 12) & ((int)$arg >= 0)) { + return false; + } + return $stats[$arg ]; + } + if (array_key_exists(trim($arg), $stats)) { + return $stats[$arg ]; + } + } + return $stats; + } + + // }}} + // {{{ escapeSimple() + + /** + * Escapes a string according to the current DBMS's standards + * + * In SQLite, this makes things safe for inserts/updates, but may + * cause problems when performing text comparisons against columns + * containing binary data. See the + * {@link http://php.net/sqlite_escape_string PHP manual} for more info. + * + * @param string $str the string to be escaped + * + * @return string the escaped string + * + * @since Method available since Release 1.6.1 + * @see DB_common::escapeSimple() + */ + function escapeSimple($str) + { + return @sqlite_escape_string($str); + } + + // }}} + // {{{ modifyLimitQuery() + + /** + * Adds LIMIT clauses to a query string according to current DBMS standards + * + * @param string $query the query to modify + * @param int $from the row to start to fetching (0 = the first row) + * @param int $count the numbers of rows to fetch + * @param mixed $params array, string or numeric data to be used in + * execution of the statement. Quantity of items + * passed must match quantity of placeholders in + * query: meaning 1 placeholder for non-array + * parameters or 1 placeholder per array element. + * + * @return string the query string with LIMIT clauses added + * + * @access protected + */ + function modifyLimitQuery($query, $from, $count, $params = array()) + { + return "$query LIMIT $count OFFSET $from"; + } + + // }}} + // {{{ modifyQuery() + + /** + * Changes a query string for various DBMS specific reasons + * + * This little hack lets you know how many rows were deleted + * when running a "DELETE FROM table" query. Only implemented + * if the DB_PORTABILITY_DELETE_COUNT portability option is on. + * + * @param string $query the query string to modify + * + * @return string the modified query string + * + * @access protected + * @see DB_common::setOption() + */ + function modifyQuery($query) + { + if ($this->options['portability'] & DB_PORTABILITY_DELETE_COUNT) { + if (preg_match('/^\s*DELETE\s+FROM\s+(\S+)\s*$/i', $query)) { + $query = preg_replace('/^\s*DELETE\s+FROM\s+(\S+)\s*$/', + 'DELETE FROM \1 WHERE 1=1', $query); + } + } + return $query; + } + + // }}} + // {{{ sqliteRaiseError() + + /** + * Produces a DB_Error object regarding the current problem + * + * @param int $errno if the error is being manually raised pass a + * DB_ERROR* constant here. If this isn't passed + * the error information gathered from the DBMS. + * + * @return object the DB_Error object + * + * @see DB_common::raiseError(), + * DB_sqlite::errorNative(), DB_sqlite::errorCode() + */ + function sqliteRaiseError($errno = null) + { + $native = $this->errorNative(); + if ($errno === null) { + $errno = $this->errorCode($native); + } + + $errorcode = @sqlite_last_error($this->connection); + $userinfo = "$errorcode ** $this->last_query"; + + return $this->raiseError($errno, null, null, $userinfo, $native); + } + + // }}} + // {{{ errorNative() + + /** + * Gets the DBMS' native error message produced by the last query + * + * {@internal This is used to retrieve more meaningfull error messages + * because sqlite_last_error() does not provide adequate info.}} + * + * @return string the DBMS' error message + */ + function errorNative() + { + return $this->_lasterror; + } + + // }}} + // {{{ errorCode() + + /** + * Determines PEAR::DB error code from the database's text error message + * + * @param string $errormsg the error message returned from the database + * + * @return integer the DB error number + */ + function errorCode($errormsg) + { + static $error_regexps; + + // PHP 5.2+ prepends the function name to $php_errormsg, so we need + // this hack to work around it, per bug #9599. + $errormsg = preg_replace('/^sqlite[a-z_]+\(\): /', '', $errormsg); + + if (!isset($error_regexps)) { + $error_regexps = array( + '/^no such table:/' => DB_ERROR_NOSUCHTABLE, + '/^no such index:/' => DB_ERROR_NOT_FOUND, + '/^(table|index) .* already exists$/' => DB_ERROR_ALREADY_EXISTS, + '/PRIMARY KEY must be unique/i' => DB_ERROR_CONSTRAINT, + '/is not unique/' => DB_ERROR_CONSTRAINT, + '/columns .* are not unique/i' => DB_ERROR_CONSTRAINT, + '/uniqueness constraint failed/' => DB_ERROR_CONSTRAINT, + '/may not be NULL/' => DB_ERROR_CONSTRAINT_NOT_NULL, + '/^no such column:/' => DB_ERROR_NOSUCHFIELD, + '/column not present in both tables/i' => DB_ERROR_NOSUCHFIELD, + '/^near ".*": syntax error$/' => DB_ERROR_SYNTAX, + '/[0-9]+ values for [0-9]+ columns/i' => DB_ERROR_VALUE_COUNT_ON_ROW, + ); + } + foreach ($error_regexps as $regexp => $code) { + if (preg_match($regexp, $errormsg)) { + return $code; + } + } + // Fall back to DB_ERROR if there was no mapping. + return DB_ERROR; + } + + // }}} + // {{{ tableInfo() + + /** + * Returns information about a table + * + * @param string $result a string containing the name of a table + * @param int $mode a valid tableInfo mode + * + * @return array an associative array with the information requested. + * A DB_Error object on failure. + * + * @see DB_common::tableInfo() + * @since Method available since Release 1.7.0 + */ + function tableInfo($result, $mode = null) + { + if (is_string($result)) { + /* + * Probably received a table name. + * Create a result resource identifier. + */ + $id = @sqlite_array_query($this->connection, + "PRAGMA table_info('$result');", + SQLITE_ASSOC); + $got_string = true; + } else { + $this->last_query = ''; + return $this->raiseError(DB_ERROR_NOT_CAPABLE, null, null, null, + 'This DBMS can not obtain tableInfo' . + ' from result sets'); + } + + if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) { + $case_func = 'strtolower'; + } else { + $case_func = 'strval'; + } + + $count = count($id); + $res = array(); + + if ($mode) { + $res['num_fields'] = $count; + } + + for ($i = 0; $i < $count; $i++) { + if (strpos($id[$i]['type'], '(') !== false) { + $bits = explode('(', $id[$i]['type']); + $type = $bits[0]; + $len = rtrim($bits[1],')'); + } else { + $type = $id[$i]['type']; + $len = 0; + } + + $flags = ''; + if ($id[$i]['pk']) { + $flags .= 'primary_key '; + } + if ($id[$i]['notnull']) { + $flags .= 'not_null '; + } + if ($id[$i]['dflt_value'] !== null) { + $flags .= 'default_' . rawurlencode($id[$i]['dflt_value']); + } + $flags = trim($flags); + + $res[$i] = array( + 'table' => $case_func($result), + 'name' => $case_func($id[$i]['name']), + 'type' => $type, + 'len' => $len, + 'flags' => $flags, + ); + + if ($mode & DB_TABLEINFO_ORDER) { + $res['order'][$res[$i]['name']] = $i; + } + if ($mode & DB_TABLEINFO_ORDERTABLE) { + $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i; + } + } + + return $res; + } + + // }}} + // {{{ getSpecialQuery() + + /** + * Obtains the query string needed for listing a given type of objects + * + * @param string $type the kind of objects you want to retrieve + * @param array $args SQLITE DRIVER ONLY: a private array of arguments + * used by the getSpecialQuery(). Do not use + * this directly. + * + * @return string the SQL query string or null if the driver doesn't + * support the object type requested + * + * @access protected + * @see DB_common::getListOf() + */ + function getSpecialQuery($type, $args = array()) + { + if (!is_array($args)) { + return $this->raiseError('no key specified', null, null, null, + 'Argument has to be an array.'); + } + + switch ($type) { + case 'master': + return 'SELECT * FROM sqlite_master;'; + case 'tables': + return "SELECT name FROM sqlite_master WHERE type='table' " + . 'UNION ALL SELECT name FROM sqlite_temp_master ' + . "WHERE type='table' ORDER BY name;"; + case 'schema': + return 'SELECT sql FROM (SELECT * FROM sqlite_master ' + . 'UNION ALL SELECT * FROM sqlite_temp_master) ' + . "WHERE type!='meta' " + . 'ORDER BY tbl_name, type DESC, name;'; + case 'schemax': + case 'schema_x': + /* + * Use like: + * $res = $db->query($db->getSpecialQuery('schema_x', + * array('table' => 'table3'))); + */ + return 'SELECT sql FROM (SELECT * FROM sqlite_master ' + . 'UNION ALL SELECT * FROM sqlite_temp_master) ' + . "WHERE tbl_name LIKE '{$args['table']}' " + . "AND type!='meta' " + . 'ORDER BY type DESC, name;'; + case 'alter': + /* + * SQLite does not support ALTER TABLE; this is a helper query + * to handle this. 'table' represents the table name, 'rows' + * the news rows to create, 'save' the row(s) to keep _with_ + * the data. + * + * Use like: + * $args = array( + * 'table' => $table, + * 'rows' => "id INTEGER PRIMARY KEY, firstname TEXT, surname TEXT, datetime TEXT", + * 'save' => "NULL, titel, content, datetime" + * ); + * $res = $db->query( $db->getSpecialQuery('alter', $args)); + */ + $rows = strtr($args['rows'], $this->keywords); + + $q = array( + 'BEGIN TRANSACTION', + "CREATE TEMPORARY TABLE {$args['table']}_backup ({$args['rows']})", + "INSERT INTO {$args['table']}_backup SELECT {$args['save']} FROM {$args['table']}", + "DROP TABLE {$args['table']}", + "CREATE TABLE {$args['table']} ({$args['rows']})", + "INSERT INTO {$args['table']} SELECT {$rows} FROM {$args['table']}_backup", + "DROP TABLE {$args['table']}_backup", + 'COMMIT', + ); + + /* + * This is a dirty hack, since the above query will not get + * executed with a single query call so here the query method + * will be called directly and return a select instead. + */ + foreach ($q as $query) { + $this->query($query); + } + return "SELECT * FROM {$args['table']};"; + default: + return null; + } + } + + // }}} +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ + +?> diff --git a/library/pear/DB/storage.php b/library/pear/DB/storage.php new file mode 100644 index 000000000..30762e87e --- /dev/null +++ b/library/pear/DB/storage.php @@ -0,0 +1,506 @@ + + * @copyright 1997-2007 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version CVS: $Id: storage.php,v 1.24 2007/08/12 05:27:25 aharvey Exp $ + * @link http://pear.php.net/package/DB + */ + +/** + * Obtain the DB class so it can be extended from + */ +require_once 'DB.php'; + +/** + * Provides an object interface to a table row + * + * It lets you add, delete and change rows using objects rather than SQL + * statements. + * + * @category Database + * @package DB + * @author Stig Bakken + * @copyright 1997-2007 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version Release: 1.7.13 + * @link http://pear.php.net/package/DB + */ +class DB_storage extends PEAR +{ + // {{{ properties + + /** the name of the table (or view, if the backend database supports + updates in views) we hold data from */ + var $_table = null; + + /** which column(s) in the table contains primary keys, can be a + string for single-column primary keys, or an array of strings + for multiple-column primary keys */ + var $_keycolumn = null; + + /** DB connection handle used for all transactions */ + var $_dbh = null; + + /** an assoc with the names of database fields stored as properties + in this object */ + var $_properties = array(); + + /** an assoc with the names of the properties in this object that + have been changed since they were fetched from the database */ + var $_changes = array(); + + /** flag that decides if data in this object can be changed. + objects that don't have their table's key column in their + property lists will be flagged as read-only. */ + var $_readonly = false; + + /** function or method that implements a validator for fields that + are set, this validator function returns true if the field is + valid, false if not */ + var $_validator = null; + + // }}} + // {{{ constructor + + /** + * Constructor + * + * @param $table string the name of the database table + * + * @param $keycolumn mixed string with name of key column, or array of + * strings if the table has a primary key of more than one column + * + * @param $dbh object database connection object + * + * @param $validator mixed function or method used to validate + * each new value, called with three parameters: the name of the + * field/column that is changing, a reference to the new value and + * a reference to this object + * + */ + function DB_storage($table, $keycolumn, &$dbh, $validator = null) + { + $this->PEAR('DB_Error'); + $this->_table = $table; + $this->_keycolumn = $keycolumn; + $this->_dbh = $dbh; + $this->_readonly = false; + $this->_validator = $validator; + } + + // }}} + // {{{ _makeWhere() + + /** + * Utility method to build a "WHERE" clause to locate ourselves in + * the table. + * + * XXX future improvement: use rowids? + * + * @access private + */ + function _makeWhere($keyval = null) + { + if (is_array($this->_keycolumn)) { + if ($keyval === null) { + for ($i = 0; $i < sizeof($this->_keycolumn); $i++) { + $keyval[] = $this->{$this->_keycolumn[$i]}; + } + } + $whereclause = ''; + for ($i = 0; $i < sizeof($this->_keycolumn); $i++) { + if ($i > 0) { + $whereclause .= ' AND '; + } + $whereclause .= $this->_keycolumn[$i]; + if (is_null($keyval[$i])) { + // there's not much point in having a NULL key, + // but we support it anyway + $whereclause .= ' IS NULL'; + } else { + $whereclause .= ' = ' . $this->_dbh->quote($keyval[$i]); + } + } + } else { + if ($keyval === null) { + $keyval = @$this->{$this->_keycolumn}; + } + $whereclause = $this->_keycolumn; + if (is_null($keyval)) { + // there's not much point in having a NULL key, + // but we support it anyway + $whereclause .= ' IS NULL'; + } else { + $whereclause .= ' = ' . $this->_dbh->quote($keyval); + } + } + return $whereclause; + } + + // }}} + // {{{ setup() + + /** + * Method used to initialize a DB_storage object from the + * configured table. + * + * @param $keyval mixed the key[s] of the row to fetch (string or array) + * + * @return int DB_OK on success, a DB error if not + */ + function setup($keyval) + { + $whereclause = $this->_makeWhere($keyval); + $query = 'SELECT * FROM ' . $this->_table . ' WHERE ' . $whereclause; + $sth = $this->_dbh->query($query); + if (DB::isError($sth)) { + return $sth; + } + $row = $sth->fetchRow(DB_FETCHMODE_ASSOC); + if (DB::isError($row)) { + return $row; + } + if (!$row) { + return $this->raiseError(null, DB_ERROR_NOT_FOUND, null, null, + $query, null, true); + } + foreach ($row as $key => $value) { + $this->_properties[$key] = true; + $this->$key = $value; + } + return DB_OK; + } + + // }}} + // {{{ insert() + + /** + * Create a new (empty) row in the configured table for this + * object. + */ + function insert($newpk) + { + if (is_array($this->_keycolumn)) { + $primarykey = $this->_keycolumn; + } else { + $primarykey = array($this->_keycolumn); + } + settype($newpk, "array"); + for ($i = 0; $i < sizeof($primarykey); $i++) { + $pkvals[] = $this->_dbh->quote($newpk[$i]); + } + + $sth = $this->_dbh->query("INSERT INTO $this->_table (" . + implode(",", $primarykey) . ") VALUES(" . + implode(",", $pkvals) . ")"); + if (DB::isError($sth)) { + return $sth; + } + if (sizeof($newpk) == 1) { + $newpk = $newpk[0]; + } + $this->setup($newpk); + } + + // }}} + // {{{ toString() + + /** + * Output a simple description of this DB_storage object. + * @return string object description + */ + function toString() + { + $info = strtolower(get_class($this)); + $info .= " (table="; + $info .= $this->_table; + $info .= ", keycolumn="; + if (is_array($this->_keycolumn)) { + $info .= "(" . implode(",", $this->_keycolumn) . ")"; + } else { + $info .= $this->_keycolumn; + } + $info .= ", dbh="; + if (is_object($this->_dbh)) { + $info .= $this->_dbh->toString(); + } else { + $info .= "null"; + } + $info .= ")"; + if (sizeof($this->_properties)) { + $info .= " [loaded, key="; + $keyname = $this->_keycolumn; + if (is_array($keyname)) { + $info .= "("; + for ($i = 0; $i < sizeof($keyname); $i++) { + if ($i > 0) { + $info .= ","; + } + $info .= $this->$keyname[$i]; + } + $info .= ")"; + } else { + $info .= $this->$keyname; + } + $info .= "]"; + } + if (sizeof($this->_changes)) { + $info .= " [modified]"; + } + return $info; + } + + // }}} + // {{{ dump() + + /** + * Dump the contents of this object to "standard output". + */ + function dump() + { + foreach ($this->_properties as $prop => $foo) { + print "$prop = "; + print htmlentities($this->$prop); + print "
    \n"; + } + } + + // }}} + // {{{ &create() + + /** + * Static method used to create new DB storage objects. + * @param $data assoc. array where the keys are the names + * of properties/columns + * @return object a new instance of DB_storage or a subclass of it + */ + function &create($table, &$data) + { + $classname = strtolower(get_class($this)); + $obj = new $classname($table); + foreach ($data as $name => $value) { + $obj->_properties[$name] = true; + $obj->$name = &$value; + } + return $obj; + } + + // }}} + // {{{ loadFromQuery() + + /** + * Loads data into this object from the given query. If this + * object already contains table data, changes will be saved and + * the object re-initialized first. + * + * @param $query SQL query + * + * @param $params parameter list in case you want to use + * prepare/execute mode + * + * @return int DB_OK on success, DB_WARNING_READ_ONLY if the + * returned object is read-only (because the object's specified + * key column was not found among the columns returned by $query), + * or another DB error code in case of errors. + */ +// XXX commented out for now +/* + function loadFromQuery($query, $params = null) + { + if (sizeof($this->_properties)) { + if (sizeof($this->_changes)) { + $this->store(); + $this->_changes = array(); + } + $this->_properties = array(); + } + $rowdata = $this->_dbh->getRow($query, DB_FETCHMODE_ASSOC, $params); + if (DB::isError($rowdata)) { + return $rowdata; + } + reset($rowdata); + $found_keycolumn = false; + while (list($key, $value) = each($rowdata)) { + if ($key == $this->_keycolumn) { + $found_keycolumn = true; + } + $this->_properties[$key] = true; + $this->$key = &$value; + unset($value); // have to unset, or all properties will + // refer to the same value + } + if (!$found_keycolumn) { + $this->_readonly = true; + return DB_WARNING_READ_ONLY; + } + return DB_OK; + } + */ + + // }}} + // {{{ set() + + /** + * Modify an attriute value. + */ + function set($property, $newvalue) + { + // only change if $property is known and object is not + // read-only + if ($this->_readonly) { + return $this->raiseError(null, DB_WARNING_READ_ONLY, null, + null, null, null, true); + } + if (@isset($this->_properties[$property])) { + if (empty($this->_validator)) { + $valid = true; + } else { + $valid = @call_user_func($this->_validator, + $this->_table, + $property, + $newvalue, + $this->$property, + $this); + } + if ($valid) { + $this->$property = $newvalue; + if (empty($this->_changes[$property])) { + $this->_changes[$property] = 0; + } else { + $this->_changes[$property]++; + } + } else { + return $this->raiseError(null, DB_ERROR_INVALID, null, + null, "invalid field: $property", + null, true); + } + return true; + } + return $this->raiseError(null, DB_ERROR_NOSUCHFIELD, null, + null, "unknown field: $property", + null, true); + } + + // }}} + // {{{ &get() + + /** + * Fetch an attribute value. + * + * @param string attribute name + * + * @return attribute contents, or null if the attribute name is + * unknown + */ + function &get($property) + { + // only return if $property is known + if (isset($this->_properties[$property])) { + return $this->$property; + } + $tmp = null; + return $tmp; + } + + // }}} + // {{{ _DB_storage() + + /** + * Destructor, calls DB_storage::store() if there are changes + * that are to be kept. + */ + function _DB_storage() + { + if (sizeof($this->_changes)) { + $this->store(); + } + $this->_properties = array(); + $this->_changes = array(); + $this->_table = null; + } + + // }}} + // {{{ store() + + /** + * Stores changes to this object in the database. + * + * @return DB_OK or a DB error + */ + function store() + { + $params = array(); + $vars = array(); + foreach ($this->_changes as $name => $foo) { + $params[] = &$this->$name; + $vars[] = $name . ' = ?'; + } + if ($vars) { + $query = 'UPDATE ' . $this->_table . ' SET ' . + implode(', ', $vars) . ' WHERE ' . + $this->_makeWhere(); + $stmt = $this->_dbh->prepare($query); + $res = $this->_dbh->execute($stmt, $params); + if (DB::isError($res)) { + return $res; + } + $this->_changes = array(); + } + return DB_OK; + } + + // }}} + // {{{ remove() + + /** + * Remove the row represented by this object from the database. + * + * @return mixed DB_OK or a DB error + */ + function remove() + { + if ($this->_readonly) { + return $this->raiseError(null, DB_WARNING_READ_ONLY, null, + null, null, null, true); + } + $query = 'DELETE FROM ' . $this->_table .' WHERE '. + $this->_makeWhere(); + $res = $this->_dbh->query($query); + if (DB::isError($res)) { + return $res; + } + foreach ($this->_properties as $prop => $foo) { + unset($this->$prop); + } + $this->_properties = array(); + $this->_changes = array(); + return DB_OK; + } + + // }}} +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ + +?> diff --git a/library/pear/DB/sybase.php b/library/pear/DB/sybase.php new file mode 100644 index 000000000..bb79c78c2 --- /dev/null +++ b/library/pear/DB/sybase.php @@ -0,0 +1,942 @@ + + * @author Antônio Carlos Venâncio Júnior + * @author Daniel Convissor + * @copyright 1997-2007 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version CVS: $Id: sybase.php,v 1.87 2007/09/21 13:40:42 aharvey Exp $ + * @link http://pear.php.net/package/DB + */ + +/** + * Obtain the DB_common class so it can be extended from + */ +require_once 'DB/common.php'; + +/** + * The methods PEAR DB uses to interact with PHP's sybase extension + * for interacting with Sybase databases + * + * These methods overload the ones declared in DB_common. + * + * WARNING: This driver may fail with multiple connections under the + * same user/pass/host and different databases. + * + * @category Database + * @package DB + * @author Sterling Hughes + * @author Antônio Carlos Venâncio Júnior + * @author Daniel Convissor + * @copyright 1997-2007 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version Release: 1.7.13 + * @link http://pear.php.net/package/DB + */ +class DB_sybase extends DB_common +{ + // {{{ properties + + /** + * The DB driver type (mysql, oci8, odbc, etc.) + * @var string + */ + var $phptype = 'sybase'; + + /** + * The database syntax variant to be used (db2, access, etc.), if any + * @var string + */ + var $dbsyntax = 'sybase'; + + /** + * The capabilities of this DB implementation + * + * The 'new_link' element contains the PHP version that first provided + * new_link support for this DBMS. Contains false if it's unsupported. + * + * Meaning of the 'limit' element: + * + 'emulate' = emulate with fetch row by number + * + 'alter' = alter the query + * + false = skip rows + * + * @var array + */ + var $features = array( + 'limit' => 'emulate', + 'new_link' => false, + 'numrows' => true, + 'pconnect' => true, + 'prepare' => false, + 'ssl' => false, + 'transactions' => true, + ); + + /** + * A mapping of native error codes to DB error codes + * @var array + */ + var $errorcode_map = array( + ); + + /** + * The raw database connection created by PHP + * @var resource + */ + var $connection; + + /** + * The DSN information for connecting to a database + * @var array + */ + var $dsn = array(); + + + /** + * Should data manipulation queries be committed automatically? + * @var bool + * @access private + */ + var $autocommit = true; + + /** + * The quantity of transactions begun + * + * {@internal While this is private, it can't actually be designated + * private in PHP 5 because it is directly accessed in the test suite.}} + * + * @var integer + * @access private + */ + var $transaction_opcount = 0; + + /** + * The database specified in the DSN + * + * It's a fix to allow calls to different databases in the same script. + * + * @var string + * @access private + */ + var $_db = ''; + + + // }}} + // {{{ constructor + + /** + * This constructor calls $this->DB_common() + * + * @return void + */ + function DB_sybase() + { + $this->DB_common(); + } + + // }}} + // {{{ connect() + + /** + * Connect to the database server, log in and open the database + * + * Don't call this method directly. Use DB::connect() instead. + * + * PEAR DB's sybase driver supports the following extra DSN options: + * + appname The application name to use on this connection. + * Available since PEAR DB 1.7.0. + * + charset The character set to use on this connection. + * Available since PEAR DB 1.7.0. + * + * @param array $dsn the data source name + * @param bool $persistent should the connection be persistent? + * + * @return int DB_OK on success. A DB_Error object on failure. + */ + function connect($dsn, $persistent = false) + { + if (!PEAR::loadExtension('sybase') && + !PEAR::loadExtension('sybase_ct')) + { + return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND); + } + + $this->dsn = $dsn; + if ($dsn['dbsyntax']) { + $this->dbsyntax = $dsn['dbsyntax']; + } + + $dsn['hostspec'] = $dsn['hostspec'] ? $dsn['hostspec'] : 'localhost'; + $dsn['password'] = !empty($dsn['password']) ? $dsn['password'] : false; + $dsn['charset'] = isset($dsn['charset']) ? $dsn['charset'] : false; + $dsn['appname'] = isset($dsn['appname']) ? $dsn['appname'] : false; + + $connect_function = $persistent ? 'sybase_pconnect' : 'sybase_connect'; + + if ($dsn['username']) { + $this->connection = @$connect_function($dsn['hostspec'], + $dsn['username'], + $dsn['password'], + $dsn['charset'], + $dsn['appname']); + } else { + return $this->raiseError(DB_ERROR_CONNECT_FAILED, + null, null, null, + 'The DSN did not contain a username.'); + } + + if (!$this->connection) { + return $this->raiseError(DB_ERROR_CONNECT_FAILED, + null, null, null, + @sybase_get_last_message()); + } + + if ($dsn['database']) { + if (!@sybase_select_db($dsn['database'], $this->connection)) { + return $this->raiseError(DB_ERROR_NODBSELECTED, + null, null, null, + @sybase_get_last_message()); + } + $this->_db = $dsn['database']; + } + + return DB_OK; + } + + // }}} + // {{{ disconnect() + + /** + * Disconnects from the database server + * + * @return bool TRUE on success, FALSE on failure + */ + function disconnect() + { + $ret = @sybase_close($this->connection); + $this->connection = null; + return $ret; + } + + // }}} + // {{{ simpleQuery() + + /** + * Sends a query to the database server + * + * @param string the SQL query string + * + * @return mixed + a PHP result resrouce for successful SELECT queries + * + the DB_OK constant for other successful queries + * + a DB_Error object on failure + */ + function simpleQuery($query) + { + $ismanip = $this->_checkManip($query); + $this->last_query = $query; + if ($this->_db && !@sybase_select_db($this->_db, $this->connection)) { + return $this->sybaseRaiseError(DB_ERROR_NODBSELECTED); + } + $query = $this->modifyQuery($query); + if (!$this->autocommit && $ismanip) { + if ($this->transaction_opcount == 0) { + $result = @sybase_query('BEGIN TRANSACTION', $this->connection); + if (!$result) { + return $this->sybaseRaiseError(); + } + } + $this->transaction_opcount++; + } + $result = @sybase_query($query, $this->connection); + if (!$result) { + return $this->sybaseRaiseError(); + } + if (is_resource($result)) { + return $result; + } + // Determine which queries that should return data, and which + // should return an error code only. + return $ismanip ? DB_OK : $result; + } + + // }}} + // {{{ nextResult() + + /** + * Move the internal sybase result pointer to the next available result + * + * @param a valid sybase result resource + * + * @access public + * + * @return true if a result is available otherwise return false + */ + function nextResult($result) + { + return false; + } + + // }}} + // {{{ fetchInto() + + /** + * Places a row from the result set into the given array + * + * Formating of the array and the data therein are configurable. + * See DB_result::fetchInto() for more information. + * + * This method is not meant to be called directly. Use + * DB_result::fetchInto() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result the query result resource + * @param array $arr the referenced array to put the data in + * @param int $fetchmode how the resulting array should be indexed + * @param int $rownum the row number to fetch (0 = first row) + * + * @return mixed DB_OK on success, NULL when the end of a result set is + * reached or on failure + * + * @see DB_result::fetchInto() + */ + function fetchInto($result, &$arr, $fetchmode, $rownum = null) + { + if ($rownum !== null) { + if (!@sybase_data_seek($result, $rownum)) { + return null; + } + } + if ($fetchmode & DB_FETCHMODE_ASSOC) { + if (function_exists('sybase_fetch_assoc')) { + $arr = @sybase_fetch_assoc($result); + } else { + if ($arr = @sybase_fetch_array($result)) { + foreach ($arr as $key => $value) { + if (is_int($key)) { + unset($arr[$key]); + } + } + } + } + if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE && $arr) { + $arr = array_change_key_case($arr, CASE_LOWER); + } + } else { + $arr = @sybase_fetch_row($result); + } + if (!$arr) { + return null; + } + if ($this->options['portability'] & DB_PORTABILITY_RTRIM) { + $this->_rtrimArrayValues($arr); + } + if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) { + $this->_convertNullArrayValuesToEmpty($arr); + } + return DB_OK; + } + + // }}} + // {{{ freeResult() + + /** + * Deletes the result set and frees the memory occupied by the result set + * + * This method is not meant to be called directly. Use + * DB_result::free() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result PHP's query result resource + * + * @return bool TRUE on success, FALSE if $result is invalid + * + * @see DB_result::free() + */ + function freeResult($result) + { + return is_resource($result) ? sybase_free_result($result) : false; + } + + // }}} + // {{{ numCols() + + /** + * Gets the number of columns in a result set + * + * This method is not meant to be called directly. Use + * DB_result::numCols() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result PHP's query result resource + * + * @return int the number of columns. A DB_Error object on failure. + * + * @see DB_result::numCols() + */ + function numCols($result) + { + $cols = @sybase_num_fields($result); + if (!$cols) { + return $this->sybaseRaiseError(); + } + return $cols; + } + + // }}} + // {{{ numRows() + + /** + * Gets the number of rows in a result set + * + * This method is not meant to be called directly. Use + * DB_result::numRows() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result PHP's query result resource + * + * @return int the number of rows. A DB_Error object on failure. + * + * @see DB_result::numRows() + */ + function numRows($result) + { + $rows = @sybase_num_rows($result); + if ($rows === false) { + return $this->sybaseRaiseError(); + } + return $rows; + } + + // }}} + // {{{ affectedRows() + + /** + * Determines the number of rows affected by a data maniuplation query + * + * 0 is returned for queries that don't manipulate data. + * + * @return int the number of rows. A DB_Error object on failure. + */ + function affectedRows() + { + if ($this->_last_query_manip) { + $result = @sybase_affected_rows($this->connection); + } else { + $result = 0; + } + return $result; + } + + // }}} + // {{{ nextId() + + /** + * Returns the next free id in a sequence + * + * @param string $seq_name name of the sequence + * @param boolean $ondemand when true, the seqence is automatically + * created if it does not exist + * + * @return int the next id number in the sequence. + * A DB_Error object on failure. + * + * @see DB_common::nextID(), DB_common::getSequenceName(), + * DB_sybase::createSequence(), DB_sybase::dropSequence() + */ + function nextId($seq_name, $ondemand = true) + { + $seqname = $this->getSequenceName($seq_name); + if ($this->_db && !@sybase_select_db($this->_db, $this->connection)) { + return $this->sybaseRaiseError(DB_ERROR_NODBSELECTED); + } + $repeat = 0; + do { + $this->pushErrorHandling(PEAR_ERROR_RETURN); + $result = $this->query("INSERT INTO $seqname (vapor) VALUES (0)"); + $this->popErrorHandling(); + if ($ondemand && DB::isError($result) && + ($result->getCode() == DB_ERROR || $result->getCode() == DB_ERROR_NOSUCHTABLE)) + { + $repeat = 1; + $result = $this->createSequence($seq_name); + if (DB::isError($result)) { + return $this->raiseError($result); + } + } elseif (!DB::isError($result)) { + $result = $this->query("SELECT @@IDENTITY FROM $seqname"); + $repeat = 0; + } else { + $repeat = false; + } + } while ($repeat); + if (DB::isError($result)) { + return $this->raiseError($result); + } + $result = $result->fetchRow(DB_FETCHMODE_ORDERED); + return $result[0]; + } + + /** + * Creates a new sequence + * + * @param string $seq_name name of the new sequence + * + * @return int DB_OK on success. A DB_Error object on failure. + * + * @see DB_common::createSequence(), DB_common::getSequenceName(), + * DB_sybase::nextID(), DB_sybase::dropSequence() + */ + function createSequence($seq_name) + { + return $this->query('CREATE TABLE ' + . $this->getSequenceName($seq_name) + . ' (id numeric(10, 0) IDENTITY NOT NULL,' + . ' vapor int NULL)'); + } + + // }}} + // {{{ dropSequence() + + /** + * Deletes a sequence + * + * @param string $seq_name name of the sequence to be deleted + * + * @return int DB_OK on success. A DB_Error object on failure. + * + * @see DB_common::dropSequence(), DB_common::getSequenceName(), + * DB_sybase::nextID(), DB_sybase::createSequence() + */ + function dropSequence($seq_name) + { + return $this->query('DROP TABLE ' . $this->getSequenceName($seq_name)); + } + + // }}} + // {{{ quoteFloat() + + /** + * Formats a float value for use within a query in a locale-independent + * manner. + * + * @param float the float value to be quoted. + * @return string the quoted string. + * @see DB_common::quoteSmart() + * @since Method available since release 1.7.8. + */ + function quoteFloat($float) { + return $this->escapeSimple(str_replace(',', '.', strval(floatval($float)))); + } + + // }}} + // {{{ autoCommit() + + /** + * Enables or disables automatic commits + * + * @param bool $onoff true turns it on, false turns it off + * + * @return int DB_OK on success. A DB_Error object if the driver + * doesn't support auto-committing transactions. + */ + function autoCommit($onoff = false) + { + // XXX if $this->transaction_opcount > 0, we should probably + // issue a warning here. + $this->autocommit = $onoff ? true : false; + return DB_OK; + } + + // }}} + // {{{ commit() + + /** + * Commits the current transaction + * + * @return int DB_OK on success. A DB_Error object on failure. + */ + function commit() + { + if ($this->transaction_opcount > 0) { + if ($this->_db && !@sybase_select_db($this->_db, $this->connection)) { + return $this->sybaseRaiseError(DB_ERROR_NODBSELECTED); + } + $result = @sybase_query('COMMIT', $this->connection); + $this->transaction_opcount = 0; + if (!$result) { + return $this->sybaseRaiseError(); + } + } + return DB_OK; + } + + // }}} + // {{{ rollback() + + /** + * Reverts the current transaction + * + * @return int DB_OK on success. A DB_Error object on failure. + */ + function rollback() + { + if ($this->transaction_opcount > 0) { + if ($this->_db && !@sybase_select_db($this->_db, $this->connection)) { + return $this->sybaseRaiseError(DB_ERROR_NODBSELECTED); + } + $result = @sybase_query('ROLLBACK', $this->connection); + $this->transaction_opcount = 0; + if (!$result) { + return $this->sybaseRaiseError(); + } + } + return DB_OK; + } + + // }}} + // {{{ sybaseRaiseError() + + /** + * Produces a DB_Error object regarding the current problem + * + * @param int $errno if the error is being manually raised pass a + * DB_ERROR* constant here. If this isn't passed + * the error information gathered from the DBMS. + * + * @return object the DB_Error object + * + * @see DB_common::raiseError(), + * DB_sybase::errorNative(), DB_sybase::errorCode() + */ + function sybaseRaiseError($errno = null) + { + $native = $this->errorNative(); + if ($errno === null) { + $errno = $this->errorCode($native); + } + return $this->raiseError($errno, null, null, null, $native); + } + + // }}} + // {{{ errorNative() + + /** + * Gets the DBMS' native error message produced by the last query + * + * @return string the DBMS' error message + */ + function errorNative() + { + return @sybase_get_last_message(); + } + + // }}} + // {{{ errorCode() + + /** + * Determines PEAR::DB error code from the database's text error message. + * + * @param string $errormsg error message returned from the database + * @return integer an error number from a DB error constant + */ + function errorCode($errormsg) + { + static $error_regexps; + + // PHP 5.2+ prepends the function name to $php_errormsg, so we need + // this hack to work around it, per bug #9599. + $errormsg = preg_replace('/^sybase[a-z_]+\(\): /', '', $errormsg); + + if (!isset($error_regexps)) { + $error_regexps = array( + '/Incorrect syntax near/' + => DB_ERROR_SYNTAX, + '/^Unclosed quote before the character string [\"\'].*[\"\']\./' + => DB_ERROR_SYNTAX, + '/Implicit conversion (from datatype|of NUMERIC value)/i' + => DB_ERROR_INVALID_NUMBER, + '/Cannot drop the table [\"\'].+[\"\'], because it doesn\'t exist in the system catalogs\./' + => DB_ERROR_NOSUCHTABLE, + '/Only the owner of object [\"\'].+[\"\'] or a user with System Administrator \(SA\) role can run this command\./' + => DB_ERROR_ACCESS_VIOLATION, + '/^.+ permission denied on object .+, database .+, owner .+/' + => DB_ERROR_ACCESS_VIOLATION, + '/^.* permission denied, database .+, owner .+/' + => DB_ERROR_ACCESS_VIOLATION, + '/[^.*] not found\./' + => DB_ERROR_NOSUCHTABLE, + '/There is already an object named/' + => DB_ERROR_ALREADY_EXISTS, + '/Invalid column name/' + => DB_ERROR_NOSUCHFIELD, + '/does not allow null values/' + => DB_ERROR_CONSTRAINT_NOT_NULL, + '/Command has been aborted/' + => DB_ERROR_CONSTRAINT, + '/^Cannot drop the index .* because it doesn\'t exist/i' + => DB_ERROR_NOT_FOUND, + '/^There is already an index/i' + => DB_ERROR_ALREADY_EXISTS, + '/^There are fewer columns in the INSERT statement than values specified/i' + => DB_ERROR_VALUE_COUNT_ON_ROW, + '/Divide by zero/i' + => DB_ERROR_DIVZERO, + ); + } + + foreach ($error_regexps as $regexp => $code) { + if (preg_match($regexp, $errormsg)) { + return $code; + } + } + return DB_ERROR; + } + + // }}} + // {{{ tableInfo() + + /** + * Returns information about a table or a result set + * + * NOTE: only supports 'table' and 'flags' if $result + * is a table name. + * + * @param object|string $result DB_result object from a query or a + * string containing the name of a table. + * While this also accepts a query result + * resource identifier, this behavior is + * deprecated. + * @param int $mode a valid tableInfo mode + * + * @return array an associative array with the information requested. + * A DB_Error object on failure. + * + * @see DB_common::tableInfo() + * @since Method available since Release 1.6.0 + */ + function tableInfo($result, $mode = null) + { + if (is_string($result)) { + /* + * Probably received a table name. + * Create a result resource identifier. + */ + if ($this->_db && !@sybase_select_db($this->_db, $this->connection)) { + return $this->sybaseRaiseError(DB_ERROR_NODBSELECTED); + } + $id = @sybase_query("SELECT * FROM $result WHERE 1=0", + $this->connection); + $got_string = true; + } elseif (isset($result->result)) { + /* + * Probably received a result object. + * Extract the result resource identifier. + */ + $id = $result->result; + $got_string = false; + } else { + /* + * Probably received a result resource identifier. + * Copy it. + * Deprecated. Here for compatibility only. + */ + $id = $result; + $got_string = false; + } + + if (!is_resource($id)) { + return $this->sybaseRaiseError(DB_ERROR_NEED_MORE_DATA); + } + + if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) { + $case_func = 'strtolower'; + } else { + $case_func = 'strval'; + } + + $count = @sybase_num_fields($id); + $res = array(); + + if ($mode) { + $res['num_fields'] = $count; + } + + for ($i = 0; $i < $count; $i++) { + $f = @sybase_fetch_field($id, $i); + // column_source is often blank + $res[$i] = array( + 'table' => $got_string + ? $case_func($result) + : $case_func($f->column_source), + 'name' => $case_func($f->name), + 'type' => $f->type, + 'len' => $f->max_length, + 'flags' => '', + ); + if ($res[$i]['table']) { + $res[$i]['flags'] = $this->_sybase_field_flags( + $res[$i]['table'], $res[$i]['name']); + } + if ($mode & DB_TABLEINFO_ORDER) { + $res['order'][$res[$i]['name']] = $i; + } + if ($mode & DB_TABLEINFO_ORDERTABLE) { + $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i; + } + } + + // free the result only if we were called on a table + if ($got_string) { + @sybase_free_result($id); + } + return $res; + } + + // }}} + // {{{ _sybase_field_flags() + + /** + * Get the flags for a field + * + * Currently supports: + * + unique_key (unique index, unique check or primary_key) + * + multiple_key (multi-key index) + * + * @param string $table the table name + * @param string $column the field name + * + * @return string space delimited string of flags. Empty string if none. + * + * @access private + */ + function _sybase_field_flags($table, $column) + { + static $tableName = null; + static $flags = array(); + + if ($table != $tableName) { + $flags = array(); + $tableName = $table; + + /* We're running sp_helpindex directly because it doesn't exist in + * older versions of ASE -- unfortunately, we can't just use + * DB::isError() because the user may be using callback error + * handling. */ + $res = @sybase_query("sp_helpindex $table", $this->connection); + + if ($res === false || $res === true) { + // Fake a valid response for BC reasons. + return ''; + } + + while (($val = sybase_fetch_assoc($res)) !== false) { + if (!isset($val['index_keys'])) { + /* No useful information returned. Break and be done with + * it, which preserves the pre-1.7.9 behaviour. */ + break; + } + + $keys = explode(', ', trim($val['index_keys'])); + + if (sizeof($keys) > 1) { + foreach ($keys as $key) { + $this->_add_flag($flags[$key], 'multiple_key'); + } + } + + if (strpos($val['index_description'], 'unique')) { + foreach ($keys as $key) { + $this->_add_flag($flags[$key], 'unique_key'); + } + } + } + + sybase_free_result($res); + + } + + if (array_key_exists($column, $flags)) { + return(implode(' ', $flags[$column])); + } + + return ''; + } + + // }}} + // {{{ _add_flag() + + /** + * Adds a string to the flags array if the flag is not yet in there + * - if there is no flag present the array is created + * + * @param array $array reference of flags array to add a value to + * @param mixed $value value to add to the flag array + * + * @return void + * + * @access private + */ + function _add_flag(&$array, $value) + { + if (!is_array($array)) { + $array = array($value); + } elseif (!in_array($value, $array)) { + array_push($array, $value); + } + } + + // }}} + // {{{ getSpecialQuery() + + /** + * Obtains the query string needed for listing a given type of objects + * + * @param string $type the kind of objects you want to retrieve + * + * @return string the SQL query string or null if the driver doesn't + * support the object type requested + * + * @access protected + * @see DB_common::getListOf() + */ + function getSpecialQuery($type) + { + switch ($type) { + case 'tables': + return "SELECT name FROM sysobjects WHERE type = 'U'" + . ' ORDER BY name'; + case 'views': + return "SELECT name FROM sysobjects WHERE type = 'V'"; + default: + return null; + } + } + + // }}} + +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ + +?> diff --git a/library/pear/File.php b/library/pear/File.php new file mode 100644 index 000000000..d3c311116 --- /dev/null +++ b/library/pear/File.php @@ -0,0 +1,543 @@ + + * @author Tal Peer + * @author Michael Wallner + * @copyright 2002-2005 The Authors + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version CVS: $Id: File.php,v 1.38 2007/03/24 16:38:56 dufuz Exp $ + * @link http://pear.php.net/package/File + */ + +/** + * Requires PEAR + */ +require_once 'PEAR.php'; + +/** + * The default number of bytes for reading + */ +if (!defined('FILE_DEFAULT_READSIZE')) { + define('FILE_DEFAULT_READSIZE', 1024, true); +} + +/** + * The maximum number of bytes for reading lines + */ +if (!defined('FILE_MAX_LINE_READSIZE')) { + define('FILE_MAX_LINE_READSIZE', 40960, true); +} + +/** + * Whether file locks should block + */ +if (!defined('FILE_LOCKS_BLOCK')) { + define('FILE_LOCKS_BLOCK', true, true); +} + +/** + * Mode to use for reading from files + */ +define('FILE_MODE_READ', 'rb', true); + +/** + * Mode to use for truncating files, then writing + */ +define('FILE_MODE_WRITE', 'wb', true); + +/** + * Mode to use for appending to files + */ +define('FILE_MODE_APPEND', 'ab', true); + +/** + * Use this when a shared (read) lock is required + */ +define('FILE_LOCK_SHARED', LOCK_SH | (FILE_LOCKS_BLOCK ? 0 : LOCK_NB), true); + +/** + * Use this when an exclusive (write) lock is required + */ +define('FILE_LOCK_EXCLUSIVE', LOCK_EX | (FILE_LOCKS_BLOCK ? 0 : LOCK_NB), true); + +/** + * Class for handling files + * + * A class with common functions for writing, + * reading and handling files and directories + * + * @author Richard Heyes + * @author Tal Peer + * @author Michael Wallner + * @access public + * @package File + * + * @static + */ +class File extends PEAR +{ + /** + * Destructor + * + * Unlocks any locked file pointers and closes all filepointers + * + * @access private + */ + function _File() + { + File::closeAll(); + } + + /** + * Handles file pointers. If a file pointer needs to be opened, + * it will be. If it already exists (based on filename and mode) + * then the existing one will be returned. + * + * @access private + * @param string $filename Filename to be used + * @param string $mode Mode to open the file in + * @param mixed $lock Type of lock to use + * @return mixed PEAR_Error on error or file pointer resource on success + */ + function _getFilePointer($filename, $mode, $lock = false) + { + $filePointers = &PEAR::getStaticProperty('File', 'filePointers'); + + // Win32 is case-insensitive + if (OS_WINDOWS) { + $filename = strtolower($filename); + } + + // check if file pointer already exists + if (!isset($filePointers[$filename][$mode]) || + !is_resource($filePointers[$filename][$mode])) { + + // check if we can open the file in the desired mode + switch ($mode) + { + case FILE_MODE_READ: + if (!preg_match('/^.+(? $modes) { + foreach (array_keys($modes) as $mode) { + if (is_resource($filePointers[$fname][$mode])) { + @fclose($filePointers[$fname][$mode]); + } + unset($filePointers[$fname][$mode]); + } + } + } + } + + /** + * This closes an open file pointer + * + * @access public + * @param string $filename The filename that was opened + * @param string $mode Mode the file was opened in + * @return mixed PEAR Error on error, true otherwise + */ + function close($filename, $mode) + { + $filePointers = &PEAR::getStaticProperty('File', 'filePointers'); + + if (OS_WINDOWS) { + $filename = strToLower($filename); + } + + if (!isset($filePointers[$filename][$mode])) { + return true; + } + + $fp = $filePointers[$filename][$mode]; + unset($filePointers[$filename][$mode]); + + if (is_resource($fp)) { + // unlock file + @flock($fp, LOCK_UN); + // close file + if (!@fclose($fp)) { + return PEAR::raiseError("Cannot close file: $filename"); + } + } + + return true; + } + + /** + * This unlocks a locked file pointer. + * + * @access public + * @param string $filename The filename that was opened + * @param string $mode Mode the file was opened in + * @return mixed PEAR Error on error, true otherwise + */ + function unlock($filename, $mode) + { + $fp = File::_getFilePointer($filename, $mode); + if (PEAR::isError($fp)) { + return $fp; + } + + if (!@flock($fp, LOCK_UN)) { + return PEAR::raiseError("Cacnnot unlock file: $filename"); + } + + return true; + } + + /** + * @deprecated + */ + function stripTrailingSeparators($path, $separator = DIRECTORY_SEPARATOR) + { + if ($path === $separator) { + return $path; + } + return rtrim($path, $separator); + } + + /** + * @deprecated + */ + function stripLeadingSeparators($path, $separator = DIRECTORY_SEPARATOR) + { + if ($path === $separator) { + return $path; + } + return ltrim($path, $separator); + } + + /** + * @deprecated Use File_Util::buildPath() instead. + */ + function buildPath($parts, $separator = DIRECTORY_SEPARATOR) + { + require_once 'File/Util.php'; + return File_Util::buildPath($parts, $separator); + } + + /** + * @deprecated Use File_Util::skipRoot() instead. + */ + function skipRoot($path) + { + require_once 'File/Util.php'; + return File_Util::skipRoot($path); + } + + /** + * @deprecated Use File_Util::tmpDir() instead. + */ + function getTempDir() + { + require_once 'File/Util.php'; + return File_Util::tmpDir(); + } + + /** + * @deprecated Use File_Util::tmpFile() instead. + */ + function getTempFile($dirname = null) + { + require_once 'File/Util.php'; + return File_Util::tmpFile($dirname); + } + + /** + * @deprecated Use File_Util::isAbsolute() instead. + */ + function isAbsolute($path) + { + require_once 'File/Util.php'; + return File_Util::isAbsolute($path); + } + + /** + * @deprecated Use File_Util::relativePath() instead. + */ + function relativePath($path, $root, $separator = DIRECTORY_SEPARATOR) + { + require_once 'File/Util.php'; + return File_Util::relativePath($path, $root, $separator); + } + + /** + * @deprecated Use File_Util::realpath() instead. + */ + function realpath($path, $separator = DIRECTORY_SEPARATOR) + { + require_once 'File/Util.php'; + return File_Util::realpath($path, $separator); + } +} + +PEAR::registerShutdownFunc(array('File', '_File')); + +?> diff --git a/library/pear/File/CSV.php b/library/pear/File/CSV.php new file mode 100644 index 000000000..a4c5141c9 --- /dev/null +++ b/library/pear/File/CSV.php @@ -0,0 +1,628 @@ + + * @author Helgi Þormar + * @copyright 2004-2005 The Authors + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version CVS: $Id: CSV.php,v 1.41 2007/05/20 12:25:14 dufuz Exp $ + * @link http://pear.php.net/package/File + */ + +require_once 'PEAR.php'; +require_once 'File.php'; + +/** +* File class for handling CSV files (Comma Separated Values), a common format +* for exchanging data. +* +* TODO: +* - Usage example and Doc +* - Use getPointer() in discoverFormat +* - Add a line counter for being able to output better error reports +* - Store the last error in GLOBALS and add File_CSV::getLastError() +* +* Wish: +* - Other methods like readAll(), writeAll(), numFields(), numRows() +* - Try to detect if a CSV has header or not in discoverFormat() (not possible with CSV) +* +* Known Bugs: +* (they has been analyzed but for the moment the impact in the speed for +* properly handle this uncommon cases is too high and won't be supported) +* - A field which is composed only by a single quoted separator (ie -> ;";";) +* is not handled properly +* - When there is exactly one field minus than the expected number and there +* is a field with a separator inside, the parser will throw the "wrong count" error +* +* Info about CSV and links to other sources +* http://www.shaftek.org/publications/drafts/mime-csv/draft-shafranovich-mime-csv-00.html#appendix +* +* @author Tomas V.V.Cox +* @author Helgi Þormar +* @package File +*/ +class File_CSV +{ + /** + * This raiseError method works in a different way. It will always return + * false (an error occurred) but it will call PEAR::raiseError() before + * it. If no default PEAR global handler is set, will trigger an error. + * + * @param string $error The error message + * @return bool always false + */ + function raiseError($error) + { + // If a default PEAR Error handler is not set trigger the error + // XXX Add a PEAR::isSetHandler() method? + if ($GLOBALS['_PEAR_default_error_mode'] == PEAR_ERROR_RETURN) { + PEAR::raiseError($error, null, PEAR_ERROR_TRIGGER, E_USER_WARNING); + } else { + PEAR::raiseError($error); + } + return false; + } + + /** + * Checks the configuration given by the user + * + * @access private + * @param string &$error The error will be written here if any + * @param array &$conf The configuration assoc array + * @return string error Returns a error message + */ + function _conf(&$error, &$conf) + { + // check conf + if (!is_array($conf)) { + return $error = 'Invalid configuration'; + } + + if (!isset($conf['fields']) || !(int)$conf['fields']) { + return $error = 'The number of fields must be numeric (the "fields" key)'; + } + + if (isset($conf['sep'])) { + if (strlen($conf['sep']) != 1) { + return $error = 'Separator can only be one char'; + } + } elseif ($conf['fields'] > 1) { + return $error = 'Missing separator (the "sep" key)'; + } + + if (isset($conf['quote'])) { + if (strlen($conf['quote']) != 1) { + return $error = 'The quote char must be one char (the "quote" key)'; + } + } else { + $conf['quote'] = null; + } + + if (!isset($conf['crlf'])) { + $conf['crlf'] = "\n"; + } + + if (!isset($conf['eol2unix'])) { + $conf['eol2unix'] = true; + } + } + + /** + * Return or create the file descriptor associated with a file + * + * @param string $file The name of the file + * @param array &$conf The configuration + * @param string $mode The open node (ex: FILE_MODE_READ or FILE_MODE_WRITE) + * @param boolean $reset if passed as true and resource for the file exists + * than the file pointer will be moved to the beginning + * + * @return mixed A file resource or false + */ + function getPointer($file, &$conf, $mode = FILE_MODE_READ, $reset = false) + { + static $resources = array(); + static $config; + if (isset($resources[$file][$mode])) { + $conf = $config; + if ($reset) { + fseek($resources[$file][$mode], 0); + } + return $resources[$file][$mode]; + } + File_CSV::_conf($error, $conf); + if ($error) { + return File_CSV::raiseError($error); + } + $config = $conf; + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $fp = File::_getFilePointer($file, $mode); + PEAR::popErrorHandling(); + if (PEAR::isError($fp)) { + return File_CSV::raiseError($fp); + } + $resources[$file][$mode] = $fp; + return $fp; + } + + /** + * Unquote data + * + * @param string $field The data to unquote + * @param string $quote The quote char + * @return string the unquoted data + */ + function unquote($field, $quote) + { + // Trim first the string. + $field = trim($field); + $quote = trim($quote); + + // Incase null fields (form: ;;) + if (!strlen($field)) { + return $field; + } + + // excel compat + if ($field[0] == '=' && $field[1] == '"') { + $field = str_replace('="', '"', $field); + } + + $field_len = strlen($field); + if ($quote && $field[0] == $quote && $field[$field_len - 1] == $quote) { + // Get rid of escaping quotes + $new = $prev = $c = ''; + for ($i = 0; $i < $field_len; ++$i) { + $prev = $c; + $c = $field[$i]; + // Deal with escaping quotes + if ($c == $quote && $prev == $quote) { + $c = ''; + } + + $new .= $c; + } + $field = substr($new, 1, -1); + } + + return $field; + } + + /** + * Reads a row of data as an array from a CSV file. It's able to + * read memo fields with multiline data. + * + * @param string $file The filename where to write the data + * @param array &$conf The configuration of the dest CSV + * + * @return mixed Array with the data read or false on error/no more data + */ + function readQuoted($file, &$conf) + { + if (!$fp = File_CSV::getPointer($file, $conf, FILE_MODE_READ)) { + return false; + } + + $buff = $old = $prev = $c = ''; + $ret = array(); + $i = 1; + $in_quote = false; + $quote = $conf['quote']; + $f = $conf['fields']; + $sep = $conf['sep']; + while (false !== $ch = fgetc($fp)) { + $old = $prev; + $prev = $c; + $c = $ch; + + // Common case + if ($c != $quote && $c != $sep && $c != "\n" && $c != "\r") { + $buff .= $c; + continue; + } + + // Start quote. + if ( + $in_quote === false && + $quote && $c == $quote && + ( + $prev == $sep || $prev == "\n" || $prev === null || + $prev == "\r" || $prev == '' || $prev == ' ' + || $prev == '=' //excel compat + ) + ) { + $in_quote = true; + // excel compat, removing the = part but only if we are in a quote + if ($prev == '=') { + $buff{strlen($buff) - 1} = ''; + } + } + + if ($in_quote) { + + // When does the quote end, make sure it's not double quoted + if ($c == $sep && $prev == $quote && $old != $quote) { + $in_quote = false; + } elseif ($c == $sep && $buff == $quote.$quote) { + // In case we are dealing with double quote but empty value + $in_quote = false; + } elseif ($c == "\n" || $c == "\r") { + $sub = ($prev == "\r") ? 2 : 1; + $buff_len = strlen($buff); + if ( + $buff_len >= $sub && + $buff[$buff_len - $sub] == $quote + ) { + $in_quote = false; + } + } + } + + if (!$in_quote && ($c == $sep || $c == "\n" || $c == "\r") && $prev != '') { + // More fields than expected + if ($c == $sep && (count($ret) + 1) == $f) { + // Seek the pointer into linebreak character. + while (true) { + $c = fgetc($fp); + if ($c == "\n" || $c == "\r" || $c == '') { + break; + } + } + + // Insert last field value. + $ret[] = File_CSV::unquote($buff, $quote); + return $ret; + } + + // Less fields than expected + if (($c == "\n" || $c == "\r") && $i != $f) { + // Insert last field value. + $ret[] = File_CSV::unquote($buff, $quote); + if (count($ret) == 1 && empty($ret[0])) { + return array(); + } + + // Pair the array elements to fields count. - inserting empty values + $ret_count = count($ret); + $sum = ($f - 1) - ($ret_count - 1); + $data = array_merge($ret, array_fill($ret_count, $sum, '')); + return $data; + } + + if ($prev == "\r") { + $buff = substr($buff, 0, -1); + } + + // Convert EOL character to Unix EOL (LF). + if ($conf['eol2unix']) { + $buff = preg_replace('/(\r\n|\r)$/', "\n", $buff); + } + + $ret[] = File_CSV::unquote($buff, $quote); + if (count($ret) == $f) { + return $ret; + } + $buff = ''; + ++$i; + continue; + } + $buff .= $c; + } + + /* If it's the end of the file and we still have something in buffer + * then we process it since files can have no CL/FR at the end + */ + $feof = feof($fp); + if ($feof && !in_array($buff, array("\r", "\n", "\r\n")) && strlen($buff) > 0) { + $ret[] = File_CSV::unquote($buff, $quote); + if (count($ret) == $f) { + return $ret; + } + } + + return !$feof ? $ret : false; + } + + /** + * Reads a "row" from a CSV file and return it as an array + * + * @param string $file The CSV file + * @param array &$conf The configuration of the dest CSV + * + * @return mixed Array or false + */ + function read($file, &$conf) + { + static $headers = array(); + if (!$fp = File_CSV::getPointer($file, $conf, FILE_MODE_READ)) { + return false; + } + + // The size is limited to 4K + if (!$line = fgets($fp, 4096)) { + return false; + } + + $fields = $conf['fields'] == 1 ? array($line) : explode($conf['sep'], $line); + + $nl = array("\n", "\r", "\r\n"); + if (in_array($fields[count($fields) - 1], $nl)) { + array_pop($fields); + } + + $field_count = count($fields); + $last =& $fields[$field_count - 1]; + $len = strlen($last); + if ( + $field_count != $conf['fields'] || + $conf['quote'] && + ( + $len !== 0 && $last[$len - 1] == "\n" + && + ( + ($last[0] == $conf['quote'] + && $last[strlen(rtrim($last)) - 1] != $conf['quote']) + || + // excel support + ($last[0] == '=' && $last[1] == $conf['quote']) + || + // if the row has spaces before the quote + preg_match('|^\s+'.preg_quote($conf['quote']) .'|Ums', $last, $match) + ) + ) + // XXX perhaps there is a separator inside a quoted field + //preg_match("|{$conf['quote']}.*{$conf['sep']}.*{$conf['quote']}|U", $line) + ) { + fseek($fp, -1 * strlen($line), SEEK_CUR); + return File_CSV::readQuoted($file, $conf); + } else { + foreach ($fields as $k => $v) { + $fields[$k] = File_CSV::unquote($v, $conf['quote']); + } + } + + if (isset($conf['header']) && empty($headers)) { + // read the first row and assign to $headers + $headers = $fields; + return $headers; + } + + if ($field_count != $conf['fields']) { + File_CSV::raiseError("Read wrong fields number count: '". $field_count . + "' expected ".$conf['fields']); + return true; + } + + if (!empty($headers)) { + $tmp = array(); + foreach ($fields as $k => $v) { + $tmp[$headers[$k]] = $v; + } + $fields = $tmp; + } + + return $fields; + } + + /** + * Internal use only, will be removed in the future + * + * @param string $str The string to debug + * @access private + */ + function _dbgBuff($str) + { + if (strpos($str, "\r") !== false) { + $str = str_replace("\r", "_r_", $str); + } + if (strpos($str, "\n") !== false) { + $str = str_replace("\n", "_n_", $str); + } + if (strpos($str, "\t") !== false) { + $str = str_replace("\t", "_t_", $str); + } + if ($str === null) { + $str = '_NULL_'; + } + if ($str === '') { + $str = 'Empty string'; + } + echo "buff: ($str)\n"; + } + + /** + * Writes a struc (array) in a file as CSV + * + * @param string $file The filename where to write the data + * @param array $fields Ordered array with the data + * @param array &$conf The configuration of the dest CSV + * + * @return bool True on success false otherwise + */ + function write($file, $fields, &$conf) + { + if (!$fp = File_CSV::getPointer($file, $conf, FILE_MODE_WRITE)) { + return false; + } + + $field_count = count($fields); + if ($field_count != $conf['fields']) { + File_CSV::raiseError("Wrong fields number count: '". $field_count . + "' expected ".$conf['fields']); + return true; + } + + $write = ''; + for ($i = 0; $i < $field_count; ++$i) { + // only quote if the field contains a sep + if (!is_numeric($fields[$i]) && $conf['quote'] + && isset($conf['sep']) && strpos($fields[$i], $conf['sep']) + ) { + $write .= $conf['quote'] . $fields[$i] . $conf['quote']; + } else { + $write .= $fields[$i]; + } + + $write .= ($i < ($field_count - 1)) ? $conf['sep']: $conf['crlf']; + } + + if (!fwrite($fp, $write, strlen($write))) { + return File_CSV::raiseError('Can not write to file'); + } + + return true; + } + + /** + * Discover the format of a CSV file (the number of fields, the separator + * and if it quote string fields) + * + * @param string the CSV file name + * @param array extra separators that should be checked for. + * @return mixed Assoc array or false + */ + function discoverFormat($file, $extraSeps = array()) + { + if (!$fp = @fopen($file, 'rb')) { + return File_CSV::raiseError("Could not open file: $file"); + } + + // Set auto detect line ending for Mac EOL support + $oldini = ini_get('auto_detect_line_endings'); + if ($oldini != '1') { + ini_set('auto_detect_line_endings', '1'); + } + + // Take the first 30 lines and store the number of ocurrences + // for each separator in each line + $lines = ''; + for ($i = 0; $i < 30 && $line = fgets($fp, 4096); $i++) { + $lines .= $line; + } + fclose($fp); + + if ($oldini != '1') { + ini_set('auto_detect_line_endings', $oldini); + } + + $seps = array("\t", ';', ':', ','); + $seps = array_merge($seps, $extraSeps); + $matches = array(); + $quotes = '"\''; + + $lines = str_replace('""', '', $lines); + while ($lines != ($newLines = preg_replace('|((["\'])[^"]*(\2))|', '\2_\2', $lines))){ + $lines = $newLines; + } + + $eol = strpos($lines, "\r") ? "\r" : "\n"; + $lines = explode($eol, $lines); + foreach ($lines as $line) { + $orgLine = $line; + foreach ($seps as $sep) { + $line = preg_replace("|^[^$quotes$sep]*$sep*([$quotes][^$quotes]*[$quotes])|sm", '_', $orgLine); + // Find all seps that are within qoutes + ///FIXME ... counts legitimit lines as bad ones + + // In case there's a whitespace infront the field + $regex = '|\s*?'; + // Match the first quote (optional), also optionally match = since it's excel stuff + $regex.= "(?:\=?[$quotes])"; + $regex.= '(.*'; + // Don't match a sep if we are inside a quote + // also don't accept the sep if it has a quote on the either side + ///FIXME has to be possible if we are inside a quote! (tests fail because of this) + $regex.= "(?:[^$quotes])$sep(?:[^$quotes])"; + $regex.= '.*)'; + // Close quote (if it's present) and the sep (optional, could be end of line) + $regex.= "(?:[$quotes](?:$sep?))|Ums"; + preg_match_all($regex, $line, $match); + // Finding all seps, within quotes or not + $sep_count = substr_count($line, $sep); + // Real count + $matches[$sep][] = $sep_count - count($match[0]); + } + } + + $final = array(); + // Group the results by amount of equal ocurrences + foreach ($matches as $sep => $res) { + $times = array(); + $times[0] = 0; + foreach ($res as $k => $num) { + if ($num > 0) { + $times[$num] = (isset($times[$num])) ? $times[$num] + 1 : 1; + } + } + arsort($times); + + // Use max fields count. + $fields[$sep] = max(array_flip($times)); + $amount[$sep] = $times[key($times)]; + } + + arsort($amount); + $sep = key($amount); + + $conf['fields'] = $fields[$sep] + 1; + $conf['sep'] = $sep; + + // Test if there are fields with quotes around in the first 30 lines + $quote = null; + + $string = implode('', $lines); + foreach (array('"', '\'') as $q) { + if (preg_match_all("|$sep(?:\s*?)(\=?[$q]).*([$q])$sep|Us", $string, $match)) { + if ($match[1][0] == $match[2][0]) { + $quote = $match[1][0]; + break; + } + } + + if ( + preg_match_all("|^(\=?[$q]).*([$q])$sep{0,1}|Ums", $string, $match) + || preg_match_all("|(\=?[$q]).*([$q])$sep\s$|Ums", $string, $match) + ) { + if ($match[1][0] == $match[2][0]) { + $quote = $match[1][0]; + break; + } + } + } + + $conf['quote'] = $quote; + return $conf; + } + + /** + * Front to call getPointer and moving the resource to the + * beginning of the file + * Reset it if you like. + * + * @param string $file The name of the file + * @param array &$conf The configuration + * @param string $mode The open node (ex: FILE_MODE_READ or FILE_MODE_WRITE) + * + * @return boolean true on success false on failure + */ + function resetPointer($file, &$conf, $mode) + { + if (!File_CSV::getPointer($file, $conf, $mode, true)) { + return false; + } + + return true; + } +} \ No newline at end of file diff --git a/library/pear/File/Find.php b/library/pear/File/Find.php new file mode 100644 index 000000000..df99baa22 --- /dev/null +++ b/library/pear/File/Find.php @@ -0,0 +1,485 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id: Find.php,v 1.27 2006/06/30 14:06:16 techtonik Exp $ +// + +require_once 'PEAR.php'; + +define('FILE_FIND_VERSION', '@package_version@'); + +// to debug uncomment this string +// define('FILE_FIND_DEBUG', ''); + +/** +* Commonly needed functions searching directory trees +* +* @access public +* @version $Id: Find.php,v 1.27 2006/06/30 14:06:16 techtonik Exp $ +* @package File +* @author Sterling Hughes +*/ +class File_Find +{ + /** + * internal dir-list + * @var array + */ + var $_dirs = array(); + + /** + * directory separator + * @var string + */ + var $dirsep = "/"; + + /** + * found files + * @var array + */ + var $files = array(); + + /** + * found dirs + * @var array + */ + var $directories = array(); + + /** + * Search specified directory to find matches for specified pattern + * + * @param string $pattern a string containing the pattern to search + * the directory for. + * + * @param string $dirpath a string containing the directory path + * to search. + * + * @param string $pattern_type a string containing the type of + * pattern matching functions to use (can either be 'php', + * 'perl' or 'shell'). + * + * @return array containing all of the files and directories + * matching the pattern or null if no matches + * + * @author Sterling Hughes + * @access public + * @static + */ + function &glob($pattern, $dirpath, $pattern_type = 'php') + { + $dh = @opendir($dirpath); + + if (!$dh) { + $pe = PEAR::raiseError("Cannot open directory $dirpath"); + return $pe; + } + + $match_function = File_Find::_determineRegex($pattern, $pattern_type); + $matches = array(); + + // empty string cannot be specified for 'php' and 'perl' pattern + if ($pattern || ($pattern_type != 'php' && $pattern_type != 'perl')) { + while (false !== ($entry = @readdir($dh))) { + if ($match_function($pattern, $entry) && + $entry != '.' && $entry != '..') { + $matches[] = $entry; + } + } + } + + @closedir($dh); + + if (0 == count($matches)) { + $matches = null; + } + + return $matches ; + } + + /** + * Map the directory tree given by the directory_path parameter. + * + * @param string $directory contains the directory path that you + * want to map. + * + * @return array a two element array, the first element containing a list + * of all the directories, the second element containing a list of all the + * files. + * + * @author Sterling Hughes + * @access public + */ + function &maptree($directory) + { + + /* if called statically */ + if (!isset($this) || !is_a($this, "File_Find")) { + $obj = &new File_Find(); + return $obj->maptree($directory); + } + + /* clear the results just in case */ + $this->files = array(); + $this->directories = array(); + + /* strip out trailing slashes */ + $directory = preg_replace('![\\\\/]+$!', '', $directory); + + $this->_dirs = array($directory); + + while (count($this->_dirs)) { + $dir = array_pop($this->_dirs); + File_Find::_build($dir, $this->dirsep); + array_push($this->directories, $dir); + } + + $retval = array($this->directories, $this->files); + return $retval; + + } + + /** + * Map the directory tree given by the directory parameter. + * + * @param string $directory contains the directory path that you + * want to map. + * @param integer $maxrecursion maximun number of folders to recursive + * map + * + * @return array a multidimensional array containing all subdirectories + * and their files. For example: + * + * Array + * ( + * [0] => file_1.php + * [1] => file_2.php + * [subdirname] => Array + * ( + * [0] => file_1.php + * ) + * ) + * + * @author Mika Tuupola + * @access public + * @static + */ + function &mapTreeMultiple($directory, $maxrecursion = 0, $count = 0) + { + $retval = array(); + + $count++; + + /* strip trailing slashes */ + $directory = preg_replace('![\\\\/]+$!', '', $directory); + + if (is_readable($directory)) { + $dh = opendir($directory); + while (false !== ($entry = @readdir($dh))) { + if ($entry != '.' && $entry != '..') { + array_push($retval, $entry); + } + } + closedir($dh); + } + + while (list($key, $val) = each($retval)) { + $path = $directory . "/" . $val; + + if (!is_array($val) && is_dir($path)) { + unset($retval[$key]); + if ($maxrecursion == 0 || $count < $maxrecursion) { + $retval[$val] = &File_Find::mapTreeMultiple($path, + $maxrecursion, $count); + } + } + } + + return $retval; + } + + /** + * Search the specified directory tree with the specified pattern. Return + * an array containing all matching files (no directories included). + * + * @param string $pattern the pattern to match every file with. + * + * @param string $directory the directory tree to search in. + * + * @param string $type the type of regular expression support to use, either + * 'php', 'perl' or 'shell'. + * + * @param bool $fullpath whether the regex should be matched against the + * full path or only against the filename + * + * @param string $match can be either 'files', 'dirs' or 'both' to specify + * the kind of list to return + * + * @return array a list of files matching the pattern parameter in the the + * directory path specified by the directory parameter + * + * @author Sterling Hughes + * @access public + * @static + */ + function &search($pattern, $directory, $type = 'php', $fullpath = true, $match = 'files') + { + + $matches = array(); + list ($directories,$files) = File_Find::maptree($directory); + switch($match) { + case 'directories': + $data = $directories; + break; + case 'both': + $data = array_merge($directories, $files); + break; + case 'files': + default: + $data = $files; + } + unset($files, $directories); + + $match_function = File_Find::_determineRegex($pattern, $type); + + reset($data); + // check if empty string given (ok for 'shell' method, but bad for others) + if ($pattern || ($type != 'php' && $type != 'perl')) { + while (list(,$entry) = each($data)) { + if ($match_function($pattern, + $fullpath ? $entry : basename($entry))) { + $matches[] = $entry; + } + } + } + + return $matches; + } + + /** + * Determine whether or not a variable is a PEAR error + * + * @param object PEAR_Error $var the variable to test. + * + * @return boolean returns true if the variable is a PEAR error, otherwise + * it returns false. + * @access public + */ + function isError(&$var) + { + return PEAR::isError($var); + } + + /** + * internal function to build singular directory trees, used by + * File_Find::maptree() + * + * @param string $directory name of the directory to read + * @param string $separator directory separator + * @return void + */ + function _build($directory, $separator = "/") + { + + $dh = @opendir($directory); + + if (!$dh) { + $pe = PEAR::raiseError("Cannot open directory"); + return $pe; + } + + while (false !== ($entry = @readdir($dh))) { + if ($entry != '.' && $entry != '..') { + + $entry = $directory.$separator.$entry; + + if (is_dir($entry)) { + array_push($this->_dirs, $entry); + } else { + array_push($this->files, $entry); + } + } + } + + @closedir($dh); + } + + /** + * internal function to determine the type of regular expression to + * use, implemented by File_Find::glob() and File_Find::search() + * + * @param string $type given RegExp type + * @return string kind of function ( "eregi", "ereg" or "preg_match") ; + * + */ + function _determineRegex($pattern, $type) + { + if (!strcasecmp($type, 'shell')) { + $match_function = 'File_Find_match_shell'; + } else if (!strcasecmp($type, 'perl')) { + $match_function = 'preg_match'; + } else if (!strcasecmp(substr($pattern, -2), '/i')) { + $match_function = 'eregi'; + } else { + $match_function = 'ereg'; + } + return $match_function; + } + +} + +/** +* Package method to match via 'shell' pattern. Provided in global +* scope, because it should be called like 'preg_match' and 'eregi' +* and can be easily copied into other packages +* +* @author techtonik +* @return mixed bool on success and PEAR_Error on failure +*/ +function File_Find_match_shell($pattern, $filename) +{ + // {{{ convert pattern to positive and negative regexps + $positive = $pattern; + $negation = substr_count($pattern, "|"); + + if ($negation > 1) { + PEAR::raiseError("Mask string contains errors!"); + return FALSE; + } elseif ($negation) { + list($positive, $negative) = explode("|", $pattern); + if (strlen($negative) == 0) { + PEAR::raiseError("File-mask string contains errors!"); + return FALSE; + } + } + + $positive = _File_Find_match_shell_get_pattern($positive); + if ($negation) { + $negative = _File_Find_match_shell_get_pattern($negative); + } + // }}} convert end + + + if (defined("FILE_FIND_DEBUG")) { + print("Method: $type\nPattern: $pattern\n Converted pattern:"); + print_r($positive); + if (isset($negative)) print_r($negative); + } + + if (!preg_match($positive, $filename)) { + return FALSE; + } else { + if (isset($negative) + && preg_match($negative, $filename)) { + return FALSE; + } else { + return TRUE; + } + } +} + +/** +* function used by File_Find_match_shell to convert 'shell' mask +* into pcre regexp. Some of the rules (see testcases for more): +* escaping all special chars and replacing +* . with \. +* * with .* +* ? with .{1} +* also adding ^ and $ as the pattern matches whole filename +* +* @author techtonik +* @return string pcre regexp for preg_match +*/ +function _File_Find_match_shell_get_pattern($mask) { + // get array of several masks (if any) delimited by comma + // do not touch commas in char class + $premasks = preg_split("|(\[[^\]]+\])|", $mask, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY ); + if (defined("FILE_FIND_DEBUG")) { + print("\nPremask: "); + print_r($premasks); + } + $pi = 0; + foreach($premasks as $pm) { + if (!isset($masks[$pi])) $masks[$pi] = ""; + if ($pm{0} == '[' && $pm{strlen($pm)-1} == ']') { + // strip commas from character class + $masks[$pi] .= str_replace(",", "", $pm); + } else { + $tarr = explode(",", $pm); + if (sizeof($tarr) == 1) { + $masks[$pi] .= $pm; + } else { + foreach ($tarr as $te) { + $masks[$pi++] .= $te; + $masks[$pi] = ""; + } + unset($masks[$pi--]); + } + } + } + + // if empty string given return *.* pattern + if (strlen($mask) == 0) return "!^.*$!"; + + // convert to preg regexp + $regexmask = implode("|", $masks); + if (defined("FILE_FIND_DEBUG")) { + print("regexMask step one(implode): $regexmask"); + } + $regexmask = addcslashes($regexmask, '^$}!{)(\/.+'); + if (defined("FILE_FIND_DEBUG")) { + print("\nregexMask step two(addcslashes): $regexmask"); + } + $regexmask = preg_replace("!(\*|\?)!", ".$1", $regexmask); + if (defined("FILE_FIND_DEBUG")) { + print("\nregexMask step three(* ? -> .* .?): $regexmask"); + } + // a special case '*.' at the end means that there is no extension + $regexmask = preg_replace("!\.\*\\\.(\||$)!", "[^\.]*$1", $regexmask); + // it is impossible to have dot at the end of filename + $regexmask = preg_replace("!\\\.(\||$)!", "$1", $regexmask); + // and .* at the end also means that there could be nothing at all + // (i.e. no dot at the end also) + $regexmask = preg_replace("!\\\.\.\*(\||$)!", "(\\\\..*)?$1", $regexmask); + if (defined("FILE_FIND_DEBUG")) { + print("\nregexMask step two and half(*.$ \\..*$ .$ -> [^.]*$ .?.* $): $regexmask"); + } + // if no extension supplied - add .* to match partially from filename start + if (strpos($regexmask, "\\.") === FALSE) $regexmask .= ".*"; + + // file mask match whole name - adding restrictions + $regexmask = preg_replace("!(\|)!", '^'."$1".'$', $regexmask); + $regexmask = '^'.$regexmask.'$'; + if (defined("FILE_FIND_DEBUG")) { + print("\nregexMask step three(^ and $ to match whole name): $regexmask"); + } + // wrap regex into ! since all ! are already escaped + $regexmask = "!$regexmask!i"; + if (defined("FILE_FIND_DEBUG")) { + print("\nWrapped regex: $regexmask\n"); + } + return $regexmask; +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ + +?> diff --git a/library/pear/File/Util.php b/library/pear/File/Util.php new file mode 100644 index 000000000..92de5bed0 --- /dev/null +++ b/library/pear/File/Util.php @@ -0,0 +1,482 @@ + + * @copyright 2004-2005 Michael Wallner + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version CVS: $Id: Util.php,v 1.25 2007/02/20 14:19:08 mike Exp $ + * @link http://pear.php.net/package/File + */ + +/**#@+ + * Sorting Constants + */ +define('FILE_SORT_NONE', 0); +define('FILE_SORT_REVERSE', 1); +define('FILE_SORT_NAME', 2); +define('FILE_SORT_SIZE', 4); +define('FILE_SORT_DATE', 8); +define('FILE_SORT_RANDOM', 16); +/**#@-*/ + +/**#@+ + * Listing Constants + */ +define('FILE_LIST_FILES', 1); +define('FILE_LIST_DIRS', 2); +define('FILE_LIST_DOTS', 4); +define('FILE_LIST_ALL', FILE_LIST_FILES | FILE_LIST_DIRS | FILE_LIST_DOTS); +/**#@-*/ + +/** + * @ignore + */ +define('FILE_WIN32', defined('OS_WINDOWS') ? OS_WINDOWS : !strncasecmp(PHP_OS, 'win', 3)); + +/** + * File_Util + * + * File and directory utility functions. + * + * @access public + * @static + */ +class File_Util +{ + /** + * Returns a string path built from the array $pathParts. Where a join + * occurs multiple separators are removed. Joins using the optional + * separator, defaulting to the PHP DIRECTORY_SEPARATOR constant. + * + * @static + * @access public + * @param array $parts Array containing the parts to be joined + * @param string $separator The directory seperator + */ + function buildPath($parts, $separator = DIRECTORY_SEPARATOR) + { + $qs = '/^'. preg_quote($separator, '/') .'+$/'; + for ($i = 0, $c = count($parts); $i < $c; $i++) { + if (!strlen($parts[$i]) || preg_match($qs, $parts[$i])) { + unset($parts[$i]); + } elseif (0 == $i) { + $parts[$i] = rtrim($parts[$i], $separator); + } elseif ($c - 1 == $i) { + $parts[$i] = ltrim($parts[$i], $separator); + } else { + $parts[$i] = trim($parts[$i], $separator); + } + } + return implode($separator, $parts); + } + + /** + * Returns a path without leading / or C:\. If this is not + * present the path is returned as is. + * + * @static + * @access public + * @param string $path The path to be processed + * @return string The processed path or the path as is + */ + function skipRoot($path) + { + if (File_Util::isAbsolute($path)) { + if (FILE_WIN32) { + return substr($path, $path{3} == '\\' ? 4 : 3); + } + return ltrim($path, '/'); + } + return $path; + } + + /** + * Returns the temp directory according to either the TMP, TMPDIR, or + * TEMP env variables. If these are not set it will also check for the + * existence of /tmp, %WINDIR%\temp + * + * @static + * @access public + * @return string The system tmp directory + */ + function tmpDir() + { + if (FILE_WIN32) { + if (isset($_ENV['TEMP'])) { + return $_ENV['TEMP']; + } + if (isset($_ENV['TMP'])) { + return $_ENV['TMP']; + } + if (isset($_ENV['windir'])) { + return $_ENV['windir'] . '\\temp'; + } + if (isset($_ENV['SystemRoot'])) { + return $_ENV['SystemRoot'] . '\\temp'; + } + if (isset($_SERVER['TEMP'])) { + return $_SERVER['TEMP']; + } + if (isset($_SERVER['TMP'])) { + return $_SERVER['TMP']; + } + if (isset($_SERVER['windir'])) { + return $_SERVER['windir'] . '\\temp'; + } + if (isset($_SERVER['SystemRoot'])) { + return $_SERVER['SystemRoot'] . '\\temp'; + } + return '\temp'; + } + if (isset($_ENV['TMPDIR'])) { + return $_ENV['TMPDIR']; + } + if (isset($_SERVER['TMPDIR'])) { + return $_SERVER['TMPDIR']; + } + return '/tmp'; + } + + /** + * Returns a temporary filename using tempnam() and File::tmpDir(). + * + * @static + * @access public + * @param string $dirname Optional directory name for the tmp file + * @return string Filename and path of the tmp file + */ + function tmpFile($dirname = null) + { + if (!isset($dirname)) { + $dirname = File_Util::tmpDir(); + } + return tempnam($dirname, 'temp.'); + } + + /** + * Returns boolean based on whether given path is absolute or not. + * + * @static + * @access public + * @param string $path Given path + * @return boolean True if the path is absolute, false if it is not + */ + function isAbsolute($path) + { + if (preg_match('/(?:\/|\\\)\.\.(?=\/|$)/', $path)) { + return false; + } + if (FILE_WIN32) { + return preg_match('/^[a-zA-Z]:(\\\|\/)/', $path); + } + return ($path{0} == '/') || ($path{0} == '~'); + } + + /** + * Checks for a file's existence, taking the current include path + * into consideration + * + * This method can be called statically + * (e.g., File_Util::isIncludable('config.php')) + * + * @param string $file + * @param string $sep the directory separator (optional) + * @return string the includable path + * @access public + * @static + */ + function isIncludable($file, $sep = DIRECTORY_SEPARATOR) + { + foreach ((array) explode(PATH_SEPARATOR, ini_get('include_path')) as $path) { + if (file_exists($path .= $sep . $file)) { + return $path; + } + } + if (file_exists($file)) { + return $file; + } + return NULL; + } + + /** + * Get path relative to another path + * + * @static + * @access public + * @return string + * @param string $path + * @param string $root + * @param string $separator + */ + function relativePath($path, $root, $separator = DIRECTORY_SEPARATOR) + { + $path = File_Util::realpath($path, $separator); + $root = File_Util::realpath($root, $separator); + $dirs = explode($separator, $path); + $comp = explode($separator, $root); + + if (FILE_WIN32) { + if (strcasecmp($dirs[0], $comp[0])) { + return $path; + } + unset($dirs[0], $comp[0]); + } + + foreach ($comp as $i => $part) { + if (isset($dirs[$i]) && $part == $dirs[$i]) { + unset($dirs[$i], $comp[$i]); + } else { + break; + } + } + + return str_repeat('..' . $separator, count($comp)) . implode($separator, $dirs); + } + + /** + * Get real path (works with non-existant paths) + * + * @static + * @access public + * @return string + * @param string $path + * @param string $separator + */ + function realPath($path, $separator = DIRECTORY_SEPARATOR) + { + if (!strlen($path)) { + return $separator; + } + + $drive = ''; + if (FILE_WIN32) { + $path = preg_replace('/[\\\\\/]/', $separator, $path); + if (preg_match('/([a-zA-Z]\:)(.*)/', $path, $matches)) { + $drive = $matches[1]; + $path = $matches[2]; + } else { + $cwd = getcwd(); + $drive = substr($cwd, 0, 2); + if ($path{0} !== $separator{0}) { + $path = substr($cwd, 3) . $separator . $path; + } + } + } elseif ($path{0} !== $separator) { + $path = getcwd() . $separator . $path; + } + + $dirStack = array(); + foreach (explode($separator, $path) as $dir) { + if (strlen($dir) && $dir !== '.') { + if ($dir == '..') { + array_pop($dirStack); + } else { + $dirStack[] = $dir; + } + } + } + + return $drive . $separator . implode($separator, $dirStack); + } + + /** + * Check whether path is in root path + * + * @static + * @access public + * @return bool + * @param string $path + * @param string $root + */ + function pathInRoot($path, $root) + { + static $realPaths = array(); + + if (!isset($realPaths[$root])) { + $realPaths[$root] = File_Util::realPath($root); + } + + return false !== strstr(File_Util::realPath($path), $realPaths[$root]); + } + + /** + * List Directory + * + * The final argument, $cb, is a callback that either evaluates to true or + * false and performs a filter operation, or it can also modify the + * directory/file names returned. To achieve the latter effect use as + * follows: + * + * + * name, "\n"; + * } + * ?> + * + * + * @static + * @access public + * @return array + * @param string $path + * @param int $list + * @param int $sort + * @param mixed $cb + */ + function listDir($path, $list = FILE_LIST_ALL, $sort = FILE_SORT_NONE, $cb = null) + { + if (!strlen($path) || !is_dir($path)) { + return null; + } + + $entries = array(); + for ($dir = dir($path); false !== $entry = $dir->read(); ) { + if ($list & FILE_LIST_DOTS || $entry{0} !== '.') { + $isRef = ($entry === '.' || $entry === '..'); + $isDir = $isRef || is_dir($path .'/'. $entry); + if ( ((!$isDir && $list & FILE_LIST_FILES) || + ($isDir && $list & FILE_LIST_DIRS)) && + (!is_callable($cb) || + call_user_func_array($cb, array(&$entry)))) { + $entries[] = (object) array( + 'name' => $entry, + 'size' => $isDir ? null : filesize($path .'/'. $entry), + 'date' => filemtime($path .'/'. $entry), + ); + } + } + } + $dir->close(); + + if ($sort) { + $entries = File_Util::sortFiles($entries, $sort); + } + + return $entries; + } + + /** + * Sort Files + * + * @static + * @access public + * @return array + * @param array $files + * @param int $sort + */ + function sortFiles($files, $sort) + { + if (!$files) { + return array(); + } + + if (!$sort) { + return $files; + } + + if ($sort === 1) { + return array_reverse($files); + } + + if ($sort & FILE_SORT_RANDOM) { + shuffle($files); + return $files; + } + + $names = array(); + $sizes = array(); + $dates = array(); + + if ($sort & FILE_SORT_NAME) { + $r = &$names; + } elseif ($sort & FILE_SORT_DATE) { + $r = &$dates; + } elseif ($sort & FILE_SORT_SIZE) { + $r = &$sizes; + } else { + asort($files, SORT_REGULAR); + return $files; + } + + $sortFlags = array( + FILE_SORT_NAME => SORT_STRING, + FILE_SORT_DATE => SORT_NUMERIC, + FILE_SORT_SIZE => SORT_NUMERIC, + ); + + foreach ($files as $file) { + $names[] = $file->name; + $sizes[] = $file->size; + $dates[] = $file->date; + } + + if ($sort & FILE_SORT_REVERSE) { + arsort($r, $sortFlags[$sort & ~1]); + } else { + asort($r, $sortFlags[$sort]); + } + + $result = array(); + foreach ($r as $i => $f) { + $result[] = $files[$i]; + } + + return $result; + } + + /** + * Switch File Extension + * + * @static + * @access public + * @return string|array + * @param string|array $filename + * @param string $to new file extension + * @param string $from change only files with this extension + * @param bool $reverse change only files not having $from extension + */ + function switchExt($filename, $to, $from = null, $reverse = false) + { + if (is_array($filename)) { + foreach ($filename as $key => $file) { + $filename[$key] = File_Util::switchExt($file, $to, $from); + } + return $filename; + } + + if ($len = strlen($from)) { + $ext = substr($filename, -$len - 1); + $cfn = FILE_WIN32 ? 'strcasecmp' : 'strcmp'; + if (!$reverse == $cfn($ext, '.'. $from)) { + return $filename; + } + return substr($filename, 0, -$len - 1) .'.'. $to; + } + + if ($pos = strpos($filename, '.')) { + return substr($filename, 0, $pos) .'.'. $to; + } + + return $filename .'.'. $to; + } +} + +?> diff --git a/library/pear/HTML/Common.php b/library/pear/HTML/Common.php new file mode 100644 index 000000000..2dab2fe58 --- /dev/null +++ b/library/pear/HTML/Common.php @@ -0,0 +1,465 @@ + + * @copyright 2001-2009 The PHP Group + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version CVS: $Id: Common.php,v 1.15 2009/04/03 15:26:22 avb Exp $ + * @link http://pear.php.net/package/HTML_Common/ + */ + +/** + * Base class for all HTML classes + * + * @category HTML + * @package HTML_Common + * @author Adam Daniel + * @version Release: 1.2.5 + * @abstract + */ +class HTML_Common +{ + /** + * Associative array of attributes + * @var array + * @access private + */ + var $_attributes = array(); + + /** + * Tab offset of the tag + * @var int + * @access private + */ + var $_tabOffset = 0; + + /** + * Tab string + * @var string + * @since 1.7 + * @access private + */ + var $_tab = "\11"; + + /** + * Contains the line end string + * @var string + * @since 1.7 + * @access private + */ + var $_lineEnd = "\12"; + + /** + * HTML comment on the object + * @var string + * @since 1.5 + * @access private + */ + var $_comment = ''; + + /** + * Class constructor + * @param mixed $attributes Associative array of table tag attributes + * or HTML attributes name="value" pairs + * @param int $tabOffset Indent offset in tabs + * @access public + */ + function HTML_Common($attributes = null, $tabOffset = 0) + { + $this->setAttributes($attributes); + $this->setTabOffset($tabOffset); + } // end constructor + + /** + * Returns the current API version + * @access public + * @returns double + */ + function apiVersion() + { + return 1.7; + } // end func apiVersion + + /** + * Returns the lineEnd + * + * @since 1.7 + * @access private + * @return string + */ + function _getLineEnd() + { + return $this->_lineEnd; + } // end func getLineEnd + + /** + * Returns a string containing the unit for indenting HTML + * + * @since 1.7 + * @access private + * @return string + */ + function _getTab() + { + return $this->_tab; + } // end func _getTab + + /** + * Returns a string containing the offset for the whole HTML code + * + * @return string + * @access private + */ + function _getTabs() + { + return str_repeat($this->_getTab(), $this->_tabOffset); + } // end func _getTabs + + /** + * Returns an HTML formatted attribute string + * @param array $attributes + * @return string + * @access private + */ + function _getAttrString($attributes) + { + $strAttr = ''; + + if (is_array($attributes)) { + $charset = HTML_Common::charset(); + foreach ($attributes as $key => $value) { + $strAttr .= ' ' . $key . '="' . htmlspecialchars($value, ENT_COMPAT, $charset) . '"'; + } + } + return $strAttr; + } // end func _getAttrString + + /** + * Returns a valid atrributes array from either a string or array + * @param mixed $attributes Either a typical HTML attribute string or an associative array + * @access private + * @return array + */ + function _parseAttributes($attributes) + { + if (is_array($attributes)) { + $ret = array(); + foreach ($attributes as $key => $value) { + if (is_int($key)) { + $key = $value = strtolower($value); + } else { + $key = strtolower($key); + } + $ret[$key] = $value; + } + return $ret; + + } elseif (is_string($attributes)) { + $preg = "/(([A-Za-z_:]|[^\\x00-\\x7F])([A-Za-z0-9_:.-]|[^\\x00-\\x7F])*)" . + "([ \\n\\t\\r]+)?(=([ \\n\\t\\r]+)?(\"[^\"]*\"|'[^']*'|[^ \\n\\t\\r]*))?/"; + if (preg_match_all($preg, $attributes, $regs)) { + for ($counter=0; $counter $value) { + $attr1[$key] = $value; + } + } // end func _updateAtrrArray + + /** + * Removes the given attribute from the given array + * + * @param string $attr Attribute name + * @param array $attributes Attribute array + * @since 1.4 + * @access private + * @return void + */ + function _removeAttr($attr, &$attributes) + { + $attr = strtolower($attr); + if (isset($attributes[$attr])) { + unset($attributes[$attr]); + } + } //end func _removeAttr + + /** + * Returns the value of the given attribute + * + * @param string $attr Attribute name + * @since 1.5 + * @access public + * @return string|null returns null if an attribute does not exist + */ + function getAttribute($attr) + { + $attr = strtolower($attr); + if (isset($this->_attributes[$attr])) { + return $this->_attributes[$attr]; + } + return null; + } //end func getAttribute + + /** + * Sets the value of the attribute + * + * @param string Attribute name + * @param string Attribute value (will be set to $name if omitted) + * @access public + */ + function setAttribute($name, $value = null) + { + $name = strtolower($name); + if (is_null($value)) { + $value = $name; + } + $this->_attributes[$name] = $value; + } // end func setAttribute + + /** + * Sets the HTML attributes + * @param mixed $attributes Either a typical HTML attribute string or an associative array + * @access public + */ + function setAttributes($attributes) + { + $this->_attributes = $this->_parseAttributes($attributes); + } // end func setAttributes + + /** + * Returns the assoc array (default) or string of attributes + * + * @param bool Whether to return the attributes as string + * @since 1.6 + * @access public + * @return mixed attributes + */ + function getAttributes($asString = false) + { + if ($asString) { + return $this->_getAttrString($this->_attributes); + } else { + return $this->_attributes; + } + } //end func getAttributes + + /** + * Updates the passed attributes without changing the other existing attributes + * @param mixed $attributes Either a typical HTML attribute string or an associative array + * @access public + */ + function updateAttributes($attributes) + { + $this->_updateAttrArray($this->_attributes, $this->_parseAttributes($attributes)); + } // end func updateAttributes + + /** + * Removes an attribute + * + * @param string $attr Attribute name + * @since 1.4 + * @access public + * @return void + */ + function removeAttribute($attr) + { + $this->_removeAttr($attr, $this->_attributes); + } //end func removeAttribute + + /** + * Sets the line end style to Windows, Mac, Unix or a custom string. + * + * @param string $style "win", "mac", "unix" or custom string. + * @since 1.7 + * @access public + * @return void + */ + function setLineEnd($style) + { + switch ($style) { + case 'win': + $this->_lineEnd = "\15\12"; + break; + case 'unix': + $this->_lineEnd = "\12"; + break; + case 'mac': + $this->_lineEnd = "\15"; + break; + default: + $this->_lineEnd = $style; + } + } // end func setLineEnd + + /** + * Sets the tab offset + * + * @param int $offset + * @access public + */ + function setTabOffset($offset) + { + $this->_tabOffset = $offset; + } // end func setTabOffset + + /** + * Returns the tabOffset + * + * @since 1.5 + * @access public + * @return int + */ + function getTabOffset() + { + return $this->_tabOffset; + } //end func getTabOffset + + /** + * Sets the string used to indent HTML + * + * @since 1.7 + * @param string $string String used to indent ("\11", "\t", ' ', etc.). + * @access public + * @return void + */ + function setTab($string) + { + $this->_tab = $string; + } // end func setTab + + /** + * Sets the HTML comment to be displayed at the beginning of the HTML string + * + * @param string + * @since 1.4 + * @access public + * @return void + */ + function setComment($comment) + { + $this->_comment = $comment; + } // end func setHtmlComment + + /** + * Returns the HTML comment + * + * @since 1.5 + * @access public + * @return string + */ + function getComment() + { + return $this->_comment; + } //end func getComment + + /** + * Abstract method. Must be extended to return the objects HTML + * + * @access public + * @return string + * @abstract + */ + function toHtml() + { + return ''; + } // end func toHtml + + /** + * Displays the HTML to the screen + * + * @access public + */ + function display() + { + print $this->toHtml(); + } // end func display + + /** + * Sets the charset to use by htmlspecialchars() function + * + * Since this parameter is expected to be global, the function is designed + * to be called statically: + * + * HTML_Common::charset('utf-8'); + * + * or + * + * $charset = HTML_Common::charset(); + * + * + * @param string New charset to use. Omit if just getting the + * current value. Consult the htmlspecialchars() docs + * for a list of supported character sets. + * @return string Current charset + * @access public + * @static + */ + function charset($newCharset = null) + { + static $charset = 'ISO-8859-1'; + + if (!is_null($newCharset)) { + $charset = $newCharset; + } + return $charset; + } // end func charset +} // end class HTML_Common +?> diff --git a/library/pear/HTML/QuickForm.php b/library/pear/HTML/QuickForm.php new file mode 100644 index 000000000..203c97f57 --- /dev/null +++ b/library/pear/HTML/QuickForm.php @@ -0,0 +1,2065 @@ + + * @author Bertrand Mansion + * @author Alexey Borzov + * @copyright 2001-2009 The PHP Group + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version CVS: $Id: QuickForm.php,v 1.166 2009/04/04 21:34:02 avb Exp $ + * @link http://pear.php.net/package/HTML_QuickForm + */ + +/** + * PEAR and PEAR_Error classes, for error handling + */ +require_once 'PEAR.php'; +/** + * Base class for all HTML classes + */ +require_once 'HTML/Common.php'; + +/** + * Element types known to HTML_QuickForm + * @see HTML_QuickForm::registerElementType(), HTML_QuickForm::getRegisteredTypes(), + * HTML_QuickForm::isTypeRegistered() + * @global array $GLOBALS['HTML_QUICKFORM_ELEMENT_TYPES'] + */ +$GLOBALS['HTML_QUICKFORM_ELEMENT_TYPES'] = + array( + 'group' =>array('HTML/QuickForm/group.php','HTML_QuickForm_group'), + 'hidden' =>array('HTML/QuickForm/hidden.php','HTML_QuickForm_hidden'), + 'reset' =>array('HTML/QuickForm/reset.php','HTML_QuickForm_reset'), + 'checkbox' =>array('HTML/QuickForm/checkbox.php','HTML_QuickForm_checkbox'), + 'file' =>array('HTML/QuickForm/file.php','HTML_QuickForm_file'), + 'image' =>array('HTML/QuickForm/image.php','HTML_QuickForm_image'), + 'password' =>array('HTML/QuickForm/password.php','HTML_QuickForm_password'), + 'radio' =>array('HTML/QuickForm/radio.php','HTML_QuickForm_radio'), + 'button' =>array('HTML/QuickForm/button.php','HTML_QuickForm_button'), + 'submit' =>array('HTML/QuickForm/submit.php','HTML_QuickForm_submit'), + 'select' =>array('HTML/QuickForm/select.php','HTML_QuickForm_select'), + 'hiddenselect' =>array('HTML/QuickForm/hiddenselect.php','HTML_QuickForm_hiddenselect'), + 'text' =>array('HTML/QuickForm/text.php','HTML_QuickForm_text'), + 'textarea' =>array('HTML/QuickForm/textarea.php','HTML_QuickForm_textarea'), + 'link' =>array('HTML/QuickForm/link.php','HTML_QuickForm_link'), + 'advcheckbox' =>array('HTML/QuickForm/advcheckbox.php','HTML_QuickForm_advcheckbox'), + 'date' =>array('HTML/QuickForm/date.php','HTML_QuickForm_date'), + 'static' =>array('HTML/QuickForm/static.php','HTML_QuickForm_static'), + 'header' =>array('HTML/QuickForm/header.php', 'HTML_QuickForm_header'), + 'html' =>array('HTML/QuickForm/html.php', 'HTML_QuickForm_html'), + 'hierselect' =>array('HTML/QuickForm/hierselect.php', 'HTML_QuickForm_hierselect'), + 'autocomplete' =>array('HTML/QuickForm/autocomplete.php', 'HTML_QuickForm_autocomplete'), + 'xbutton' =>array('HTML/QuickForm/xbutton.php','HTML_QuickForm_xbutton') + ); + +/** + * Validation rules known to HTML_QuickForm + * @see HTML_QuickForm::registerRule(), HTML_QuickForm::getRegisteredRules(), + * HTML_QuickForm::isRuleRegistered() + * @global array $GLOBALS['_HTML_QuickForm_registered_rules'] + */ +$GLOBALS['_HTML_QuickForm_registered_rules'] = array( + 'required' => array('html_quickform_rule_required', 'HTML/QuickForm/Rule/Required.php'), + 'maxlength' => array('html_quickform_rule_range', 'HTML/QuickForm/Rule/Range.php'), + 'minlength' => array('html_quickform_rule_range', 'HTML/QuickForm/Rule/Range.php'), + 'rangelength' => array('html_quickform_rule_range', 'HTML/QuickForm/Rule/Range.php'), + 'email' => array('html_quickform_rule_email', 'HTML/QuickForm/Rule/Email.php'), + 'regex' => array('html_quickform_rule_regex', 'HTML/QuickForm/Rule/Regex.php'), + 'lettersonly' => array('html_quickform_rule_regex', 'HTML/QuickForm/Rule/Regex.php'), + 'alphanumeric' => array('html_quickform_rule_regex', 'HTML/QuickForm/Rule/Regex.php'), + 'numeric' => array('html_quickform_rule_regex', 'HTML/QuickForm/Rule/Regex.php'), + 'nopunctuation' => array('html_quickform_rule_regex', 'HTML/QuickForm/Rule/Regex.php'), + 'nonzero' => array('html_quickform_rule_regex', 'HTML/QuickForm/Rule/Regex.php'), + 'callback' => array('html_quickform_rule_callback', 'HTML/QuickForm/Rule/Callback.php'), + 'compare' => array('html_quickform_rule_compare', 'HTML/QuickForm/Rule/Compare.php') +); + +// {{{ error codes + +/**#@+ + * Error codes for HTML_QuickForm + * + * Codes are mapped to textual messages by errorMessage() method, if you add a + * new code be sure to add a new message for it to errorMessage() + * + * @see HTML_QuickForm::errorMessage() + */ +define('QUICKFORM_OK', 1); +define('QUICKFORM_ERROR', -1); +define('QUICKFORM_INVALID_RULE', -2); +define('QUICKFORM_NONEXIST_ELEMENT', -3); +define('QUICKFORM_INVALID_FILTER', -4); +define('QUICKFORM_UNREGISTERED_ELEMENT', -5); +define('QUICKFORM_INVALID_ELEMENT_NAME', -6); +define('QUICKFORM_INVALID_PROCESS', -7); +define('QUICKFORM_DEPRECATED', -8); +define('QUICKFORM_INVALID_DATASOURCE', -9); +/**#@-*/ + +// }}} + +/** + * Create, validate and process HTML forms + * + * @category HTML + * @package HTML_QuickForm + * @author Adam Daniel + * @author Bertrand Mansion + * @author Alexey Borzov + * @version Release: 3.2.11 + */ +class HTML_QuickForm extends HTML_Common +{ + // {{{ properties + + /** + * Array containing the form fields + * @since 1.0 + * @var array + * @access private + */ + var $_elements = array(); + + /** + * Array containing element name to index map + * @since 1.1 + * @var array + * @access private + */ + var $_elementIndex = array(); + + /** + * Array containing indexes of duplicate elements + * @since 2.10 + * @var array + * @access private + */ + var $_duplicateIndex = array(); + + /** + * Array containing required field IDs + * @since 1.0 + * @var array + * @access private + */ + var $_required = array(); + + /** + * Prefix message in javascript alert if error + * @since 1.0 + * @var string + * @access public + */ + var $_jsPrefix = 'Invalid information entered.'; + + /** + * Postfix message in javascript alert if error + * @since 1.0 + * @var string + * @access public + */ + var $_jsPostfix = 'Please correct these fields.'; + + /** + * Datasource object implementing the informal + * datasource protocol + * @since 3.3 + * @var object + * @access private + */ + var $_datasource; + + /** + * Array of default form values + * @since 2.0 + * @var array + * @access private + */ + var $_defaultValues = array(); + + /** + * Array of constant form values + * @since 2.0 + * @var array + * @access private + */ + var $_constantValues = array(); + + /** + * Array of submitted form values + * @since 1.0 + * @var array + * @access private + */ + var $_submitValues = array(); + + /** + * Array of submitted form files + * @since 1.0 + * @var integer + * @access public + */ + var $_submitFiles = array(); + + /** + * Value for maxfilesize hidden element if form contains file input + * @since 1.0 + * @var integer + * @access public + */ + var $_maxFileSize = 1048576; // 1 Mb = 1048576 + + /** + * Flag to know if all fields are frozen + * @since 1.0 + * @var boolean + * @access private + */ + var $_freezeAll = false; + + /** + * Array containing the form rules + * @since 1.0 + * @var array + * @access private + */ + var $_rules = array(); + + /** + * Form rules, global variety + * @var array + * @access private + */ + var $_formRules = array(); + + /** + * Array containing the validation errors + * @since 1.0 + * @var array + * @access private + */ + var $_errors = array(); + + /** + * Note for required fields in the form + * @var string + * @since 1.0 + * @access private + */ + var $_requiredNote = '* denotes required field'; + + /** + * Whether the form was submitted + * @var boolean + * @access private + */ + var $_flagSubmitted = false; + + // }}} + // {{{ constructor + + /** + * Class constructor + * @param string $formName Form's name. + * @param string $method (optional)Form's method defaults to 'POST' + * @param string $action (optional)Form's action + * @param string $target (optional)Form's target defaults to '_self' + * @param mixed $attributes (optional)Extra attributes for
    tag + * @param bool $trackSubmit (optional)Whether to track if the form was submitted by adding a special hidden field + * @access public + */ + function HTML_QuickForm($formName='', $method='post', $action='', $target='', $attributes=null, $trackSubmit = false) + { + HTML_Common::HTML_Common($attributes); + $method = (strtoupper($method) == 'GET') ? 'get' : 'post'; + $action = ($action == '') ? $_SERVER['PHP_SELF'] : $action; + $target = empty($target) ? array() : array('target' => $target); + $attributes = array('action'=>$action, 'method'=>$method, 'name'=>$formName, 'id'=>$formName) + $target; + $this->updateAttributes($attributes); + if (!$trackSubmit || isset($_REQUEST['_qf__' . $formName])) { + if (1 == get_magic_quotes_gpc()) { + $this->_submitValues = $this->_recursiveFilter('stripslashes', 'get' == $method? $_GET: $_POST); + foreach ($_FILES as $keyFirst => $valFirst) { + foreach ($valFirst as $keySecond => $valSecond) { + if ('name' == $keySecond) { + $this->_submitFiles[$keyFirst][$keySecond] = $this->_recursiveFilter('stripslashes', $valSecond); + } else { + $this->_submitFiles[$keyFirst][$keySecond] = $valSecond; + } + } + } + } else { + $this->_submitValues = 'get' == $method? $_GET: $_POST; + $this->_submitFiles = $_FILES; + } + $this->_flagSubmitted = count($this->_submitValues) > 0 || count($this->_submitFiles) > 0; + } + if ($trackSubmit) { + unset($this->_submitValues['_qf__' . $formName]); + $this->addElement('hidden', '_qf__' . $formName, null); + } + if (preg_match('/^([0-9]+)([a-zA-Z]*)$/', ini_get('upload_max_filesize'), $matches)) { + // see http://www.php.net/manual/en/faq.using.php#faq.using.shorthandbytes + switch (strtoupper($matches['2'])) { + case 'G': + $this->_maxFileSize = $matches['1'] * 1073741824; + break; + case 'M': + $this->_maxFileSize = $matches['1'] * 1048576; + break; + case 'K': + $this->_maxFileSize = $matches['1'] * 1024; + break; + default: + $this->_maxFileSize = $matches['1']; + } + } + } // end constructor + + // }}} + // {{{ apiVersion() + + /** + * Returns the current API version + * + * @since 1.0 + * @access public + * @return float + */ + function apiVersion() + { + return 3.2; + } // end func apiVersion + + // }}} + // {{{ registerElementType() + + /** + * Registers a new element type + * + * @param string $typeName Name of element type + * @param string $include Include path for element type + * @param string $className Element class name + * @since 1.0 + * @access public + * @return void + */ + function registerElementType($typeName, $include, $className) + { + $GLOBALS['HTML_QUICKFORM_ELEMENT_TYPES'][strtolower($typeName)] = array($include, $className); + } // end func registerElementType + + // }}} + // {{{ registerRule() + + /** + * Registers a new validation rule + * + * @param string $ruleName Name of validation rule + * @param string $type Either: 'regex', 'function' or 'rule' for an HTML_QuickForm_Rule object + * @param string $data1 Name of function, regular expression or HTML_QuickForm_Rule classname + * @param string $data2 Object parent of above function or HTML_QuickForm_Rule file path + * @since 1.0 + * @access public + * @return void + */ + function registerRule($ruleName, $type, $data1, $data2 = null) + { + include_once('HTML/QuickForm/RuleRegistry.php'); + $registry =& HTML_QuickForm_RuleRegistry::singleton(); + $registry->registerRule($ruleName, $type, $data1, $data2); + } // end func registerRule + + // }}} + // {{{ elementExists() + + /** + * Returns true if element is in the form + * + * @param string $element form name of element to check + * @since 1.0 + * @access public + * @return boolean + */ + function elementExists($element=null) + { + return isset($this->_elementIndex[$element]); + } // end func elementExists + + // }}} + // {{{ setDatasource() + + /** + * Sets a datasource object for this form object + * + * Datasource default and constant values will feed the QuickForm object if + * the datasource implements defaultValues() and constantValues() methods. + * + * @param object $datasource datasource object implementing the informal datasource protocol + * @param mixed $defaultsFilter string or array of filter(s) to apply to default values + * @param mixed $constantsFilter string or array of filter(s) to apply to constants values + * @since 3.3 + * @access public + * @return void + * @throws HTML_QuickForm_Error + */ + function setDatasource(&$datasource, $defaultsFilter = null, $constantsFilter = null) + { + if (is_object($datasource)) { + $this->_datasource =& $datasource; + if (is_callable(array($datasource, 'defaultValues'))) { + $this->setDefaults($datasource->defaultValues($this), $defaultsFilter); + } + if (is_callable(array($datasource, 'constantValues'))) { + $this->setConstants($datasource->constantValues($this), $constantsFilter); + } + } else { + return PEAR::raiseError(null, QUICKFORM_INVALID_DATASOURCE, null, E_USER_WARNING, "Datasource is not an object in QuickForm::setDatasource()", 'HTML_QuickForm_Error', true); + } + } // end func setDatasource + + // }}} + // {{{ setDefaults() + + /** + * Initializes default form values + * + * @param array $defaultValues values used to fill the form + * @param mixed $filter (optional) filter(s) to apply to all default values + * @since 1.0 + * @access public + * @return void + * @throws HTML_QuickForm_Error + */ + function setDefaults($defaultValues = null, $filter = null) + { + if (is_array($defaultValues)) { + if (isset($filter)) { + if (is_array($filter) && (2 != count($filter) || !is_callable($filter))) { + foreach ($filter as $val) { + if (!is_callable($val)) { + return PEAR::raiseError(null, QUICKFORM_INVALID_FILTER, null, E_USER_WARNING, "Callback function does not exist in QuickForm::setDefaults()", 'HTML_QuickForm_Error', true); + } else { + $defaultValues = $this->_recursiveFilter($val, $defaultValues); + } + } + } elseif (!is_callable($filter)) { + return PEAR::raiseError(null, QUICKFORM_INVALID_FILTER, null, E_USER_WARNING, "Callback function does not exist in QuickForm::setDefaults()", 'HTML_QuickForm_Error', true); + } else { + $defaultValues = $this->_recursiveFilter($filter, $defaultValues); + } + } + $this->_defaultValues = HTML_QuickForm::arrayMerge($this->_defaultValues, $defaultValues); + foreach (array_keys($this->_elements) as $key) { + $this->_elements[$key]->onQuickFormEvent('updateValue', null, $this); + } + } + } // end func setDefaults + + // }}} + // {{{ setConstants() + + /** + * Initializes constant form values. + * These values won't get overridden by POST or GET vars + * + * @param array $constantValues values used to fill the form + * @param mixed $filter (optional) filter(s) to apply to all default values + * + * @since 2.0 + * @access public + * @return void + * @throws HTML_QuickForm_Error + */ + function setConstants($constantValues = null, $filter = null) + { + if (is_array($constantValues)) { + if (isset($filter)) { + if (is_array($filter) && (2 != count($filter) || !is_callable($filter))) { + foreach ($filter as $val) { + if (!is_callable($val)) { + return PEAR::raiseError(null, QUICKFORM_INVALID_FILTER, null, E_USER_WARNING, "Callback function does not exist in QuickForm::setConstants()", 'HTML_QuickForm_Error', true); + } else { + $constantValues = $this->_recursiveFilter($val, $constantValues); + } + } + } elseif (!is_callable($filter)) { + return PEAR::raiseError(null, QUICKFORM_INVALID_FILTER, null, E_USER_WARNING, "Callback function does not exist in QuickForm::setConstants()", 'HTML_QuickForm_Error', true); + } else { + $constantValues = $this->_recursiveFilter($filter, $constantValues); + } + } + $this->_constantValues = HTML_QuickForm::arrayMerge($this->_constantValues, $constantValues); + foreach (array_keys($this->_elements) as $key) { + $this->_elements[$key]->onQuickFormEvent('updateValue', null, $this); + } + } + } // end func setConstants + + // }}} + // {{{ setMaxFileSize() + + /** + * Sets the value of MAX_FILE_SIZE hidden element + * + * @param int $bytes Size in bytes + * @since 3.0 + * @access public + * @return void + */ + function setMaxFileSize($bytes = 0) + { + if ($bytes > 0) { + $this->_maxFileSize = $bytes; + } + if (!$this->elementExists('MAX_FILE_SIZE')) { + $this->addElement('hidden', 'MAX_FILE_SIZE', $this->_maxFileSize); + } else { + $el =& $this->getElement('MAX_FILE_SIZE'); + $el->updateAttributes(array('value' => $this->_maxFileSize)); + } + } // end func setMaxFileSize + + // }}} + // {{{ getMaxFileSize() + + /** + * Returns the value of MAX_FILE_SIZE hidden element + * + * @since 3.0 + * @access public + * @return int max file size in bytes + */ + function getMaxFileSize() + { + return $this->_maxFileSize; + } // end func getMaxFileSize + + // }}} + // {{{ &createElement() + + /** + * Creates a new form element of the given type. + * + * This method accepts variable number of parameters, their + * meaning and count depending on $elementType + * + * @param string $elementType type of element to add (text, textarea, file...) + * @since 1.0 + * @access public + * @return HTML_QuickForm_Element + * @throws HTML_QuickForm_Error + */ + function &createElement($elementType) + { + $args = func_get_args(); + $element =& HTML_QuickForm::_loadElement('createElement', $elementType, array_slice($args, 1)); + return $element; + } // end func createElement + + // }}} + // {{{ _loadElement() + + /** + * Returns a form element of the given type + * + * @param string $event event to send to newly created element ('createElement' or 'addElement') + * @param string $type element type + * @param array $args arguments for event + * @since 2.0 + * @access private + * @return HTML_QuickForm_Element + * @throws HTML_QuickForm_Error + */ + function &_loadElement($event, $type, $args) + { + $type = strtolower($type); + if (!HTML_QuickForm::isTypeRegistered($type)) { + $error = PEAR::raiseError(null, QUICKFORM_UNREGISTERED_ELEMENT, null, E_USER_WARNING, "Element '$type' does not exist in HTML_QuickForm::_loadElement()", 'HTML_QuickForm_Error', true); + return $error; + } + $className = $GLOBALS['HTML_QUICKFORM_ELEMENT_TYPES'][$type][1]; + $includeFile = $GLOBALS['HTML_QUICKFORM_ELEMENT_TYPES'][$type][0]; + include_once($includeFile); + $elementObject =& new $className(); + for ($i = 0; $i < 5; $i++) { + if (!isset($args[$i])) { + $args[$i] = null; + } + } + $err = $elementObject->onQuickFormEvent($event, $args, $this); + if ($err !== true) { + return $err; + } + return $elementObject; + } // end func _loadElement + + // }}} + // {{{ addElement() + + /** + * Adds an element into the form + * + * If $element is a string representing element type, then this + * method accepts variable number of parameters, their meaning + * and count depending on $element + * + * @param mixed $element element object or type of element to add (text, textarea, file...) + * @since 1.0 + * @return HTML_QuickForm_Element a reference to newly added element + * @access public + * @throws HTML_QuickForm_Error + */ + function &addElement($element) + { + if (is_object($element) && is_subclass_of($element, 'html_quickform_element')) { + $elementObject = &$element; + $elementObject->onQuickFormEvent('updateValue', null, $this); + } else { + $args = func_get_args(); + $elementObject =& $this->_loadElement('addElement', $element, array_slice($args, 1)); + if (PEAR::isError($elementObject)) { + return $elementObject; + } + } + $elementName = $elementObject->getName(); + + // Add the element if it is not an incompatible duplicate + if (!empty($elementName) && isset($this->_elementIndex[$elementName])) { + if ($this->_elements[$this->_elementIndex[$elementName]]->getType() == + $elementObject->getType()) { + $this->_elements[] =& $elementObject; + $elKeys = array_keys($this->_elements); + $this->_duplicateIndex[$elementName][] = end($elKeys); + } else { + $error = PEAR::raiseError(null, QUICKFORM_INVALID_ELEMENT_NAME, null, E_USER_WARNING, "Element '$elementName' already exists in HTML_QuickForm::addElement()", 'HTML_QuickForm_Error', true); + return $error; + } + } else { + $this->_elements[] =& $elementObject; + $elKeys = array_keys($this->_elements); + $this->_elementIndex[$elementName] = end($elKeys); + } + if ($this->_freezeAll) { + $elementObject->freeze(); + } + + return $elementObject; + } // end func addElement + + // }}} + // {{{ insertElementBefore() + + /** + * Inserts a new element right before the other element + * + * Warning: it is not possible to check whether the $element is already + * added to the form, therefore if you want to move the existing form + * element to a new position, you'll have to use removeElement(): + * $form->insertElementBefore($form->removeElement('foo', false), 'bar'); + * + * @access public + * @since 3.2.4 + * @param HTML_QuickForm_element Element to insert + * @param string Name of the element before which the new + * one is inserted + * @return HTML_QuickForm_element reference to inserted element + * @throws HTML_QuickForm_Error + */ + function &insertElementBefore(&$element, $nameAfter) + { + if (!empty($this->_duplicateIndex[$nameAfter])) { + $error = PEAR::raiseError(null, QUICKFORM_INVALID_ELEMENT_NAME, null, E_USER_WARNING, 'Several elements named "' . $nameAfter . '" exist in HTML_QuickForm::insertElementBefore().', 'HTML_QuickForm_Error', true); + return $error; + } elseif (!$this->elementExists($nameAfter)) { + $error = PEAR::raiseError(null, QUICKFORM_NONEXIST_ELEMENT, null, E_USER_WARNING, "Element '$nameAfter' does not exist in HTML_QuickForm::insertElementBefore()", 'HTML_QuickForm_Error', true); + return $error; + } + $elementName = $element->getName(); + $targetIdx = $this->_elementIndex[$nameAfter]; + $duplicate = false; + // Like in addElement(), check that it's not an incompatible duplicate + if (!empty($elementName) && isset($this->_elementIndex[$elementName])) { + if ($this->_elements[$this->_elementIndex[$elementName]]->getType() != $element->getType()) { + $error = PEAR::raiseError(null, QUICKFORM_INVALID_ELEMENT_NAME, null, E_USER_WARNING, "Element '$elementName' already exists in HTML_QuickForm::insertElementBefore()", 'HTML_QuickForm_Error', true); + return $error; + } + $duplicate = true; + } + // Move all the elements after added back one place, reindex _elementIndex and/or _duplicateIndex + $elKeys = array_keys($this->_elements); + for ($i = end($elKeys); $i >= $targetIdx; $i--) { + if (isset($this->_elements[$i])) { + $currentName = $this->_elements[$i]->getName(); + $this->_elements[$i + 1] =& $this->_elements[$i]; + if ($this->_elementIndex[$currentName] == $i) { + $this->_elementIndex[$currentName] = $i + 1; + } else { + $dupIdx = array_search($i, $this->_duplicateIndex[$currentName]); + $this->_duplicateIndex[$currentName][$dupIdx] = $i + 1; + } + unset($this->_elements[$i]); + } + } + // Put the element in place finally + $this->_elements[$targetIdx] =& $element; + if (!$duplicate) { + $this->_elementIndex[$elementName] = $targetIdx; + } else { + $this->_duplicateIndex[$elementName][] = $targetIdx; + } + $element->onQuickFormEvent('updateValue', null, $this); + if ($this->_freezeAll) { + $element->freeze(); + } + // If not done, the elements will appear in reverse order + ksort($this->_elements); + return $element; + } + + // }}} + // {{{ addGroup() + + /** + * Adds an element group + * @param array $elements array of elements composing the group + * @param string $name (optional)group name + * @param string $groupLabel (optional)group label + * @param string $separator (optional)string to separate elements + * @param string $appendName (optional)specify whether the group name should be + * used in the form element name ex: group[element] + * @return HTML_QuickForm_group reference to a newly added group + * @since 2.8 + * @access public + * @throws HTML_QuickForm_Error + */ + function &addGroup($elements, $name=null, $groupLabel='', $separator=null, $appendName = true) + { + static $anonGroups = 1; + + if (0 == strlen($name)) { + $name = 'qf_group_' . $anonGroups++; + $appendName = false; + } + $group =& $this->addElement('group', $name, $groupLabel, $elements, $separator, $appendName); + return $group; + } // end func addGroup + + // }}} + // {{{ &getElement() + + /** + * Returns a reference to the element + * + * @param string $element Element name + * @since 2.0 + * @access public + * @return HTML_QuickForm_element reference to element + * @throws HTML_QuickForm_Error + */ + function &getElement($element) + { + if (isset($this->_elementIndex[$element])) { + return $this->_elements[$this->_elementIndex[$element]]; + } else { + $error = PEAR::raiseError(null, QUICKFORM_NONEXIST_ELEMENT, null, E_USER_WARNING, "Element '$element' does not exist in HTML_QuickForm::getElement()", 'HTML_QuickForm_Error', true); + return $error; + } + } // end func getElement + + // }}} + // {{{ &getElementValue() + + /** + * Returns the element's raw value + * + * This returns the value as submitted by the form (not filtered) + * or set via setDefaults() or setConstants() + * + * @param string $element Element name + * @since 2.0 + * @access public + * @return mixed element value + * @throws HTML_QuickForm_Error + */ + function &getElementValue($element) + { + if (!isset($this->_elementIndex[$element])) { + $error = PEAR::raiseError(null, QUICKFORM_NONEXIST_ELEMENT, null, E_USER_WARNING, "Element '$element' does not exist in HTML_QuickForm::getElementValue()", 'HTML_QuickForm_Error', true); + return $error; + } + $value = $this->_elements[$this->_elementIndex[$element]]->getValue(); + if (isset($this->_duplicateIndex[$element])) { + foreach ($this->_duplicateIndex[$element] as $index) { + if (null !== ($v = $this->_elements[$index]->getValue())) { + if (is_array($value)) { + $value[] = $v; + } else { + $value = (null === $value)? $v: array($value, $v); + } + } + } + } + return $value; + } // end func getElementValue + + // }}} + // {{{ getSubmitValue() + + /** + * Returns the elements value after submit and filter + * + * @param string Element name + * @since 2.0 + * @access public + * @return mixed submitted element value or null if not set + */ + function getSubmitValue($elementName) + { + $value = null; + if (isset($this->_submitValues[$elementName]) || isset($this->_submitFiles[$elementName])) { + $value = isset($this->_submitValues[$elementName])? $this->_submitValues[$elementName]: array(); + if (is_array($value) && isset($this->_submitFiles[$elementName])) { + foreach ($this->_submitFiles[$elementName] as $k => $v) { + $value = HTML_QuickForm::arrayMerge($value, $this->_reindexFiles($this->_submitFiles[$elementName][$k], $k)); + } + } + + } elseif ('file' == $this->getElementType($elementName)) { + return $this->getElementValue($elementName); + + } elseif (false !== ($pos = strpos($elementName, '['))) { + $base = str_replace( + array('\\', '\''), array('\\\\', '\\\''), + substr($elementName, 0, $pos) + ); + $idx = "['" . str_replace( + array('\\', '\'', ']', '['), array('\\\\', '\\\'', '', "']['"), + substr($elementName, $pos + 1, -1) + ) . "']"; + if (isset($this->_submitValues[$base])) { + $value = eval("return (isset(\$this->_submitValues['{$base}']{$idx})) ? \$this->_submitValues['{$base}']{$idx} : null;"); + } + + if ((is_array($value) || null === $value) && isset($this->_submitFiles[$base])) { + $props = array('name', 'type', 'size', 'tmp_name', 'error'); + $code = "if (!isset(\$this->_submitFiles['{$base}']['name']{$idx})) {\n" . + " return null;\n" . + "} else {\n" . + " \$v = array();\n"; + foreach ($props as $prop) { + $code .= " \$v = HTML_QuickForm::arrayMerge(\$v, \$this->_reindexFiles(\$this->_submitFiles['{$base}']['{$prop}']{$idx}, '{$prop}'));\n"; + } + $fileValue = eval($code . " return \$v;\n}\n"); + if (null !== $fileValue) { + $value = null === $value? $fileValue: HTML_QuickForm::arrayMerge($value, $fileValue); + } + } + } + + // This is only supposed to work for groups with appendName = false + if (null === $value && 'group' == $this->getElementType($elementName)) { + $group =& $this->getElement($elementName); + $elements =& $group->getElements(); + foreach (array_keys($elements) as $key) { + $name = $group->getElementName($key); + // prevent endless recursion in case of radios and such + if ($name != $elementName) { + if (null !== ($v = $this->getSubmitValue($name))) { + $value[$name] = $v; + } + } + } + } + return $value; + } // end func getSubmitValue + + // }}} + // {{{ _reindexFiles() + + /** + * A helper function to change the indexes in $_FILES array + * + * @param mixed Some value from the $_FILES array + * @param string The key from the $_FILES array that should be appended + * @return array + */ + function _reindexFiles($value, $key) + { + if (!is_array($value)) { + return array($key => $value); + } else { + $ret = array(); + foreach ($value as $k => $v) { + $ret[$k] = $this->_reindexFiles($v, $key); + } + return $ret; + } + } + + // }}} + // {{{ getElementError() + + /** + * Returns error corresponding to validated element + * + * @param string $element Name of form element to check + * @since 1.0 + * @access public + * @return string error message corresponding to checked element + */ + function getElementError($element) + { + if (isset($this->_errors[$element])) { + return $this->_errors[$element]; + } + } // end func getElementError + + // }}} + // {{{ setElementError() + + /** + * Set error message for a form element + * + * @param string $element Name of form element to set error for + * @param string $message Error message, if empty then removes the current error message + * @since 1.0 + * @access public + * @return void + */ + function setElementError($element, $message = null) + { + if (!empty($message)) { + $this->_errors[$element] = $message; + } else { + unset($this->_errors[$element]); + } + } // end func setElementError + + // }}} + // {{{ getElementType() + + /** + * Returns the type of the given element + * + * @param string $element Name of form element + * @since 1.1 + * @access public + * @return string Type of the element, false if the element is not found + */ + function getElementType($element) + { + if (isset($this->_elementIndex[$element])) { + return $this->_elements[$this->_elementIndex[$element]]->getType(); + } + return false; + } // end func getElementType + + // }}} + // {{{ updateElementAttr() + + /** + * Updates Attributes for one or more elements + * + * @param mixed $elements Array of element names/objects or string of elements to be updated + * @param mixed $attrs Array or sting of html attributes + * @since 2.10 + * @access public + * @return void + */ + function updateElementAttr($elements, $attrs) + { + if (is_string($elements)) { + $elements = split('[ ]?,[ ]?', $elements); + } + foreach (array_keys($elements) as $key) { + if (is_object($elements[$key]) && is_a($elements[$key], 'HTML_QuickForm_element')) { + $elements[$key]->updateAttributes($attrs); + } elseif (isset($this->_elementIndex[$elements[$key]])) { + $this->_elements[$this->_elementIndex[$elements[$key]]]->updateAttributes($attrs); + if (isset($this->_duplicateIndex[$elements[$key]])) { + foreach ($this->_duplicateIndex[$elements[$key]] as $index) { + $this->_elements[$index]->updateAttributes($attrs); + } + } + } + } + } // end func updateElementAttr + + // }}} + // {{{ removeElement() + + /** + * Removes an element + * + * The method "unlinks" an element from the form, returning the reference + * to the element object. If several elements named $elementName exist, + * it removes the first one, leaving the others intact. + * + * @param string $elementName The element name + * @param boolean $removeRules True if rules for this element are to be removed too + * @access public + * @since 2.0 + * @return HTML_QuickForm_element a reference to the removed element + * @throws HTML_QuickForm_Error + */ + function &removeElement($elementName, $removeRules = true) + { + if (!isset($this->_elementIndex[$elementName])) { + $error = PEAR::raiseError(null, QUICKFORM_NONEXIST_ELEMENT, null, E_USER_WARNING, "Element '$elementName' does not exist in HTML_QuickForm::removeElement()", 'HTML_QuickForm_Error', true); + return $error; + } + $el =& $this->_elements[$this->_elementIndex[$elementName]]; + unset($this->_elements[$this->_elementIndex[$elementName]]); + if (empty($this->_duplicateIndex[$elementName])) { + unset($this->_elementIndex[$elementName]); + } else { + $this->_elementIndex[$elementName] = array_shift($this->_duplicateIndex[$elementName]); + } + if ($removeRules) { + $this->_required = array_diff($this->_required, array($elementName)); + unset($this->_rules[$elementName], $this->_errors[$elementName]); + if ('group' == $el->getType()) { + foreach (array_keys($el->getElements()) as $key) { + unset($this->_rules[$el->getElementName($key)]); + } + } + } + return $el; + } // end func removeElement + + // }}} + // {{{ addRule() + + /** + * Adds a validation rule for the given field + * + * If the element is in fact a group, it will be considered as a whole. + * To validate grouped elements as separated entities, + * use addGroupRule instead of addRule. + * + * @param string $element Form element name + * @param string $message Message to display for invalid data + * @param string $type Rule type, use getRegisteredRules() to get types + * @param string $format (optional)Required for extra rule data + * @param string $validation (optional)Where to perform validation: "server", "client" + * @param boolean $reset Client-side validation: reset the form element to its original value if there is an error? + * @param boolean $force Force the rule to be applied, even if the target form element does not exist + * @since 1.0 + * @access public + * @throws HTML_QuickForm_Error + */ + function addRule($element, $message, $type, $format=null, $validation='server', $reset = false, $force = false) + { + if (!$force) { + if (!is_array($element) && !$this->elementExists($element)) { + return PEAR::raiseError(null, QUICKFORM_NONEXIST_ELEMENT, null, E_USER_WARNING, "Element '$element' does not exist in HTML_QuickForm::addRule()", 'HTML_QuickForm_Error', true); + } elseif (is_array($element)) { + foreach ($element as $el) { + if (!$this->elementExists($el)) { + return PEAR::raiseError(null, QUICKFORM_NONEXIST_ELEMENT, null, E_USER_WARNING, "Element '$el' does not exist in HTML_QuickForm::addRule()", 'HTML_QuickForm_Error', true); + } + } + } + } + if (false === ($newName = $this->isRuleRegistered($type, true))) { + return PEAR::raiseError(null, QUICKFORM_INVALID_RULE, null, E_USER_WARNING, "Rule '$type' is not registered in HTML_QuickForm::addRule()", 'HTML_QuickForm_Error', true); + } elseif (is_string($newName)) { + $type = $newName; + } + if (is_array($element)) { + $dependent = $element; + $element = array_shift($dependent); + } else { + $dependent = null; + } + if ($type == 'required' || $type == 'uploadedfile') { + $this->_required[] = $element; + } + if (!isset($this->_rules[$element])) { + $this->_rules[$element] = array(); + } + if ($validation == 'client') { + $this->updateAttributes(array('onsubmit' => 'try { var myValidator = validate_' . $this->_attributes['id'] . '; } catch(e) { return true; } return myValidator(this);')); + } + $this->_rules[$element][] = array( + 'type' => $type, + 'format' => $format, + 'message' => $message, + 'validation' => $validation, + 'reset' => $reset, + 'dependent' => $dependent + ); + } // end func addRule + + // }}} + // {{{ addGroupRule() + + /** + * Adds a validation rule for the given group of elements + * + * Only groups with a name can be assigned a validation rule + * Use addGroupRule when you need to validate elements inside the group. + * Use addRule if you need to validate the group as a whole. In this case, + * the same rule will be applied to all elements in the group. + * Use addRule if you need to validate the group against a function. + * + * @param string $group Form group name + * @param mixed $arg1 Array for multiple elements or error message string for one element + * @param string $type (optional)Rule type use getRegisteredRules() to get types + * @param string $format (optional)Required for extra rule data + * @param int $howmany (optional)How many valid elements should be in the group + * @param string $validation (optional)Where to perform validation: "server", "client" + * @param bool $reset Client-side: whether to reset the element's value to its original state if validation failed. + * @since 2.5 + * @access public + * @throws HTML_QuickForm_Error + */ + function addGroupRule($group, $arg1, $type='', $format=null, $howmany=0, $validation = 'server', $reset = false) + { + if (!$this->elementExists($group)) { + return PEAR::raiseError(null, QUICKFORM_NONEXIST_ELEMENT, null, E_USER_WARNING, "Group '$group' does not exist in HTML_QuickForm::addGroupRule()", 'HTML_QuickForm_Error', true); + } + + $groupObj =& $this->getElement($group); + if (is_array($arg1)) { + $required = 0; + foreach ($arg1 as $elementIndex => $rules) { + $elementName = $groupObj->getElementName($elementIndex); + foreach ($rules as $rule) { + $format = (isset($rule[2])) ? $rule[2] : null; + $validation = (isset($rule[3]) && 'client' == $rule[3])? 'client': 'server'; + $reset = isset($rule[4]) && $rule[4]; + $type = $rule[1]; + if (false === ($newName = $this->isRuleRegistered($type, true))) { + return PEAR::raiseError(null, QUICKFORM_INVALID_RULE, null, E_USER_WARNING, "Rule '$type' is not registered in HTML_QuickForm::addGroupRule()", 'HTML_QuickForm_Error', true); + } elseif (is_string($newName)) { + $type = $newName; + } + + $this->_rules[$elementName][] = array( + 'type' => $type, + 'format' => $format, + 'message' => $rule[0], + 'validation' => $validation, + 'reset' => $reset, + 'group' => $group); + + if ('required' == $type || 'uploadedfile' == $type) { + $groupObj->_required[] = $elementName; + $this->_required[] = $elementName; + $required++; + } + if ('client' == $validation) { + $this->updateAttributes(array('onsubmit' => 'try { var myValidator = validate_' . $this->_attributes['id'] . '; } catch(e) { return true; } return myValidator(this);')); + } + } + } + if ($required > 0 && count($groupObj->getElements()) == $required) { + $this->_required[] = $group; + } + } elseif (is_string($arg1)) { + if (false === ($newName = $this->isRuleRegistered($type, true))) { + return PEAR::raiseError(null, QUICKFORM_INVALID_RULE, null, E_USER_WARNING, "Rule '$type' is not registered in HTML_QuickForm::addGroupRule()", 'HTML_QuickForm_Error', true); + } elseif (is_string($newName)) { + $type = $newName; + } + + // addGroupRule() should also handle form elements + */ +require_once 'HTML/QuickForm/input.php'; + +/** + * HTML class for an elements + * + * @category HTML + * @package HTML_QuickForm + * @author Adam Daniel + * @author Bertrand Mansion + * @version Release: 3.2.11 + * @since 1.0 + */ +class HTML_QuickForm_button extends HTML_QuickForm_input +{ + // {{{ constructor + + /** + * Class constructor + * + * @param string $elementName (optional)Input field name attribute + * @param string $value (optional)Input field value + * @param mixed $attributes (optional)Either a typical HTML attribute string + * or an associative array + * @since 1.0 + * @access public + * @return void + */ + function HTML_QuickForm_button($elementName=null, $value=null, $attributes=null) + { + HTML_QuickForm_input::HTML_QuickForm_input($elementName, null, $attributes); + $this->_persistantFreeze = false; + $this->setValue($value); + $this->setType('button'); + } //end constructor + + // }}} + // {{{ freeze() + + /** + * Freeze the element so that only its value is returned + * + * @access public + * @return void + */ + function freeze() + { + return false; + } //end func freeze + + // }}} + +} //end class HTML_QuickForm_button +?> diff --git a/library/pear/HTML/QuickForm/checkbox.php b/library/pear/HTML/QuickForm/checkbox.php new file mode 100644 index 000000000..36d8e7764 --- /dev/null +++ b/library/pear/HTML/QuickForm/checkbox.php @@ -0,0 +1,277 @@ + + * @author Bertrand Mansion + * @author Alexey Borzov + * @copyright 2001-2009 The PHP Group + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version CVS: $Id: checkbox.php,v 1.23 2009/04/04 21:34:02 avb Exp $ + * @link http://pear.php.net/package/HTML_QuickForm + */ + +/** + * Base class for form elements + */ +require_once 'HTML/QuickForm/input.php'; + +/** + * HTML class for a checkbox type field + * + * @category HTML + * @package HTML_QuickForm + * @author Adam Daniel + * @author Bertrand Mansion + * @author Alexey Borzov + * @version Release: 3.2.11 + * @since 1.0 + */ +class HTML_QuickForm_checkbox extends HTML_QuickForm_input +{ + // {{{ properties + + /** + * Checkbox display text + * @var string + * @since 1.1 + * @access private + */ + var $_text = ''; + + // }}} + // {{{ constructor + + /** + * Class constructor + * + * @param string $elementName (optional)Input field name attribute + * @param string $elementLabel (optional)Input field value + * @param string $text (optional)Checkbox display text + * @param mixed $attributes (optional)Either a typical HTML attribute string + * or an associative array + * @since 1.0 + * @access public + * @return void + */ + function HTML_QuickForm_checkbox($elementName=null, $elementLabel=null, $text='', $attributes=null) + { + HTML_QuickForm_input::HTML_QuickForm_input($elementName, $elementLabel, $attributes); + $this->_persistantFreeze = true; + $this->_text = $text; + $this->setType('checkbox'); + $this->updateAttributes(array('value'=>1)); + $this->_generateId(); + } //end constructor + + // }}} + // {{{ setChecked() + + /** + * Sets whether a checkbox is checked + * + * @param bool $checked Whether the field is checked or not + * @since 1.0 + * @access public + * @return void + */ + function setChecked($checked) + { + if (!$checked) { + $this->removeAttribute('checked'); + } else { + $this->updateAttributes(array('checked'=>'checked')); + } + } //end func setChecked + + // }}} + // {{{ getChecked() + + /** + * Returns whether a checkbox is checked + * + * @since 1.0 + * @access public + * @return bool + */ + function getChecked() + { + return (bool)$this->getAttribute('checked'); + } //end func getChecked + + // }}} + // {{{ toHtml() + + /** + * Returns the checkbox element in HTML + * + * @since 1.0 + * @access public + * @return string + */ + function toHtml() + { + if (0 == strlen($this->_text)) { + $label = ''; + } elseif ($this->_flagFrozen) { + $label = $this->_text; + } else { + $label = ''; + } + return HTML_QuickForm_input::toHtml() . $label; + } //end func toHtml + + // }}} + // {{{ getFrozenHtml() + + /** + * Returns the value of field without HTML tags + * + * @since 1.0 + * @access public + * @return string + */ + function getFrozenHtml() + { + if ($this->getChecked()) { + return '[x]' . + $this->_getPersistantData(); + } else { + return '[ ]'; + } + } //end func getFrozenHtml + + // }}} + // {{{ setText() + + /** + * Sets the checkbox text + * + * @param string $text + * @since 1.1 + * @access public + * @return void + */ + function setText($text) + { + $this->_text = $text; + } //end func setText + + // }}} + // {{{ getText() + + /** + * Returns the checkbox text + * + * @since 1.1 + * @access public + * @return string + */ + function getText() + { + return $this->_text; + } //end func getText + + // }}} + // {{{ setValue() + + /** + * Sets the value of the form element + * + * @param string $value Default value of the form element + * @since 1.0 + * @access public + * @return void + */ + function setValue($value) + { + return $this->setChecked($value); + } // end func setValue + + // }}} + // {{{ getValue() + + /** + * Returns the value of the form element + * + * @since 1.0 + * @access public + * @return bool + */ + function getValue() + { + return $this->getChecked(); + } // end func getValue + + // }}} + // {{{ onQuickFormEvent() + + /** + * Called by HTML_QuickForm whenever form event is made on this element + * + * @param string $event Name of event + * @param mixed $arg event arguments + * @param object &$caller calling object + * @since 1.0 + * @access public + * @return void + */ + function onQuickFormEvent($event, $arg, &$caller) + { + switch ($event) { + case 'updateValue': + // constant values override both default and submitted ones + // default values are overriden by submitted + $value = $this->_findValue($caller->_constantValues); + if (null === $value) { + // if no boxes were checked, then there is no value in the array + // yet we don't want to display default value in this case + if ($caller->isSubmitted()) { + $value = $this->_findValue($caller->_submitValues); + } else { + $value = $this->_findValue($caller->_defaultValues); + } + } + if (null !== $value || $caller->isSubmitted()) { + $this->setChecked($value); + } + break; + case 'setGroupValue': + $this->setChecked($arg); + break; + default: + parent::onQuickFormEvent($event, $arg, $caller); + } + return true; + } // end func onQuickFormEvent + + // }}} + // {{{ exportValue() + + /** + * Return true if the checkbox is checked, null if it is not checked (getValue() returns false) + */ + function exportValue(&$submitValues, $assoc = false) + { + $value = $this->_findValue($submitValues); + if (null === $value) { + $value = $this->getChecked()? true: null; + } + return $this->_prepareValue($value, $assoc); + } + + // }}} +} //end class HTML_QuickForm_checkbox +?> diff --git a/library/pear/HTML/QuickForm/date.php b/library/pear/HTML/QuickForm/date.php new file mode 100644 index 000000000..94c8d69e4 --- /dev/null +++ b/library/pear/HTML/QuickForm/date.php @@ -0,0 +1,528 @@ + + * @copyright 2001-2009 The PHP Group + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version CVS: $Id: date.php,v 1.62 2009/04/04 21:34:02 avb Exp $ + * @link http://pear.php.net/package/HTML_QuickForm + */ + +/** + * Class for a group of form elements + */ +require_once 'HTML/QuickForm/group.php'; +/** + * Class for elements + */ +require_once 'HTML/QuickForm/select.php'; + +/** + * Class for a group of elements used to input dates (and times). + * + * Inspired by original 'date' element but reimplemented as a subclass + * of HTML_QuickForm_group + * + * @category HTML + * @package HTML_QuickForm + * @author Alexey Borzov + * @version Release: 3.2.11 + * @since 3.1 + */ +class HTML_QuickForm_date extends HTML_QuickForm_group +{ + // {{{ properties + + /** + * Various options to control the element's display. + * + * @access private + * @var array + */ + var $_options = array( + 'language' => 'en', + 'format' => 'dMY', + 'minYear' => 2001, + 'maxYear' => 2010, + 'addEmptyOption' => false, + 'emptyOptionValue' => '', + 'emptyOptionText' => ' ', + 'optionIncrement' => array('i' => 1, 's' => 1) + ); + + /** + * These complement separators, they are appended to the resultant HTML + * @access private + * @var array + */ + var $_wrap = array('', ''); + + /** + * Options in different languages + * + * Note to potential translators: to avoid encoding problems please send + * your translations with "weird" letters encoded as HTML Unicode entities + * + * @access private + * @var array + */ + var $_locale = array( + 'en' => array ( + 'weekdays_short'=> array ('Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'), + 'weekdays_long' => array ('Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'), + 'months_short' => array ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'), + 'months_long' => array ('January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December') + ), + 'de' => array ( + 'weekdays_short'=> array ('So', 'Mon', 'Di', 'Mi', 'Do', 'Fr', 'Sa'), + 'weekdays_long' => array ('Sonntag', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag'), + 'months_short' => array ('Jan', 'Feb', 'März', 'April', 'Mai', 'Juni', 'Juli', 'Aug', 'Sept', 'Okt', 'Nov', 'Dez'), + 'months_long' => array ('Januar', 'Februar', 'März', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember') + ), + 'fr' => array ( + 'weekdays_short'=> array ('Dim', 'Lun', 'Mar', 'Mer', 'Jeu', 'Ven', 'Sam'), + 'weekdays_long' => array ('Dimanche', 'Lundi', 'Mardi', 'Mercredi', 'Jeudi', 'Vendredi', 'Samedi'), + 'months_short' => array ('Jan', 'Fév', 'Mar', 'Avr', 'Mai', 'Juin', 'Juil', 'Août', 'Sep', 'Oct', 'Nov', 'Déc'), + 'months_long' => array ('Janvier', 'Février', 'Mars', 'Avril', 'Mai', 'Juin', 'Juillet', 'Août', 'Septembre', 'Octobre', 'Novembre', 'Décembre') + ), + 'hu' => array ( + 'weekdays_short'=> array ('V', 'H', 'K', 'Sze', 'Cs', 'P', 'Szo'), + 'weekdays_long' => array ('vasárnap', 'hétfő', 'kedd', 'szerda', 'csütörtök', 'péntek', 'szombat'), + 'months_short' => array ('jan', 'feb', 'márc', 'ápr', 'máj', 'jún', 'júl', 'aug', 'szept', 'okt', 'nov', 'dec'), + 'months_long' => array ('január', 'február', 'március', 'április', 'május', 'június', 'július', 'augusztus', 'szeptember', 'október', 'november', 'december') + ), + 'pl' => array ( + 'weekdays_short'=> array ('Nie', 'Pn', 'Wt', 'Śr', 'Czw', 'Pt', 'Sob'), + 'weekdays_long' => array ('Niedziela', 'Poniedziałek', 'Wtorek', 'Środa', 'Czwartek', 'Piątek', 'Sobota'), + 'months_short' => array ('Sty', 'Lut', 'Mar', 'Kwi', 'Maj', 'Cze', 'Lip', 'Sie', 'Wrz', 'Paź', 'Lis', 'Gru'), + 'months_long' => array ('Styczeń', 'Luty', 'Marzec', 'Kwiecień', 'Maj', 'Czerwiec', 'Lipiec', 'Sierpień', 'Wrzesień', 'Październik', 'Listopad', 'Grudzień') + ), + 'sl' => array ( + 'weekdays_short'=> array ('Ned', 'Pon', 'Tor', 'Sre', 'Cet', 'Pet', 'Sob'), + 'weekdays_long' => array ('Nedelja', 'Ponedeljek', 'Torek', 'Sreda', 'Cetrtek', 'Petek', 'Sobota'), + 'months_short' => array ('Jan', 'Feb', 'Mar', 'Apr', 'Maj', 'Jun', 'Jul', 'Avg', 'Sep', 'Okt', 'Nov', 'Dec'), + 'months_long' => array ('Januar', 'Februar', 'Marec', 'April', 'Maj', 'Junij', 'Julij', 'Avgust', 'September', 'Oktober', 'November', 'December') + ), + 'ru' => array ( + 'weekdays_short'=> array ('Вс', 'Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб'), + 'weekdays_long' => array ('Воскресенье', 'Понедельник', 'Вторник', 'Среда', 'Четверг', 'Пятница', 'Суббота'), + 'months_short' => array ('Янв', 'Фев', 'Мар', 'Апр', 'Май', 'Июн', 'Июл', 'Авг', 'Сен', 'Окт', 'Ноя', 'Дек'), + 'months_long' => array ('Январь', 'Февраль', 'Март', 'Апрель', 'Май', 'Июнь', 'Июль', 'Август', 'Сентябрь', 'Октябрь', 'Ноябрь', 'Декабрь') + ), + 'es' => array ( + 'weekdays_short'=> array ('Dom', 'Lun', 'Mar', 'Mié', 'Jue', 'Vie', 'Sáb'), + 'weekdays_long' => array ('Domingo', 'Lunes', 'Martes', 'Miércoles', 'Jueves', 'Viernes', 'Sábado'), + 'months_short' => array ('Ene', 'Feb', 'Mar', 'Abr', 'May', 'Jun', 'Jul', 'Ago', 'Sep', 'Oct', 'Nov', 'Dic'), + 'months_long' => array ('Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio', 'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre') + ), + 'da' => array ( + 'weekdays_short'=> array ('Søn', 'Man', 'Tir', 'Ons', 'Tor', 'Fre', 'Lør'), + 'weekdays_long' => array ('Søndag', 'Mandag', 'Tirsdag', 'Onsdag', 'Torsdag', 'Fredag', 'Lørdag'), + 'months_short' => array ('Jan', 'Feb', 'Mar', 'Apr', 'Maj', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dec'), + 'months_long' => array ('Januar', 'Februar', 'Marts', 'April', 'Maj', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'December') + ), + 'is' => array ( + 'weekdays_short'=> array ('Sun', 'Mán', 'Þri', 'Mið', 'Fim', 'Fös', 'Lau'), + 'weekdays_long' => array ('Sunnudagur', 'Mánudagur', 'Þriðjudagur', 'Miðvikudagur', 'Fimmtudagur', 'Föstudagur', 'Laugardagur'), + 'months_short' => array ('Jan', 'Feb', 'Mar', 'Apr', 'Maí', 'Jún', 'Júl', 'Ágú', 'Sep', 'Okt', 'Nóv', 'Des'), + 'months_long' => array ('Janúar', 'Febrúar', 'Mars', 'Apríl', 'Maí', 'Júní', 'Júlí', 'Ágúst', 'September', 'Október', 'Nóvember', 'Desember') + ), + 'it' => array ( + 'weekdays_short'=> array ('Dom', 'Lun', 'Mar', 'Mer', 'Gio', 'Ven', 'Sab'), + 'weekdays_long' => array ('Domenica', 'Lunedì', 'Martedì', 'Mercoledì', 'Giovedì', 'Venerdì', 'Sabato'), + 'months_short' => array ('Gen', 'Feb', 'Mar', 'Apr', 'Mag', 'Giu', 'Lug', 'Ago', 'Set', 'Ott', 'Nov', 'Dic'), + 'months_long' => array ('Gennaio', 'Febbraio', 'Marzo', 'Aprile', 'Maggio', 'Giugno', 'Luglio', 'Agosto', 'Settembre', 'Ottobre', 'Novembre', 'Dicembre') + ), + 'sk' => array ( + 'weekdays_short'=> array ('Ned', 'Pon', 'Uto', 'Str', 'Štv', 'Pia', 'Sob'), + 'weekdays_long' => array ('Nedeža', 'Pondelok', 'Utorok', 'Streda', 'Štvrtok', 'Piatok', 'Sobota'), + 'months_short' => array ('Jan', 'Feb', 'Mar', 'Apr', 'Máj', 'Jún', 'Júl', 'Aug', 'Sep', 'Okt', 'Nov', 'Dec'), + 'months_long' => array ('Január', 'Február', 'Marec', 'Apríl', 'Máj', 'Jún', 'Júl', 'August', 'September', 'Október', 'November', 'December') + ), + 'cs' => array ( + 'weekdays_short'=> array ('Ne', 'Po', 'Út', 'St', 'Čt', 'Pá', 'So'), + 'weekdays_long' => array ('Neděle', 'Pondělí', 'Úterý', 'Středa', 'Čtvrtek', 'Pátek', 'Sobota'), + 'months_short' => array ('Led', 'Úno', 'Bře', 'Dub', 'Kvě', 'Čen', 'Čec', 'Srp', 'Zář', 'Říj', 'Lis', 'Pro'), + 'months_long' => array ('Leden', 'Únor', 'Březen', 'Duben', 'Květen', 'Červen', 'Červenec', 'Srpen', 'Září', 'Říjen', 'Listopad', 'Prosinec') + ), + 'hy' => array ( + 'weekdays_short'=> array ('Կրկ', 'Երկ', 'Երք', 'Չրք', 'Հնգ', 'Ուր', 'Շբթ'), + 'weekdays_long' => array ('Կիրակի', 'Երկուշաբթի', 'Երեքշաբթի', 'Չորեքշաբթի', 'Հինգշաբթի', 'Ուրբաթ', 'Շաբաթ'), + 'months_short' => array ('Հնվ', 'Փտր', 'Մրտ', 'Ապր', 'Մյս', 'Հնս', 'Հլս', 'Օգս', 'Սպտ', 'Հկտ', 'Նյմ', 'Դկտ'), + 'months_long' => array ('Հունվար', 'Փետրվար', 'Մարտ', 'Ապրիլ', 'Մայիս', 'Հունիս', 'Հուլիս', 'Օգոստոս', 'Սեպտեմբեր', 'Հոկտեմբեր', 'Նոյեմբեր', 'Դեկտեմբեր') + ), + 'nl' => array ( + 'weekdays_short'=> array ('Zo', 'Ma', 'Di', 'Wo', 'Do', 'Vr', 'Za'), + 'weekdays_long' => array ('Zondag', 'Maandag', 'Dinsdag', 'Woensdag', 'Donderdag', 'Vrijdag', 'Zaterdag'), + 'months_short' => array ('Jan', 'Feb', 'Mar', 'Apr', 'Mei', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dec'), + 'months_long' => array ('Januari', 'Februari', 'Maart', 'April', 'Mei', 'Juni', 'Juli', 'Augustus', 'September', 'Oktober', 'November', 'December') + ), + 'et' => array ( + 'weekdays_short'=> array ('P', 'E', 'T', 'K', 'N', 'R', 'L'), + 'weekdays_long' => array ('Pühapäev', 'Esmaspäev', 'Teisipäev', 'Kolmapäev', 'Neljapäev', 'Reede', 'Laupäev'), + 'months_short' => array ('Jaan', 'Veebr', 'Märts', 'Aprill', 'Mai', 'Juuni', 'Juuli', 'Aug', 'Sept', 'Okt', 'Nov', 'Dets'), + 'months_long' => array ('Jaanuar', 'Veebruar', 'Märts', 'Aprill', 'Mai', 'Juuni', 'Juuli', 'August', 'September', 'Oktoober', 'November', 'Detsember') + ), + 'tr' => array ( + 'weekdays_short'=> array ('Paz', 'Pzt', 'Sal', 'Çar', 'Per', 'Cum', 'Cts'), + 'weekdays_long' => array ('Pazar', 'Pazartesi', 'Salı', 'Çarşamba', 'Perşembe', 'Cuma', 'Cumartesi'), + 'months_short' => array ('Ock', 'Şbt', 'Mrt', 'Nsn', 'Mys', 'Hzrn', 'Tmmz', 'Ağst', 'Eyl', 'Ekm', 'Ksm', 'Arlk'), + 'months_long' => array ('Ocak', 'Şubat', 'Mart', 'Nisan', 'Mayıs', 'Haziran', 'Temmuz', 'Ağustos', 'Eylül', 'Ekim', 'Kasım', 'Aralık') + ), + 'no' => array ( + 'weekdays_short'=> array ('Søn', 'Man', 'Tir', 'Ons', 'Tor', 'Fre', 'Lør'), + 'weekdays_long' => array ('Søndag', 'Mandag', 'Tirsdag', 'Onsdag', 'Torsdag', 'Fredag', 'Lørdag'), + 'months_short' => array ('Jan', 'Feb', 'Mar', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Des'), + 'months_long' => array ('Januar', 'Februar', 'Mars', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Desember') + ), + 'eo' => array ( + 'weekdays_short'=> array ('Dim', 'Lun', 'Mar', 'Mer', 'Ĵaŭ', 'Ven', 'Sab'), + 'weekdays_long' => array ('Dimanĉo', 'Lundo', 'Mardo', 'Merkredo', 'Ĵaŭdo', 'Vendredo', 'Sabato'), + 'months_short' => array ('Jan', 'Feb', 'Mar', 'Apr', 'Maj', 'Jun', 'Jul', 'Aŭg', 'Sep', 'Okt', 'Nov', 'Dec'), + 'months_long' => array ('Januaro', 'Februaro', 'Marto', 'Aprilo', 'Majo', 'Junio', 'Julio', 'Aŭgusto', 'Septembro', 'Oktobro', 'Novembro', 'Decembro') + ), + 'ua' => array ( + 'weekdays_short'=> array('Ндл', 'Пнд', 'Втр', 'Срд', 'Чтв', 'Птн', 'Сбт'), + 'weekdays_long' => array('Неділя', 'Понеділок', 'Вівторок', 'Середа', 'Четвер', 'П\'ятниця', 'Субота'), + 'months_short' => array('Січ', 'Лют', 'Бер', 'Кві', 'Тра', 'Чер', 'Лип', 'Сер', 'Вер', 'Жов', 'Лис', 'Гру'), + 'months_long' => array('Січень', 'Лютий', 'Березень', 'Квітень', 'Травень', 'Червень', 'Липень', 'Серпень', 'Вересень', 'Жовтень', 'Листопад', 'Грудень') + ), + 'ro' => array ( + 'weekdays_short'=> array ('Dum', 'Lun', 'Mar', 'Mie', 'Joi', 'Vin', 'Sam'), + 'weekdays_long' => array ('Duminica', 'Luni', 'Marti', 'Miercuri', 'Joi', 'Vineri', 'Sambata'), + 'months_short' => array ('Ian', 'Feb', 'Mar', 'Apr', 'Mai', 'Iun', 'Iul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'), + 'months_long' => array ('Ianuarie', 'Februarie', 'Martie', 'Aprilie', 'Mai', 'Iunie', 'Iulie', 'August', 'Septembrie', 'Octombrie', 'Noiembrie', 'Decembrie') + ), + 'he' => array ( + 'weekdays_short'=> array ('ראשון', 'שני', 'שלישי', 'רביעי', 'חמישי', 'שישי', 'שבת'), + 'weekdays_long' => array ('יום ראשון', 'יום שני', 'יום שלישי', 'יום רביעי', 'יום חמישי', 'יום שישי', 'שבת'), + 'months_short' => array ('ינואר', 'פברואר', 'מרץ', 'אפריל', 'מאי', 'יוני', 'יולי', 'אוגוסט', 'ספטמבר', 'אוקטובר', 'נובמבר', 'דצמבר'), + 'months_long' => array ('ינואר', 'פברואר', 'מרץ', 'אפריל', 'מאי', 'יוני', 'יולי', 'אוגוסט', 'ספטמבר', 'אוקטובר', 'נובמבר', 'דצמבר') + ), + 'sv' => array ( + 'weekdays_short'=> array ('Sön', 'Mån', 'Tis', 'Ons', 'Tor', 'Fre', 'Lör'), + 'weekdays_long' => array ('Söndag', 'Måndag', 'Tisdag', 'Onsdag', 'Torsdag', 'Fredag', 'Lördag'), + 'months_short' => array ('Jan', 'Feb', 'Mar', 'Apr', 'Maj', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dec'), + 'months_long' => array ('Januari', 'Februari', 'Mars', 'April', 'Maj', 'Juni', 'Juli', 'Augusti', 'September', 'Oktober', 'November', 'December') + ), + 'pt' => array ( + 'weekdays_short'=> array ('Dom', 'Seg', 'Ter', 'Qua', 'Qui', 'Sex', 'Sáb'), + 'weekdays_long' => array ('Domingo', 'Segunda-feira', 'Terça-feira', 'Quarta-feira', 'Quinta-feira', 'Sexta-feira', 'Sábado'), + 'months_short' => array ('Jan', 'Fev', 'Mar', 'Abr', 'Mai', 'Jun', 'Jul', 'Ago', 'Set', 'Out', 'Nov', 'Dez'), + 'months_long' => array ('Janeiro', 'Fevereiro', 'Março', 'Abril', 'Maio', 'Junho', 'Julho', 'Agosto', 'Setembro', 'Outubro', 'Novembro', 'Dezembro') + ), + 'tw' => array ( + 'weekdays_short'=> array ('週日','週一', '週二','週三', '週四','週五', '週六'), + 'weekdays_long' => array ('星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'), + 'months_short' => array ('一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'), + 'months_long' => array ('一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月') + ), + 'pt-br' => array ( + 'weekdays_short'=> array ('Dom', 'Seg', 'Ter', 'Qua', 'Qui', 'Sex', 'Sáb'), + 'weekdays_long' => array ('Domingo', 'Segunda', 'Terça', 'Quarta', 'Quinta', 'Sexta', 'Sábado'), + 'months_short' => array ('Jan', 'Fev', 'Mar', 'Abr', 'Mai', 'Jun', 'Jul', 'Ago', 'Set', 'Out', 'Nov', 'Dez'), + 'months_long' => array ('Janeiro', 'Fevereiro', 'Março', 'Abril', 'Maio', 'Junho', 'Julho', 'Agosto', 'Setembro', 'Outubro', 'Novembro', 'Dezembro') + ) + ); + + // }}} + // {{{ constructor + + /** + * Class constructor + * + * The following keys may appear in $options array: + * - 'language': date language + * - 'format': Format of the date, based on PHP's date() function. + * The following characters are currently recognised in format string: + *
      
    +    *       D => Short names of days
    +    *       l => Long names of days
    +    *       d => Day numbers
    +    *       M => Short names of months
    +    *       F => Long names of months
    +    *       m => Month numbers
    +    *       Y => Four digit year
    +    *       y => Two digit year
    +    *       h => 12 hour format
    +    *       H => 23 hour  format
    +    *       i => Minutes
    +    *       s => Seconds
    +    *       a => am/pm
    +    *       A => AM/PM
    +    *   
    + * - 'minYear': Minimum year in year select + * - 'maxYear': Maximum year in year select + * - 'addEmptyOption': Should an empty option be added to the top of + * each select box? + * - 'emptyOptionValue': The value passed by the empty option. + * - 'emptyOptionText': The text displayed for the empty option. + * - 'optionIncrement': Step to increase the option values by (works for 'i' and 's') + * + * @access public + * @param string Element's name + * @param mixed Label(s) for an element + * @param array Options to control the element's display + * @param mixed Either a typical HTML attribute string or an associative array + */ + function HTML_QuickForm_date($elementName = null, $elementLabel = null, $options = array(), $attributes = null) + { + $this->HTML_QuickForm_element($elementName, $elementLabel, $attributes); + $this->_persistantFreeze = true; + $this->_appendName = true; + $this->_type = 'date'; + // set the options, do not bother setting bogus ones + if (is_array($options)) { + foreach ($options as $name => $value) { + if ('language' == $name) { + $this->_options['language'] = isset($this->_locale[$value])? $value: 'en'; + } elseif (isset($this->_options[$name])) { + if (is_array($value) && is_array($this->_options[$name])) { + $this->_options[$name] = @array_merge($this->_options[$name], $value); + } else { + $this->_options[$name] = $value; + } + } + } + } + } + + // }}} + // {{{ _createElements() + + function _createElements() + { + $this->_separator = $this->_elements = array(); + $separator = ''; + $locale =& $this->_locale[$this->_options['language']]; + $backslash = false; + for ($i = 0, $length = strlen($this->_options['format']); $i < $length; $i++) { + $sign = $this->_options['format']{$i}; + if ($backslash) { + $backslash = false; + $separator .= $sign; + } else { + $loadSelect = true; + switch ($sign) { + case 'D': + // Sunday is 0 like with 'w' in date() + $options = $locale['weekdays_short']; + break; + case 'l': + $options = $locale['weekdays_long']; + break; + case 'd': + $options = $this->_createOptionList(1, 31); + break; + case 'M': + $options = $locale['months_short']; + array_unshift($options , ''); + unset($options[0]); + break; + case 'm': + $options = $this->_createOptionList(1, 12); + break; + case 'F': + $options = $locale['months_long']; + array_unshift($options , ''); + unset($options[0]); + break; + case 'Y': + $options = $this->_createOptionList( + $this->_options['minYear'], + $this->_options['maxYear'], + $this->_options['minYear'] > $this->_options['maxYear']? -1: 1 + ); + break; + case 'y': + $options = $this->_createOptionList( + $this->_options['minYear'], + $this->_options['maxYear'], + $this->_options['minYear'] > $this->_options['maxYear']? -1: 1 + ); + array_walk($options, create_function('&$v,$k','$v = substr($v,-2);')); + break; + case 'h': + $options = $this->_createOptionList(1, 12); + break; + case 'g': + $options = $this->_createOptionList(1, 12); + array_walk($options, create_function('&$v,$k', '$v = intval($v);')); + break; + case 'H': + $options = $this->_createOptionList(0, 23); + break; + case 'i': + $options = $this->_createOptionList(0, 59, $this->_options['optionIncrement']['i']); + break; + case 's': + $options = $this->_createOptionList(0, 59, $this->_options['optionIncrement']['s']); + break; + case 'a': + $options = array('am' => 'am', 'pm' => 'pm'); + break; + case 'A': + $options = array('AM' => 'AM', 'PM' => 'PM'); + break; + case 'W': + $options = $this->_createOptionList(1, 53); + break; + case '\\': + $backslash = true; + $loadSelect = false; + break; + default: + $separator .= (' ' == $sign? ' ': $sign); + $loadSelect = false; + } + + if ($loadSelect) { + if (0 < count($this->_elements)) { + $this->_separator[] = $separator; + } else { + $this->_wrap[0] = $separator; + } + $separator = ''; + // Should we add an empty option to the top of the select? + if (!is_array($this->_options['addEmptyOption']) && $this->_options['addEmptyOption'] || + is_array($this->_options['addEmptyOption']) && !empty($this->_options['addEmptyOption'][$sign])) { + + // Using '+' array operator to preserve the keys + if (is_array($this->_options['emptyOptionText']) && !empty($this->_options['emptyOptionText'][$sign])) { + $options = array($this->_options['emptyOptionValue'] => $this->_options['emptyOptionText'][$sign]) + $options; + } else { + $options = array($this->_options['emptyOptionValue'] => $this->_options['emptyOptionText']) + $options; + } + } + $this->_elements[] =& new HTML_QuickForm_select($sign, null, $options, $this->getAttributes()); + } + } + } + $this->_wrap[1] = $separator . ($backslash? '\\': ''); + } + + // }}} + // {{{ _createOptionList() + + /** + * Creates an option list containing the numbers from the start number to the end, inclusive + * + * @param int The start number + * @param int The end number + * @param int Increment by this value + * @access private + * @return array An array of numeric options. + */ + function _createOptionList($start, $end, $step = 1) + { + for ($i = $start, $options = array(); $start > $end? $i >= $end: $i <= $end; $i += $step) { + $options[$i] = sprintf('%02d', $i); + } + return $options; + } + + // }}} + // {{{ _trimLeadingZeros() + + /** + * Trims leading zeros from the (numeric) string + * + * @param string A numeric string, possibly with leading zeros + * @return string String with leading zeros removed + */ + function _trimLeadingZeros($str) + { + if (0 == strcmp($str, $this->_options['emptyOptionValue'])) { + return $str; + } + $trimmed = ltrim($str, '0'); + return strlen($trimmed)? $trimmed: '0'; + } + + // }}} + // {{{ setValue() + + function setValue($value) + { + if (empty($value)) { + $value = array(); + } elseif (is_scalar($value)) { + if (!is_numeric($value)) { + $value = strtotime($value); + } + // might be a unix epoch, then we fill all possible values + $arr = explode('-', date('w-j-n-Y-g-G-i-s-a-A-W', (int)$value)); + $value = array( + 'D' => $arr[0], + 'l' => $arr[0], + 'd' => $arr[1], + 'M' => $arr[2], + 'm' => $arr[2], + 'F' => $arr[2], + 'Y' => $arr[3], + 'y' => $arr[3], + 'h' => $arr[4], + 'g' => $arr[4], + 'H' => $arr[5], + 'i' => $this->_trimLeadingZeros($arr[6]), + 's' => $this->_trimLeadingZeros($arr[7]), + 'a' => $arr[8], + 'A' => $arr[9], + 'W' => $this->_trimLeadingZeros($arr[10]) + ); + } else { + $value = array_map(array($this, '_trimLeadingZeros'), $value); + } + parent::setValue($value); + } + + // }}} + // {{{ toHtml() + + function toHtml() + { + include_once('HTML/QuickForm/Renderer/Default.php'); + $renderer =& new HTML_QuickForm_Renderer_Default(); + $renderer->setElementTemplate('{element}'); + parent::accept($renderer); + return $this->_wrap[0] . $renderer->toHtml() . $this->_wrap[1]; + } + + // }}} + // {{{ accept() + + function accept(&$renderer, $required = false, $error = null) + { + $renderer->renderElement($this, $required, $error); + } + + // }}} + // {{{ onQuickFormEvent() + + function onQuickFormEvent($event, $arg, &$caller) + { + if ('updateValue' == $event) { + // we need to call setValue(), 'cause the default/constant value + // may be in fact a timestamp, not an array + return HTML_QuickForm_element::onQuickFormEvent($event, $arg, $caller); + } else { + return parent::onQuickFormEvent($event, $arg, $caller); + } + } + + // }}} +} +?> \ No newline at end of file diff --git a/library/pear/HTML/QuickForm/element.php b/library/pear/HTML/QuickForm/element.php new file mode 100644 index 000000000..9309612cc --- /dev/null +++ b/library/pear/HTML/QuickForm/element.php @@ -0,0 +1,494 @@ + + * @author Bertrand Mansion + * @author Alexey Borzov + * @copyright 2001-2009 The PHP Group + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version CVS: $Id: element.php,v 1.37 2009/04/04 21:34:02 avb Exp $ + * @link http://pear.php.net/package/HTML_QuickForm + */ + +/** + * Base class for all HTML classes + */ +require_once 'HTML/Common.php'; + +/** + * Base class for form elements + * + * @category HTML + * @package HTML_QuickForm + * @author Adam Daniel + * @author Bertrand Mansion + * @author Alexey Borzov + * @version Release: 3.2.11 + * @since 1.0 + * @abstract + */ +class HTML_QuickForm_element extends HTML_Common +{ + // {{{ properties + + /** + * Label of the field + * @var string + * @since 1.3 + * @access private + */ + var $_label = ''; + + /** + * Form element type + * @var string + * @since 1.0 + * @access private + */ + var $_type = ''; + + /** + * Flag to tell if element is frozen + * @var boolean + * @since 1.0 + * @access private + */ + var $_flagFrozen = false; + + /** + * Does the element support persistant data when frozen + * @var boolean + * @since 1.3 + * @access private + */ + var $_persistantFreeze = false; + + // }}} + // {{{ constructor + + /** + * Class constructor + * + * @param string Name of the element + * @param mixed Label(s) for the element + * @param mixed Associative array of tag attributes or HTML attributes name="value" pairs + * @since 1.0 + * @access public + * @return void + */ + function HTML_QuickForm_element($elementName=null, $elementLabel=null, $attributes=null) + { + HTML_Common::HTML_Common($attributes); + if (isset($elementName)) { + $this->setName($elementName); + } + if (isset($elementLabel)) { + $this->setLabel($elementLabel); + } + } //end constructor + + // }}} + // {{{ apiVersion() + + /** + * Returns the current API version + * + * @since 1.0 + * @access public + * @return float + */ + function apiVersion() + { + return 3.2; + } // end func apiVersion + + // }}} + // {{{ getType() + + /** + * Returns element type + * + * @since 1.0 + * @access public + * @return string + */ + function getType() + { + return $this->_type; + } // end func getType + + // }}} + // {{{ setName() + + /** + * Sets the input field name + * + * @param string $name Input field name attribute + * @since 1.0 + * @access public + * @return void + */ + function setName($name) + { + // interface method + } //end func setName + + // }}} + // {{{ getName() + + /** + * Returns the element name + * + * @since 1.0 + * @access public + * @return string + */ + function getName() + { + // interface method + } //end func getName + + // }}} + // {{{ setValue() + + /** + * Sets the value of the form element + * + * @param string $value Default value of the form element + * @since 1.0 + * @access public + * @return void + */ + function setValue($value) + { + // interface + } // end func setValue + + // }}} + // {{{ getValue() + + /** + * Returns the value of the form element + * + * @since 1.0 + * @access public + * @return mixed + */ + function getValue() + { + // interface + return null; + } // end func getValue + + // }}} + // {{{ freeze() + + /** + * Freeze the element so that only its value is returned + * + * @access public + * @return void + */ + function freeze() + { + $this->_flagFrozen = true; + } //end func freeze + + // }}} + // {{{ unfreeze() + + /** + * Unfreezes the element so that it becomes editable + * + * @access public + * @return void + * @since 3.2.4 + */ + function unfreeze() + { + $this->_flagFrozen = false; + } + + // }}} + // {{{ getFrozenHtml() + + /** + * Returns the value of field without HTML tags + * + * @since 1.0 + * @access public + * @return string + */ + function getFrozenHtml() + { + $value = $this->getValue(); + return (strlen($value)? htmlspecialchars($value): ' ') . + $this->_getPersistantData(); + } //end func getFrozenHtml + + // }}} + // {{{ _getPersistantData() + + /** + * Used by getFrozenHtml() to pass the element's value if _persistantFreeze is on + * + * @access private + * @return string + */ + function _getPersistantData() + { + if (!$this->_persistantFreeze) { + return ''; + } else { + $id = $this->getAttribute('id'); + return '_getAttrString(array( + 'type' => 'hidden', + 'name' => $this->getName(), + 'value' => $this->getValue() + ) + (isset($id)? array('id' => $id): array())) . ' />'; + } + } + + // }}} + // {{{ isFrozen() + + /** + * Returns whether or not the element is frozen + * + * @since 1.3 + * @access public + * @return bool + */ + function isFrozen() + { + return $this->_flagFrozen; + } // end func isFrozen + + // }}} + // {{{ setPersistantFreeze() + + /** + * Sets wether an element value should be kept in an hidden field + * when the element is frozen or not + * + * @param bool $persistant True if persistant value + * @since 2.0 + * @access public + * @return void + */ + function setPersistantFreeze($persistant=false) + { + $this->_persistantFreeze = $persistant; + } //end func setPersistantFreeze + + // }}} + // {{{ setLabel() + + /** + * Sets display text for the element + * + * @param string $label Display text for the element + * @since 1.3 + * @access public + * @return void + */ + function setLabel($label) + { + $this->_label = $label; + } //end func setLabel + + // }}} + // {{{ getLabel() + + /** + * Returns display text for the element + * + * @since 1.3 + * @access public + * @return string + */ + function getLabel() + { + return $this->_label; + } //end func getLabel + + // }}} + // {{{ _findValue() + + /** + * Tries to find the element value from the values array + * + * @since 2.7 + * @access private + * @return mixed + */ + function _findValue(&$values) + { + if (empty($values)) { + return null; + } + $elementName = $this->getName(); + if (isset($values[$elementName])) { + return $values[$elementName]; + } elseif (strpos($elementName, '[')) { + $myVar = "['" . str_replace( + array('\\', '\'', ']', '['), array('\\\\', '\\\'', '', "']['"), + $elementName + ) . "']"; + return eval("return (isset(\$values$myVar)) ? \$values$myVar : null;"); + } else { + return null; + } + } //end func _findValue + + // }}} + // {{{ onQuickFormEvent() + + /** + * Called by HTML_QuickForm whenever form event is made on this element + * + * @param string $event Name of event + * @param mixed $arg event arguments + * @param object &$caller calling object + * @since 1.0 + * @access public + * @return void + */ + function onQuickFormEvent($event, $arg, &$caller) + { + switch ($event) { + case 'createElement': + $className = get_class($this); + $this->$className($arg[0], $arg[1], $arg[2], $arg[3], $arg[4]); + break; + case 'addElement': + $this->onQuickFormEvent('createElement', $arg, $caller); + $this->onQuickFormEvent('updateValue', null, $caller); + break; + case 'updateValue': + // constant values override both default and submitted ones + // default values are overriden by submitted + $value = $this->_findValue($caller->_constantValues); + if (null === $value) { + $value = $this->_findValue($caller->_submitValues); + if (null === $value) { + $value = $this->_findValue($caller->_defaultValues); + } + } + if (null !== $value) { + $this->setValue($value); + } + break; + case 'setGroupValue': + $this->setValue($arg); + } + return true; + } // end func onQuickFormEvent + + // }}} + // {{{ accept() + + /** + * Accepts a renderer + * + * @param HTML_QuickForm_Renderer renderer object + * @param bool Whether an element is required + * @param string An error message associated with an element + * @access public + * @return void + */ + function accept(&$renderer, $required=false, $error=null) + { + $renderer->renderElement($this, $required, $error); + } // end func accept + + // }}} + // {{{ _generateId() + + /** + * Automatically generates and assigns an 'id' attribute for the element. + * + * Currently used to ensure that labels work on radio buttons and + * checkboxes. Per idea of Alexander Radivanovich. + * + * @access private + * @return void + */ + function _generateId() + { + static $idx = 1; + + if (!$this->getAttribute('id')) { + $this->updateAttributes(array('id' => 'qf_' . substr(md5(microtime() . $idx++), 0, 6))); + } + } // end func _generateId + + // }}} + // {{{ exportValue() + + /** + * Returns a 'safe' element's value + * + * @param array array of submitted values to search + * @param bool whether to return the value as associative array + * @access public + * @return mixed + */ + function exportValue(&$submitValues, $assoc = false) + { + $value = $this->_findValue($submitValues); + if (null === $value) { + $value = $this->getValue(); + } + return $this->_prepareValue($value, $assoc); + } + + // }}} + // {{{ _prepareValue() + + /** + * Used by exportValue() to prepare the value for returning + * + * @param mixed the value found in exportValue() + * @param bool whether to return the value as associative array + * @access private + * @return mixed + */ + function _prepareValue($value, $assoc) + { + if (null === $value) { + return null; + } elseif (!$assoc) { + return $value; + } else { + $name = $this->getName(); + if (!strpos($name, '[')) { + return array($name => $value); + } else { + $valueAry = array(); + $myIndex = "['" . str_replace( + array('\\', '\'', ']', '['), array('\\\\', '\\\'', '', "']['"), + $name + ) . "']"; + eval("\$valueAry$myIndex = \$value;"); + return $valueAry; + } + } + } + + // }}} +} // end class HTML_QuickForm_element +?> \ No newline at end of file diff --git a/library/pear/HTML/QuickForm/file.php b/library/pear/HTML/QuickForm/file.php new file mode 100644 index 000000000..4e4db1229 --- /dev/null +++ b/library/pear/HTML/QuickForm/file.php @@ -0,0 +1,358 @@ + + * @author Bertrand Mansion + * @author Alexey Borzov + * @copyright 2001-2009 The PHP Group + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version CVS: $Id: file.php,v 1.25 2009/04/04 21:34:02 avb Exp $ + * @link http://pear.php.net/package/HTML_QuickForm + */ + +/** + * Base class for form elements + */ +require_once 'HTML/QuickForm/input.php'; + +// register file-related rules +if (class_exists('HTML_QuickForm')) { + HTML_QuickForm::registerRule('uploadedfile', 'callback', '_ruleIsUploadedFile', 'HTML_QuickForm_file'); + HTML_QuickForm::registerRule('maxfilesize', 'callback', '_ruleCheckMaxFileSize', 'HTML_QuickForm_file'); + HTML_QuickForm::registerRule('mimetype', 'callback', '_ruleCheckMimeType', 'HTML_QuickForm_file'); + HTML_QuickForm::registerRule('filename', 'callback', '_ruleCheckFileName', 'HTML_QuickForm_file'); +} + +/** + * HTML class for a file upload field + * + * @category HTML + * @package HTML_QuickForm + * @author Adam Daniel + * @author Bertrand Mansion + * @author Alexey Borzov + * @version Release: 3.2.11 + * @since 1.0 + */ +class HTML_QuickForm_file extends HTML_QuickForm_input +{ + // {{{ properties + + /** + * Uploaded file data, from $_FILES + * @var array + */ + var $_value = null; + + // }}} + // {{{ constructor + + /** + * Class constructor + * + * @param string Input field name attribute + * @param string Input field label + * @param mixed (optional)Either a typical HTML attribute string + * or an associative array + * @since 1.0 + * @access public + */ + function HTML_QuickForm_file($elementName=null, $elementLabel=null, $attributes=null) + { + HTML_QuickForm_input::HTML_QuickForm_input($elementName, $elementLabel, $attributes); + $this->setType('file'); + } //end constructor + + // }}} + // {{{ setSize() + + /** + * Sets size of file element + * + * @param int Size of file element + * @since 1.0 + * @access public + */ + function setSize($size) + { + $this->updateAttributes(array('size' => $size)); + } //end func setSize + + // }}} + // {{{ getSize() + + /** + * Returns size of file element + * + * @since 1.0 + * @access public + * @return int + */ + function getSize() + { + return $this->getAttribute('size'); + } //end func getSize + + // }}} + // {{{ freeze() + + /** + * Freeze the element so that only its value is returned + * + * @access public + * @return bool + */ + function freeze() + { + return false; + } //end func freeze + + // }}} + // {{{ setValue() + + /** + * Sets value for file element. + * + * Actually this does nothing. The function is defined here to override + * HTML_Quickform_input's behaviour of setting the 'value' attribute. As + * no sane user-agent uses 's value for anything + * (because of security implications) we implement file's value as a + * read-only property with a special meaning. + * + * @param mixed Value for file element + * @since 3.0 + * @access public + */ + function setValue($value) + { + return null; + } //end func setValue + + // }}} + // {{{ getValue() + + /** + * Returns information about the uploaded file + * + * @since 3.0 + * @access public + * @return array + */ + function getValue() + { + return $this->_value; + } // end func getValue + + // }}} + // {{{ onQuickFormEvent() + + /** + * Called by HTML_QuickForm whenever form event is made on this element + * + * @param string Name of event + * @param mixed event arguments + * @param object calling object + * @since 1.0 + * @access public + * @return bool + */ + function onQuickFormEvent($event, $arg, &$caller) + { + switch ($event) { + case 'updateValue': + if ($caller->getAttribute('method') == 'get') { + return PEAR::raiseError('Cannot add a file upload field to a GET method form'); + } + $this->_value = $this->_findValue(); + $caller->updateAttributes(array('enctype' => 'multipart/form-data')); + $caller->setMaxFileSize(); + break; + case 'addElement': + $this->onQuickFormEvent('createElement', $arg, $caller); + return $this->onQuickFormEvent('updateValue', null, $caller); + break; + case 'createElement': + $className = get_class($this); + $this->$className($arg[0], $arg[1], $arg[2]); + break; + } + return true; + } // end func onQuickFormEvent + + // }}} + // {{{ moveUploadedFile() + + /** + * Moves an uploaded file into the destination + * + * @param string Destination directory path + * @param string New file name + * @access public + * @return bool Whether the file was moved successfully + */ + function moveUploadedFile($dest, $fileName = '') + { + if ($dest != '' && substr($dest, -1) != '/') { + $dest .= '/'; + } + $fileName = ($fileName != '') ? $fileName : basename($this->_value['name']); + return move_uploaded_file($this->_value['tmp_name'], $dest . $fileName); + } // end func moveUploadedFile + + // }}} + // {{{ isUploadedFile() + + /** + * Checks if the element contains an uploaded file + * + * @access public + * @return bool true if file has been uploaded, false otherwise + */ + function isUploadedFile() + { + return $this->_ruleIsUploadedFile($this->_value); + } // end func isUploadedFile + + // }}} + // {{{ _ruleIsUploadedFile() + + /** + * Checks if the given element contains an uploaded file + * + * @param array Uploaded file info (from $_FILES) + * @access private + * @return bool true if file has been uploaded, false otherwise + */ + function _ruleIsUploadedFile($elementValue) + { + if ((isset($elementValue['error']) && $elementValue['error'] == 0) || + (!empty($elementValue['tmp_name']) && $elementValue['tmp_name'] != 'none')) { + return is_uploaded_file($elementValue['tmp_name']); + } else { + return false; + } + } // end func _ruleIsUploadedFile + + // }}} + // {{{ _ruleCheckMaxFileSize() + + /** + * Checks that the file does not exceed the max file size + * + * @param array Uploaded file info (from $_FILES) + * @param int Max file size + * @access private + * @return bool true if filesize is lower than maxsize, false otherwise + */ + function _ruleCheckMaxFileSize($elementValue, $maxSize) + { + if (!empty($elementValue['error']) && + (UPLOAD_ERR_FORM_SIZE == $elementValue['error'] || UPLOAD_ERR_INI_SIZE == $elementValue['error'])) { + return false; + } + if (!HTML_QuickForm_file::_ruleIsUploadedFile($elementValue)) { + return true; + } + return ($maxSize >= @filesize($elementValue['tmp_name'])); + } // end func _ruleCheckMaxFileSize + + // }}} + // {{{ _ruleCheckMimeType() + + /** + * Checks if the given element contains an uploaded file of the right mime type + * + * @param array Uploaded file info (from $_FILES) + * @param mixed Mime Type (can be an array of allowed types) + * @access private + * @return bool true if mimetype is correct, false otherwise + */ + function _ruleCheckMimeType($elementValue, $mimeType) + { + if (!HTML_QuickForm_file::_ruleIsUploadedFile($elementValue)) { + return true; + } + if (is_array($mimeType)) { + return in_array($elementValue['type'], $mimeType); + } + return $elementValue['type'] == $mimeType; + } // end func _ruleCheckMimeType + + // }}} + // {{{ _ruleCheckFileName() + + /** + * Checks if the given element contains an uploaded file of the filename regex + * + * @param array Uploaded file info (from $_FILES) + * @param string Regular expression + * @access private + * @return bool true if name matches regex, false otherwise + */ + function _ruleCheckFileName($elementValue, $regex) + { + if (!HTML_QuickForm_file::_ruleIsUploadedFile($elementValue)) { + return true; + } + return (bool)preg_match($regex, $elementValue['name']); + } // end func _ruleCheckFileName + + // }}} + // {{{ _findValue() + + /** + * Tries to find the element value from the values array + * + * Needs to be redefined here as $_FILES is populated differently from + * other arrays when element name is of the form foo[bar] + * + * @access private + * @return mixed + */ + function _findValue() + { + if (empty($_FILES)) { + return null; + } + $elementName = $this->getName(); + if (isset($_FILES[$elementName])) { + return $_FILES[$elementName]; + } elseif (false !== ($pos = strpos($elementName, '['))) { + $base = str_replace( + array('\\', '\''), array('\\\\', '\\\''), + substr($elementName, 0, $pos) + ); + $idx = "['" . str_replace( + array('\\', '\'', ']', '['), array('\\\\', '\\\'', '', "']['"), + substr($elementName, $pos + 1, -1) + ) . "']"; + $props = array('name', 'type', 'size', 'tmp_name', 'error'); + $code = "if (!isset(\$_FILES['{$base}']['name']{$idx})) {\n" . + " return null;\n" . + "} else {\n" . + " \$value = array();\n"; + foreach ($props as $prop) { + $code .= " \$value['{$prop}'] = \$_FILES['{$base}']['{$prop}']{$idx};\n"; + } + return eval($code . " return \$value;\n}\n"); + } else { + return null; + } + } + + // }}} +} // end class HTML_QuickForm_file +?> diff --git a/library/pear/HTML/QuickForm/group.php b/library/pear/HTML/QuickForm/group.php new file mode 100644 index 000000000..c857c5db6 --- /dev/null +++ b/library/pear/HTML/QuickForm/group.php @@ -0,0 +1,588 @@ + + * @author Bertrand Mansion + * @author Alexey Borzov + * @copyright 2001-2009 The PHP Group + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version CVS: $Id: group.php,v 1.40 2009/04/04 21:34:03 avb Exp $ + * @link http://pear.php.net/package/HTML_QuickForm + */ + +/** + * Base class for form elements + */ +require_once 'HTML/QuickForm/element.php'; + +/** + * HTML class for a form element group + * + * @category HTML + * @package HTML_QuickForm + * @author Adam Daniel + * @author Bertrand Mansion + * @author Alexey Borzov + * @version Release: 3.2.11 + * @since 1.0 + */ +class HTML_QuickForm_group extends HTML_QuickForm_element +{ + // {{{ properties + + /** + * Name of the element + * @var string + * @since 1.0 + * @access private + */ + var $_name = ''; + + /** + * Array of grouped elements + * @var array + * @since 1.0 + * @access private + */ + var $_elements = array(); + + /** + * String to separate elements + * @var mixed + * @since 2.5 + * @access private + */ + var $_separator = null; + + /** + * Required elements in this group + * @var array + * @since 2.5 + * @access private + */ + var $_required = array(); + + /** + * Whether to change elements' names to $groupName[$elementName] or leave them as is + * @var bool + * @since 3.0 + * @access private + */ + var $_appendName = true; + + // }}} + // {{{ constructor + + /** + * Class constructor + * + * @param string $elementName (optional)Group name + * @param array $elementLabel (optional)Group label + * @param array $elements (optional)Group elements + * @param mixed $separator (optional)Use a string for one separator, + * use an array to alternate the separators. + * @param bool $appendName (optional)whether to change elements' names to + * the form $groupName[$elementName] or leave + * them as is. + * @since 1.0 + * @access public + * @return void + */ + function HTML_QuickForm_group($elementName=null, $elementLabel=null, $elements=null, $separator=null, $appendName = true) + { + $this->HTML_QuickForm_element($elementName, $elementLabel); + $this->_type = 'group'; + if (isset($elements) && is_array($elements)) { + $this->setElements($elements); + } + if (isset($separator)) { + $this->_separator = $separator; + } + if (isset($appendName)) { + $this->_appendName = $appendName; + } + } //end constructor + + // }}} + // {{{ setName() + + /** + * Sets the group name + * + * @param string $name Group name + * @since 1.0 + * @access public + * @return void + */ + function setName($name) + { + $this->_name = $name; + } //end func setName + + // }}} + // {{{ getName() + + /** + * Returns the group name + * + * @since 1.0 + * @access public + * @return string + */ + function getName() + { + return $this->_name; + } //end func getName + + // }}} + // {{{ setValue() + + /** + * Sets values for group's elements + * + * @param mixed Values for group's elements + * @since 1.0 + * @access public + * @return void + */ + function setValue($value) + { + $this->_createElementsIfNotExist(); + foreach (array_keys($this->_elements) as $key) { + if (!$this->_appendName) { + $v = $this->_elements[$key]->_findValue($value); + if (null !== $v) { + $this->_elements[$key]->onQuickFormEvent('setGroupValue', $v, $this); + } + + } else { + $elementName = $this->_elements[$key]->getName(); + $index = strlen($elementName) ? $elementName : $key; + if (is_array($value)) { + if (isset($value[$index])) { + $this->_elements[$key]->onQuickFormEvent('setGroupValue', $value[$index], $this); + } + } elseif (isset($value)) { + $this->_elements[$key]->onQuickFormEvent('setGroupValue', $value, $this); + } + } + } + } //end func setValue + + // }}} + // {{{ getValue() + + /** + * Returns the value of the group + * + * @since 1.0 + * @access public + * @return mixed + */ + function getValue() + { + $value = null; + foreach (array_keys($this->_elements) as $key) { + $element =& $this->_elements[$key]; + switch ($element->getType()) { + case 'radio': + $v = $element->getChecked()? $element->getValue(): null; + break; + case 'checkbox': + $v = $element->getChecked()? true: null; + break; + default: + $v = $element->getValue(); + } + if (null !== $v) { + $elementName = $element->getName(); + if (is_null($elementName)) { + $value = $v; + } else { + if (!is_array($value)) { + $value = is_null($value)? array(): array($value); + } + if ('' === $elementName) { + $value[] = $v; + } else { + $value[$elementName] = $v; + } + } + } + } + return $value; + } // end func getValue + + // }}} + // {{{ setElements() + + /** + * Sets the grouped elements + * + * @param array $elements Array of elements + * @since 1.1 + * @access public + * @return void + */ + function setElements($elements) + { + $this->_elements = array_values($elements); + if ($this->_flagFrozen) { + $this->freeze(); + } + } // end func setElements + + // }}} + // {{{ getElements() + + /** + * Gets the grouped elements + * + * @since 2.4 + * @access public + * @return array + */ + function &getElements() + { + $this->_createElementsIfNotExist(); + return $this->_elements; + } // end func getElements + + // }}} + // {{{ getGroupType() + + /** + * Gets the group type based on its elements + * Will return 'mixed' if elements contained in the group + * are of different types. + * + * @access public + * @return string group elements type + */ + function getGroupType() + { + $this->_createElementsIfNotExist(); + $prevType = ''; + foreach (array_keys($this->_elements) as $key) { + $type = $this->_elements[$key]->getType(); + if ($type != $prevType && $prevType != '') { + return 'mixed'; + } + $prevType = $type; + } + return $type; + } // end func getGroupType + + // }}} + // {{{ toHtml() + + /** + * Returns Html for the group + * + * @since 1.0 + * @access public + * @return string + */ + function toHtml() + { + include_once('HTML/QuickForm/Renderer/Default.php'); + $renderer =& new HTML_QuickForm_Renderer_Default(); + $renderer->setElementTemplate('{element}'); + $this->accept($renderer); + return $renderer->toHtml(); + } //end func toHtml + + // }}} + // {{{ getElementName() + + /** + * Returns the element name inside the group such as found in the html form + * + * @param mixed $index Element name or element index in the group + * @since 3.0 + * @access public + * @return mixed string with element name, false if not found + */ + function getElementName($index) + { + $this->_createElementsIfNotExist(); + $elementName = false; + if (is_int($index) && isset($this->_elements[$index])) { + $elementName = $this->_elements[$index]->getName(); + if (isset($elementName) && $elementName == '') { + $elementName = $index; + } + if ($this->_appendName) { + if (is_null($elementName)) { + $elementName = $this->getName(); + } else { + $elementName = $this->getName().'['.$elementName.']'; + } + } + + } elseif (is_string($index)) { + foreach (array_keys($this->_elements) as $key) { + $elementName = $this->_elements[$key]->getName(); + if ($index == $elementName) { + if ($this->_appendName) { + $elementName = $this->getName().'['.$elementName.']'; + } + break; + } elseif ($this->_appendName && $this->getName().'['.$elementName.']' == $index) { + break; + } + } + } + return $elementName; + } //end func getElementName + + // }}} + // {{{ getFrozenHtml() + + /** + * Returns the value of field without HTML tags + * + * @since 1.3 + * @access public + * @return string + */ + function getFrozenHtml() + { + $flags = array(); + $this->_createElementsIfNotExist(); + foreach (array_keys($this->_elements) as $key) { + if (false === ($flags[$key] = $this->_elements[$key]->isFrozen())) { + $this->_elements[$key]->freeze(); + } + } + $html = $this->toHtml(); + foreach (array_keys($this->_elements) as $key) { + if (!$flags[$key]) { + $this->_elements[$key]->unfreeze(); + } + } + return $html; + } //end func getFrozenHtml + + // }}} + // {{{ onQuickFormEvent() + + /** + * Called by HTML_QuickForm whenever form event is made on this element + * + * @param string $event Name of event + * @param mixed $arg event arguments + * @param object &$caller calling object + * @since 1.0 + * @access public + * @return void + */ + function onQuickFormEvent($event, $arg, &$caller) + { + switch ($event) { + case 'updateValue': + $this->_createElementsIfNotExist(); + foreach (array_keys($this->_elements) as $key) { + if ($this->_appendName) { + $elementName = $this->_elements[$key]->getName(); + if (is_null($elementName)) { + $this->_elements[$key]->setName($this->getName()); + } elseif ('' === $elementName) { + $this->_elements[$key]->setName($this->getName() . '[' . $key . ']'); + } else { + $this->_elements[$key]->setName($this->getName() . '[' . $elementName . ']'); + } + } + $this->_elements[$key]->onQuickFormEvent('updateValue', $arg, $caller); + if ($this->_appendName) { + $this->_elements[$key]->setName($elementName); + } + } + break; + + default: + parent::onQuickFormEvent($event, $arg, $caller); + } + return true; + } // end func onQuickFormEvent + + // }}} + // {{{ accept() + + /** + * Accepts a renderer + * + * @param HTML_QuickForm_Renderer renderer object + * @param bool Whether a group is required + * @param string An error message associated with a group + * @access public + * @return void + */ + function accept(&$renderer, $required = false, $error = null) + { + $this->_createElementsIfNotExist(); + $renderer->startGroup($this, $required, $error); + $name = $this->getName(); + foreach (array_keys($this->_elements) as $key) { + $element =& $this->_elements[$key]; + + if ($this->_appendName) { + $elementName = $element->getName(); + if (isset($elementName)) { + $element->setName($name . '['. (strlen($elementName)? $elementName: $key) .']'); + } else { + $element->setName($name); + } + } + + $required = !$element->isFrozen() && in_array($element->getName(), $this->_required); + + $element->accept($renderer, $required); + + // restore the element's name + if ($this->_appendName) { + $element->setName($elementName); + } + } + $renderer->finishGroup($this); + } // end func accept + + // }}} + // {{{ exportValue() + + /** + * As usual, to get the group's value we access its elements and call + * their exportValue() methods + */ + function exportValue(&$submitValues, $assoc = false) + { + $value = null; + foreach (array_keys($this->_elements) as $key) { + $elementName = $this->_elements[$key]->getName(); + if ($this->_appendName) { + if (is_null($elementName)) { + $this->_elements[$key]->setName($this->getName()); + } elseif ('' === $elementName) { + $this->_elements[$key]->setName($this->getName() . '[' . $key . ']'); + } else { + $this->_elements[$key]->setName($this->getName() . '[' . $elementName . ']'); + } + } + $v = $this->_elements[$key]->exportValue($submitValues, $assoc); + if ($this->_appendName) { + $this->_elements[$key]->setName($elementName); + } + if (null !== $v) { + // Make $value an array, we will use it like one + if (null === $value) { + $value = array(); + } + if ($assoc) { + // just like HTML_QuickForm::exportValues() + $value = HTML_QuickForm::arrayMerge($value, $v); + } else { + // just like getValue(), but should work OK every time here + if (is_null($elementName)) { + $value = $v; + } elseif ('' === $elementName) { + $value[] = $v; + } else { + $value[$elementName] = $v; + } + } + } + } + // do not pass the value through _prepareValue, we took care of this already + return $value; + } + + // }}} + // {{{ _createElements() + + /** + * Creates the group's elements. + * + * This should be overriden by child classes that need to create their + * elements. The method will be called automatically when needed, calling + * it from the constructor is discouraged as the constructor is usually + * called _twice_ on element creation, first time with _no_ parameters. + * + * @access private + * @abstract + */ + function _createElements() + { + // abstract + } + + // }}} + // {{{ _createElementsIfNotExist() + + /** + * A wrapper around _createElements() + * + * This method calls _createElements() if the group's _elements array + * is empty. It also performs some updates, e.g. freezes the created + * elements if the group is already frozen. + * + * @access private + */ + function _createElementsIfNotExist() + { + if (empty($this->_elements)) { + $this->_createElements(); + if ($this->_flagFrozen) { + $this->freeze(); + } + } + } + + // }}} + // {{{ freeze() + + function freeze() + { + parent::freeze(); + foreach (array_keys($this->_elements) as $key) { + $this->_elements[$key]->freeze(); + } + } + + // }}} + // {{{ unfreeze() + + function unfreeze() + { + parent::unfreeze(); + foreach (array_keys($this->_elements) as $key) { + $this->_elements[$key]->unfreeze(); + } + } + + // }}} + // {{{ setPersistantFreeze() + + function setPersistantFreeze($persistant = false) + { + parent::setPersistantFreeze($persistant); + foreach (array_keys($this->_elements) as $key) { + $this->_elements[$key]->setPersistantFreeze($persistant); + } + } + + // }}} +} //end class HTML_QuickForm_group +?> \ No newline at end of file diff --git a/library/pear/HTML/QuickForm/header.php b/library/pear/HTML/QuickForm/header.php new file mode 100644 index 000000000..7a30ae259 --- /dev/null +++ b/library/pear/HTML/QuickForm/header.php @@ -0,0 +1,74 @@ + + * @copyright 2001-2009 The PHP Group + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version CVS: $Id: header.php,v 1.3 2009/04/04 21:34:03 avb Exp $ + * @link http://pear.php.net/package/HTML_QuickForm + */ + +/** + * HTML class for static data + */ +require_once 'HTML/QuickForm/static.php'; + +/** + * A pseudo-element used for adding headers to form + * + * @category HTML + * @package HTML_QuickForm + * @author Alexey Borzov + * @version Release: 3.2.11 + * @since 3.0 + */ +class HTML_QuickForm_header extends HTML_QuickForm_static +{ + // {{{ constructor + + /** + * Class constructor + * + * @param string $elementName Header name + * @param string $text Header text + * @access public + * @return void + */ + function HTML_QuickForm_header($elementName = null, $text = null) + { + $this->HTML_QuickForm_static($elementName, null, $text); + $this->_type = 'header'; + } + + // }}} + // {{{ accept() + + /** + * Accepts a renderer + * + * @param HTML_QuickForm_Renderer renderer object + * @access public + * @return void + */ + function accept(&$renderer) + { + $renderer->renderHeader($this); + } // end func accept + + // }}} + +} //end class HTML_QuickForm_header +?> diff --git a/library/pear/HTML/QuickForm/hidden.php b/library/pear/HTML/QuickForm/hidden.php new file mode 100644 index 000000000..8ff593856 --- /dev/null +++ b/library/pear/HTML/QuickForm/hidden.php @@ -0,0 +1,94 @@ + + * @author Bertrand Mansion + * @copyright 2001-2009 The PHP Group + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version CVS: $Id: hidden.php,v 1.12 2009/04/04 21:34:03 avb Exp $ + * @link http://pear.php.net/package/HTML_QuickForm + */ + +/** + * Base class for form elements + */ +require_once 'HTML/QuickForm/input.php'; + +/** + * HTML class for a hidden type element + * + * @category HTML + * @package HTML_QuickForm + * @author Adam Daniel + * @author Bertrand Mansion + * @version Release: 3.2.11 + * @since 1.0 + */ +class HTML_QuickForm_hidden extends HTML_QuickForm_input +{ + // {{{ constructor + + /** + * Class constructor + * + * @param string $elementName (optional)Input field name attribute + * @param string $value (optional)Input field value + * @param mixed $attributes (optional)Either a typical HTML attribute string + * or an associative array + * @since 1.0 + * @access public + * @return void + */ + function HTML_QuickForm_hidden($elementName=null, $value='', $attributes=null) + { + HTML_QuickForm_input::HTML_QuickForm_input($elementName, null, $attributes); + $this->setType('hidden'); + $this->setValue($value); + } //end constructor + + // }}} + // {{{ freeze() + + /** + * Freeze the element so that only its value is returned + * + * @access public + * @return void + */ + function freeze() + { + return false; + } //end func freeze + + // }}} + // {{{ accept() + + /** + * Accepts a renderer + * + * @param HTML_QuickForm_Renderer renderer object + * @access public + * @return void + */ + function accept(&$renderer) + { + $renderer->renderHidden($this); + } // end func accept + + // }}} + +} //end class HTML_QuickForm_hidden +?> diff --git a/library/pear/HTML/QuickForm/hiddenselect.php b/library/pear/HTML/QuickForm/hiddenselect.php new file mode 100644 index 000000000..ac415386f --- /dev/null +++ b/library/pear/HTML/QuickForm/hiddenselect.php @@ -0,0 +1,118 @@ + + * @copyright 2001-2009 The PHP Group + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version CVS: $Id: hiddenselect.php,v 1.7 2009/04/04 21:34:03 avb Exp $ + * @link http://pear.php.net/package/HTML_QuickForm + */ + +/** + * Class for elements + */ +require_once 'HTML/QuickForm/select.php'; + +/** + * Hidden select pseudo-element + * + * This class takes the same arguments as a select element, but instead + * of creating a select ring it creates hidden elements for all values + * already selected with setDefault or setConstant. This is useful if + * you have a select ring that you don't want visible, but you need all + * selected values to be passed. + * + * @category HTML + * @package HTML_QuickForm + * @author Isaac Shepard + * @version Release: 3.2.11 + * @since 2.1 + */ +class HTML_QuickForm_hiddenselect extends HTML_QuickForm_select +{ + // {{{ constructor + + /** + * Class constructor + * + * @param string Select name attribute + * @param mixed Label(s) for the select (not used) + * @param mixed Data to be used to populate options + * @param mixed Either a typical HTML attribute string or an associative array (not used) + * @since 1.0 + * @access public + * @return void + */ + function HTML_QuickForm_hiddenselect($elementName=null, $elementLabel=null, $options=null, $attributes=null) + { + HTML_QuickForm_element::HTML_QuickForm_element($elementName, $elementLabel, $attributes); + $this->_persistantFreeze = true; + $this->_type = 'hiddenselect'; + if (isset($options)) { + $this->load($options); + } + } //end constructor + + // }}} + // {{{ toHtml() + + /** + * Returns the SELECT in HTML + * + * @since 1.0 + * @access public + * @return string + * @throws + */ + function toHtml() + { + if (empty($this->_values)) { + return ''; + } + + $tabs = $this->_getTabs(); + $name = $this->getPrivateName(); + $strHtml = ''; + + foreach ($this->_values as $key => $val) { + for ($i = 0, $optCount = count($this->_options); $i < $optCount; $i++) { + if ($val == $this->_options[$i]['attr']['value']) { + $strHtml .= $tabs . '_getAttrString(array( + 'type' => 'hidden', + 'name' => $name, + 'value' => $val + )) . " />\n" ; + } + } + } + + return $strHtml; + } //end func toHtml + + // }}} + // {{{ accept() + + /** + * This is essentially a hidden element and should be rendered as one + */ + function accept(&$renderer) + { + $renderer->renderHidden($this); + } + + // }}} +} //end class HTML_QuickForm_hiddenselect +?> diff --git a/library/pear/HTML/QuickForm/hierselect.php b/library/pear/HTML/QuickForm/hierselect.php new file mode 100644 index 000000000..856491cf5 --- /dev/null +++ b/library/pear/HTML/QuickForm/hierselect.php @@ -0,0 +1,596 @@ + + * @author Bertrand Mansion + * @author Alexey Borzov + * @copyright 2001-2009 The PHP Group + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version CVS: $Id: hierselect.php,v 1.20 2009/04/04 21:34:03 avb Exp $ + * @link http://pear.php.net/package/HTML_QuickForm + */ + +/** + * Class for a group of form elements + */ +require_once 'HTML/QuickForm/group.php'; +/** + * Class for elements + */ +require_once 'HTML/QuickForm/select.php'; + +/** + * Hierarchical select element + * + * Class to dynamically create two or more HTML Select elements + * The first select changes the content of the second select and so on. + * This element is considered as a group. Selects will be named + * groupName[0], groupName[1], groupName[2]... + * + * @category HTML + * @package HTML_QuickForm + * @author Herim Vasquez + * @author Bertrand Mansion + * @author Alexey Borzov + * @version Release: 3.2.11 + * @since 3.1 + */ +class HTML_QuickForm_hierselect extends HTML_QuickForm_group +{ + // {{{ properties + + /** + * Options for all the select elements + * + * @see setOptions() + * @var array + * @access private + */ + var $_options = array(); + + /** + * Number of select elements on this group + * + * @var int + * @access private + */ + var $_nbElements = 0; + + /** + * The javascript used to set and change the options + * + * @var string + * @access private + */ + var $_js = ''; + + // }}} + // {{{ constructor + + /** + * Class constructor + * + * @param string $elementName (optional)Input field name attribute + * @param string $elementLabel (optional)Input field label in form + * @param mixed $attributes (optional)Either a typical HTML attribute string + * or an associative array. Date format is passed along the attributes. + * @param mixed $separator (optional)Use a string for one separator, + * use an array to alternate the separators. + * @access public + * @return void + */ + function HTML_QuickForm_hierselect($elementName=null, $elementLabel=null, $attributes=null, $separator=null) + { + $this->HTML_QuickForm_element($elementName, $elementLabel, $attributes); + $this->_persistantFreeze = true; + if (isset($separator)) { + $this->_separator = $separator; + } + $this->_type = 'hierselect'; + $this->_appendName = true; + } //end constructor + + // }}} + // {{{ setOptions() + + /** + * Initialize the array structure containing the options for each select element. + * Call the functions that actually do the magic. + * + * Format is a bit more complex than for a simple select as we need to know + * which options are related to the ones in the previous select: + * + * Ex: + * + * // first select + * $select1[0] = 'Pop'; + * $select1[1] = 'Classical'; + * $select1[2] = 'Funeral doom'; + * + * // second select + * $select2[0][0] = 'Red Hot Chil Peppers'; + * $select2[0][1] = 'The Pixies'; + * $select2[1][0] = 'Wagner'; + * $select2[1][1] = 'Strauss'; + * $select2[2][0] = 'Pantheist'; + * $select2[2][1] = 'Skepticism'; + * + * // If only need two selects + * // - and using the deprecated functions + * $sel =& $form->addElement('hierselect', 'cds', 'Choose CD:'); + * $sel->setMainOptions($select1); + * $sel->setSecOptions($select2); + * + * // - and using the new setOptions function + * $sel =& $form->addElement('hierselect', 'cds', 'Choose CD:'); + * $sel->setOptions(array($select1, $select2)); + * + * // If you have a third select with prices for the cds + * $select3[0][0][0] = '15.00$'; + * $select3[0][0][1] = '17.00$'; + * // etc + * + * // You can now use + * $sel =& $form->addElement('hierselect', 'cds', 'Choose CD:'); + * $sel->setOptions(array($select1, $select2, $select3)); + * + * + * @param array $options Array of options defining each element + * @access public + * @return void + */ + function setOptions($options) + { + $this->_options = $options; + + if (empty($this->_elements)) { + $this->_nbElements = count($this->_options); + $this->_createElements(); + } else { + // setDefaults has probably been called before this function + // check if all elements have been created + $totalNbElements = count($this->_options); + for ($i = $this->_nbElements; $i < $totalNbElements; $i ++) { + $this->_elements[] =& new HTML_QuickForm_select($i, null, array(), $this->getAttributes()); + $this->_nbElements++; + } + } + + $this->_setOptions(); + } // end func setMainOptions + + // }}} + // {{{ setMainOptions() + + /** + * Sets the options for the first select element. Deprecated. setOptions() should be used. + * + * @param array $array Options for the first select element + * + * @access public + * @deprecated Deprecated since release 3.2.2 + * @return void + */ + function setMainOptions($array) + { + $this->_options[0] = $array; + + if (empty($this->_elements)) { + $this->_nbElements = 2; + $this->_createElements(); + } + } // end func setMainOptions + + // }}} + // {{{ setSecOptions() + + /** + * Sets the options for the second select element. Deprecated. setOptions() should be used. + * The main _options array is initialized and the _setOptions function is called. + * + * @param array $array Options for the second select element + * + * @access public + * @deprecated Deprecated since release 3.2.2 + * @return void + */ + function setSecOptions($array) + { + $this->_options[1] = $array; + + if (empty($this->_elements)) { + $this->_nbElements = 2; + $this->_createElements(); + } else { + // setDefaults has probably been called before this function + // check if all elements have been created + $totalNbElements = 2; + for ($i = $this->_nbElements; $i < $totalNbElements; $i ++) { + $this->_elements[] =& new HTML_QuickForm_select($i, null, array(), $this->getAttributes()); + $this->_nbElements++; + } + } + + $this->_setOptions(); + } // end func setSecOptions + + // }}} + // {{{ _setOptions() + + /** + * Sets the options for each select element + * + * @access private + * @return void + */ + function _setOptions() + { + $toLoad = ''; + foreach (array_keys($this->_elements) AS $key) { + $array = eval("return isset(\$this->_options[{$key}]{$toLoad})? \$this->_options[{$key}]{$toLoad}: null;"); + if (is_array($array)) { + $select =& $this->_elements[$key]; + $select->_options = array(); + $select->loadArray($array); + + $value = is_array($v = $select->getValue()) ? $v[0] : key($array); + $toLoad .= '[\'' . str_replace(array('\\', '\''), array('\\\\', '\\\''), $value) . '\']'; + } + } + } // end func _setOptions + + // }}} + // {{{ setValue() + + /** + * Sets values for group's elements + * + * @param array $value An array of 2 or more values, for the first, + * the second, the third etc. select + * + * @access public + * @return void + */ + function setValue($value) + { + // fix for bug #6766. Hope this doesn't break anything more + // after bug #7961. Forgot that _nbElements was used in + // _createElements() called in several places... + $this->_nbElements = max($this->_nbElements, count($value)); + parent::setValue($value); + $this->_setOptions(); + } // end func setValue + + // }}} + // {{{ _createElements() + + /** + * Creates all the elements for the group + * + * @access private + * @return void + */ + function _createElements() + { + for ($i = 0; $i < $this->_nbElements; $i++) { + $this->_elements[] =& new HTML_QuickForm_select($i, null, array(), $this->getAttributes()); + } + } // end func _createElements + + // }}} + // {{{ toHtml() + + function toHtml() + { + $this->_js = ''; + if (!$this->_flagFrozen) { + // set the onchange attribute for each element except last + $keys = array_keys($this->_elements); + $onChange = array(); + for ($i = 0; $i < count($keys) - 1; $i++) { + $select =& $this->_elements[$keys[$i]]; + $onChange[$i] = $select->getAttribute('onchange'); + $select->updateAttributes( + array('onchange' => '_hs_swapOptions(this.form, \'' . $this->_escapeString($this->getName()) . '\', ' . $keys[$i] . ');' . $onChange[$i]) + ); + } + + // create the js function to call + if (!defined('HTML_QUICKFORM_HIERSELECT_EXISTS')) { + $this->_js .= <<_nbElements; $i++) { + $jsParts[] = $this->_convertArrayToJavascript($this->_options[$i]); + } + $this->_js .= "\n_hs_options['" . $this->_escapeString($this->getName()) . "'] = [\n" . + implode(",\n", $jsParts) . + "\n];\n"; + // default value; if we don't actually have any values yet just use + // the first option (for single selects) or empty array (for multiple) + $values = array(); + foreach (array_keys($this->_elements) as $key) { + if (is_array($v = $this->_elements[$key]->getValue())) { + $values[] = count($v) > 1? $v: $v[0]; + } else { + // XXX: accessing the supposedly private _options array + $values[] = $this->_elements[$key]->getMultiple() || empty($this->_elements[$key]->_options[0])? + array(): + $this->_elements[$key]->_options[0]['attr']['value']; + } + } + $this->_js .= "_hs_defaults['" . $this->_escapeString($this->getName()) . "'] = " . + $this->_convertArrayToJavascript($values, false) . ";\n"; + } + include_once('HTML/QuickForm/Renderer/Default.php'); + $renderer =& new HTML_QuickForm_Renderer_Default(); + $renderer->setElementTemplate('{element}'); + parent::accept($renderer); + + if (!empty($onChange)) { + $keys = array_keys($this->_elements); + for ($i = 0; $i < count($keys) - 1; $i++) { + $this->_elements[$keys[$i]]->updateAttributes(array('onchange' => $onChange[$i])); + } + } + return (empty($this->_js)? '': "") . + $renderer->toHtml(); + } // end func toHtml + + // }}} + // {{{ accept() + + function accept(&$renderer, $required = false, $error = null) + { + $renderer->renderElement($this, $required, $error); + } // end func accept + + // }}} + // {{{ onQuickFormEvent() + + function onQuickFormEvent($event, $arg, &$caller) + { + if ('updateValue' == $event) { + // we need to call setValue() so that the secondary option + // matches the main option + return HTML_QuickForm_element::onQuickFormEvent($event, $arg, $caller); + } else { + $ret = parent::onQuickFormEvent($event, $arg, $caller); + // add onreset handler to form to properly reset hierselect (see bug #2970) + if ('addElement' == $event) { + $onReset = $caller->getAttribute('onreset'); + if (strlen($onReset)) { + if (strpos($onReset, '_hs_setupOnReset')) { + $caller->updateAttributes(array('onreset' => str_replace('_hs_setupOnReset(this, [', "_hs_setupOnReset(this, ['" . $this->_escapeString($this->getName()) . "', ", $onReset))); + } else { + $caller->updateAttributes(array('onreset' => "var temp = function() { {$onReset} } ; if (!temp()) { return false; } ; if (typeof _hs_setupOnReset != 'undefined') { return _hs_setupOnReset(this, ['" . $this->_escapeString($this->getName()) . "']); } ")); + } + } else { + $caller->updateAttributes(array('onreset' => "if (typeof _hs_setupOnReset != 'undefined') { return _hs_setupOnReset(this, ['" . $this->_escapeString($this->getName()) . "']); } ")); + } + } + return $ret; + } + } // end func onQuickFormEvent + + // }}} + // {{{ _convertArrayToJavascript() + + /** + * Converts PHP array to its Javascript analog + * + * @access private + * @param array PHP array to convert + * @param bool Generate Javascript object literal (default, works like PHP's associative array) or array literal + * @return string Javascript representation of the value + */ + function _convertArrayToJavascript($array, $assoc = true) + { + if (!is_array($array)) { + return $this->_convertScalarToJavascript($array); + } else { + $items = array(); + foreach ($array as $key => $val) { + $item = $assoc? "'" . $this->_escapeString($key) . "': ": ''; + if (is_array($val)) { + $item .= $this->_convertArrayToJavascript($val, $assoc); + } else { + $item .= $this->_convertScalarToJavascript($val); + } + $items[] = $item; + } + } + $js = implode(', ', $items); + return $assoc? '{ ' . $js . ' }': '[' . $js . ']'; + } + + // }}} + // {{{ _convertScalarToJavascript() + + /** + * Converts PHP's scalar value to its Javascript analog + * + * @access private + * @param mixed PHP value to convert + * @return string Javascript representation of the value + */ + function _convertScalarToJavascript($val) + { + if (is_bool($val)) { + return $val ? 'true' : 'false'; + } elseif (is_int($val) || is_double($val)) { + return $val; + } elseif (is_string($val)) { + return "'" . $this->_escapeString($val) . "'"; + } elseif (is_null($val)) { + return 'null'; + } else { + // don't bother + return '{}'; + } + } + + // }}} + // {{{ _escapeString() + + /** + * Quotes the string so that it can be used in Javascript string constants + * + * @access private + * @param string + * @return string + */ + function _escapeString($str) + { + return strtr($str,array( + "\r" => '\r', + "\n" => '\n', + "\t" => '\t', + "'" => "\\'", + '"' => '\"', + '\\' => '\\\\' + )); + } + + // }}} +} // end class HTML_QuickForm_hierselect +?> \ No newline at end of file diff --git a/library/pear/HTML/QuickForm/html.php b/library/pear/HTML/QuickForm/html.php new file mode 100644 index 000000000..60c9ec166 --- /dev/null +++ b/library/pear/HTML/QuickForm/html.php @@ -0,0 +1,77 @@ + + * @copyright 2001-2009 The PHP Group + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version CVS: $Id: html.php,v 1.3 2009/04/04 21:34:03 avb Exp $ + * @link http://pear.php.net/package/HTML_QuickForm + */ + +/** + * HTML class for static data + */ +require_once 'HTML/QuickForm/static.php'; + +/** + * A pseudo-element used for adding raw HTML to form + * + * Intended for use with the default renderer only, template-based + * ones may (and probably will) completely ignore this + * + * @category HTML + * @package HTML_QuickForm + * @author Alexey Borzov + * @version Release: 3.2.11 + * @since 3.0 + * @deprecated Please use the templates rather than add raw HTML via this element + */ +class HTML_QuickForm_html extends HTML_QuickForm_static +{ + // {{{ constructor + + /** + * Class constructor + * + * @param string $text raw HTML to add + * @access public + * @return void + */ + function HTML_QuickForm_html($text = null) + { + $this->HTML_QuickForm_static(null, null, $text); + $this->_type = 'html'; + } + + // }}} + // {{{ accept() + + /** + * Accepts a renderer + * + * @param HTML_QuickForm_Renderer renderer object (only works with Default renderer!) + * @access public + * @return void + */ + function accept(&$renderer) + { + $renderer->renderHtml($this); + } // end func accept + + // }}} + +} //end class HTML_QuickForm_html +?> diff --git a/library/pear/HTML/QuickForm/image.php b/library/pear/HTML/QuickForm/image.php new file mode 100644 index 000000000..429442445 --- /dev/null +++ b/library/pear/HTML/QuickForm/image.php @@ -0,0 +1,127 @@ + element + * + * PHP versions 4 and 5 + * + * LICENSE: This source file is subject to version 3.01 of the PHP license + * that is available through the world-wide-web at the following URI: + * http://www.php.net/license/3_01.txt If you did not receive a copy of + * the PHP License and are unable to obtain it through the web, please + * send a note to license@php.net so we can mail you a copy immediately. + * + * @category HTML + * @package HTML_QuickForm + * @author Adam Daniel + * @author Bertrand Mansion + * @copyright 2001-2009 The PHP Group + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version CVS: $Id: image.php,v 1.6 2009/04/04 21:34:03 avb Exp $ + * @link http://pear.php.net/package/HTML_QuickForm + */ + +/** + * Base class for form elements + */ +require_once 'HTML/QuickForm/input.php'; + +/** + * HTML class for an element + * + * @category HTML + * @package HTML_QuickForm + * @author Adam Daniel + * @author Bertrand Mansion + * @version Release: 3.2.11 + * @since 1.0 + */ +class HTML_QuickForm_image extends HTML_QuickForm_input +{ + // {{{ constructor + + /** + * Class constructor + * + * @param string $elementName (optional)Element name attribute + * @param string $src (optional)Image source + * @param mixed $attributes (optional)Either a typical HTML attribute string + * or an associative array + * @since 1.0 + * @access public + * @return void + */ + function HTML_QuickForm_image($elementName=null, $src='', $attributes=null) + { + HTML_QuickForm_input::HTML_QuickForm_input($elementName, null, $attributes); + $this->setType('image'); + $this->setSource($src); + } // end class constructor + + // }}} + // {{{ setSource() + + /** + * Sets source for image element + * + * @param string $src source for image element + * @since 1.0 + * @access public + * @return void + */ + function setSource($src) + { + $this->updateAttributes(array('src' => $src)); + } // end func setSource + + // }}} + // {{{ setBorder() + + /** + * Sets border size for image element + * + * @param string $border border for image element + * @since 1.0 + * @access public + * @return void + */ + function setBorder($border) + { + $this->updateAttributes(array('border' => $border)); + } // end func setBorder + + // }}} + // {{{ setAlign() + + /** + * Sets alignment for image element + * + * @param string $align alignment for image element + * @since 1.0 + * @access public + * @return void + */ + function setAlign($align) + { + $this->updateAttributes(array('align' => $align)); + } // end func setAlign + + // }}} + // {{{ freeze() + + /** + * Freeze the element so that only its value is returned + * + * @access public + * @return void + */ + function freeze() + { + return false; + } //end func freeze + + // }}} + +} // end class HTML_QuickForm_image +?> diff --git a/library/pear/HTML/QuickForm/input.php b/library/pear/HTML/QuickForm/input.php new file mode 100644 index 000000000..473d8cb2b --- /dev/null +++ b/library/pear/HTML/QuickForm/input.php @@ -0,0 +1,209 @@ + form elements + * + * PHP versions 4 and 5 + * + * LICENSE: This source file is subject to version 3.01 of the PHP license + * that is available through the world-wide-web at the following URI: + * http://www.php.net/license/3_01.txt If you did not receive a copy of + * the PHP License and are unable to obtain it through the web, please + * send a note to license@php.net so we can mail you a copy immediately. + * + * @category HTML + * @package HTML_QuickForm + * @author Adam Daniel + * @author Bertrand Mansion + * @copyright 2001-2009 The PHP Group + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version CVS: $Id: input.php,v 1.10 2009/04/04 21:34:03 avb Exp $ + * @link http://pear.php.net/package/HTML_QuickForm + */ + +/** + * Base class for form elements + */ +require_once 'HTML/QuickForm/element.php'; + +/** + * Base class for form elements + * + * @category HTML + * @package HTML_QuickForm + * @author Adam Daniel + * @author Bertrand Mansion + * @version Release: 3.2.11 + * @since 1.0 + * @abstract + */ +class HTML_QuickForm_input extends HTML_QuickForm_element +{ + // {{{ constructor + + /** + * Class constructor + * + * @param string Input field name attribute + * @param mixed Label(s) for the input field + * @param mixed Either a typical HTML attribute string or an associative array + * @since 1.0 + * @access public + * @return void + */ + function HTML_QuickForm_input($elementName=null, $elementLabel=null, $attributes=null) + { + $this->HTML_QuickForm_element($elementName, $elementLabel, $attributes); + } //end constructor + + // }}} + // {{{ setType() + + /** + * Sets the element type + * + * @param string $type Element type + * @since 1.0 + * @access public + * @return void + */ + function setType($type) + { + $this->_type = $type; + $this->updateAttributes(array('type'=>$type)); + } // end func setType + + // }}} + // {{{ setName() + + /** + * Sets the input field name + * + * @param string $name Input field name attribute + * @since 1.0 + * @access public + * @return void + */ + function setName($name) + { + $this->updateAttributes(array('name'=>$name)); + } //end func setName + + // }}} + // {{{ getName() + + /** + * Returns the element name + * + * @since 1.0 + * @access public + * @return string + */ + function getName() + { + return $this->getAttribute('name'); + } //end func getName + + // }}} + // {{{ setValue() + + /** + * Sets the value of the form element + * + * @param string $value Default value of the form element + * @since 1.0 + * @access public + * @return void + */ + function setValue($value) + { + $this->updateAttributes(array('value'=>$value)); + } // end func setValue + + // }}} + // {{{ getValue() + + /** + * Returns the value of the form element + * + * @since 1.0 + * @access public + * @return string + */ + function getValue() + { + return $this->getAttribute('value'); + } // end func getValue + + // }}} + // {{{ toHtml() + + /** + * Returns the input field in HTML + * + * @since 1.0 + * @access public + * @return string + */ + function toHtml() + { + if ($this->_flagFrozen) { + return $this->getFrozenHtml(); + } else { + return $this->_getTabs() . '_getAttrString($this->_attributes) . ' />'; + } + } //end func toHtml + + // }}} + // {{{ onQuickFormEvent() + + /** + * Called by HTML_QuickForm whenever form event is made on this element + * + * @param string $event Name of event + * @param mixed $arg event arguments + * @param object &$caller calling object + * @since 1.0 + * @access public + * @return void + * @throws + */ + function onQuickFormEvent($event, $arg, &$caller) + { + // do not use submit values for button-type elements + $type = $this->getType(); + if (('updateValue' != $event) || + ('submit' != $type && 'reset' != $type && 'image' != $type && 'button' != $type)) { + parent::onQuickFormEvent($event, $arg, $caller); + } else { + $value = $this->_findValue($caller->_constantValues); + if (null === $value) { + $value = $this->_findValue($caller->_defaultValues); + } + if (null !== $value) { + $this->setValue($value); + } + } + return true; + } // end func onQuickFormEvent + + // }}} + // {{{ exportValue() + + /** + * We don't need values from button-type elements (except submit) and files + */ + function exportValue(&$submitValues, $assoc = false) + { + $type = $this->getType(); + if ('reset' == $type || 'image' == $type || 'button' == $type || 'file' == $type) { + return null; + } else { + return parent::exportValue($submitValues, $assoc); + } + } + + // }}} +} // end class HTML_QuickForm_element +?> diff --git a/library/pear/HTML/QuickForm/link.php b/library/pear/HTML/QuickForm/link.php new file mode 100644 index 000000000..0b7ace40c --- /dev/null +++ b/library/pear/HTML/QuickForm/link.php @@ -0,0 +1,200 @@ + + * @author Bertrand Mansion + * @copyright 2001-2009 The PHP Group + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version CVS: $Id: link.php,v 1.4 2009/04/04 21:34:04 avb Exp $ + * @link http://pear.php.net/package/HTML_QuickForm + */ + +/** + * HTML class for static data + */ +require_once 'HTML/QuickForm/static.php'; + +/** + * HTML class for a link type field + * + * @category HTML + * @package HTML_QuickForm + * @author Adam Daniel + * @author Bertrand Mansion + * @version Release: 3.2.11 + * @since 2.0 + */ +class HTML_QuickForm_link extends HTML_QuickForm_static +{ + // {{{ properties + + /** + * Link display text + * @var string + * @since 1.0 + * @access private + */ + var $_text = ""; + + // }}} + // {{{ constructor + + /** + * Class constructor + * + * @param string $elementLabel (optional)Link label + * @param string $href (optional)Link href + * @param string $text (optional)Link display text + * @param mixed $attributes (optional)Either a typical HTML attribute string + * or an associative array + * @since 1.0 + * @access public + * @return void + * @throws + */ + function HTML_QuickForm_link($elementName=null, $elementLabel=null, $href=null, $text=null, $attributes=null) + { + HTML_QuickForm_element::HTML_QuickForm_element($elementName, $elementLabel, $attributes); + $this->_persistantFreeze = false; + $this->_type = 'link'; + $this->setHref($href); + $this->_text = $text; + } //end constructor + + // }}} + // {{{ setName() + + /** + * Sets the input field name + * + * @param string $name Input field name attribute + * @since 1.0 + * @access public + * @return void + * @throws + */ + function setName($name) + { + $this->updateAttributes(array('name'=>$name)); + } //end func setName + + // }}} + // {{{ getName() + + /** + * Returns the element name + * + * @since 1.0 + * @access public + * @return string + * @throws + */ + function getName() + { + return $this->getAttribute('name'); + } //end func getName + + // }}} + // {{{ setValue() + + /** + * Sets value for textarea element + * + * @param string $value Value for password element + * @since 1.0 + * @access public + * @return void + * @throws + */ + function setValue($value) + { + return; + } //end func setValue + + // }}} + // {{{ getValue() + + /** + * Returns the value of the form element + * + * @since 1.0 + * @access public + * @return void + * @throws + */ + function getValue() + { + return; + } // end func getValue + + + // }}} + // {{{ setHref() + + /** + * Sets the links href + * + * @param string $href + * @since 1.0 + * @access public + * @return void + * @throws + */ + function setHref($href) + { + $this->updateAttributes(array('href'=>$href)); + } // end func setHref + + // }}} + // {{{ toHtml() + + /** + * Returns the textarea element in HTML + * + * @since 1.0 + * @access public + * @return string + * @throws + */ + function toHtml() + { + $tabs = $this->_getTabs(); + $html = "$tabs_getAttrString($this->_attributes).">"; + $html .= $this->_text; + $html .= ""; + return $html; + } //end func toHtml + + // }}} + // {{{ getFrozenHtml() + + /** + * Returns the value of field without HTML tags (in this case, value is changed to a mask) + * + * @since 1.0 + * @access public + * @return string + * @throws + */ + function getFrozenHtml() + { + return; + } //end func getFrozenHtml + + // }}} + +} //end class HTML_QuickForm_textarea +?> diff --git a/library/pear/HTML/QuickForm/password.php b/library/pear/HTML/QuickForm/password.php new file mode 100644 index 000000000..5e0197819 --- /dev/null +++ b/library/pear/HTML/QuickForm/password.php @@ -0,0 +1,115 @@ + + * @author Bertrand Mansion + * @copyright 2001-2009 The PHP Group + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version CVS: $Id: password.php,v 1.8 2009/04/04 21:34:04 avb Exp $ + * @link http://pear.php.net/package/HTML_QuickForm + */ + +/** + * Base class for form elements + */ +require_once 'HTML/QuickForm/input.php'; + +/** + * HTML class for a password type field + * + * @category HTML + * @package HTML_QuickForm + * @author Adam Daniel + * @author Bertrand Mansion + * @version Release: 3.2.11 + * @since 1.0 + */ +class HTML_QuickForm_password extends HTML_QuickForm_input +{ + // {{{ constructor + + /** + * Class constructor + * + * @param string $elementName (optional)Input field name attribute + * @param string $elementLabel (optional)Input field label + * @param mixed $attributes (optional)Either a typical HTML attribute string + * or an associative array + * @since 1.0 + * @access public + * @return void + * @throws + */ + function HTML_QuickForm_password($elementName=null, $elementLabel=null, $attributes=null) + { + HTML_QuickForm_input::HTML_QuickForm_input($elementName, $elementLabel, $attributes); + $this->setType('password'); + } //end constructor + + // }}} + // {{{ setSize() + + /** + * Sets size of password element + * + * @param string $size Size of password field + * @since 1.0 + * @access public + * @return void + */ + function setSize($size) + { + $this->updateAttributes(array('size'=>$size)); + } //end func setSize + + // }}} + // {{{ setMaxlength() + + /** + * Sets maxlength of password element + * + * @param string $maxlength Maximum length of password field + * @since 1.0 + * @access public + * @return void + */ + function setMaxlength($maxlength) + { + $this->updateAttributes(array('maxlength'=>$maxlength)); + } //end func setMaxlength + + // }}} + // {{{ getFrozenHtml() + + /** + * Returns the value of field without HTML tags (in this case, value is changed to a mask) + * + * @since 1.0 + * @access public + * @return string + * @throws + */ + function getFrozenHtml() + { + $value = $this->getValue(); + return ('' != $value? '**********': ' ') . + $this->_getPersistantData(); + } //end func getFrozenHtml + + // }}} + +} //end class HTML_QuickForm_password +?> diff --git a/library/pear/HTML/QuickForm/radio.php b/library/pear/HTML/QuickForm/radio.php new file mode 100644 index 000000000..c244871fd --- /dev/null +++ b/library/pear/HTML/QuickForm/radio.php @@ -0,0 +1,251 @@ + + * @author Bertrand Mansion + * @copyright 2001-2009 The PHP Group + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version CVS: $Id: radio.php,v 1.20 2009/04/04 21:34:04 avb Exp $ + * @link http://pear.php.net/package/HTML_QuickForm + */ + +/** + * Base class for form elements + */ +require_once 'HTML/QuickForm/input.php'; + +/** + * HTML class for a radio type element + * + * @category HTML + * @package HTML_QuickForm + * @author Adam Daniel + * @author Bertrand Mansion + * @version Release: 3.2.11 + * @since 1.0 + */ +class HTML_QuickForm_radio extends HTML_QuickForm_input +{ + // {{{ properties + + /** + * Radio display text + * @var string + * @since 1.1 + * @access private + */ + var $_text = ''; + + // }}} + // {{{ constructor + + /** + * Class constructor + * + * @param string Input field name attribute + * @param mixed Label(s) for a field + * @param string Text to display near the radio + * @param string Input field value + * @param mixed Either a typical HTML attribute string or an associative array + * @since 1.0 + * @access public + * @return void + */ + function HTML_QuickForm_radio($elementName=null, $elementLabel=null, $text=null, $value=null, $attributes=null) + { + $this->HTML_QuickForm_element($elementName, $elementLabel, $attributes); + if (isset($value)) { + $this->setValue($value); + } + $this->_persistantFreeze = true; + $this->setType('radio'); + $this->_text = $text; + $this->_generateId(); + } //end constructor + + // }}} + // {{{ setChecked() + + /** + * Sets whether radio button is checked + * + * @param bool $checked Whether the field is checked or not + * @since 1.0 + * @access public + * @return void + */ + function setChecked($checked) + { + if (!$checked) { + $this->removeAttribute('checked'); + } else { + $this->updateAttributes(array('checked'=>'checked')); + } + } //end func setChecked + + // }}} + // {{{ getChecked() + + /** + * Returns whether radio button is checked + * + * @since 1.0 + * @access public + * @return string + */ + function getChecked() + { + return $this->getAttribute('checked'); + } //end func getChecked + + // }}} + // {{{ toHtml() + + /** + * Returns the radio element in HTML + * + * @since 1.0 + * @access public + * @return string + */ + function toHtml() + { + if (0 == strlen($this->_text)) { + $label = ''; + } elseif ($this->_flagFrozen) { + $label = $this->_text; + } else { + $label = ''; + } + return HTML_QuickForm_input::toHtml() . $label; + } //end func toHtml + + // }}} + // {{{ getFrozenHtml() + + /** + * Returns the value of field without HTML tags + * + * @since 1.0 + * @access public + * @return string + */ + function getFrozenHtml() + { + if ($this->getChecked()) { + return '(x)' . + $this->_getPersistantData(); + } else { + return '( )'; + } + } //end func getFrozenHtml + + // }}} + // {{{ setText() + + /** + * Sets the radio text + * + * @param string $text Text to display near the radio button + * @since 1.1 + * @access public + * @return void + */ + function setText($text) + { + $this->_text = $text; + } //end func setText + + // }}} + // {{{ getText() + + /** + * Returns the radio text + * + * @since 1.1 + * @access public + * @return string + */ + function getText() + { + return $this->_text; + } //end func getText + + // }}} + // {{{ onQuickFormEvent() + + /** + * Called by HTML_QuickForm whenever form event is made on this element + * + * @param string $event Name of event + * @param mixed $arg event arguments + * @param object &$caller calling object + * @since 1.0 + * @access public + * @return void + */ + function onQuickFormEvent($event, $arg, &$caller) + { + switch ($event) { + case 'updateValue': + // constant values override both default and submitted ones + // default values are overriden by submitted + $value = $this->_findValue($caller->_constantValues); + if (null === $value) { + $value = $this->_findValue($caller->_submitValues); + if (null === $value) { + $value = $this->_findValue($caller->_defaultValues); + } + } + if (!is_null($value) && $value == $this->getValue()) { + $this->setChecked(true); + } else { + $this->setChecked(false); + } + break; + case 'setGroupValue': + if ($arg == $this->getValue()) { + $this->setChecked(true); + } else { + $this->setChecked(false); + } + break; + default: + parent::onQuickFormEvent($event, $arg, $caller); + } + return true; + } // end func onQuickFormLoad + + // }}} + // {{{ exportValue() + + /** + * Returns the value attribute if the radio is checked, null if it is not + */ + function exportValue(&$submitValues, $assoc = false) + { + $value = $this->_findValue($submitValues); + if (null === $value) { + $value = $this->getChecked()? $this->getValue(): null; + } elseif ($value != $this->getValue()) { + $value = null; + } + return $this->_prepareValue($value, $assoc); + } + + // }}} +} //end class HTML_QuickForm_radio +?> diff --git a/library/pear/HTML/QuickForm/reset.php b/library/pear/HTML/QuickForm/reset.php new file mode 100644 index 000000000..d784d159a --- /dev/null +++ b/library/pear/HTML/QuickForm/reset.php @@ -0,0 +1,79 @@ + + * @author Bertrand Mansion + * @copyright 2001-2009 The PHP Group + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version CVS: $Id: reset.php,v 1.6 2009/04/04 21:34:04 avb Exp $ + * @link http://pear.php.net/package/HTML_QuickForm + */ + +/** + * Base class for form elements + */ +require_once 'HTML/QuickForm/input.php'; + +/** + * HTML class for a reset type element + * + * @category HTML + * @package HTML_QuickForm + * @author Adam Daniel + * @author Bertrand Mansion + * @version Release: 3.2.11 + * @since 1.0 + */ +class HTML_QuickForm_reset extends HTML_QuickForm_input +{ + // {{{ constructor + + /** + * Class constructor + * + * @param string $elementName (optional)Input field name attribute + * @param string $value (optional)Input field value + * @param mixed $attributes (optional)Either a typical HTML attribute string + * or an associative array + * @since 1.0 + * @access public + * @return void + */ + function HTML_QuickForm_reset($elementName=null, $value=null, $attributes=null) + { + HTML_QuickForm_input::HTML_QuickForm_input($elementName, null, $attributes); + $this->setValue($value); + $this->setType('reset'); + } //end constructor + + // }}} + // {{{ freeze() + + /** + * Freeze the element so that only its value is returned + * + * @access public + * @return void + */ + function freeze() + { + return false; + } //end func freeze + + // }}} + +} //end class HTML_QuickForm_reset +?> diff --git a/library/pear/HTML/QuickForm/select.php b/library/pear/HTML/QuickForm/select.php new file mode 100644 index 000000000..60a577857 --- /dev/null +++ b/library/pear/HTML/QuickForm/select.php @@ -0,0 +1,614 @@ + + * @author Bertrand Mansion + * @author Alexey Borzov + * @copyright 2001-2009 The PHP Group + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version CVS: $Id: select.php,v 1.34 2009/04/04 21:34:04 avb Exp $ + * @link http://pear.php.net/package/HTML_QuickForm + */ + +/** + * Base class for form elements + */ +require_once 'HTML/QuickForm/element.php'; + +/** + * Class to dynamically create an HTML SELECT + * + * @category HTML + * @package HTML_QuickForm + * @author Adam Daniel + * @author Bertrand Mansion + * @author Alexey Borzov + * @version Release: 3.2.11 + * @since 1.0 + */ +class HTML_QuickForm_select extends HTML_QuickForm_element { + + // {{{ properties + + /** + * Contains the select options + * + * @var array + * @since 1.0 + * @access private + */ + var $_options = array(); + + /** + * Default values of the SELECT + * + * @var string + * @since 1.0 + * @access private + */ + var $_values = null; + + // }}} + // {{{ constructor + + /** + * Class constructor + * + * @param string Select name attribute + * @param mixed Label(s) for the select + * @param mixed Data to be used to populate options + * @param mixed Either a typical HTML attribute string or an associative array + * @since 1.0 + * @access public + * @return void + */ + function HTML_QuickForm_select($elementName=null, $elementLabel=null, $options=null, $attributes=null) + { + HTML_QuickForm_element::HTML_QuickForm_element($elementName, $elementLabel, $attributes); + $this->_persistantFreeze = true; + $this->_type = 'select'; + if (isset($options)) { + $this->load($options); + } + } //end constructor + + // }}} + // {{{ apiVersion() + + /** + * Returns the current API version + * + * @since 1.0 + * @access public + * @return double + */ + function apiVersion() + { + return 2.3; + } //end func apiVersion + + // }}} + // {{{ setSelected() + + /** + * Sets the default values of the select box + * + * @param mixed $values Array or comma delimited string of selected values + * @since 1.0 + * @access public + * @return void + */ + function setSelected($values) + { + if (is_string($values) && $this->getMultiple()) { + $values = split("[ ]?,[ ]?", $values); + } + if (is_array($values)) { + $this->_values = array_values($values); + } else { + $this->_values = array($values); + } + } //end func setSelected + + // }}} + // {{{ getSelected() + + /** + * Returns an array of the selected values + * + * @since 1.0 + * @access public + * @return array of selected values + */ + function getSelected() + { + return $this->_values; + } // end func getSelected + + // }}} + // {{{ setName() + + /** + * Sets the input field name + * + * @param string $name Input field name attribute + * @since 1.0 + * @access public + * @return void + */ + function setName($name) + { + $this->updateAttributes(array('name' => $name)); + } //end func setName + + // }}} + // {{{ getName() + + /** + * Returns the element name + * + * @since 1.0 + * @access public + * @return string + */ + function getName() + { + return $this->getAttribute('name'); + } //end func getName + + // }}} + // {{{ getPrivateName() + + /** + * Returns the element name (possibly with brackets appended) + * + * @since 1.0 + * @access public + * @return string + */ + function getPrivateName() + { + if ($this->getAttribute('multiple')) { + return $this->getName() . '[]'; + } else { + return $this->getName(); + } + } //end func getPrivateName + + // }}} + // {{{ setValue() + + /** + * Sets the value of the form element + * + * @param mixed $values Array or comma delimited string of selected values + * @since 1.0 + * @access public + * @return void + */ + function setValue($value) + { + $this->setSelected($value); + } // end func setValue + + // }}} + // {{{ getValue() + + /** + * Returns an array of the selected values + * + * @since 1.0 + * @access public + * @return array of selected values + */ + function getValue() + { + return $this->_values; + } // end func getValue + + // }}} + // {{{ setSize() + + /** + * Sets the select field size, only applies to 'multiple' selects + * + * @param int $size Size of select field + * @since 1.0 + * @access public + * @return void + */ + function setSize($size) + { + $this->updateAttributes(array('size' => $size)); + } //end func setSize + + // }}} + // {{{ getSize() + + /** + * Returns the select field size + * + * @since 1.0 + * @access public + * @return int + */ + function getSize() + { + return $this->getAttribute('size'); + } //end func getSize + + // }}} + // {{{ setMultiple() + + /** + * Sets the select mutiple attribute + * + * @param bool $multiple Whether the select supports multi-selections + * @since 1.2 + * @access public + * @return void + */ + function setMultiple($multiple) + { + if ($multiple) { + $this->updateAttributes(array('multiple' => 'multiple')); + } else { + $this->removeAttribute('multiple'); + } + } //end func setMultiple + + // }}} + // {{{ getMultiple() + + /** + * Returns the select mutiple attribute + * + * @since 1.2 + * @access public + * @return bool true if multiple select, false otherwise + */ + function getMultiple() + { + return (bool)$this->getAttribute('multiple'); + } //end func getMultiple + + // }}} + // {{{ addOption() + + /** + * Adds a new OPTION to the SELECT + * + * @param string $text Display text for the OPTION + * @param string $value Value for the OPTION + * @param mixed $attributes Either a typical HTML attribute string + * or an associative array + * @since 1.0 + * @access public + * @return void + */ + function addOption($text, $value, $attributes=null) + { + if (null === $attributes) { + $attributes = array('value' => (string)$value); + } else { + $attributes = $this->_parseAttributes($attributes); + if (isset($attributes['selected'])) { + // the 'selected' attribute will be set in toHtml() + $this->_removeAttr('selected', $attributes); + if (is_null($this->_values)) { + $this->_values = array($value); + } elseif (!in_array($value, $this->_values)) { + $this->_values[] = $value; + } + } + $this->_updateAttrArray($attributes, array('value' => (string)$value)); + } + $this->_options[] = array('text' => $text, 'attr' => $attributes); + } // end func addOption + + // }}} + // {{{ loadArray() + + /** + * Loads the options from an associative array + * + * @param array $arr Associative array of options + * @param mixed $values (optional) Array or comma delimited string of selected values + * @since 1.0 + * @access public + * @return PEAR_Error on error or true + * @throws PEAR_Error + */ + function loadArray($arr, $values=null) + { + if (!is_array($arr)) { + return PEAR::raiseError('Argument 1 of HTML_Select::loadArray is not a valid array'); + } + if (isset($values)) { + $this->setSelected($values); + } + foreach ($arr as $key => $val) { + // Warning: new API since release 2.3 + $this->addOption($val, $key); + } + return true; + } // end func loadArray + + // }}} + // {{{ loadDbResult() + + /** + * Loads the options from DB_result object + * + * If no column names are specified the first two columns of the result are + * used as the text and value columns respectively + * @param object $result DB_result object + * @param string $textCol (optional) Name of column to display as the OPTION text + * @param string $valueCol (optional) Name of column to use as the OPTION value + * @param mixed $values (optional) Array or comma delimited string of selected values + * @since 1.0 + * @access public + * @return PEAR_Error on error or true + * @throws PEAR_Error + */ + function loadDbResult(&$result, $textCol=null, $valueCol=null, $values=null) + { + if (!is_object($result) || !is_a($result, 'db_result')) { + return PEAR::raiseError('Argument 1 of HTML_Select::loadDbResult is not a valid DB_result'); + } + if (isset($values)) { + $this->setValue($values); + } + $fetchMode = ($textCol && $valueCol) ? DB_FETCHMODE_ASSOC : DB_FETCHMODE_ORDERED; + while (is_array($row = $result->fetchRow($fetchMode)) ) { + if ($fetchMode == DB_FETCHMODE_ASSOC) { + $this->addOption($row[$textCol], $row[$valueCol]); + } else { + $this->addOption($row[0], $row[1]); + } + } + return true; + } // end func loadDbResult + + // }}} + // {{{ loadQuery() + + /** + * Queries a database and loads the options from the results + * + * @param mixed $conn Either an existing DB connection or a valid dsn + * @param string $sql SQL query string + * @param string $textCol (optional) Name of column to display as the OPTION text + * @param string $valueCol (optional) Name of column to use as the OPTION value + * @param mixed $values (optional) Array or comma delimited string of selected values + * @since 1.1 + * @access public + * @return void + * @throws PEAR_Error + */ + function loadQuery(&$conn, $sql, $textCol=null, $valueCol=null, $values=null) + { + if (is_string($conn)) { + require_once('DB.php'); + $dbConn = &DB::connect($conn, true); + if (DB::isError($dbConn)) { + return $dbConn; + } + } elseif (is_subclass_of($conn, "db_common")) { + $dbConn = &$conn; + } else { + return PEAR::raiseError('Argument 1 of HTML_Select::loadQuery is not a valid type'); + } + $result = $dbConn->query($sql); + if (DB::isError($result)) { + return $result; + } + $this->loadDbResult($result, $textCol, $valueCol, $values); + $result->free(); + if (is_string($conn)) { + $dbConn->disconnect(); + } + return true; + } // end func loadQuery + + // }}} + // {{{ load() + + /** + * Loads options from different types of data sources + * + * This method is a simulated overloaded method. The arguments, other than the + * first are optional and only mean something depending on the type of the first argument. + * If the first argument is an array then all arguments are passed in order to loadArray. + * If the first argument is a db_result then all arguments are passed in order to loadDbResult. + * If the first argument is a string or a DB connection then all arguments are + * passed in order to loadQuery. + * @param mixed $options Options source currently supports assoc array or DB_result + * @param mixed $param1 (optional) See function detail + * @param mixed $param2 (optional) See function detail + * @param mixed $param3 (optional) See function detail + * @param mixed $param4 (optional) See function detail + * @since 1.1 + * @access public + * @return PEAR_Error on error or true + * @throws PEAR_Error + */ + function load(&$options, $param1=null, $param2=null, $param3=null, $param4=null) + { + switch (true) { + case is_array($options): + return $this->loadArray($options, $param1); + break; + case (is_a($options, 'db_result')): + return $this->loadDbResult($options, $param1, $param2, $param3); + break; + case (is_string($options) && !empty($options) || is_subclass_of($options, "db_common")): + return $this->loadQuery($options, $param1, $param2, $param3, $param4); + break; + } + } // end func load + + // }}} + // {{{ toHtml() + + /** + * Returns the SELECT in HTML + * + * @since 1.0 + * @access public + * @return string + */ + function toHtml() + { + if ($this->_flagFrozen) { + return $this->getFrozenHtml(); + } else { + $tabs = $this->_getTabs(); + $strHtml = ''; + + if ($this->getComment() != '') { + $strHtml .= $tabs . '\n"; + } + + if (!$this->getMultiple()) { + $attrString = $this->_getAttrString($this->_attributes); + } else { + $myName = $this->getName(); + $this->setName($myName . '[]'); + $attrString = $this->_getAttrString($this->_attributes); + $this->setName($myName); + } + $strHtml .= $tabs . '\n"; + + $strValues = is_array($this->_values)? array_map('strval', $this->_values): array(); + foreach ($this->_options as $option) { + if (!empty($strValues) && in_array($option['attr']['value'], $strValues, true)) { + $option['attr']['selected'] = 'selected'; + } + $strHtml .= $tabs . "\t_getAttrString($option['attr']) . '>' . + $option['text'] . "\n"; + } + + return $strHtml . $tabs . ''; + } + } //end func toHtml + + // }}} + // {{{ getFrozenHtml() + + /** + * Returns the value of field without HTML tags + * + * @since 1.0 + * @access public + * @return string + */ + function getFrozenHtml() + { + $value = array(); + if (is_array($this->_values)) { + foreach ($this->_values as $key => $val) { + for ($i = 0, $optCount = count($this->_options); $i < $optCount; $i++) { + if (0 == strcmp($val, $this->_options[$i]['attr']['value'])) { + $value[$key] = $this->_options[$i]['text']; + break; + } + } + } + } + $html = empty($value)? ' ': join('
    ', $value); + if ($this->_persistantFreeze) { + $name = $this->getPrivateName(); + // Only use id attribute if doing single hidden input + if (1 == count($value)) { + $id = $this->getAttribute('id'); + $idAttr = isset($id)? array('id' => $id): array(); + } else { + $idAttr = array(); + } + foreach ($value as $key => $item) { + $html .= '_getAttrString(array( + 'type' => 'hidden', + 'name' => $name, + 'value' => $this->_values[$key] + ) + $idAttr) . ' />'; + } + } + return $html; + } //end func getFrozenHtml + + // }}} + // {{{ exportValue() + + /** + * We check the options and return only the values that _could_ have been + * selected. We also return a scalar value if select is not "multiple" + */ + function exportValue(&$submitValues, $assoc = false) + { + $value = $this->_findValue($submitValues); + if (is_null($value)) { + $value = $this->getValue(); + } elseif(!is_array($value)) { + $value = array($value); + } + if (is_array($value) && !empty($this->_options)) { + $cleanValue = null; + foreach ($value as $v) { + for ($i = 0, $optCount = count($this->_options); $i < $optCount; $i++) { + if (0 == strcmp($v, $this->_options[$i]['attr']['value'])) { + $cleanValue[] = $v; + break; + } + } + } + } else { + $cleanValue = $value; + } + if (is_array($cleanValue) && !$this->getMultiple()) { + return $this->_prepareValue($cleanValue[0], $assoc); + } else { + return $this->_prepareValue($cleanValue, $assoc); + } + } + + // }}} + // {{{ onQuickFormEvent() + + function onQuickFormEvent($event, $arg, &$caller) + { + if ('updateValue' == $event) { + $value = $this->_findValue($caller->_constantValues); + if (null === $value) { + $value = $this->_findValue($caller->_submitValues); + // Fix for bug #4465 & #5269 + // XXX: should we push this to element::onQuickFormEvent()? + if (null === $value && (!$caller->isSubmitted() || !$this->getMultiple())) { + $value = $this->_findValue($caller->_defaultValues); + } + } + if (null !== $value) { + $this->setValue($value); + } + return true; + } else { + return parent::onQuickFormEvent($event, $arg, $caller); + } + } + + // }}} +} //end class HTML_QuickForm_select +?> diff --git a/library/pear/HTML/QuickForm/static.php b/library/pear/HTML/QuickForm/static.php new file mode 100644 index 000000000..096b61204 --- /dev/null +++ b/library/pear/HTML/QuickForm/static.php @@ -0,0 +1,201 @@ + + * @copyright 2001-2009 The PHP Group + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version CVS: $Id: static.php,v 1.8 2009/04/04 21:34:04 avb Exp $ + * @link http://pear.php.net/package/HTML_QuickForm + */ + +/** + * Base class for form elements + */ +require_once 'HTML/QuickForm/element.php'; + +/** + * HTML class for static data + * + * @category HTML + * @package HTML_QuickForm + * @author Wojciech Gdela + * @version Release: 3.2.11 + * @since 2.7 + */ +class HTML_QuickForm_static extends HTML_QuickForm_element { + + // {{{ properties + + /** + * Display text + * @var string + * @access private + */ + var $_text = null; + + // }}} + // {{{ constructor + + /** + * Class constructor + * + * @param string $elementLabel (optional)Label + * @param string $text (optional)Display text + * @access public + * @return void + */ + function HTML_QuickForm_static($elementName=null, $elementLabel=null, $text=null) + { + HTML_QuickForm_element::HTML_QuickForm_element($elementName, $elementLabel); + $this->_persistantFreeze = false; + $this->_type = 'static'; + $this->_text = $text; + } //end constructor + + // }}} + // {{{ setName() + + /** + * Sets the element name + * + * @param string $name Element name + * @access public + * @return void + */ + function setName($name) + { + $this->updateAttributes(array('name'=>$name)); + } //end func setName + + // }}} + // {{{ getName() + + /** + * Returns the element name + * + * @access public + * @return string + */ + function getName() + { + return $this->getAttribute('name'); + } //end func getName + + // }}} + // {{{ setText() + + /** + * Sets the text + * + * @param string $text + * @access public + * @return void + */ + function setText($text) + { + $this->_text = $text; + } // end func setText + + // }}} + // {{{ setValue() + + /** + * Sets the text (uses the standard setValue call to emulate a form element. + * + * @param string $text + * @access public + * @return void + */ + function setValue($text) + { + $this->setText($text); + } // end func setValue + + // }}} + // {{{ toHtml() + + /** + * Returns the static text element in HTML + * + * @access public + * @return string + */ + function toHtml() + { + return $this->_getTabs() . $this->_text; + } //end func toHtml + + // }}} + // {{{ getFrozenHtml() + + /** + * Returns the value of field without HTML tags + * + * @access public + * @return string + */ + function getFrozenHtml() + { + return $this->toHtml(); + } //end func getFrozenHtml + + // }}} + // {{{ onQuickFormEvent() + + /** + * Called by HTML_QuickForm whenever form event is made on this element + * + * @param string $event Name of event + * @param mixed $arg event arguments + * @param object &$caller calling object + * @since 1.0 + * @access public + * @return void + * @throws + */ + function onQuickFormEvent($event, $arg, &$caller) + { + switch ($event) { + case 'updateValue': + // do NOT use submitted values for static elements + $value = $this->_findValue($caller->_constantValues); + if (null === $value) { + $value = $this->_findValue($caller->_defaultValues); + } + if (null !== $value) { + $this->setValue($value); + } + break; + default: + parent::onQuickFormEvent($event, $arg, $caller); + } + return true; + } // end func onQuickFormEvent + + // }}} + // {{{ exportValue() + + /** + * We override this here because we don't want any values from static elements + */ + function exportValue(&$submitValues, $assoc = false) + { + return null; + } + + // }}} +} //end class HTML_QuickForm_static +?> diff --git a/library/pear/HTML/QuickForm/submit.php b/library/pear/HTML/QuickForm/submit.php new file mode 100644 index 000000000..2c12d73f7 --- /dev/null +++ b/library/pear/HTML/QuickForm/submit.php @@ -0,0 +1,89 @@ + + * @author Bertrand Mansion + * @copyright 2001-2009 The PHP Group + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version CVS: $Id: submit.php,v 1.6 2009/04/04 21:34:04 avb Exp $ + * @link http://pear.php.net/package/HTML_QuickForm + */ + +/** + * Base class for form elements + */ +require_once 'HTML/QuickForm/input.php'; + +/** + * HTML class for a submit type element + * + * @category HTML + * @package HTML_QuickForm + * @author Adam Daniel + * @author Bertrand Mansion + * @version Release: 3.2.11 + * @since 1.0 + */ +class HTML_QuickForm_submit extends HTML_QuickForm_input +{ + // {{{ constructor + + /** + * Class constructor + * + * @param string Input field name attribute + * @param string Input field value + * @param mixed Either a typical HTML attribute string or an associative array + * @since 1.0 + * @access public + * @return void + */ + function HTML_QuickForm_submit($elementName=null, $value=null, $attributes=null) + { + HTML_QuickForm_input::HTML_QuickForm_input($elementName, null, $attributes); + $this->setValue($value); + $this->setType('submit'); + } //end constructor + + // }}} + // {{{ freeze() + + /** + * Freeze the element so that only its value is returned + * + * @access public + * @return void + */ + function freeze() + { + return false; + } //end func freeze + + // }}} + // {{{ exportValue() + + /** + * Only return the value if it is found within $submitValues (i.e. if + * this particular submit button was clicked) + */ + function exportValue(&$submitValues, $assoc = false) + { + return $this->_prepareValue($this->_findValue($submitValues), $assoc); + } + + // }}} +} //end class HTML_QuickForm_submit +?> diff --git a/library/pear/HTML/QuickForm/text.php b/library/pear/HTML/QuickForm/text.php new file mode 100644 index 000000000..7c0f90842 --- /dev/null +++ b/library/pear/HTML/QuickForm/text.php @@ -0,0 +1,98 @@ + + * @author Bertrand Mansion + * @copyright 2001-2009 The PHP Group + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version CVS: $Id: text.php,v 1.7 2009/04/04 21:34:04 avb Exp $ + * @link http://pear.php.net/package/HTML_QuickForm + */ + +/** + * Base class for form elements + */ +require_once 'HTML/QuickForm/input.php'; + +/** + * HTML class for a text field + * + * @category HTML + * @package HTML_QuickForm + * @author Adam Daniel + * @author Bertrand Mansion + * @version Release: 3.2.11 + * @since 1.0 + */ +class HTML_QuickForm_text extends HTML_QuickForm_input +{ + + // {{{ constructor + + /** + * Class constructor + * + * @param string $elementName (optional)Input field name attribute + * @param string $elementLabel (optional)Input field label + * @param mixed $attributes (optional)Either a typical HTML attribute string + * or an associative array + * @since 1.0 + * @access public + * @return void + */ + function HTML_QuickForm_text($elementName=null, $elementLabel=null, $attributes=null) + { + HTML_QuickForm_input::HTML_QuickForm_input($elementName, $elementLabel, $attributes); + $this->_persistantFreeze = true; + $this->setType('text'); + } //end constructor + + // }}} + // {{{ setSize() + + /** + * Sets size of text field + * + * @param string $size Size of text field + * @since 1.3 + * @access public + * @return void + */ + function setSize($size) + { + $this->updateAttributes(array('size'=>$size)); + } //end func setSize + + // }}} + // {{{ setMaxlength() + + /** + * Sets maxlength of text field + * + * @param string $maxlength Maximum length of text field + * @since 1.3 + * @access public + * @return void + */ + function setMaxlength($maxlength) + { + $this->updateAttributes(array('maxlength'=>$maxlength)); + } //end func setMaxlength + + // }}} + +} //end class HTML_QuickForm_text +?> diff --git a/library/pear/HTML/QuickForm/textarea.php b/library/pear/HTML/QuickForm/textarea.php new file mode 100644 index 000000000..873b1d47f --- /dev/null +++ b/library/pear/HTML/QuickForm/textarea.php @@ -0,0 +1,229 @@ + + * @author Bertrand Mansion + * @copyright 2001-2009 The PHP Group + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version CVS: $Id: textarea.php,v 1.13 2009/04/04 21:34:04 avb Exp $ + * @link http://pear.php.net/package/HTML_QuickForm + */ + +/** + * Base class for form elements + */ +require_once 'HTML/QuickForm/element.php'; + +/** + * HTML class for a textarea type field + * + * @category HTML + * @package HTML_QuickForm + * @author Adam Daniel + * @author Bertrand Mansion + * @version Release: 3.2.11 + * @since 1.0 + */ +class HTML_QuickForm_textarea extends HTML_QuickForm_element +{ + // {{{ properties + + /** + * Field value + * @var string + * @since 1.0 + * @access private + */ + var $_value = null; + + // }}} + // {{{ constructor + + /** + * Class constructor + * + * @param string Input field name attribute + * @param mixed Label(s) for a field + * @param mixed Either a typical HTML attribute string or an associative array + * @since 1.0 + * @access public + * @return void + */ + function HTML_QuickForm_textarea($elementName=null, $elementLabel=null, $attributes=null) + { + HTML_QuickForm_element::HTML_QuickForm_element($elementName, $elementLabel, $attributes); + $this->_persistantFreeze = true; + $this->_type = 'textarea'; + } //end constructor + + // }}} + // {{{ setName() + + /** + * Sets the input field name + * + * @param string $name Input field name attribute + * @since 1.0 + * @access public + * @return void + */ + function setName($name) + { + $this->updateAttributes(array('name'=>$name)); + } //end func setName + + // }}} + // {{{ getName() + + /** + * Returns the element name + * + * @since 1.0 + * @access public + * @return string + */ + function getName() + { + return $this->getAttribute('name'); + } //end func getName + + // }}} + // {{{ setValue() + + /** + * Sets value for textarea element + * + * @param string $value Value for textarea element + * @since 1.0 + * @access public + * @return void + */ + function setValue($value) + { + $this->_value = $value; + } //end func setValue + + // }}} + // {{{ getValue() + + /** + * Returns the value of the form element + * + * @since 1.0 + * @access public + * @return string + */ + function getValue() + { + return $this->_value; + } // end func getValue + + // }}} + // {{{ setWrap() + + /** + * Sets wrap type for textarea element + * + * @param string $wrap Wrap type + * @since 1.0 + * @access public + * @return void + */ + function setWrap($wrap) + { + $this->updateAttributes(array('wrap' => $wrap)); + } //end func setWrap + + // }}} + // {{{ setRows() + + /** + * Sets height in rows for textarea element + * + * @param string $rows Height expressed in rows + * @since 1.0 + * @access public + * @return void + */ + function setRows($rows) + { + $this->updateAttributes(array('rows' => $rows)); + } //end func setRows + + // }}} + // {{{ setCols() + + /** + * Sets width in cols for textarea element + * + * @param string $cols Width expressed in cols + * @since 1.0 + * @access public + * @return void + */ + function setCols($cols) + { + $this->updateAttributes(array('cols' => $cols)); + } //end func setCols + + // }}} + // {{{ toHtml() + + /** + * Returns the textarea element in HTML + * + * @since 1.0 + * @access public + * @return string + */ + function toHtml() + { + if ($this->_flagFrozen) { + return $this->getFrozenHtml(); + } else { + return $this->_getTabs() . + '_getAttrString($this->_attributes) . '>' . + // because we wrap the form later we don't want the text indented + preg_replace("/(\r\n|\n|\r)/", ' ', htmlspecialchars($this->_value)) . + ''; + } + } //end func toHtml + + // }}} + // {{{ getFrozenHtml() + + /** + * Returns the value of field without HTML tags (in this case, value is changed to a mask) + * + * @since 1.0 + * @access public + * @return string + */ + function getFrozenHtml() + { + $value = htmlspecialchars($this->getValue()); + if ($this->getAttribute('wrap') == 'off') { + $html = $this->_getTabs() . '
    ' . $value."
    \n"; + } else { + $html = nl2br($value)."\n"; + } + return $html . $this->_getPersistantData(); + } //end func getFrozenHtml + + // }}} + +} //end class HTML_QuickForm_textarea +?> diff --git a/library/pear/HTML/QuickForm/xbutton.php b/library/pear/HTML/QuickForm/xbutton.php new file mode 100644 index 000000000..9e5c639aa --- /dev/null +++ b/library/pear/HTML/QuickForm/xbutton.php @@ -0,0 +1,153 @@ + element + * + * PHP versions 4 and 5 + * + * LICENSE: This source file is subject to version 3.01 of the PHP license + * that is available through the world-wide-web at the following URI: + * http://www.php.net/license/3_01.txt If you did not receive a copy of + * the PHP License and are unable to obtain it through the web, please + * send a note to license@php.net so we can mail you a copy immediately. + * + * @category HTML + * @package HTML_QuickForm + * @author Alexey Borzov + * @copyright 2001-2009 The PHP Group + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version CVS: $Id: xbutton.php,v 1.3 2009/04/04 21:34:04 avb Exp $ + * @link http://pear.php.net/package/HTML_QuickForm + */ + +/** + * Base class for form elements + */ +require_once 'HTML/QuickForm/element.php'; + +/** + * Class for HTML 4.0 tags) + * @param mixed Either a typical HTML attribute string or an associative array + * @access public + */ + function HTML_QuickForm_xbutton($elementName = null, $elementContent = null, $attributes = null) + { + $this->HTML_QuickForm_element($elementName, null, $attributes); + $this->setContent($elementContent); + $this->setPersistantFreeze(false); + $this->_type = 'xbutton'; + } + + + function toHtml() + { + return 'getAttributes(true) . '>' . $this->_content . ''; + } + + + function getFrozenHtml() + { + return $this->toHtml(); + } + + + function freeze() + { + return false; + } + + + function setName($name) + { + $this->updateAttributes(array( + 'name' => $name + )); + } + + + function getName() + { + return $this->getAttribute('name'); + } + + + function setValue($value) + { + $this->updateAttributes(array( + 'value' => $value + )); + } + + + function getValue() + { + return $this->getAttribute('value'); + } + + + /** + * Sets the contents of the button element + * + * @param string Button content (HTML to add between tags) + */ + function setContent($content) + { + $this->_content = $content; + } + + + function onQuickFormEvent($event, $arg, &$caller) + { + if ('updateValue' != $event) { + return parent::onQuickFormEvent($event, $arg, $caller); + } else { + $value = $this->_findValue($caller->_constantValues); + if (null === $value) { + $value = $this->_findValue($caller->_defaultValues); + } + if (null !== $value) { + $this->setValue($value); + } + } + return true; + } + + + /** + * Returns a 'safe' element's value + * + * The value is only returned if the button's type is "submit" and if this + * particlular button was clicked + */ + function exportValue(&$submitValues, $assoc = false) + { + if ('submit' == $this->getAttribute('type')) { + return $this->_prepareValue($this->_findValue($submitValues), $assoc); + } else { + return null; + } + } +} +?> diff --git a/library/pear/OS/Guess.php b/library/pear/OS/Guess.php new file mode 100644 index 000000000..e557ae414 --- /dev/null +++ b/library/pear/OS/Guess.php @@ -0,0 +1,338 @@ + + * @author Gregory Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Guess.php 278521 2009-04-09 22:24:12Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since PEAR 0.1 + */ + +// {{{ uname examples + +// php_uname() without args returns the same as 'uname -a', or a PHP-custom +// string for Windows. +// PHP versions prior to 4.3 return the uname of the host where PHP was built, +// as of 4.3 it returns the uname of the host running the PHP code. +// +// PC RedHat Linux 7.1: +// Linux host.example.com 2.4.2-2 #1 Sun Apr 8 20:41:30 EDT 2001 i686 unknown +// +// PC Debian Potato: +// Linux host 2.4.17 #2 SMP Tue Feb 12 15:10:04 CET 2002 i686 unknown +// +// PC FreeBSD 3.3: +// FreeBSD host.example.com 3.3-STABLE FreeBSD 3.3-STABLE #0: Mon Feb 21 00:42:31 CET 2000 root@example.com:/usr/src/sys/compile/CONFIG i386 +// +// PC FreeBSD 4.3: +// FreeBSD host.example.com 4.3-RELEASE FreeBSD 4.3-RELEASE #1: Mon Jun 25 11:19:43 EDT 2001 root@example.com:/usr/src/sys/compile/CONFIG i386 +// +// PC FreeBSD 4.5: +// FreeBSD host.example.com 4.5-STABLE FreeBSD 4.5-STABLE #0: Wed Feb 6 23:59:23 CET 2002 root@example.com:/usr/src/sys/compile/CONFIG i386 +// +// PC FreeBSD 4.5 w/uname from GNU shellutils: +// FreeBSD host.example.com 4.5-STABLE FreeBSD 4.5-STABLE #0: Wed Feb i386 unknown +// +// HP 9000/712 HP-UX 10: +// HP-UX iq B.10.10 A 9000/712 2008429113 two-user license +// +// HP 9000/712 HP-UX 10 w/uname from GNU shellutils: +// HP-UX host B.10.10 A 9000/712 unknown +// +// IBM RS6000/550 AIX 4.3: +// AIX host 3 4 000003531C00 +// +// AIX 4.3 w/uname from GNU shellutils: +// AIX host 3 4 000003531C00 unknown +// +// SGI Onyx IRIX 6.5 w/uname from GNU shellutils: +// IRIX64 host 6.5 01091820 IP19 mips +// +// SGI Onyx IRIX 6.5: +// IRIX64 host 6.5 01091820 IP19 +// +// SparcStation 20 Solaris 8 w/uname from GNU shellutils: +// SunOS host.example.com 5.8 Generic_108528-12 sun4m sparc +// +// SparcStation 20 Solaris 8: +// SunOS host.example.com 5.8 Generic_108528-12 sun4m sparc SUNW,SPARCstation-20 +// +// Mac OS X (Darwin) +// Darwin home-eden.local 7.5.0 Darwin Kernel Version 7.5.0: Thu Aug 5 19:26:16 PDT 2004; root:xnu/xnu-517.7.21.obj~3/RELEASE_PPC Power Macintosh +// +// Mac OS X early versions +// + +// }}} + +/* TODO: + * - define endianness, to allow matchSignature("bigend") etc. + */ + +/** + * Retrieves information about the current operating system + * + * This class uses php_uname() to grok information about the current OS + * + * @category pear + * @package PEAR + * @author Stig Bakken + * @author Gregory Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.1 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 0.1 + */ +class OS_Guess +{ + var $sysname; + var $nodename; + var $cpu; + var $release; + var $extra; + + function OS_Guess($uname = null) + { + list($this->sysname, + $this->release, + $this->cpu, + $this->extra, + $this->nodename) = $this->parseSignature($uname); + } + + function parseSignature($uname = null) + { + static $sysmap = array( + 'HP-UX' => 'hpux', + 'IRIX64' => 'irix', + ); + static $cpumap = array( + 'i586' => 'i386', + 'i686' => 'i386', + 'ppc' => 'powerpc', + ); + if ($uname === null) { + $uname = php_uname(); + } + $parts = preg_split('/\s+/', trim($uname)); + $n = count($parts); + + $release = $machine = $cpu = ''; + $sysname = $parts[0]; + $nodename = $parts[1]; + $cpu = $parts[$n-1]; + $extra = ''; + if ($cpu == 'unknown') { + $cpu = $parts[$n - 2]; + } + + switch ($sysname) { + case 'AIX' : + $release = "$parts[3].$parts[2]"; + break; + case 'Windows' : + switch ($parts[1]) { + case '95/98': + $release = '9x'; + break; + default: + $release = $parts[1]; + break; + } + $cpu = 'i386'; + break; + case 'Linux' : + $extra = $this->_detectGlibcVersion(); + // use only the first two digits from the kernel version + $release = preg_replace('/^([0-9]+\.[0-9]+).*/', '\1', $parts[2]); + break; + case 'Mac' : + $sysname = 'darwin'; + $nodename = $parts[2]; + $release = $parts[3]; + if ($cpu == 'Macintosh') { + if ($parts[$n - 2] == 'Power') { + $cpu = 'powerpc'; + } + } + break; + case 'Darwin' : + if ($cpu == 'Macintosh') { + if ($parts[$n - 2] == 'Power') { + $cpu = 'powerpc'; + } + } + $release = preg_replace('/^([0-9]+\.[0-9]+).*/', '\1', $parts[2]); + break; + default: + $release = preg_replace('/-.*/', '', $parts[2]); + break; + } + + if (isset($sysmap[$sysname])) { + $sysname = $sysmap[$sysname]; + } else { + $sysname = strtolower($sysname); + } + if (isset($cpumap[$cpu])) { + $cpu = $cpumap[$cpu]; + } + return array($sysname, $release, $cpu, $extra, $nodename); + } + + function _detectGlibcVersion() + { + static $glibc = false; + if ($glibc !== false) { + return $glibc; // no need to run this multiple times + } + $major = $minor = 0; + include_once "System.php"; + // Use glibc's header file to + // get major and minor version number: + if (@file_exists('/usr/include/features.h') && + @is_readable('/usr/include/features.h')) { + if (!@file_exists('/usr/bin/cpp') || !@is_executable('/usr/bin/cpp')) { + $features_file = fopen('/usr/include/features.h', 'rb'); + while (!feof($features_file)) { + $line = fgets($features_file, 8192); + if (!$line || (strpos($line, '#define') === false)) { + continue; + } + if (strpos($line, '__GLIBC__')) { + // major version number #define __GLIBC__ version + $line = preg_split('/\s+/', $line); + $glibc_major = trim($line[2]); + if (isset($glibc_minor)) { + break; + } + continue; + } + + if (strpos($line, '__GLIBC_MINOR__')) { + // got the minor version number + // #define __GLIBC_MINOR__ version + $line = preg_split('/\s+/', $line); + $glibc_minor = trim($line[2]); + if (isset($glibc_major)) { + break; + } + continue; + } + } + fclose($features_file); + if (!isset($glibc_major) || !isset($glibc_minor)) { + return $glibc = ''; + } + return $glibc = 'glibc' . trim($glibc_major) . "." . trim($glibc_minor) ; + } // no cpp + + $tmpfile = System::mktemp("glibctest"); + $fp = fopen($tmpfile, "w"); + fwrite($fp, "#include \n__GLIBC__ __GLIBC_MINOR__\n"); + fclose($fp); + $cpp = popen("/usr/bin/cpp $tmpfile", "r"); + while ($line = fgets($cpp, 1024)) { + if ($line{0} == '#' || trim($line) == '') { + continue; + } + + if (list($major, $minor) = explode(' ', trim($line))) { + break; + } + } + pclose($cpp); + unlink($tmpfile); + } // features.h + + if (!($major && $minor) && @is_link('/lib/libc.so.6')) { + // Let's try reading the libc.so.6 symlink + if (preg_match('/^libc-(.*)\.so$/', basename(readlink('/lib/libc.so.6')), $matches)) { + list($major, $minor) = explode('.', $matches[1]); + } + } + + if (!($major && $minor)) { + return $glibc = ''; + } + + return $glibc = "glibc{$major}.{$minor}"; + } + + function getSignature() + { + if (empty($this->extra)) { + return "{$this->sysname}-{$this->release}-{$this->cpu}"; + } + return "{$this->sysname}-{$this->release}-{$this->cpu}-{$this->extra}"; + } + + function getSysname() + { + return $this->sysname; + } + + function getNodename() + { + return $this->nodename; + } + + function getCpu() + { + return $this->cpu; + } + + function getRelease() + { + return $this->release; + } + + function getExtra() + { + return $this->extra; + } + + function matchSignature($match) + { + $fragments = is_array($match) ? $match : explode('-', $match); + $n = count($fragments); + $matches = 0; + if ($n > 0) { + $matches += $this->_matchFragment($fragments[0], $this->sysname); + } + if ($n > 1) { + $matches += $this->_matchFragment($fragments[1], $this->release); + } + if ($n > 2) { + $matches += $this->_matchFragment($fragments[2], $this->cpu); + } + if ($n > 3) { + $matches += $this->_matchFragment($fragments[3], $this->extra); + } + return ($matches == $n); + } + + function _matchFragment($fragment, $value) + { + if (strcspn($fragment, '*?') < strlen($fragment)) { + $reg = '/^' . str_replace(array('*', '?', '/'), array('.*', '.', '\\/'), $fragment) . '\\z/'; + return preg_match($reg, $value); + } + return ($fragment == '*' || !strcasecmp($fragment, $value)); + } + +} +/* + * Local Variables: + * indent-tabs-mode: nil + * c-basic-offset: 4 + * End: + */ \ No newline at end of file diff --git a/library/pear/PEAR.php b/library/pear/PEAR.php new file mode 100644 index 000000000..03a4b37ff --- /dev/null +++ b/library/pear/PEAR.php @@ -0,0 +1,1063 @@ + + * @author Stig Bakken + * @author Tomas V.V.Cox + * @author Greg Beaver + * @copyright 1997-2010 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: PEAR.php 299159 2010-05-08 22:32:52Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 0.1 + */ + +/**#@+ + * ERROR constants + */ +define('PEAR_ERROR_RETURN', 1); +define('PEAR_ERROR_PRINT', 2); +define('PEAR_ERROR_TRIGGER', 4); +define('PEAR_ERROR_DIE', 8); +define('PEAR_ERROR_CALLBACK', 16); +/** + * WARNING: obsolete + * @deprecated + */ +define('PEAR_ERROR_EXCEPTION', 32); +/**#@-*/ +define('PEAR_ZE2', (function_exists('version_compare') && + version_compare(zend_version(), "2-dev", "ge"))); + +if (substr(PHP_OS, 0, 3) == 'WIN') { + define('OS_WINDOWS', true); + define('OS_UNIX', false); + define('PEAR_OS', 'Windows'); +} else { + define('OS_WINDOWS', false); + define('OS_UNIX', true); + define('PEAR_OS', 'Unix'); // blatant assumption +} + +$GLOBALS['_PEAR_default_error_mode'] = PEAR_ERROR_RETURN; +$GLOBALS['_PEAR_default_error_options'] = E_USER_NOTICE; +$GLOBALS['_PEAR_destructor_object_list'] = array(); +$GLOBALS['_PEAR_shutdown_funcs'] = array(); +$GLOBALS['_PEAR_error_handler_stack'] = array(); + +@ini_set('track_errors', true); + +/** + * Base class for other PEAR classes. Provides rudimentary + * emulation of destructors. + * + * If you want a destructor in your class, inherit PEAR and make a + * destructor method called _yourclassname (same name as the + * constructor, but with a "_" prefix). Also, in your constructor you + * have to call the PEAR constructor: $this->PEAR();. + * The destructor method will be called without parameters. Note that + * at in some SAPI implementations (such as Apache), any output during + * the request shutdown (in which destructors are called) seems to be + * discarded. If you need to get any debug information from your + * destructor, use error_log(), syslog() or something similar. + * + * IMPORTANT! To use the emulated destructors you need to create the + * objects by reference: $obj =& new PEAR_child; + * + * @category pear + * @package PEAR + * @author Stig Bakken + * @author Tomas V.V. Cox + * @author Greg Beaver + * @copyright 1997-2006 The PHP Group + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.1 + * @link http://pear.php.net/package/PEAR + * @see PEAR_Error + * @since Class available since PHP 4.0.2 + * @link http://pear.php.net/manual/en/core.pear.php#core.pear.pear + */ +class PEAR +{ + /** + * Whether to enable internal debug messages. + * + * @var bool + * @access private + */ + var $_debug = false; + + /** + * Default error mode for this object. + * + * @var int + * @access private + */ + var $_default_error_mode = null; + + /** + * Default error options used for this object when error mode + * is PEAR_ERROR_TRIGGER. + * + * @var int + * @access private + */ + var $_default_error_options = null; + + /** + * Default error handler (callback) for this object, if error mode is + * PEAR_ERROR_CALLBACK. + * + * @var string + * @access private + */ + var $_default_error_handler = ''; + + /** + * Which class to use for error objects. + * + * @var string + * @access private + */ + var $_error_class = 'PEAR_Error'; + + /** + * An array of expected errors. + * + * @var array + * @access private + */ + var $_expected_errors = array(); + + /** + * Constructor. Registers this object in + * $_PEAR_destructor_object_list for destructor emulation if a + * destructor object exists. + * + * @param string $error_class (optional) which class to use for + * error objects, defaults to PEAR_Error. + * @access public + * @return void + */ + function PEAR($error_class = null) + { + $classname = strtolower(get_class($this)); + if ($this->_debug) { + print "PEAR constructor called, class=$classname\n"; + } + + if ($error_class !== null) { + $this->_error_class = $error_class; + } + + while ($classname && strcasecmp($classname, "pear")) { + $destructor = "_$classname"; + if (method_exists($this, $destructor)) { + global $_PEAR_destructor_object_list; + $_PEAR_destructor_object_list[] = &$this; + if (!isset($GLOBALS['_PEAR_SHUTDOWN_REGISTERED'])) { + register_shutdown_function("_PEAR_call_destructors"); + $GLOBALS['_PEAR_SHUTDOWN_REGISTERED'] = true; + } + break; + } else { + $classname = get_parent_class($classname); + } + } + } + + /** + * Destructor (the emulated type of...). Does nothing right now, + * but is included for forward compatibility, so subclass + * destructors should always call it. + * + * See the note in the class desciption about output from + * destructors. + * + * @access public + * @return void + */ + function _PEAR() { + if ($this->_debug) { + printf("PEAR destructor called, class=%s\n", strtolower(get_class($this))); + } + } + + /** + * If you have a class that's mostly/entirely static, and you need static + * properties, you can use this method to simulate them. Eg. in your method(s) + * do this: $myVar = &PEAR::getStaticProperty('myclass', 'myVar'); + * You MUST use a reference, or they will not persist! + * + * @access public + * @param string $class The calling classname, to prevent clashes + * @param string $var The variable to retrieve. + * @return mixed A reference to the variable. If not set it will be + * auto initialised to NULL. + */ + function &getStaticProperty($class, $var) + { + static $properties; + if (!isset($properties[$class])) { + $properties[$class] = array(); + } + + if (!array_key_exists($var, $properties[$class])) { + $properties[$class][$var] = null; + } + + return $properties[$class][$var]; + } + + /** + * Use this function to register a shutdown method for static + * classes. + * + * @access public + * @param mixed $func The function name (or array of class/method) to call + * @param mixed $args The arguments to pass to the function + * @return void + */ + function registerShutdownFunc($func, $args = array()) + { + // if we are called statically, there is a potential + // that no shutdown func is registered. Bug #6445 + if (!isset($GLOBALS['_PEAR_SHUTDOWN_REGISTERED'])) { + register_shutdown_function("_PEAR_call_destructors"); + $GLOBALS['_PEAR_SHUTDOWN_REGISTERED'] = true; + } + $GLOBALS['_PEAR_shutdown_funcs'][] = array($func, $args); + } + + /** + * Tell whether a value is a PEAR error. + * + * @param mixed $data the value to test + * @param int $code if $data is an error object, return true + * only if $code is a string and + * $obj->getMessage() == $code or + * $code is an integer and $obj->getCode() == $code + * @access public + * @return bool true if parameter is an error + */ + function isError($data, $code = null) + { + if (!is_a($data, 'PEAR_Error')) { + return false; + } + + if (is_null($code)) { + return true; + } elseif (is_string($code)) { + return $data->getMessage() == $code; + } + + return $data->getCode() == $code; + } + + /** + * Sets how errors generated by this object should be handled. + * Can be invoked both in objects and statically. If called + * statically, setErrorHandling sets the default behaviour for all + * PEAR objects. If called in an object, setErrorHandling sets + * the default behaviour for that object. + * + * @param int $mode + * One of PEAR_ERROR_RETURN, PEAR_ERROR_PRINT, + * PEAR_ERROR_TRIGGER, PEAR_ERROR_DIE, + * PEAR_ERROR_CALLBACK or PEAR_ERROR_EXCEPTION. + * + * @param mixed $options + * When $mode is PEAR_ERROR_TRIGGER, this is the error level (one + * of E_USER_NOTICE, E_USER_WARNING or E_USER_ERROR). + * + * When $mode is PEAR_ERROR_CALLBACK, this parameter is expected + * to be the callback function or method. A callback + * function is a string with the name of the function, a + * callback method is an array of two elements: the element + * at index 0 is the object, and the element at index 1 is + * the name of the method to call in the object. + * + * When $mode is PEAR_ERROR_PRINT or PEAR_ERROR_DIE, this is + * a printf format string used when printing the error + * message. + * + * @access public + * @return void + * @see PEAR_ERROR_RETURN + * @see PEAR_ERROR_PRINT + * @see PEAR_ERROR_TRIGGER + * @see PEAR_ERROR_DIE + * @see PEAR_ERROR_CALLBACK + * @see PEAR_ERROR_EXCEPTION + * + * @since PHP 4.0.5 + */ + function setErrorHandling($mode = null, $options = null) + { + if (isset($this) && is_a($this, 'PEAR')) { + $setmode = &$this->_default_error_mode; + $setoptions = &$this->_default_error_options; + } else { + $setmode = &$GLOBALS['_PEAR_default_error_mode']; + $setoptions = &$GLOBALS['_PEAR_default_error_options']; + } + + switch ($mode) { + case PEAR_ERROR_EXCEPTION: + case PEAR_ERROR_RETURN: + case PEAR_ERROR_PRINT: + case PEAR_ERROR_TRIGGER: + case PEAR_ERROR_DIE: + case null: + $setmode = $mode; + $setoptions = $options; + break; + + case PEAR_ERROR_CALLBACK: + $setmode = $mode; + // class/object method callback + if (is_callable($options)) { + $setoptions = $options; + } else { + trigger_error("invalid error callback", E_USER_WARNING); + } + break; + + default: + trigger_error("invalid error mode", E_USER_WARNING); + break; + } + } + + /** + * This method is used to tell which errors you expect to get. + * Expected errors are always returned with error mode + * PEAR_ERROR_RETURN. Expected error codes are stored in a stack, + * and this method pushes a new element onto it. The list of + * expected errors are in effect until they are popped off the + * stack with the popExpect() method. + * + * Note that this method can not be called statically + * + * @param mixed $code a single error code or an array of error codes to expect + * + * @return int the new depth of the "expected errors" stack + * @access public + */ + function expectError($code = '*') + { + if (is_array($code)) { + array_push($this->_expected_errors, $code); + } else { + array_push($this->_expected_errors, array($code)); + } + return count($this->_expected_errors); + } + + /** + * This method pops one element off the expected error codes + * stack. + * + * @return array the list of error codes that were popped + */ + function popExpect() + { + return array_pop($this->_expected_errors); + } + + /** + * This method checks unsets an error code if available + * + * @param mixed error code + * @return bool true if the error code was unset, false otherwise + * @access private + * @since PHP 4.3.0 + */ + function _checkDelExpect($error_code) + { + $deleted = false; + foreach ($this->_expected_errors as $key => $error_array) { + if (in_array($error_code, $error_array)) { + unset($this->_expected_errors[$key][array_search($error_code, $error_array)]); + $deleted = true; + } + + // clean up empty arrays + if (0 == count($this->_expected_errors[$key])) { + unset($this->_expected_errors[$key]); + } + } + + return $deleted; + } + + /** + * This method deletes all occurences of the specified element from + * the expected error codes stack. + * + * @param mixed $error_code error code that should be deleted + * @return mixed list of error codes that were deleted or error + * @access public + * @since PHP 4.3.0 + */ + function delExpect($error_code) + { + $deleted = false; + if ((is_array($error_code) && (0 != count($error_code)))) { + // $error_code is a non-empty array here; we walk through it trying + // to unset all values + foreach ($error_code as $key => $error) { + $deleted = $this->_checkDelExpect($error) ? true : false; + } + + return $deleted ? true : PEAR::raiseError("The expected error you submitted does not exist"); // IMPROVE ME + } elseif (!empty($error_code)) { + // $error_code comes alone, trying to unset it + if ($this->_checkDelExpect($error_code)) { + return true; + } + + return PEAR::raiseError("The expected error you submitted does not exist"); // IMPROVE ME + } + + // $error_code is empty + return PEAR::raiseError("The expected error you submitted is empty"); // IMPROVE ME + } + + /** + * This method is a wrapper that returns an instance of the + * configured error class with this object's default error + * handling applied. If the $mode and $options parameters are not + * specified, the object's defaults are used. + * + * @param mixed $message a text error message or a PEAR error object + * + * @param int $code a numeric error code (it is up to your class + * to define these if you want to use codes) + * + * @param int $mode One of PEAR_ERROR_RETURN, PEAR_ERROR_PRINT, + * PEAR_ERROR_TRIGGER, PEAR_ERROR_DIE, + * PEAR_ERROR_CALLBACK, PEAR_ERROR_EXCEPTION. + * + * @param mixed $options If $mode is PEAR_ERROR_TRIGGER, this parameter + * specifies the PHP-internal error level (one of + * E_USER_NOTICE, E_USER_WARNING or E_USER_ERROR). + * If $mode is PEAR_ERROR_CALLBACK, this + * parameter specifies the callback function or + * method. In other error modes this parameter + * is ignored. + * + * @param string $userinfo If you need to pass along for example debug + * information, this parameter is meant for that. + * + * @param string $error_class The returned error object will be + * instantiated from this class, if specified. + * + * @param bool $skipmsg If true, raiseError will only pass error codes, + * the error message parameter will be dropped. + * + * @access public + * @return object a PEAR error object + * @see PEAR::setErrorHandling + * @since PHP 4.0.5 + */ + function &raiseError($message = null, + $code = null, + $mode = null, + $options = null, + $userinfo = null, + $error_class = null, + $skipmsg = false) + { + // The error is yet a PEAR error object + if (is_object($message)) { + $code = $message->getCode(); + $userinfo = $message->getUserInfo(); + $error_class = $message->getType(); + $message->error_message_prefix = ''; + $message = $message->getMessage(); + } + + if ( + isset($this) && + isset($this->_expected_errors) && + count($this->_expected_errors) > 0 && + count($exp = end($this->_expected_errors)) + ) { + if ($exp[0] == "*" || + (is_int(reset($exp)) && in_array($code, $exp)) || + (is_string(reset($exp)) && in_array($message, $exp)) + ) { + $mode = PEAR_ERROR_RETURN; + } + } + + // No mode given, try global ones + if ($mode === null) { + // Class error handler + if (isset($this) && isset($this->_default_error_mode)) { + $mode = $this->_default_error_mode; + $options = $this->_default_error_options; + // Global error handler + } elseif (isset($GLOBALS['_PEAR_default_error_mode'])) { + $mode = $GLOBALS['_PEAR_default_error_mode']; + $options = $GLOBALS['_PEAR_default_error_options']; + } + } + + if ($error_class !== null) { + $ec = $error_class; + } elseif (isset($this) && isset($this->_error_class)) { + $ec = $this->_error_class; + } else { + $ec = 'PEAR_Error'; + } + + if (intval(PHP_VERSION) < 5) { + // little non-eval hack to fix bug #12147 + include 'PEAR/FixPHP5PEARWarnings.php'; + return $a; + } + + if ($skipmsg) { + $a = new $ec($code, $mode, $options, $userinfo); + } else { + $a = new $ec($message, $code, $mode, $options, $userinfo); + } + + return $a; + } + + /** + * Simpler form of raiseError with fewer options. In most cases + * message, code and userinfo are enough. + * + * @param mixed $message a text error message or a PEAR error object + * + * @param int $code a numeric error code (it is up to your class + * to define these if you want to use codes) + * + * @param string $userinfo If you need to pass along for example debug + * information, this parameter is meant for that. + * + * @access public + * @return object a PEAR error object + * @see PEAR::raiseError + */ + function &throwError($message = null, $code = null, $userinfo = null) + { + if (isset($this) && is_a($this, 'PEAR')) { + $a = &$this->raiseError($message, $code, null, null, $userinfo); + return $a; + } + + $a = &PEAR::raiseError($message, $code, null, null, $userinfo); + return $a; + } + + function staticPushErrorHandling($mode, $options = null) + { + $stack = &$GLOBALS['_PEAR_error_handler_stack']; + $def_mode = &$GLOBALS['_PEAR_default_error_mode']; + $def_options = &$GLOBALS['_PEAR_default_error_options']; + $stack[] = array($def_mode, $def_options); + switch ($mode) { + case PEAR_ERROR_EXCEPTION: + case PEAR_ERROR_RETURN: + case PEAR_ERROR_PRINT: + case PEAR_ERROR_TRIGGER: + case PEAR_ERROR_DIE: + case null: + $def_mode = $mode; + $def_options = $options; + break; + + case PEAR_ERROR_CALLBACK: + $def_mode = $mode; + // class/object method callback + if (is_callable($options)) { + $def_options = $options; + } else { + trigger_error("invalid error callback", E_USER_WARNING); + } + break; + + default: + trigger_error("invalid error mode", E_USER_WARNING); + break; + } + $stack[] = array($mode, $options); + return true; + } + + function staticPopErrorHandling() + { + $stack = &$GLOBALS['_PEAR_error_handler_stack']; + $setmode = &$GLOBALS['_PEAR_default_error_mode']; + $setoptions = &$GLOBALS['_PEAR_default_error_options']; + array_pop($stack); + list($mode, $options) = $stack[sizeof($stack) - 1]; + array_pop($stack); + switch ($mode) { + case PEAR_ERROR_EXCEPTION: + case PEAR_ERROR_RETURN: + case PEAR_ERROR_PRINT: + case PEAR_ERROR_TRIGGER: + case PEAR_ERROR_DIE: + case null: + $setmode = $mode; + $setoptions = $options; + break; + + case PEAR_ERROR_CALLBACK: + $setmode = $mode; + // class/object method callback + if (is_callable($options)) { + $setoptions = $options; + } else { + trigger_error("invalid error callback", E_USER_WARNING); + } + break; + + default: + trigger_error("invalid error mode", E_USER_WARNING); + break; + } + return true; + } + + /** + * Push a new error handler on top of the error handler options stack. With this + * you can easily override the actual error handler for some code and restore + * it later with popErrorHandling. + * + * @param mixed $mode (same as setErrorHandling) + * @param mixed $options (same as setErrorHandling) + * + * @return bool Always true + * + * @see PEAR::setErrorHandling + */ + function pushErrorHandling($mode, $options = null) + { + $stack = &$GLOBALS['_PEAR_error_handler_stack']; + if (isset($this) && is_a($this, 'PEAR')) { + $def_mode = &$this->_default_error_mode; + $def_options = &$this->_default_error_options; + } else { + $def_mode = &$GLOBALS['_PEAR_default_error_mode']; + $def_options = &$GLOBALS['_PEAR_default_error_options']; + } + $stack[] = array($def_mode, $def_options); + + if (isset($this) && is_a($this, 'PEAR')) { + $this->setErrorHandling($mode, $options); + } else { + PEAR::setErrorHandling($mode, $options); + } + $stack[] = array($mode, $options); + return true; + } + + /** + * Pop the last error handler used + * + * @return bool Always true + * + * @see PEAR::pushErrorHandling + */ + function popErrorHandling() + { + $stack = &$GLOBALS['_PEAR_error_handler_stack']; + array_pop($stack); + list($mode, $options) = $stack[sizeof($stack) - 1]; + array_pop($stack); + if (isset($this) && is_a($this, 'PEAR')) { + $this->setErrorHandling($mode, $options); + } else { + PEAR::setErrorHandling($mode, $options); + } + return true; + } + + /** + * OS independant PHP extension load. Remember to take care + * on the correct extension name for case sensitive OSes. + * + * @param string $ext The extension name + * @return bool Success or not on the dl() call + */ + function loadExtension($ext) + { + if (extension_loaded($ext)) { + return true; + } + + // if either returns true dl() will produce a FATAL error, stop that + if ( + function_exists('dl') === false || + ini_get('enable_dl') != 1 || + ini_get('safe_mode') == 1 + ) { + return false; + } + + if (OS_WINDOWS) { + $suffix = '.dll'; + } elseif (PHP_OS == 'HP-UX') { + $suffix = '.sl'; + } elseif (PHP_OS == 'AIX') { + $suffix = '.a'; + } elseif (PHP_OS == 'OSX') { + $suffix = '.bundle'; + } else { + $suffix = '.so'; + } + + return @dl('php_'.$ext.$suffix) || @dl($ext.$suffix); + } +} + +if (PEAR_ZE2) { + include_once 'PEAR5.php'; +} + +function _PEAR_call_destructors() +{ + global $_PEAR_destructor_object_list; + if (is_array($_PEAR_destructor_object_list) && + sizeof($_PEAR_destructor_object_list)) + { + reset($_PEAR_destructor_object_list); + if (PEAR_ZE2) { + $destructLifoExists = PEAR5::getStaticProperty('PEAR', 'destructlifo'); + } else { + $destructLifoExists = PEAR::getStaticProperty('PEAR', 'destructlifo'); + } + + if ($destructLifoExists) { + $_PEAR_destructor_object_list = array_reverse($_PEAR_destructor_object_list); + } + + while (list($k, $objref) = each($_PEAR_destructor_object_list)) { + $classname = get_class($objref); + while ($classname) { + $destructor = "_$classname"; + if (method_exists($objref, $destructor)) { + $objref->$destructor(); + break; + } else { + $classname = get_parent_class($classname); + } + } + } + // Empty the object list to ensure that destructors are + // not called more than once. + $_PEAR_destructor_object_list = array(); + } + + // Now call the shutdown functions + if ( + isset($GLOBALS['_PEAR_shutdown_funcs']) && + is_array($GLOBALS['_PEAR_shutdown_funcs']) && + !empty($GLOBALS['_PEAR_shutdown_funcs']) + ) { + foreach ($GLOBALS['_PEAR_shutdown_funcs'] as $value) { + call_user_func_array($value[0], $value[1]); + } + } +} + +/** + * Standard PEAR error class for PHP 4 + * + * This class is supserseded by {@link PEAR_Exception} in PHP 5 + * + * @category pear + * @package PEAR + * @author Stig Bakken + * @author Tomas V.V. Cox + * @author Gregory Beaver + * @copyright 1997-2006 The PHP Group + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.1 + * @link http://pear.php.net/manual/en/core.pear.pear-error.php + * @see PEAR::raiseError(), PEAR::throwError() + * @since Class available since PHP 4.0.2 + */ +class PEAR_Error +{ + var $error_message_prefix = ''; + var $mode = PEAR_ERROR_RETURN; + var $level = E_USER_NOTICE; + var $code = -1; + var $message = ''; + var $userinfo = ''; + var $backtrace = null; + + /** + * PEAR_Error constructor + * + * @param string $message message + * + * @param int $code (optional) error code + * + * @param int $mode (optional) error mode, one of: PEAR_ERROR_RETURN, + * PEAR_ERROR_PRINT, PEAR_ERROR_DIE, PEAR_ERROR_TRIGGER, + * PEAR_ERROR_CALLBACK or PEAR_ERROR_EXCEPTION + * + * @param mixed $options (optional) error level, _OR_ in the case of + * PEAR_ERROR_CALLBACK, the callback function or object/method + * tuple. + * + * @param string $userinfo (optional) additional user/debug info + * + * @access public + * + */ + function PEAR_Error($message = 'unknown error', $code = null, + $mode = null, $options = null, $userinfo = null) + { + if ($mode === null) { + $mode = PEAR_ERROR_RETURN; + } + $this->message = $message; + $this->code = $code; + $this->mode = $mode; + $this->userinfo = $userinfo; + + if (PEAR_ZE2) { + $skiptrace = PEAR5::getStaticProperty('PEAR_Error', 'skiptrace'); + } else { + $skiptrace = PEAR::getStaticProperty('PEAR_Error', 'skiptrace'); + } + + if (!$skiptrace) { + $this->backtrace = debug_backtrace(); + if (isset($this->backtrace[0]) && isset($this->backtrace[0]['object'])) { + unset($this->backtrace[0]['object']); + } + } + + if ($mode & PEAR_ERROR_CALLBACK) { + $this->level = E_USER_NOTICE; + $this->callback = $options; + } else { + if ($options === null) { + $options = E_USER_NOTICE; + } + + $this->level = $options; + $this->callback = null; + } + + if ($this->mode & PEAR_ERROR_PRINT) { + if (is_null($options) || is_int($options)) { + $format = "%s"; + } else { + $format = $options; + } + + printf($format, $this->getMessage()); + } + + if ($this->mode & PEAR_ERROR_TRIGGER) { + trigger_error($this->getMessage(), $this->level); + } + + if ($this->mode & PEAR_ERROR_DIE) { + $msg = $this->getMessage(); + if (is_null($options) || is_int($options)) { + $format = "%s"; + if (substr($msg, -1) != "\n") { + $msg .= "\n"; + } + } else { + $format = $options; + } + die(sprintf($format, $msg)); + } + + if ($this->mode & PEAR_ERROR_CALLBACK && is_callable($this->callback)) { + call_user_func($this->callback, $this); + } + + if ($this->mode & PEAR_ERROR_EXCEPTION) { + trigger_error("PEAR_ERROR_EXCEPTION is obsolete, use class PEAR_Exception for exceptions", E_USER_WARNING); + eval('$e = new Exception($this->message, $this->code);throw($e);'); + } + } + + /** + * Get the error mode from an error object. + * + * @return int error mode + * @access public + */ + function getMode() + { + return $this->mode; + } + + /** + * Get the callback function/method from an error object. + * + * @return mixed callback function or object/method array + * @access public + */ + function getCallback() + { + return $this->callback; + } + + /** + * Get the error message from an error object. + * + * @return string full error message + * @access public + */ + function getMessage() + { + return ($this->error_message_prefix . $this->message); + } + + /** + * Get error code from an error object + * + * @return int error code + * @access public + */ + function getCode() + { + return $this->code; + } + + /** + * Get the name of this error/exception. + * + * @return string error/exception name (type) + * @access public + */ + function getType() + { + return get_class($this); + } + + /** + * Get additional user-supplied information. + * + * @return string user-supplied information + * @access public + */ + function getUserInfo() + { + return $this->userinfo; + } + + /** + * Get additional debug information supplied by the application. + * + * @return string debug information + * @access public + */ + function getDebugInfo() + { + return $this->getUserInfo(); + } + + /** + * Get the call backtrace from where the error was generated. + * Supported with PHP 4.3.0 or newer. + * + * @param int $frame (optional) what frame to fetch + * @return array Backtrace, or NULL if not available. + * @access public + */ + function getBacktrace($frame = null) + { + if (defined('PEAR_IGNORE_BACKTRACE')) { + return null; + } + if ($frame === null) { + return $this->backtrace; + } + return $this->backtrace[$frame]; + } + + function addUserInfo($info) + { + if (empty($this->userinfo)) { + $this->userinfo = $info; + } else { + $this->userinfo .= " ** $info"; + } + } + + function __toString() + { + return $this->getMessage(); + } + + /** + * Make a string representation of this object. + * + * @return string a string with an object summary + * @access public + */ + function toString() + { + $modes = array(); + $levels = array(E_USER_NOTICE => 'notice', + E_USER_WARNING => 'warning', + E_USER_ERROR => 'error'); + if ($this->mode & PEAR_ERROR_CALLBACK) { + if (is_array($this->callback)) { + $callback = (is_object($this->callback[0]) ? + strtolower(get_class($this->callback[0])) : + $this->callback[0]) . '::' . + $this->callback[1]; + } else { + $callback = $this->callback; + } + return sprintf('[%s: message="%s" code=%d mode=callback '. + 'callback=%s prefix="%s" info="%s"]', + strtolower(get_class($this)), $this->message, $this->code, + $callback, $this->error_message_prefix, + $this->userinfo); + } + if ($this->mode & PEAR_ERROR_PRINT) { + $modes[] = 'print'; + } + if ($this->mode & PEAR_ERROR_TRIGGER) { + $modes[] = 'trigger'; + } + if ($this->mode & PEAR_ERROR_DIE) { + $modes[] = 'die'; + } + if ($this->mode & PEAR_ERROR_RETURN) { + $modes[] = 'return'; + } + return sprintf('[%s: message="%s" code=%d mode=%s level=%s '. + 'prefix="%s" info="%s"]', + strtolower(get_class($this)), $this->message, $this->code, + implode("|", $modes), $levels[$this->level], + $this->error_message_prefix, + $this->userinfo); + } +} + +/* + * Local Variables: + * mode: php + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ diff --git a/library/pear/PEAR/Autoloader.php b/library/pear/PEAR/Autoloader.php new file mode 100644 index 000000000..817d1ff84 --- /dev/null +++ b/library/pear/PEAR/Autoloader.php @@ -0,0 +1,218 @@ + + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Autoloader.php 276383 2009-02-24 23:39:37Z dufuz $ + * @link http://pear.php.net/manual/en/core.ppm.php#core.ppm.pear-autoloader + * @since File available since Release 0.1 + * @deprecated File deprecated in Release 1.4.0a1 + */ + +// /* vim: set expandtab tabstop=4 shiftwidth=4: */ + +if (!extension_loaded("overload")) { + // die hard without ext/overload + die("Rebuild PHP with the `overload' extension to use PEAR_Autoloader"); +} + +/** + * Include for PEAR_Error and PEAR classes + */ +require_once "PEAR.php"; + +/** + * This class is for objects where you want to separate the code for + * some methods into separate classes. This is useful if you have a + * class with not-frequently-used methods that contain lots of code + * that you would like to avoid always parsing. + * + * The PEAR_Autoloader class provides autoloading and aggregation. + * The autoloading lets you set up in which classes the separated + * methods are found. Aggregation is the technique used to import new + * methods, an instance of each class providing separated methods is + * stored and called every time the aggregated method is called. + * + * @category pear + * @package PEAR + * @author Stig Bakken + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.1 + * @link http://pear.php.net/manual/en/core.ppm.php#core.ppm.pear-autoloader + * @since File available since Release 0.1 + * @deprecated File deprecated in Release 1.4.0a1 + */ +class PEAR_Autoloader extends PEAR +{ + // {{{ properties + + /** + * Map of methods and classes where they are defined + * + * @var array + * + * @access private + */ + var $_autoload_map = array(); + + /** + * Map of methods and aggregate objects + * + * @var array + * + * @access private + */ + var $_method_map = array(); + + // }}} + // {{{ addAutoload() + + /** + * Add one or more autoload entries. + * + * @param string $method which method to autoload + * + * @param string $classname (optional) which class to find the method in. + * If the $method parameter is an array, this + * parameter may be omitted (and will be ignored + * if not), and the $method parameter will be + * treated as an associative array with method + * names as keys and class names as values. + * + * @return void + * + * @access public + */ + function addAutoload($method, $classname = null) + { + if (is_array($method)) { + array_walk($method, create_function('$a,&$b', '$b = strtolower($b);')); + $this->_autoload_map = array_merge($this->_autoload_map, $method); + } else { + $this->_autoload_map[strtolower($method)] = $classname; + } + } + + // }}} + // {{{ removeAutoload() + + /** + * Remove an autoload entry. + * + * @param string $method which method to remove the autoload entry for + * + * @return bool TRUE if an entry was removed, FALSE if not + * + * @access public + */ + function removeAutoload($method) + { + $method = strtolower($method); + $ok = isset($this->_autoload_map[$method]); + unset($this->_autoload_map[$method]); + return $ok; + } + + // }}} + // {{{ addAggregateObject() + + /** + * Add an aggregate object to this object. If the specified class + * is not defined, loading it will be attempted following PEAR's + * file naming scheme. All the methods in the class will be + * aggregated, except private ones (name starting with an + * underscore) and constructors. + * + * @param string $classname what class to instantiate for the object. + * + * @return void + * + * @access public + */ + function addAggregateObject($classname) + { + $classname = strtolower($classname); + if (!class_exists($classname)) { + $include_file = preg_replace('/[^a-z0-9]/i', '_', $classname); + include_once $include_file; + } + $obj =& new $classname; + $methods = get_class_methods($classname); + foreach ($methods as $method) { + // don't import priviate methods and constructors + if ($method{0} != '_' && $method != $classname) { + $this->_method_map[$method] = $obj; + } + } + } + + // }}} + // {{{ removeAggregateObject() + + /** + * Remove an aggregate object. + * + * @param string $classname the class of the object to remove + * + * @return bool TRUE if an object was removed, FALSE if not + * + * @access public + */ + function removeAggregateObject($classname) + { + $ok = false; + $classname = strtolower($classname); + reset($this->_method_map); + while (list($method, $obj) = each($this->_method_map)) { + if (is_a($obj, $classname)) { + unset($this->_method_map[$method]); + $ok = true; + } + } + return $ok; + } + + // }}} + // {{{ __call() + + /** + * Overloaded object call handler, called each time an + * undefined/aggregated method is invoked. This method repeats + * the call in the right aggregate object and passes on the return + * value. + * + * @param string $method which method that was called + * + * @param string $args An array of the parameters passed in the + * original call + * + * @return mixed The return value from the aggregated method, or a PEAR + * error if the called method was unknown. + */ + function __call($method, $args, &$retval) + { + $method = strtolower($method); + if (empty($this->_method_map[$method]) && isset($this->_autoload_map[$method])) { + $this->addAggregateObject($this->_autoload_map[$method]); + } + if (isset($this->_method_map[$method])) { + $retval = call_user_func_array(array($this->_method_map[$method], $method), $args); + return true; + } + return false; + } + + // }}} +} + +overload("PEAR_Autoloader"); + +?> diff --git a/library/pear/PEAR/Builder.php b/library/pear/PEAR/Builder.php new file mode 100644 index 000000000..b194fd050 --- /dev/null +++ b/library/pear/PEAR/Builder.php @@ -0,0 +1,474 @@ + + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Builder.php 276383 2009-02-24 23:39:37Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 0.1 + * + * TODO: log output parameters in PECL command line + * TODO: msdev path in configuration + */ + +/** + * Needed for extending PEAR_Builder + */ +require_once 'PEAR/Common.php'; +require_once 'PEAR/PackageFile.php'; + +/** + * Class to handle building (compiling) extensions. + * + * @category pear + * @package PEAR + * @author Stig Bakken + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.1 + * @link http://pear.php.net/package/PEAR + * @since Class available since PHP 4.0.2 + * @see http://pear.php.net/manual/en/core.ppm.pear-builder.php + */ +class PEAR_Builder extends PEAR_Common +{ + var $php_api_version = 0; + var $zend_module_api_no = 0; + var $zend_extension_api_no = 0; + + var $extensions_built = array(); + + /** + * @var string Used for reporting when it is not possible to pass function + * via extra parameter, e.g. log, msdevCallback + */ + var $current_callback = null; + + // used for msdev builds + var $_lastline = null; + var $_firstline = null; + + /** + * PEAR_Builder constructor. + * + * @param object $ui user interface object (instance of PEAR_Frontend_*) + * + * @access public + */ + function PEAR_Builder(&$ui) + { + parent::PEAR_Common(); + $this->setFrontendObject($ui); + } + + /** + * Build an extension from source on windows. + * requires msdev + */ + function _build_win32($descfile, $callback = null) + { + if (is_object($descfile)) { + $pkg = $descfile; + $descfile = $pkg->getPackageFile(); + } else { + $pf = &new PEAR_PackageFile($this->config, $this->debug); + $pkg = &$pf->fromPackageFile($descfile, PEAR_VALIDATE_NORMAL); + if (PEAR::isError($pkg)) { + return $pkg; + } + } + $dir = dirname($descfile); + $old_cwd = getcwd(); + + if (!file_exists($dir) || !is_dir($dir) || !chdir($dir)) { + return $this->raiseError("could not chdir to $dir"); + } + + // packages that were in a .tar have the packagefile in this directory + $vdir = $pkg->getPackage() . '-' . $pkg->getVersion(); + if (file_exists($dir) && is_dir($vdir)) { + if (!chdir($vdir)) { + return $this->raiseError("could not chdir to " . realpath($vdir)); + } + + $dir = getcwd(); + } + + $this->log(2, "building in $dir"); + + $dsp = $pkg->getPackage().'.dsp'; + if (!file_exists("$dir/$dsp")) { + return $this->raiseError("The DSP $dsp does not exist."); + } + // XXX TODO: make release build type configurable + $command = 'msdev '.$dsp.' /MAKE "'.$pkg->getPackage(). ' - Release"'; + + $err = $this->_runCommand($command, array(&$this, 'msdevCallback')); + if (PEAR::isError($err)) { + return $err; + } + + // figure out the build platform and type + $platform = 'Win32'; + $buildtype = 'Release'; + if (preg_match('/.*?'.$pkg->getPackage().'\s-\s(\w+)\s(.*?)-+/i',$this->_firstline,$matches)) { + $platform = $matches[1]; + $buildtype = $matches[2]; + } + + if (preg_match('/(.*)?\s-\s(\d+).*?(\d+)/', $this->_lastline, $matches)) { + if ($matches[2]) { + // there were errors in the build + return $this->raiseError("There were errors during compilation."); + } + $out = $matches[1]; + } else { + return $this->raiseError("Did not understand the completion status returned from msdev.exe."); + } + + // msdev doesn't tell us the output directory :/ + // open the dsp, find /out and use that directory + $dsptext = join(file($dsp),''); + + // this regex depends on the build platform and type having been + // correctly identified above. + $regex ='/.*?!IF\s+"\$\(CFG\)"\s+==\s+("'. + $pkg->getPackage().'\s-\s'. + $platform.'\s'. + $buildtype.'").*?'. + '\/out:"(.*?)"/is'; + + if ($dsptext && preg_match($regex, $dsptext, $matches)) { + // what we get back is a relative path to the output file itself. + $outfile = realpath($matches[2]); + } else { + return $this->raiseError("Could not retrieve output information from $dsp."); + } + // realpath returns false if the file doesn't exist + if ($outfile && copy($outfile, "$dir/$out")) { + $outfile = "$dir/$out"; + } + + $built_files[] = array( + 'file' => "$outfile", + 'php_api' => $this->php_api_version, + 'zend_mod_api' => $this->zend_module_api_no, + 'zend_ext_api' => $this->zend_extension_api_no, + ); + + return $built_files; + } + // }}} + + // {{{ msdevCallback() + function msdevCallback($what, $data) + { + if (!$this->_firstline) + $this->_firstline = $data; + $this->_lastline = $data; + call_user_func($this->current_callback, $what, $data); + } + + /** + * @param string + * @param string + * @param array + * @access private + */ + function _harvestInstDir($dest_prefix, $dirname, &$built_files) + { + $d = opendir($dirname); + if (!$d) + return false; + + $ret = true; + while (($ent = readdir($d)) !== false) { + if ($ent{0} == '.') + continue; + + $full = $dirname . DIRECTORY_SEPARATOR . $ent; + if (is_dir($full)) { + if (!$this->_harvestInstDir( + $dest_prefix . DIRECTORY_SEPARATOR . $ent, + $full, $built_files)) { + $ret = false; + break; + } + } else { + $dest = $dest_prefix . DIRECTORY_SEPARATOR . $ent; + $built_files[] = array( + 'file' => $full, + 'dest' => $dest, + 'php_api' => $this->php_api_version, + 'zend_mod_api' => $this->zend_module_api_no, + 'zend_ext_api' => $this->zend_extension_api_no, + ); + } + } + closedir($d); + return $ret; + } + + /** + * Build an extension from source. Runs "phpize" in the source + * directory, but compiles in a temporary directory + * (/var/tmp/pear-build-USER/PACKAGE-VERSION). + * + * @param string|PEAR_PackageFile_v* $descfile path to XML package description file, or + * a PEAR_PackageFile object + * + * @param mixed $callback callback function used to report output, + * see PEAR_Builder::_runCommand for details + * + * @return array an array of associative arrays with built files, + * format: + * array( array( 'file' => '/path/to/ext.so', + * 'php_api' => YYYYMMDD, + * 'zend_mod_api' => YYYYMMDD, + * 'zend_ext_api' => YYYYMMDD ), + * ... ) + * + * @access public + * + * @see PEAR_Builder::_runCommand + */ + function build($descfile, $callback = null) + { + if (preg_match('/(\\/|\\\\|^)([^\\/\\\\]+)?php(.+)?$/', + $this->config->get('php_bin'), $matches)) { + if (isset($matches[2]) && strlen($matches[2]) && + trim($matches[2]) != trim($this->config->get('php_prefix'))) { + $this->log(0, 'WARNING: php_bin ' . $this->config->get('php_bin') . + ' appears to have a prefix ' . $matches[2] . ', but' . + ' config variable php_prefix does not match'); + } + if (isset($matches[3]) && strlen($matches[3]) && + trim($matches[3]) != trim($this->config->get('php_suffix'))) { + $this->log(0, 'WARNING: php_bin ' . $this->config->get('php_bin') . + ' appears to have a suffix ' . $matches[3] . ', but' . + ' config variable php_suffix does not match'); + } + } + + + $this->current_callback = $callback; + if (PEAR_OS == "Windows") { + return $this->_build_win32($descfile, $callback); + } + if (PEAR_OS != 'Unix') { + return $this->raiseError("building extensions not supported on this platform"); + } + if (is_object($descfile)) { + $pkg = $descfile; + $descfile = $pkg->getPackageFile(); + if (is_a($pkg, 'PEAR_PackageFile_v1')) { + $dir = dirname($descfile); + } else { + $dir = $pkg->_config->get('temp_dir') . '/' . $pkg->getName(); + // automatically delete at session end + $this->addTempFile($dir); + } + } else { + $pf = &new PEAR_PackageFile($this->config); + $pkg = &$pf->fromPackageFile($descfile, PEAR_VALIDATE_NORMAL); + if (PEAR::isError($pkg)) { + return $pkg; + } + $dir = dirname($descfile); + } + $old_cwd = getcwd(); + if (!file_exists($dir) || !is_dir($dir) || !chdir($dir)) { + return $this->raiseError("could not chdir to $dir"); + } + $vdir = $pkg->getPackage() . '-' . $pkg->getVersion(); + if (is_dir($vdir)) { + chdir($vdir); + } + $dir = getcwd(); + $this->log(2, "building in $dir"); + putenv('PATH=' . $this->config->get('bin_dir') . ':' . getenv('PATH')); + $err = $this->_runCommand($this->config->get('php_prefix') + . "phpize" . + $this->config->get('php_suffix'), + array(&$this, 'phpizeCallback')); + if (PEAR::isError($err)) { + return $err; + } + if (!$err) { + return $this->raiseError("`phpize' failed"); + } + + // {{{ start of interactive part + $configure_command = "$dir/configure"; + $configure_options = $pkg->getConfigureOptions(); + if ($configure_options) { + foreach ($configure_options as $o) { + $default = array_key_exists('default', $o) ? $o['default'] : null; + list($r) = $this->ui->userDialog('build', + array($o['prompt']), + array('text'), + array($default)); + if (substr($o['name'], 0, 5) == 'with-' && + ($r == 'yes' || $r == 'autodetect')) { + $configure_command .= " --$o[name]"; + } else { + $configure_command .= " --$o[name]=".trim($r); + } + } + } + // }}} end of interactive part + + // FIXME make configurable + if(!$user=getenv('USER')){ + $user='defaultuser'; + } + $build_basedir = "/var/tmp/pear-build-$user"; + $build_dir = "$build_basedir/$vdir"; + $inst_dir = "$build_basedir/install-$vdir"; + $this->log(1, "building in $build_dir"); + if (is_dir($build_dir)) { + System::rm(array('-rf', $build_dir)); + } + if (!System::mkDir(array('-p', $build_dir))) { + return $this->raiseError("could not create build dir: $build_dir"); + } + $this->addTempFile($build_dir); + if (!System::mkDir(array('-p', $inst_dir))) { + return $this->raiseError("could not create temporary install dir: $inst_dir"); + } + $this->addTempFile($inst_dir); + + if (getenv('MAKE')) { + $make_command = getenv('MAKE'); + } else { + $make_command = 'make'; + } + $to_run = array( + $configure_command, + $make_command, + "$make_command INSTALL_ROOT=\"$inst_dir\" install", + "find \"$inst_dir\" | xargs ls -dils" + ); + if (!file_exists($build_dir) || !is_dir($build_dir) || !chdir($build_dir)) { + return $this->raiseError("could not chdir to $build_dir"); + } + putenv('PHP_PEAR_VERSION=1.9.1'); + foreach ($to_run as $cmd) { + $err = $this->_runCommand($cmd, $callback); + if (PEAR::isError($err)) { + chdir($old_cwd); + return $err; + } + if (!$err) { + chdir($old_cwd); + return $this->raiseError("`$cmd' failed"); + } + } + if (!($dp = opendir("modules"))) { + chdir($old_cwd); + return $this->raiseError("no `modules' directory found"); + } + $built_files = array(); + $prefix = exec($this->config->get('php_prefix') + . "php-config" . + $this->config->get('php_suffix') . " --prefix"); + $this->_harvestInstDir($prefix, $inst_dir . DIRECTORY_SEPARATOR . $prefix, $built_files); + chdir($old_cwd); + return $built_files; + } + + /** + * Message callback function used when running the "phpize" + * program. Extracts the API numbers used. Ignores other message + * types than "cmdoutput". + * + * @param string $what the type of message + * @param mixed $data the message + * + * @return void + * + * @access public + */ + function phpizeCallback($what, $data) + { + if ($what != 'cmdoutput') { + return; + } + $this->log(1, rtrim($data)); + if (preg_match('/You should update your .aclocal.m4/', $data)) { + return; + } + $matches = array(); + if (preg_match('/^\s+(\S[^:]+):\s+(\d{8})/', $data, $matches)) { + $member = preg_replace('/[^a-z]/', '_', strtolower($matches[1])); + $apino = (int)$matches[2]; + if (isset($this->$member)) { + $this->$member = $apino; + //$msg = sprintf("%-22s : %d", $matches[1], $apino); + //$this->log(1, $msg); + } + } + } + + /** + * Run an external command, using a message callback to report + * output. The command will be run through popen and output is + * reported for every line with a "cmdoutput" message with the + * line string, including newlines, as payload. + * + * @param string $command the command to run + * + * @param mixed $callback (optional) function to use as message + * callback + * + * @return bool whether the command was successful (exit code 0 + * means success, any other means failure) + * + * @access private + */ + function _runCommand($command, $callback = null) + { + $this->log(1, "running: $command"); + $pp = popen("$command 2>&1", "r"); + if (!$pp) { + return $this->raiseError("failed to run `$command'"); + } + if ($callback && $callback[0]->debug == 1) { + $olddbg = $callback[0]->debug; + $callback[0]->debug = 2; + } + + while ($line = fgets($pp, 1024)) { + if ($callback) { + call_user_func($callback, 'cmdoutput', $line); + } else { + $this->log(2, rtrim($line)); + } + } + if ($callback && isset($olddbg)) { + $callback[0]->debug = $olddbg; + } + + $exitcode = is_resource($pp) ? pclose($pp) : -1; + return ($exitcode == 0); + } + + function log($level, $msg) + { + if ($this->current_callback) { + if ($this->debug >= $level) { + call_user_func($this->current_callback, 'output', $msg); + } + return; + } + return PEAR_Common::log($level, $msg); + } +} \ No newline at end of file diff --git a/library/pear/PEAR/ChannelFile.php b/library/pear/PEAR/ChannelFile.php new file mode 100644 index 000000000..4000c88bb --- /dev/null +++ b/library/pear/PEAR/ChannelFile.php @@ -0,0 +1,1559 @@ + + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: ChannelFile.php 286951 2009-08-09 14:41:22Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ + +/** + * Needed for error handling + */ +require_once 'PEAR/ErrorStack.php'; +require_once 'PEAR/XMLParser.php'; +require_once 'PEAR/Common.php'; + +/** + * Error code if the channel.xml tag does not contain a valid version + */ +define('PEAR_CHANNELFILE_ERROR_NO_VERSION', 1); +/** + * Error code if the channel.xml tag version is not supported (version 1.0 is the only supported version, + * currently + */ +define('PEAR_CHANNELFILE_ERROR_INVALID_VERSION', 2); + +/** + * Error code if parsing is attempted with no xml extension + */ +define('PEAR_CHANNELFILE_ERROR_NO_XML_EXT', 3); + +/** + * Error code if creating the xml parser resource fails + */ +define('PEAR_CHANNELFILE_ERROR_CANT_MAKE_PARSER', 4); + +/** + * Error code used for all sax xml parsing errors + */ +define('PEAR_CHANNELFILE_ERROR_PARSER_ERROR', 5); + +/**#@+ + * Validation errors + */ +/** + * Error code when channel name is missing + */ +define('PEAR_CHANNELFILE_ERROR_NO_NAME', 6); +/** + * Error code when channel name is invalid + */ +define('PEAR_CHANNELFILE_ERROR_INVALID_NAME', 7); +/** + * Error code when channel summary is missing + */ +define('PEAR_CHANNELFILE_ERROR_NO_SUMMARY', 8); +/** + * Error code when channel summary is multi-line + */ +define('PEAR_CHANNELFILE_ERROR_MULTILINE_SUMMARY', 9); +/** + * Error code when channel server is missing for protocol + */ +define('PEAR_CHANNELFILE_ERROR_NO_HOST', 10); +/** + * Error code when channel server is invalid for protocol + */ +define('PEAR_CHANNELFILE_ERROR_INVALID_HOST', 11); +/** + * Error code when a mirror name is invalid + */ +define('PEAR_CHANNELFILE_ERROR_INVALID_MIRROR', 21); +/** + * Error code when a mirror type is invalid + */ +define('PEAR_CHANNELFILE_ERROR_INVALID_MIRRORTYPE', 22); +/** + * Error code when an attempt is made to generate xml, but the parsed content is invalid + */ +define('PEAR_CHANNELFILE_ERROR_INVALID', 23); +/** + * Error code when an empty package name validate regex is passed in + */ +define('PEAR_CHANNELFILE_ERROR_EMPTY_REGEX', 24); +/** + * Error code when a tag has no version + */ +define('PEAR_CHANNELFILE_ERROR_NO_FUNCTIONVERSION', 25); +/** + * Error code when a tag has no name + */ +define('PEAR_CHANNELFILE_ERROR_NO_FUNCTIONNAME', 26); +/** + * Error code when a tag has no name + */ +define('PEAR_CHANNELFILE_ERROR_NOVALIDATE_NAME', 27); +/** + * Error code when a tag has no version attribute + */ +define('PEAR_CHANNELFILE_ERROR_NOVALIDATE_VERSION', 28); +/** + * Error code when a mirror does not exist but is called for in one of the set* + * methods. + */ +define('PEAR_CHANNELFILE_ERROR_MIRROR_NOT_FOUND', 32); +/** + * Error code when a server port is not numeric + */ +define('PEAR_CHANNELFILE_ERROR_INVALID_PORT', 33); +/** + * Error code when contains no version attribute + */ +define('PEAR_CHANNELFILE_ERROR_NO_STATICVERSION', 34); +/** + * Error code when contains no type attribute in a protocol definition + */ +define('PEAR_CHANNELFILE_ERROR_NOBASEURLTYPE', 35); +/** + * Error code when a mirror is defined and the channel.xml represents the __uri pseudo-channel + */ +define('PEAR_CHANNELFILE_URI_CANT_MIRROR', 36); +/** + * Error code when ssl attribute is present and is not "yes" + */ +define('PEAR_CHANNELFILE_ERROR_INVALID_SSL', 37); +/**#@-*/ + +/** + * Mirror types allowed. Currently only internet servers are recognized. + */ +$GLOBALS['_PEAR_CHANNELS_MIRROR_TYPES'] = array('server'); + + +/** + * The Channel handling class + * + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.1 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_ChannelFile +{ + /** + * @access private + * @var PEAR_ErrorStack + * @access private + */ + var $_stack; + + /** + * Supported channel.xml versions, for parsing + * @var array + * @access private + */ + var $_supportedVersions = array('1.0'); + + /** + * Parsed channel information + * @var array + * @access private + */ + var $_channelInfo; + + /** + * index into the subchannels array, used for parsing xml + * @var int + * @access private + */ + var $_subchannelIndex; + + /** + * index into the mirrors array, used for parsing xml + * @var int + * @access private + */ + var $_mirrorIndex; + + /** + * Flag used to determine the validity of parsed content + * @var boolean + * @access private + */ + var $_isValid = false; + + function PEAR_ChannelFile() + { + $this->_stack = &new PEAR_ErrorStack('PEAR_ChannelFile'); + $this->_stack->setErrorMessageTemplate($this->_getErrorMessage()); + $this->_isValid = false; + } + + /** + * @return array + * @access protected + */ + function _getErrorMessage() + { + return + array( + PEAR_CHANNELFILE_ERROR_INVALID_VERSION => + 'While parsing channel.xml, an invalid version number "%version% was passed in, expecting one of %versions%', + PEAR_CHANNELFILE_ERROR_NO_VERSION => + 'No version number found in tag', + PEAR_CHANNELFILE_ERROR_NO_XML_EXT => + '%error%', + PEAR_CHANNELFILE_ERROR_CANT_MAKE_PARSER => + 'Unable to create XML parser', + PEAR_CHANNELFILE_ERROR_PARSER_ERROR => + '%error%', + PEAR_CHANNELFILE_ERROR_NO_NAME => + 'Missing channel name', + PEAR_CHANNELFILE_ERROR_INVALID_NAME => + 'Invalid channel %tag% "%name%"', + PEAR_CHANNELFILE_ERROR_NO_SUMMARY => + 'Missing channel summary', + PEAR_CHANNELFILE_ERROR_MULTILINE_SUMMARY => + 'Channel summary should be on one line, but is multi-line', + PEAR_CHANNELFILE_ERROR_NO_HOST => + 'Missing channel server for %type% server', + PEAR_CHANNELFILE_ERROR_INVALID_HOST => + 'Server name "%server%" is invalid for %type% server', + PEAR_CHANNELFILE_ERROR_INVALID_MIRROR => + 'Invalid mirror name "%name%", mirror type %type%', + PEAR_CHANNELFILE_ERROR_INVALID_MIRRORTYPE => + 'Invalid mirror type "%type%"', + PEAR_CHANNELFILE_ERROR_INVALID => + 'Cannot generate xml, contents are invalid', + PEAR_CHANNELFILE_ERROR_EMPTY_REGEX => + 'packagenameregex cannot be empty', + PEAR_CHANNELFILE_ERROR_NO_FUNCTIONVERSION => + '%parent% %protocol% function has no version', + PEAR_CHANNELFILE_ERROR_NO_FUNCTIONNAME => + '%parent% %protocol% function has no name', + PEAR_CHANNELFILE_ERROR_NOBASEURLTYPE => + '%parent% rest baseurl has no type', + PEAR_CHANNELFILE_ERROR_NOVALIDATE_NAME => + 'Validation package has no name in tag', + PEAR_CHANNELFILE_ERROR_NOVALIDATE_VERSION => + 'Validation package "%package%" has no version', + PEAR_CHANNELFILE_ERROR_MIRROR_NOT_FOUND => + 'Mirror "%mirror%" does not exist', + PEAR_CHANNELFILE_ERROR_INVALID_PORT => + 'Port "%port%" must be numeric', + PEAR_CHANNELFILE_ERROR_NO_STATICVERSION => + ' tag must contain version attribute', + PEAR_CHANNELFILE_URI_CANT_MIRROR => + 'The __uri pseudo-channel cannot have mirrors', + PEAR_CHANNELFILE_ERROR_INVALID_SSL => + '%server% has invalid ssl attribute "%ssl%" can only be yes or not present', + ); + } + + /** + * @param string contents of package.xml file + * @return bool success of parsing + */ + function fromXmlString($data) + { + if (preg_match('/_supportedVersions)) { + $this->_stack->push(PEAR_CHANNELFILE_ERROR_INVALID_VERSION, 'error', + array('version' => $channelversion[1])); + return false; + } + $parser = new PEAR_XMLParser; + $result = $parser->parse($data); + if ($result !== true) { + if ($result->getCode() == 1) { + $this->_stack->push(PEAR_CHANNELFILE_ERROR_NO_XML_EXT, 'error', + array('error' => $result->getMessage())); + } else { + $this->_stack->push(PEAR_CHANNELFILE_ERROR_CANT_MAKE_PARSER, 'error'); + } + return false; + } + $this->_channelInfo = $parser->getData(); + return true; + } else { + $this->_stack->push(PEAR_CHANNELFILE_ERROR_NO_VERSION, 'error', array('xml' => $data)); + return false; + } + } + + /** + * @return array + */ + function toArray() + { + if (!$this->_isValid && !$this->validate()) { + return false; + } + return $this->_channelInfo; + } + + /** + * @param array + * @static + * @return PEAR_ChannelFile|false false if invalid + */ + function &fromArray($data, $compatibility = false, $stackClass = 'PEAR_ErrorStack') + { + $a = new PEAR_ChannelFile($compatibility, $stackClass); + $a->_fromArray($data); + if (!$a->validate()) { + $a = false; + return $a; + } + return $a; + } + + /** + * Unlike {@link fromArray()} this does not do any validation + * @param array + * @static + * @return PEAR_ChannelFile + */ + function &fromArrayWithErrors($data, $compatibility = false, + $stackClass = 'PEAR_ErrorStack') + { + $a = new PEAR_ChannelFile($compatibility, $stackClass); + $a->_fromArray($data); + return $a; + } + + /** + * @param array + * @access private + */ + function _fromArray($data) + { + $this->_channelInfo = $data; + } + + /** + * Wrapper to {@link PEAR_ErrorStack::getErrors()} + * @param boolean determines whether to purge the error stack after retrieving + * @return array + */ + function getErrors($purge = false) + { + return $this->_stack->getErrors($purge); + } + + /** + * Unindent given string (?) + * + * @param string $str The string that has to be unindented. + * @return string + * @access private + */ + function _unIndent($str) + { + // remove leading newlines + $str = preg_replace('/^[\r\n]+/', '', $str); + // find whitespace at the beginning of the first line + $indent_len = strspn($str, " \t"); + $indent = substr($str, 0, $indent_len); + $data = ''; + // remove the same amount of whitespace from following lines + foreach (explode("\n", $str) as $line) { + if (substr($line, 0, $indent_len) == $indent) { + $data .= substr($line, $indent_len) . "\n"; + } + } + return $data; + } + + /** + * Parse a channel.xml file. Expects the name of + * a channel xml file as input. + * + * @param string $descfile name of channel xml file + * @return bool success of parsing + */ + function fromXmlFile($descfile) + { + if (!file_exists($descfile) || !is_file($descfile) || !is_readable($descfile) || + (!$fp = fopen($descfile, 'r'))) { + require_once 'PEAR.php'; + return PEAR::raiseError("Unable to open $descfile"); + } + + // read the whole thing so we only get one cdata callback + // for each block of cdata + fclose($fp); + $data = file_get_contents($descfile); + return $this->fromXmlString($data); + } + + /** + * Parse channel information from different sources + * + * This method is able to extract information about a channel + * from an .xml file or a string + * + * @access public + * @param string Filename of the source or the source itself + * @return bool + */ + function fromAny($info) + { + if (is_string($info) && file_exists($info) && strlen($info) < 255) { + $tmp = substr($info, -4); + if ($tmp == '.xml') { + $info = $this->fromXmlFile($info); + } else { + $fp = fopen($info, "r"); + $test = fread($fp, 5); + fclose($fp); + if ($test == "fromXmlFile($info); + } + } + if (PEAR::isError($info)) { + require_once 'PEAR.php'; + return PEAR::raiseError($info); + } + } + if (is_string($info)) { + $info = $this->fromXmlString($info); + } + return $info; + } + + /** + * Return an XML document based on previous parsing and modifications + * + * @return string XML data + * + * @access public + */ + function toXml() + { + if (!$this->_isValid && !$this->validate()) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_INVALID); + return false; + } + if (!isset($this->_channelInfo['attribs']['version'])) { + $this->_channelInfo['attribs']['version'] = '1.0'; + } + $channelInfo = $this->_channelInfo; + $ret = "\n"; + $ret .= " + $channelInfo[name] + " . htmlspecialchars($channelInfo['summary'])." +"; + if (isset($channelInfo['suggestedalias'])) { + $ret .= ' ' . $channelInfo['suggestedalias'] . "\n"; + } + if (isset($channelInfo['validatepackage'])) { + $ret .= ' ' . + htmlspecialchars($channelInfo['validatepackage']['_content']) . + "\n"; + } + $ret .= " \n"; + $ret .= ' _makeRestXml($channelInfo['servers']['primary']['rest'], ' '); + } + $ret .= " \n"; + if (isset($channelInfo['servers']['mirror'])) { + $ret .= $this->_makeMirrorsXml($channelInfo); + } + $ret .= " \n"; + $ret .= ""; + return str_replace("\r", "\n", str_replace("\r\n", "\n", $ret)); + } + + /** + * Generate the tag + * @access private + */ + function _makeRestXml($info, $indent) + { + $ret = $indent . "\n"; + if (isset($info['baseurl']) && !isset($info['baseurl'][0])) { + $info['baseurl'] = array($info['baseurl']); + } + + if (isset($info['baseurl'])) { + foreach ($info['baseurl'] as $url) { + $ret .= "$indent \n"; + } + } + $ret .= $indent . "\n"; + return $ret; + } + + /** + * Generate the tag + * @access private + */ + function _makeMirrorsXml($channelInfo) + { + $ret = ""; + if (!isset($channelInfo['servers']['mirror'][0])) { + $channelInfo['servers']['mirror'] = array($channelInfo['servers']['mirror']); + } + foreach ($channelInfo['servers']['mirror'] as $mirror) { + $ret .= ' _makeRestXml($mirror['rest'], ' '); + } + $ret .= " \n"; + } else { + $ret .= "/>\n"; + } + } + return $ret; + } + + /** + * Generate the tag + * @access private + */ + function _makeFunctionsXml($functions, $indent, $rest = false) + { + $ret = ''; + if (!isset($functions[0])) { + $functions = array($functions); + } + foreach ($functions as $function) { + $ret .= "$indent\n"; + } + return $ret; + } + + /** + * Validation error. Also marks the object contents as invalid + * @param error code + * @param array error information + * @access private + */ + function _validateError($code, $params = array()) + { + $this->_stack->push($code, 'error', $params); + $this->_isValid = false; + } + + /** + * Validation warning. Does not mark the object contents invalid. + * @param error code + * @param array error information + * @access private + */ + function _validateWarning($code, $params = array()) + { + $this->_stack->push($code, 'warning', $params); + } + + /** + * Validate parsed file. + * + * @access public + * @return boolean + */ + function validate() + { + $this->_isValid = true; + $info = $this->_channelInfo; + if (empty($info['name'])) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_NO_NAME); + } elseif (!$this->validChannelServer($info['name'])) { + if ($info['name'] != '__uri') { + $this->_validateError(PEAR_CHANNELFILE_ERROR_INVALID_NAME, array('tag' => 'name', + 'name' => $info['name'])); + } + } + if (empty($info['summary'])) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_NO_SUMMARY); + } elseif (strpos(trim($info['summary']), "\n") !== false) { + $this->_validateWarning(PEAR_CHANNELFILE_ERROR_MULTILINE_SUMMARY, + array('summary' => $info['summary'])); + } + if (isset($info['suggestedalias'])) { + if (!$this->validChannelServer($info['suggestedalias'])) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_INVALID_NAME, + array('tag' => 'suggestedalias', 'name' =>$info['suggestedalias'])); + } + } + if (isset($info['localalias'])) { + if (!$this->validChannelServer($info['localalias'])) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_INVALID_NAME, + array('tag' => 'localalias', 'name' =>$info['localalias'])); + } + } + if (isset($info['validatepackage'])) { + if (!isset($info['validatepackage']['_content'])) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_NOVALIDATE_NAME); + } + if (!isset($info['validatepackage']['attribs']['version'])) { + $content = isset($info['validatepackage']['_content']) ? + $info['validatepackage']['_content'] : + null; + $this->_validateError(PEAR_CHANNELFILE_ERROR_NOVALIDATE_VERSION, + array('package' => $content)); + } + } + + if (isset($info['servers']['primary']['attribs'], $info['servers']['primary']['attribs']['port']) && + !is_numeric($info['servers']['primary']['attribs']['port'])) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_INVALID_PORT, + array('port' => $info['servers']['primary']['attribs']['port'])); + } + + if (isset($info['servers']['primary']['attribs'], $info['servers']['primary']['attribs']['ssl']) && + $info['servers']['primary']['attribs']['ssl'] != 'yes') { + $this->_validateError(PEAR_CHANNELFILE_ERROR_INVALID_SSL, + array('ssl' => $info['servers']['primary']['attribs']['ssl'], + 'server' => $info['name'])); + } + + if (isset($info['servers']['primary']['rest']) && + isset($info['servers']['primary']['rest']['baseurl'])) { + $this->_validateFunctions('rest', $info['servers']['primary']['rest']['baseurl']); + } + if (isset($info['servers']['mirror'])) { + if ($this->_channelInfo['name'] == '__uri') { + $this->_validateError(PEAR_CHANNELFILE_URI_CANT_MIRROR); + } + if (!isset($info['servers']['mirror'][0])) { + $info['servers']['mirror'] = array($info['servers']['mirror']); + } + foreach ($info['servers']['mirror'] as $mirror) { + if (!isset($mirror['attribs']['host'])) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_NO_HOST, + array('type' => 'mirror')); + } elseif (!$this->validChannelServer($mirror['attribs']['host'])) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_INVALID_HOST, + array('server' => $mirror['attribs']['host'], 'type' => 'mirror')); + } + if (isset($mirror['attribs']['ssl']) && $mirror['attribs']['ssl'] != 'yes') { + $this->_validateError(PEAR_CHANNELFILE_ERROR_INVALID_SSL, + array('ssl' => $info['ssl'], 'server' => $mirror['attribs']['host'])); + } + if (isset($mirror['rest'])) { + $this->_validateFunctions('rest', $mirror['rest']['baseurl'], + $mirror['attribs']['host']); + } + } + } + return $this->_isValid; + } + + /** + * @param string rest - protocol name this function applies to + * @param array the functions + * @param string the name of the parent element (mirror name, for instance) + */ + function _validateFunctions($protocol, $functions, $parent = '') + { + if (!isset($functions[0])) { + $functions = array($functions); + } + + foreach ($functions as $function) { + if (!isset($function['_content']) || empty($function['_content'])) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_NO_FUNCTIONNAME, + array('parent' => $parent, 'protocol' => $protocol)); + } + + if ($protocol == 'rest') { + if (!isset($function['attribs']['type']) || + empty($function['attribs']['type'])) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_NOBASEURLTYPE, + array('parent' => $parent, 'protocol' => $protocol)); + } + } else { + if (!isset($function['attribs']['version']) || + empty($function['attribs']['version'])) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_NO_FUNCTIONVERSION, + array('parent' => $parent, 'protocol' => $protocol)); + } + } + } + } + + /** + * Test whether a string contains a valid channel server. + * @param string $ver the package version to test + * @return bool + */ + function validChannelServer($server) + { + if ($server == '__uri') { + return true; + } + return (bool) preg_match(PEAR_CHANNELS_SERVER_PREG, $server); + } + + /** + * @return string|false + */ + function getName() + { + if (isset($this->_channelInfo['name'])) { + return $this->_channelInfo['name']; + } + + return false; + } + + /** + * @return string|false + */ + function getServer() + { + if (isset($this->_channelInfo['name'])) { + return $this->_channelInfo['name']; + } + + return false; + } + + /** + * @return int|80 port number to connect to + */ + function getPort($mirror = false) + { + if ($mirror) { + if ($mir = $this->getMirror($mirror)) { + if (isset($mir['attribs']['port'])) { + return $mir['attribs']['port']; + } + + if ($this->getSSL($mirror)) { + return 443; + } + + return 80; + } + + return false; + } + + if (isset($this->_channelInfo['servers']['primary']['attribs']['port'])) { + return $this->_channelInfo['servers']['primary']['attribs']['port']; + } + + if ($this->getSSL()) { + return 443; + } + + return 80; + } + + /** + * @return bool Determines whether secure sockets layer (SSL) is used to connect to this channel + */ + function getSSL($mirror = false) + { + if ($mirror) { + if ($mir = $this->getMirror($mirror)) { + if (isset($mir['attribs']['ssl'])) { + return true; + } + + return false; + } + + return false; + } + + if (isset($this->_channelInfo['servers']['primary']['attribs']['ssl'])) { + return true; + } + + return false; + } + + /** + * @return string|false + */ + function getSummary() + { + if (isset($this->_channelInfo['summary'])) { + return $this->_channelInfo['summary']; + } + + return false; + } + + /** + * @param string protocol type + * @param string Mirror name + * @return array|false + */ + function getFunctions($protocol, $mirror = false) + { + if ($this->getName() == '__uri') { + return false; + } + + $function = $protocol == 'rest' ? 'baseurl' : 'function'; + if ($mirror) { + if ($mir = $this->getMirror($mirror)) { + if (isset($mir[$protocol][$function])) { + return $mir[$protocol][$function]; + } + } + + return false; + } + + if (isset($this->_channelInfo['servers']['primary'][$protocol][$function])) { + return $this->_channelInfo['servers']['primary'][$protocol][$function]; + } + + return false; + } + + /** + * @param string Protocol type + * @param string Function name (null to return the + * first protocol of the type requested) + * @param string Mirror name, if any + * @return array + */ + function getFunction($type, $name = null, $mirror = false) + { + $protocols = $this->getFunctions($type, $mirror); + if (!$protocols) { + return false; + } + + foreach ($protocols as $protocol) { + if ($name === null) { + return $protocol; + } + + if ($protocol['_content'] != $name) { + continue; + } + + return $protocol; + } + + return false; + } + + /** + * @param string protocol type + * @param string protocol name + * @param string version + * @param string mirror name + * @return boolean + */ + function supports($type, $name = null, $mirror = false, $version = '1.0') + { + $protocols = $this->getFunctions($type, $mirror); + if (!$protocols) { + return false; + } + + foreach ($protocols as $protocol) { + if ($protocol['attribs']['version'] != $version) { + continue; + } + + if ($name === null) { + return true; + } + + if ($protocol['_content'] != $name) { + continue; + } + + return true; + } + + return false; + } + + /** + * Determines whether a channel supports Representational State Transfer (REST) protocols + * for retrieving channel information + * @param string + * @return bool + */ + function supportsREST($mirror = false) + { + if ($mirror == $this->_channelInfo['name']) { + $mirror = false; + } + + if ($mirror) { + if ($mir = $this->getMirror($mirror)) { + return isset($mir['rest']); + } + + return false; + } + + return isset($this->_channelInfo['servers']['primary']['rest']); + } + + /** + * Get the URL to access a base resource. + * + * Hyperlinks in the returned xml will be used to retrieve the proper information + * needed. This allows extreme extensibility and flexibility in implementation + * @param string Resource Type to retrieve + */ + function getBaseURL($resourceType, $mirror = false) + { + if ($mirror == $this->_channelInfo['name']) { + $mirror = false; + } + + if ($mirror) { + $mir = $this->getMirror($mirror); + if (!$mir) { + return false; + } + + $rest = $mir['rest']; + } else { + $rest = $this->_channelInfo['servers']['primary']['rest']; + } + + if (!isset($rest['baseurl'][0])) { + $rest['baseurl'] = array($rest['baseurl']); + } + + foreach ($rest['baseurl'] as $baseurl) { + if (strtolower($baseurl['attribs']['type']) == strtolower($resourceType)) { + return $baseurl['_content']; + } + } + + return false; + } + + /** + * Since REST does not implement RPC, provide this as a logical wrapper around + * resetFunctions for REST + * @param string|false mirror name, if any + */ + function resetREST($mirror = false) + { + return $this->resetFunctions('rest', $mirror); + } + + /** + * Empty all protocol definitions + * @param string protocol type + * @param string|false mirror name, if any + */ + function resetFunctions($type, $mirror = false) + { + if ($mirror) { + if (isset($this->_channelInfo['servers']['mirror'])) { + $mirrors = $this->_channelInfo['servers']['mirror']; + if (!isset($mirrors[0])) { + $mirrors = array($mirrors); + } + + foreach ($mirrors as $i => $mir) { + if ($mir['attribs']['host'] == $mirror) { + if (isset($this->_channelInfo['servers']['mirror'][$i][$type])) { + unset($this->_channelInfo['servers']['mirror'][$i][$type]); + } + + return true; + } + } + + return false; + } + + return false; + } + + if (isset($this->_channelInfo['servers']['primary'][$type])) { + unset($this->_channelInfo['servers']['primary'][$type]); + } + + return true; + } + + /** + * Set a channel's protocols to the protocols supported by pearweb + */ + function setDefaultPEARProtocols($version = '1.0', $mirror = false) + { + switch ($version) { + case '1.0' : + $this->resetREST($mirror); + + if (!isset($this->_channelInfo['servers'])) { + $this->_channelInfo['servers'] = array('primary' => + array('rest' => array())); + } elseif (!isset($this->_channelInfo['servers']['primary'])) { + $this->_channelInfo['servers']['primary'] = array('rest' => array()); + } + + return true; + break; + default : + return false; + break; + } + } + + /** + * @return array + */ + function getMirrors() + { + if (isset($this->_channelInfo['servers']['mirror'])) { + $mirrors = $this->_channelInfo['servers']['mirror']; + if (!isset($mirrors[0])) { + $mirrors = array($mirrors); + } + + return $mirrors; + } + + return array(); + } + + /** + * Get the unserialized XML representing a mirror + * @return array|false + */ + function getMirror($server) + { + foreach ($this->getMirrors() as $mirror) { + if ($mirror['attribs']['host'] == $server) { + return $mirror; + } + } + + return false; + } + + /** + * @param string + * @return string|false + * @error PEAR_CHANNELFILE_ERROR_NO_NAME + * @error PEAR_CHANNELFILE_ERROR_INVALID_NAME + */ + function setName($name) + { + return $this->setServer($name); + } + + /** + * Set the socket number (port) that is used to connect to this channel + * @param integer + * @param string|false name of the mirror server, or false for the primary + */ + function setPort($port, $mirror = false) + { + if ($mirror) { + if (!isset($this->_channelInfo['servers']['mirror'])) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_MIRROR_NOT_FOUND, + array('mirror' => $mirror)); + return false; + } + + if (isset($this->_channelInfo['servers']['mirror'][0])) { + foreach ($this->_channelInfo['servers']['mirror'] as $i => $mir) { + if ($mirror == $mir['attribs']['host']) { + $this->_channelInfo['servers']['mirror'][$i]['attribs']['port'] = $port; + return true; + } + } + + return false; + } elseif ($this->_channelInfo['servers']['mirror']['attribs']['host'] == $mirror) { + $this->_channelInfo['servers']['mirror']['attribs']['port'] = $port; + $this->_isValid = false; + return true; + } + } + + $this->_channelInfo['servers']['primary']['attribs']['port'] = $port; + $this->_isValid = false; + return true; + } + + /** + * Set the socket number (port) that is used to connect to this channel + * @param bool Determines whether to turn on SSL support or turn it off + * @param string|false name of the mirror server, or false for the primary + */ + function setSSL($ssl = true, $mirror = false) + { + if ($mirror) { + if (!isset($this->_channelInfo['servers']['mirror'])) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_MIRROR_NOT_FOUND, + array('mirror' => $mirror)); + return false; + } + + if (isset($this->_channelInfo['servers']['mirror'][0])) { + foreach ($this->_channelInfo['servers']['mirror'] as $i => $mir) { + if ($mirror == $mir['attribs']['host']) { + if (!$ssl) { + if (isset($this->_channelInfo['servers']['mirror'][$i] + ['attribs']['ssl'])) { + unset($this->_channelInfo['servers']['mirror'][$i]['attribs']['ssl']); + } + } else { + $this->_channelInfo['servers']['mirror'][$i]['attribs']['ssl'] = 'yes'; + } + + return true; + } + } + + return false; + } elseif ($this->_channelInfo['servers']['mirror']['attribs']['host'] == $mirror) { + if (!$ssl) { + if (isset($this->_channelInfo['servers']['mirror']['attribs']['ssl'])) { + unset($this->_channelInfo['servers']['mirror']['attribs']['ssl']); + } + } else { + $this->_channelInfo['servers']['mirror']['attribs']['ssl'] = 'yes'; + } + + $this->_isValid = false; + return true; + } + } + + if ($ssl) { + $this->_channelInfo['servers']['primary']['attribs']['ssl'] = 'yes'; + } else { + if (isset($this->_channelInfo['servers']['primary']['attribs']['ssl'])) { + unset($this->_channelInfo['servers']['primary']['attribs']['ssl']); + } + } + + $this->_isValid = false; + return true; + } + + /** + * @param string + * @return string|false + * @error PEAR_CHANNELFILE_ERROR_NO_SERVER + * @error PEAR_CHANNELFILE_ERROR_INVALID_SERVER + */ + function setServer($server, $mirror = false) + { + if (empty($server)) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_NO_SERVER); + return false; + } elseif (!$this->validChannelServer($server)) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_INVALID_NAME, + array('tag' => 'name', 'name' => $server)); + return false; + } + + if ($mirror) { + $found = false; + foreach ($this->_channelInfo['servers']['mirror'] as $i => $mir) { + if ($mirror == $mir['attribs']['host']) { + $found = true; + break; + } + } + + if (!$found) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_MIRROR_NOT_FOUND, + array('mirror' => $mirror)); + return false; + } + + $this->_channelInfo['mirror'][$i]['attribs']['host'] = $server; + return true; + } + + $this->_channelInfo['name'] = $server; + return true; + } + + /** + * @param string + * @return boolean success + * @error PEAR_CHANNELFILE_ERROR_NO_SUMMARY + * @warning PEAR_CHANNELFILE_ERROR_MULTILINE_SUMMARY + */ + function setSummary($summary) + { + if (empty($summary)) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_NO_SUMMARY); + return false; + } elseif (strpos(trim($summary), "\n") !== false) { + $this->_validateWarning(PEAR_CHANNELFILE_ERROR_MULTILINE_SUMMARY, + array('summary' => $summary)); + } + + $this->_channelInfo['summary'] = $summary; + return true; + } + + /** + * @param string + * @param boolean determines whether the alias is in channel.xml or local + * @return boolean success + */ + function setAlias($alias, $local = false) + { + if (!$this->validChannelServer($alias)) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_INVALID_NAME, + array('tag' => 'suggestedalias', 'name' => $alias)); + return false; + } + + if ($local) { + $this->_channelInfo['localalias'] = $alias; + } else { + $this->_channelInfo['suggestedalias'] = $alias; + } + + return true; + } + + /** + * @return string + */ + function getAlias() + { + if (isset($this->_channelInfo['localalias'])) { + return $this->_channelInfo['localalias']; + } + if (isset($this->_channelInfo['suggestedalias'])) { + return $this->_channelInfo['suggestedalias']; + } + if (isset($this->_channelInfo['name'])) { + return $this->_channelInfo['name']; + } + return ''; + } + + /** + * Set the package validation object if it differs from PEAR's default + * The class must be includeable via changing _ in the classname to path separator, + * but no checking of this is made. + * @param string|false pass in false to reset to the default packagename regex + * @return boolean success + */ + function setValidationPackage($validateclass, $version) + { + if (empty($validateclass)) { + unset($this->_channelInfo['validatepackage']); + } + $this->_channelInfo['validatepackage'] = array('_content' => $validateclass); + $this->_channelInfo['validatepackage']['attribs'] = array('version' => $version); + } + + /** + * Add a protocol to the provides section + * @param string protocol type + * @param string protocol version + * @param string protocol name, if any + * @param string mirror name, if this is a mirror's protocol + * @return bool + */ + function addFunction($type, $version, $name = '', $mirror = false) + { + if ($mirror) { + return $this->addMirrorFunction($mirror, $type, $version, $name); + } + + $set = array('attribs' => array('version' => $version), '_content' => $name); + if (!isset($this->_channelInfo['servers']['primary'][$type]['function'])) { + if (!isset($this->_channelInfo['servers'])) { + $this->_channelInfo['servers'] = array('primary' => + array($type => array())); + } elseif (!isset($this->_channelInfo['servers']['primary'])) { + $this->_channelInfo['servers']['primary'] = array($type => array()); + } + + $this->_channelInfo['servers']['primary'][$type]['function'] = $set; + $this->_isValid = false; + return true; + } elseif (!isset($this->_channelInfo['servers']['primary'][$type]['function'][0])) { + $this->_channelInfo['servers']['primary'][$type]['function'] = array( + $this->_channelInfo['servers']['primary'][$type]['function']); + } + + $this->_channelInfo['servers']['primary'][$type]['function'][] = $set; + return true; + } + /** + * Add a protocol to a mirror's provides section + * @param string mirror name (server) + * @param string protocol type + * @param string protocol version + * @param string protocol name, if any + */ + function addMirrorFunction($mirror, $type, $version, $name = '') + { + if (!isset($this->_channelInfo['servers']['mirror'])) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_MIRROR_NOT_FOUND, + array('mirror' => $mirror)); + return false; + } + + $setmirror = false; + if (isset($this->_channelInfo['servers']['mirror'][0])) { + foreach ($this->_channelInfo['servers']['mirror'] as $i => $mir) { + if ($mirror == $mir['attribs']['host']) { + $setmirror = &$this->_channelInfo['servers']['mirror'][$i]; + break; + } + } + } else { + if ($this->_channelInfo['servers']['mirror']['attribs']['host'] == $mirror) { + $setmirror = &$this->_channelInfo['servers']['mirror']; + } + } + + if (!$setmirror) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_MIRROR_NOT_FOUND, + array('mirror' => $mirror)); + return false; + } + + $set = array('attribs' => array('version' => $version), '_content' => $name); + if (!isset($setmirror[$type]['function'])) { + $setmirror[$type]['function'] = $set; + $this->_isValid = false; + return true; + } elseif (!isset($setmirror[$type]['function'][0])) { + $setmirror[$type]['function'] = array($setmirror[$type]['function']); + } + + $setmirror[$type]['function'][] = $set; + $this->_isValid = false; + return true; + } + + /** + * @param string Resource Type this url links to + * @param string URL + * @param string|false mirror name, if this is not a primary server REST base URL + */ + function setBaseURL($resourceType, $url, $mirror = false) + { + if ($mirror) { + if (!isset($this->_channelInfo['servers']['mirror'])) { + $this->_validateError(PEAR_CHANNELFILE_ERROR_MIRROR_NOT_FOUND, + array('mirror' => $mirror)); + return false; + } + + $setmirror = false; + if (isset($this->_channelInfo['servers']['mirror'][0])) { + foreach ($this->_channelInfo['servers']['mirror'] as $i => $mir) { + if ($mirror == $mir['attribs']['host']) { + $setmirror = &$this->_channelInfo['servers']['mirror'][$i]; + break; + } + } + } else { + if ($this->_channelInfo['servers']['mirror']['attribs']['host'] == $mirror) { + $setmirror = &$this->_channelInfo['servers']['mirror']; + } + } + } else { + $setmirror = &$this->_channelInfo['servers']['primary']; + } + + $set = array('attribs' => array('type' => $resourceType), '_content' => $url); + if (!isset($setmirror['rest'])) { + $setmirror['rest'] = array(); + } + + if (!isset($setmirror['rest']['baseurl'])) { + $setmirror['rest']['baseurl'] = $set; + $this->_isValid = false; + return true; + } elseif (!isset($setmirror['rest']['baseurl'][0])) { + $setmirror['rest']['baseurl'] = array($setmirror['rest']['baseurl']); + } + + foreach ($setmirror['rest']['baseurl'] as $i => $url) { + if ($url['attribs']['type'] == $resourceType) { + $this->_isValid = false; + $setmirror['rest']['baseurl'][$i] = $set; + return true; + } + } + + $setmirror['rest']['baseurl'][] = $set; + $this->_isValid = false; + return true; + } + + /** + * @param string mirror server + * @param int mirror http port + * @return boolean + */ + function addMirror($server, $port = null) + { + if ($this->_channelInfo['name'] == '__uri') { + return false; // the __uri channel cannot have mirrors by definition + } + + $set = array('attribs' => array('host' => $server)); + if (is_numeric($port)) { + $set['attribs']['port'] = $port; + } + + if (!isset($this->_channelInfo['servers']['mirror'])) { + $this->_channelInfo['servers']['mirror'] = $set; + return true; + } + + if (!isset($this->_channelInfo['servers']['mirror'][0])) { + $this->_channelInfo['servers']['mirror'] = + array($this->_channelInfo['servers']['mirror']); + } + + $this->_channelInfo['servers']['mirror'][] = $set; + return true; + } + + /** + * Retrieve the name of the validation package for this channel + * @return string|false + */ + function getValidationPackage() + { + if (!$this->_isValid && !$this->validate()) { + return false; + } + + if (!isset($this->_channelInfo['validatepackage'])) { + return array('attribs' => array('version' => 'default'), + '_content' => 'PEAR_Validate'); + } + + return $this->_channelInfo['validatepackage']; + } + + /** + * Retrieve the object that can be used for custom validation + * @param string|false the name of the package to validate. If the package is + * the channel validation package, PEAR_Validate is returned + * @return PEAR_Validate|false false is returned if the validation package + * cannot be located + */ + function &getValidationObject($package = false) + { + if (!class_exists('PEAR_Validate')) { + require_once 'PEAR/Validate.php'; + } + + if (!$this->_isValid) { + if (!$this->validate()) { + $a = false; + return $a; + } + } + + if (isset($this->_channelInfo['validatepackage'])) { + if ($package == $this->_channelInfo['validatepackage']) { + // channel validation packages are always validated by PEAR_Validate + $val = &new PEAR_Validate; + return $val; + } + + if (!class_exists(str_replace('.', '_', + $this->_channelInfo['validatepackage']['_content']))) { + if ($this->isIncludeable(str_replace('_', '/', + $this->_channelInfo['validatepackage']['_content']) . '.php')) { + include_once str_replace('_', '/', + $this->_channelInfo['validatepackage']['_content']) . '.php'; + $vclass = str_replace('.', '_', + $this->_channelInfo['validatepackage']['_content']); + $val = &new $vclass; + } else { + $a = false; + return $a; + } + } else { + $vclass = str_replace('.', '_', + $this->_channelInfo['validatepackage']['_content']); + $val = &new $vclass; + } + } else { + $val = &new PEAR_Validate; + } + + return $val; + } + + function isIncludeable($path) + { + $possibilities = explode(PATH_SEPARATOR, ini_get('include_path')); + foreach ($possibilities as $dir) { + if (file_exists($dir . DIRECTORY_SEPARATOR . $path) + && is_readable($dir . DIRECTORY_SEPARATOR . $path)) { + return true; + } + } + + return false; + } + + /** + * This function is used by the channel updater and retrieves a value set by + * the registry, or the current time if it has not been set + * @return string + */ + function lastModified() + { + if (isset($this->_channelInfo['_lastmodified'])) { + return $this->_channelInfo['_lastmodified']; + } + + return time(); + } +} \ No newline at end of file diff --git a/library/pear/PEAR/ChannelFile/Parser.php b/library/pear/PEAR/ChannelFile/Parser.php new file mode 100644 index 000000000..14ff45411 --- /dev/null +++ b/library/pear/PEAR/ChannelFile/Parser.php @@ -0,0 +1,68 @@ + + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Parser.php 276383 2009-02-24 23:39:37Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ + +/** + * base xml parser class + */ +require_once 'PEAR/XMLParser.php'; +require_once 'PEAR/ChannelFile.php'; +/** + * Parser for channel.xml + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.1 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_ChannelFile_Parser extends PEAR_XMLParser +{ + var $_config; + var $_logger; + var $_registry; + + function setConfig(&$c) + { + $this->_config = &$c; + $this->_registry = &$c->getRegistry(); + } + + function setLogger(&$l) + { + $this->_logger = &$l; + } + + function parse($data, $file) + { + if (PEAR::isError($err = parent::parse($data, $file))) { + return $err; + } + + $ret = new PEAR_ChannelFile; + $ret->setConfig($this->_config); + if (isset($this->_logger)) { + $ret->setLogger($this->_logger); + } + + $ret->fromArray($this->_unserializedData); + // make sure the filelist is in the easy to read format needed + $ret->flattenFilelist(); + $ret->setPackagefile($file, $archive); + return $ret; + } +} \ No newline at end of file diff --git a/library/pear/PEAR/Command.php b/library/pear/PEAR/Command.php new file mode 100644 index 000000000..26ec17eea --- /dev/null +++ b/library/pear/PEAR/Command.php @@ -0,0 +1,414 @@ + + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Command.php 286494 2009-07-29 06:57:11Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 0.1 + */ + +/** + * Needed for error handling + */ +require_once 'PEAR.php'; +require_once 'PEAR/Frontend.php'; +require_once 'PEAR/XMLParser.php'; + +/** + * List of commands and what classes they are implemented in. + * @var array command => implementing class + */ +$GLOBALS['_PEAR_Command_commandlist'] = array(); + +/** + * List of commands and their descriptions + * @var array command => description + */ +$GLOBALS['_PEAR_Command_commanddesc'] = array(); + +/** + * List of shortcuts to common commands. + * @var array shortcut => command + */ +$GLOBALS['_PEAR_Command_shortcuts'] = array(); + +/** + * Array of command objects + * @var array class => object + */ +$GLOBALS['_PEAR_Command_objects'] = array(); + +/** + * PEAR command class, a simple factory class for administrative + * commands. + * + * How to implement command classes: + * + * - The class must be called PEAR_Command_Nnn, installed in the + * "PEAR/Common" subdir, with a method called getCommands() that + * returns an array of the commands implemented by the class (see + * PEAR/Command/Install.php for an example). + * + * - The class must implement a run() function that is called with three + * params: + * + * (string) command name + * (array) assoc array with options, freely defined by each + * command, for example: + * array('force' => true) + * (array) list of the other parameters + * + * The run() function returns a PEAR_CommandResponse object. Use + * these methods to get information: + * + * int getStatus() Returns PEAR_COMMAND_(SUCCESS|FAILURE|PARTIAL) + * *_PARTIAL means that you need to issue at least + * one more command to complete the operation + * (used for example for validation steps). + * + * string getMessage() Returns a message for the user. Remember, + * no HTML or other interface-specific markup. + * + * If something unexpected happens, run() returns a PEAR error. + * + * - DON'T OUTPUT ANYTHING! Return text for output instead. + * + * - DON'T USE HTML! The text you return will be used from both Gtk, + * web and command-line interfaces, so for now, keep everything to + * plain text. + * + * - DON'T USE EXIT OR DIE! Always use pear errors. From static + * classes do PEAR::raiseError(), from other classes do + * $this->raiseError(). + * @category pear + * @package PEAR + * @author Stig Bakken + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.1 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 0.1 + */ +class PEAR_Command +{ + // {{{ factory() + + /** + * Get the right object for executing a command. + * + * @param string $command The name of the command + * @param object $config Instance of PEAR_Config object + * + * @return object the command object or a PEAR error + * + * @access public + * @static + */ + function &factory($command, &$config) + { + if (empty($GLOBALS['_PEAR_Command_commandlist'])) { + PEAR_Command::registerCommands(); + } + if (isset($GLOBALS['_PEAR_Command_shortcuts'][$command])) { + $command = $GLOBALS['_PEAR_Command_shortcuts'][$command]; + } + if (!isset($GLOBALS['_PEAR_Command_commandlist'][$command])) { + $a = PEAR::raiseError("unknown command `$command'"); + return $a; + } + $class = $GLOBALS['_PEAR_Command_commandlist'][$command]; + if (!class_exists($class)) { + require_once $GLOBALS['_PEAR_Command_objects'][$class]; + } + if (!class_exists($class)) { + $a = PEAR::raiseError("unknown command `$command'"); + return $a; + } + $ui =& PEAR_Command::getFrontendObject(); + $obj = &new $class($ui, $config); + return $obj; + } + + // }}} + // {{{ & getObject() + function &getObject($command) + { + $class = $GLOBALS['_PEAR_Command_commandlist'][$command]; + if (!class_exists($class)) { + require_once $GLOBALS['_PEAR_Command_objects'][$class]; + } + if (!class_exists($class)) { + return PEAR::raiseError("unknown command `$command'"); + } + $ui =& PEAR_Command::getFrontendObject(); + $config = &PEAR_Config::singleton(); + $obj = &new $class($ui, $config); + return $obj; + } + + // }}} + // {{{ & getFrontendObject() + + /** + * Get instance of frontend object. + * + * @return object|PEAR_Error + * @static + */ + function &getFrontendObject() + { + $a = &PEAR_Frontend::singleton(); + return $a; + } + + // }}} + // {{{ & setFrontendClass() + + /** + * Load current frontend class. + * + * @param string $uiclass Name of class implementing the frontend + * + * @return object the frontend object, or a PEAR error + * @static + */ + function &setFrontendClass($uiclass) + { + $a = &PEAR_Frontend::setFrontendClass($uiclass); + return $a; + } + + // }}} + // {{{ setFrontendType() + + /** + * Set current frontend. + * + * @param string $uitype Name of the frontend type (for example "CLI") + * + * @return object the frontend object, or a PEAR error + * @static + */ + function setFrontendType($uitype) + { + $uiclass = 'PEAR_Frontend_' . $uitype; + return PEAR_Command::setFrontendClass($uiclass); + } + + // }}} + // {{{ registerCommands() + + /** + * Scan through the Command directory looking for classes + * and see what commands they implement. + * + * @param bool (optional) if FALSE (default), the new list of + * commands should replace the current one. If TRUE, + * new entries will be merged with old. + * + * @param string (optional) where (what directory) to look for + * classes, defaults to the Command subdirectory of + * the directory from where this file (__FILE__) is + * included. + * + * @return bool TRUE on success, a PEAR error on failure + * + * @access public + * @static + */ + function registerCommands($merge = false, $dir = null) + { + $parser = new PEAR_XMLParser; + if ($dir === null) { + $dir = dirname(__FILE__) . '/Command'; + } + if (!is_dir($dir)) { + return PEAR::raiseError("registerCommands: opendir($dir) '$dir' does not exist or is not a directory"); + } + $dp = @opendir($dir); + if (empty($dp)) { + return PEAR::raiseError("registerCommands: opendir($dir) failed"); + } + if (!$merge) { + $GLOBALS['_PEAR_Command_commandlist'] = array(); + } + + while ($file = readdir($dp)) { + if ($file{0} == '.' || substr($file, -4) != '.xml') { + continue; + } + + $f = substr($file, 0, -4); + $class = "PEAR_Command_" . $f; + // List of commands + if (empty($GLOBALS['_PEAR_Command_objects'][$class])) { + $GLOBALS['_PEAR_Command_objects'][$class] = "$dir/" . $f . '.php'; + } + + $parser->parse(file_get_contents("$dir/$file")); + $implements = $parser->getData(); + foreach ($implements as $command => $desc) { + if ($command == 'attribs') { + continue; + } + + if (isset($GLOBALS['_PEAR_Command_commandlist'][$command])) { + return PEAR::raiseError('Command "' . $command . '" already registered in ' . + 'class "' . $GLOBALS['_PEAR_Command_commandlist'][$command] . '"'); + } + + $GLOBALS['_PEAR_Command_commandlist'][$command] = $class; + $GLOBALS['_PEAR_Command_commanddesc'][$command] = $desc['summary']; + if (isset($desc['shortcut'])) { + $shortcut = $desc['shortcut']; + if (isset($GLOBALS['_PEAR_Command_shortcuts'][$shortcut])) { + return PEAR::raiseError('Command shortcut "' . $shortcut . '" already ' . + 'registered to command "' . $command . '" in class "' . + $GLOBALS['_PEAR_Command_commandlist'][$command] . '"'); + } + $GLOBALS['_PEAR_Command_shortcuts'][$shortcut] = $command; + } + + if (isset($desc['options']) && $desc['options']) { + foreach ($desc['options'] as $oname => $option) { + if (isset($option['shortopt']) && strlen($option['shortopt']) > 1) { + return PEAR::raiseError('Option "' . $oname . '" short option "' . + $option['shortopt'] . '" must be ' . + 'only 1 character in Command "' . $command . '" in class "' . + $class . '"'); + } + } + } + } + } + + ksort($GLOBALS['_PEAR_Command_shortcuts']); + ksort($GLOBALS['_PEAR_Command_commandlist']); + @closedir($dp); + return true; + } + + // }}} + // {{{ getCommands() + + /** + * Get the list of currently supported commands, and what + * classes implement them. + * + * @return array command => implementing class + * + * @access public + * @static + */ + function getCommands() + { + if (empty($GLOBALS['_PEAR_Command_commandlist'])) { + PEAR_Command::registerCommands(); + } + return $GLOBALS['_PEAR_Command_commandlist']; + } + + // }}} + // {{{ getShortcuts() + + /** + * Get the list of command shortcuts. + * + * @return array shortcut => command + * + * @access public + * @static + */ + function getShortcuts() + { + if (empty($GLOBALS['_PEAR_Command_shortcuts'])) { + PEAR_Command::registerCommands(); + } + return $GLOBALS['_PEAR_Command_shortcuts']; + } + + // }}} + // {{{ getGetoptArgs() + + /** + * Compiles arguments for getopt. + * + * @param string $command command to get optstring for + * @param string $short_args (reference) short getopt format + * @param array $long_args (reference) long getopt format + * + * @return void + * + * @access public + * @static + */ + function getGetoptArgs($command, &$short_args, &$long_args) + { + if (empty($GLOBALS['_PEAR_Command_commandlist'])) { + PEAR_Command::registerCommands(); + } + if (isset($GLOBALS['_PEAR_Command_shortcuts'][$command])) { + $command = $GLOBALS['_PEAR_Command_shortcuts'][$command]; + } + if (!isset($GLOBALS['_PEAR_Command_commandlist'][$command])) { + return null; + } + $obj = &PEAR_Command::getObject($command); + return $obj->getGetoptArgs($command, $short_args, $long_args); + } + + // }}} + // {{{ getDescription() + + /** + * Get description for a command. + * + * @param string $command Name of the command + * + * @return string command description + * + * @access public + * @static + */ + function getDescription($command) + { + if (!isset($GLOBALS['_PEAR_Command_commanddesc'][$command])) { + return null; + } + return $GLOBALS['_PEAR_Command_commanddesc'][$command]; + } + + // }}} + // {{{ getHelp() + + /** + * Get help for command. + * + * @param string $command Name of the command to return help for + * + * @access public + * @static + */ + function getHelp($command) + { + $cmds = PEAR_Command::getCommands(); + if (isset($GLOBALS['_PEAR_Command_shortcuts'][$command])) { + $command = $GLOBALS['_PEAR_Command_shortcuts'][$command]; + } + if (isset($cmds[$command])) { + $obj = &PEAR_Command::getObject($command); + return $obj->getHelp($command); + } + return false; + } + // }}} +} \ No newline at end of file diff --git a/library/pear/PEAR/Command/Auth.php b/library/pear/PEAR/Command/Auth.php new file mode 100644 index 000000000..8ae829804 --- /dev/null +++ b/library/pear/PEAR/Command/Auth.php @@ -0,0 +1,81 @@ + + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Auth.php 276383 2009-02-24 23:39:37Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 0.1 + * @deprecated since 1.8.0alpha1 + */ + +/** + * base class + */ +require_once 'PEAR/Command/Channels.php'; + +/** + * PEAR commands for login/logout + * + * @category pear + * @package PEAR + * @author Stig Bakken + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.1 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 0.1 + * @deprecated since 1.8.0alpha1 + */ +class PEAR_Command_Auth extends PEAR_Command_Channels +{ + var $commands = array( + 'login' => array( + 'summary' => 'Connects and authenticates to remote server [Deprecated in favor of channel-login]', + 'shortcut' => 'li', + 'function' => 'doLogin', + 'options' => array(), + 'doc' => ' +WARNING: This function is deprecated in favor of using channel-login + +Log in to a remote channel server. If is not supplied, +the default channel is used. To use remote functions in the installer +that require any kind of privileges, you need to log in first. The +username and password you enter here will be stored in your per-user +PEAR configuration (~/.pearrc on Unix-like systems). After logging +in, your username and password will be sent along in subsequent +operations on the remote server.', + ), + 'logout' => array( + 'summary' => 'Logs out from the remote server [Deprecated in favor of channel-logout]', + 'shortcut' => 'lo', + 'function' => 'doLogout', + 'options' => array(), + 'doc' => ' +WARNING: This function is deprecated in favor of using channel-logout + +Logs out from the remote server. This command does not actually +connect to the remote server, it only deletes the stored username and +password from your user configuration.', + ) + + ); + + /** + * PEAR_Command_Auth constructor. + * + * @access public + */ + function PEAR_Command_Auth(&$ui, &$config) + { + parent::PEAR_Command_Channels($ui, $config); + } +} \ No newline at end of file diff --git a/library/pear/PEAR/Command/Auth.xml b/library/pear/PEAR/Command/Auth.xml new file mode 100644 index 000000000..590193d14 --- /dev/null +++ b/library/pear/PEAR/Command/Auth.xml @@ -0,0 +1,30 @@ + + + Connects and authenticates to remote server [Deprecated in favor of channel-login] + doLogin + li + + <channel name> +WARNING: This function is deprecated in favor of using channel-login + +Log in to a remote channel server. If <channel name> is not supplied, +the default channel is used. To use remote functions in the installer +that require any kind of privileges, you need to log in first. The +username and password you enter here will be stored in your per-user +PEAR configuration (~/.pearrc on Unix-like systems). After logging +in, your username and password will be sent along in subsequent +operations on the remote server. + + + Logs out from the remote server [Deprecated in favor of channel-logout] + doLogout + lo + + +WARNING: This function is deprecated in favor of using channel-logout + +Logs out from the remote server. This command does not actually +connect to the remote server, it only deletes the stored username and +password from your user configuration. + + \ No newline at end of file diff --git a/library/pear/PEAR/Command/Build.php b/library/pear/PEAR/Command/Build.php new file mode 100644 index 000000000..2f38c5bf3 --- /dev/null +++ b/library/pear/PEAR/Command/Build.php @@ -0,0 +1,85 @@ + + * @author Tomas V.V.Cox + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Build.php 276383 2009-02-24 23:39:37Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 0.1 + */ + +/** + * base class + */ +require_once 'PEAR/Command/Common.php'; + +/** + * PEAR commands for building extensions. + * + * @category pear + * @package PEAR + * @author Stig Bakken + * @author Tomas V.V.Cox + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.1 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 0.1 + */ +class PEAR_Command_Build extends PEAR_Command_Common +{ + var $commands = array( + 'build' => array( + 'summary' => 'Build an Extension From C Source', + 'function' => 'doBuild', + 'shortcut' => 'b', + 'options' => array(), + 'doc' => '[package.xml] +Builds one or more extensions contained in a package.' + ), + ); + + /** + * PEAR_Command_Build constructor. + * + * @access public + */ + function PEAR_Command_Build(&$ui, &$config) + { + parent::PEAR_Command_Common($ui, $config); + } + + function doBuild($command, $options, $params) + { + require_once 'PEAR/Builder.php'; + if (sizeof($params) < 1) { + $params[0] = 'package.xml'; + } + + $builder = &new PEAR_Builder($this->ui); + $this->debug = $this->config->get('verbose'); + $err = $builder->build($params[0], array(&$this, 'buildCallback')); + if (PEAR::isError($err)) { + return $err; + } + + return true; + } + + function buildCallback($what, $data) + { + if (($what == 'cmdoutput' && $this->debug > 1) || + ($what == 'output' && $this->debug > 0)) { + $this->ui->outputData(rtrim($data), 'build'); + } + } +} \ No newline at end of file diff --git a/library/pear/PEAR/Command/Build.xml b/library/pear/PEAR/Command/Build.xml new file mode 100644 index 000000000..ec4e6f554 --- /dev/null +++ b/library/pear/PEAR/Command/Build.xml @@ -0,0 +1,10 @@ + + + Build an Extension From C Source + doBuild + b + + [package.xml] +Builds one or more extensions contained in a package. + + \ No newline at end of file diff --git a/library/pear/PEAR/Command/Channels.php b/library/pear/PEAR/Command/Channels.php new file mode 100644 index 000000000..b0cc2e807 --- /dev/null +++ b/library/pear/PEAR/Command/Channels.php @@ -0,0 +1,883 @@ + + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Channels.php 287561 2009-08-21 22:42:58Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ + +/** + * base class + */ +require_once 'PEAR/Command/Common.php'; + +define('PEAR_COMMAND_CHANNELS_CHANNEL_EXISTS', -500); + +/** + * PEAR commands for managing channels. + * + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.1 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_Command_Channels extends PEAR_Command_Common +{ + var $commands = array( + 'list-channels' => array( + 'summary' => 'List Available Channels', + 'function' => 'doList', + 'shortcut' => 'lc', + 'options' => array(), + 'doc' => ' +List all available channels for installation. +', + ), + 'update-channels' => array( + 'summary' => 'Update the Channel List', + 'function' => 'doUpdateAll', + 'shortcut' => 'uc', + 'options' => array(), + 'doc' => ' +List all installed packages in all channels. +' + ), + 'channel-delete' => array( + 'summary' => 'Remove a Channel From the List', + 'function' => 'doDelete', + 'shortcut' => 'cde', + 'options' => array(), + 'doc' => ' +Delete a channel from the registry. You may not +remove any channel that has installed packages. +' + ), + 'channel-add' => array( + 'summary' => 'Add a Channel', + 'function' => 'doAdd', + 'shortcut' => 'ca', + 'options' => array(), + 'doc' => ' +Add a private channel to the channel list. Note that all +public channels should be synced using "update-channels". +Parameter may be either a local file or remote URL to a +channel.xml. +' + ), + 'channel-update' => array( + 'summary' => 'Update an Existing Channel', + 'function' => 'doUpdate', + 'shortcut' => 'cu', + 'options' => array( + 'force' => array( + 'shortopt' => 'f', + 'doc' => 'will force download of new channel.xml if an existing channel name is used', + ), + 'channel' => array( + 'shortopt' => 'c', + 'arg' => 'CHANNEL', + 'doc' => 'will force download of new channel.xml if an existing channel name is used', + ), +), + 'doc' => '[|] +Update a channel in the channel list directly. Note that all +public channels can be synced using "update-channels". +Parameter may be a local or remote channel.xml, or the name of +an existing channel. +' + ), + 'channel-info' => array( + 'summary' => 'Retrieve Information on a Channel', + 'function' => 'doInfo', + 'shortcut' => 'ci', + 'options' => array(), + 'doc' => ' +List the files in an installed package. +' + ), + 'channel-alias' => array( + 'summary' => 'Specify an alias to a channel name', + 'function' => 'doAlias', + 'shortcut' => 'cha', + 'options' => array(), + 'doc' => ' +Specify a specific alias to use for a channel name. +The alias may not be an existing channel name or +alias. +' + ), + 'channel-discover' => array( + 'summary' => 'Initialize a Channel from its server', + 'function' => 'doDiscover', + 'shortcut' => 'di', + 'options' => array(), + 'doc' => '[|] +Initialize a channel from its server and create a local channel.xml. +If is in the format ":@" then + and will be set as the login username/password for +. Use caution when passing the username/password in this way, as +it may allow other users on your computer to briefly view your username/ +password via the system\'s process list. +' + ), + 'channel-login' => array( + 'summary' => 'Connects and authenticates to remote channel server', + 'shortcut' => 'cli', + 'function' => 'doLogin', + 'options' => array(), + 'doc' => ' +Log in to a remote channel server. If is not supplied, +the default channel is used. To use remote functions in the installer +that require any kind of privileges, you need to log in first. The +username and password you enter here will be stored in your per-user +PEAR configuration (~/.pearrc on Unix-like systems). After logging +in, your username and password will be sent along in subsequent +operations on the remote server.', + ), + 'channel-logout' => array( + 'summary' => 'Logs out from the remote channel server', + 'shortcut' => 'clo', + 'function' => 'doLogout', + 'options' => array(), + 'doc' => ' +Logs out from a remote channel server. If is not supplied, +the default channel is used. This command does not actually connect to the +remote server, it only deletes the stored username and password from your user +configuration.', + ), + ); + + /** + * PEAR_Command_Registry constructor. + * + * @access public + */ + function PEAR_Command_Channels(&$ui, &$config) + { + parent::PEAR_Command_Common($ui, $config); + } + + function _sortChannels($a, $b) + { + return strnatcasecmp($a->getName(), $b->getName()); + } + + function doList($command, $options, $params) + { + $reg = &$this->config->getRegistry(); + $registered = $reg->getChannels(); + usort($registered, array(&$this, '_sortchannels')); + $i = $j = 0; + $data = array( + 'caption' => 'Registered Channels:', + 'border' => true, + 'headline' => array('Channel', 'Alias', 'Summary') + ); + foreach ($registered as $channel) { + $data['data'][] = array($channel->getName(), + $channel->getAlias(), + $channel->getSummary()); + } + + if (count($registered) === 0) { + $data = '(no registered channels)'; + } + $this->ui->outputData($data, $command); + return true; + } + + function doUpdateAll($command, $options, $params) + { + $reg = &$this->config->getRegistry(); + $channels = $reg->getChannels(); + + $success = true; + foreach ($channels as $channel) { + if ($channel->getName() != '__uri') { + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $err = $this->doUpdate('channel-update', + $options, + array($channel->getName())); + if (PEAR::isError($err)) { + $this->ui->outputData($err->getMessage(), $command); + $success = false; + } else { + $success &= $err; + } + } + } + return $success; + } + + function doInfo($command, $options, $params) + { + if (count($params) !== 1) { + return $this->raiseError("No channel specified"); + } + + $reg = &$this->config->getRegistry(); + $channel = strtolower($params[0]); + if ($reg->channelExists($channel)) { + $chan = $reg->getChannel($channel); + if (PEAR::isError($chan)) { + return $this->raiseError($chan); + } + } else { + if (strpos($channel, '://')) { + $downloader = &$this->getDownloader(); + $tmpdir = $this->config->get('temp_dir'); + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $loc = $downloader->downloadHttp($channel, $this->ui, $tmpdir); + PEAR::staticPopErrorHandling(); + if (PEAR::isError($loc)) { + return $this->raiseError('Cannot open "' . $channel . + '" (' . $loc->getMessage() . ')'); + } else { + $contents = implode('', file($loc)); + } + } else { + if (!file_exists($params[0])) { + return $this->raiseError('Unknown channel "' . $channel . '"'); + } + + $fp = fopen($params[0], 'r'); + if (!$fp) { + return $this->raiseError('Cannot open "' . $params[0] . '"'); + } + + $contents = ''; + while (!feof($fp)) { + $contents .= fread($fp, 1024); + } + fclose($fp); + } + + if (!class_exists('PEAR_ChannelFile')) { + require_once 'PEAR/ChannelFile.php'; + } + + $chan = new PEAR_ChannelFile; + $chan->fromXmlString($contents); + $chan->validate(); + if ($errs = $chan->getErrors(true)) { + foreach ($errs as $err) { + $this->ui->outputData($err['level'] . ': ' . $err['message']); + } + return $this->raiseError('Channel file "' . $params[0] . '" is not valid'); + } + } + + if (!$chan) { + return $this->raiseError('Serious error: Channel "' . $params[0] . + '" has a corrupted registry entry'); + } + + $channel = $chan->getName(); + $caption = 'Channel ' . $channel . ' Information:'; + $data1 = array( + 'caption' => $caption, + 'border' => true); + $data1['data']['server'] = array('Name and Server', $chan->getName()); + if ($chan->getAlias() != $chan->getName()) { + $data1['data']['alias'] = array('Alias', $chan->getAlias()); + } + + $data1['data']['summary'] = array('Summary', $chan->getSummary()); + $validate = $chan->getValidationPackage(); + $data1['data']['vpackage'] = array('Validation Package Name', $validate['_content']); + $data1['data']['vpackageversion'] = + array('Validation Package Version', $validate['attribs']['version']); + $d = array(); + $d['main'] = $data1; + + $data['data'] = array(); + $data['caption'] = 'Server Capabilities'; + $data['headline'] = array('Type', 'Version/REST type', 'Function Name/REST base'); + if ($chan->supportsREST()) { + if ($chan->supportsREST()) { + $funcs = $chan->getFunctions('rest'); + if (!isset($funcs[0])) { + $funcs = array($funcs); + } + foreach ($funcs as $protocol) { + $data['data'][] = array('rest', $protocol['attribs']['type'], + $protocol['_content']); + } + } + } else { + $data['data'][] = array('No supported protocols'); + } + + $d['protocols'] = $data; + $data['data'] = array(); + $mirrors = $chan->getMirrors(); + if ($mirrors) { + $data['caption'] = 'Channel ' . $channel . ' Mirrors:'; + unset($data['headline']); + foreach ($mirrors as $mirror) { + $data['data'][] = array($mirror['attribs']['host']); + $d['mirrors'] = $data; + } + + foreach ($mirrors as $i => $mirror) { + $data['data'] = array(); + $data['caption'] = 'Mirror ' . $mirror['attribs']['host'] . ' Capabilities'; + $data['headline'] = array('Type', 'Version/REST type', 'Function Name/REST base'); + if ($chan->supportsREST($mirror['attribs']['host'])) { + if ($chan->supportsREST($mirror['attribs']['host'])) { + $funcs = $chan->getFunctions('rest', $mirror['attribs']['host']); + if (!isset($funcs[0])) { + $funcs = array($funcs); + } + + foreach ($funcs as $protocol) { + $data['data'][] = array('rest', $protocol['attribs']['type'], + $protocol['_content']); + } + } + } else { + $data['data'][] = array('No supported protocols'); + } + $d['mirrorprotocols' . $i] = $data; + } + } + $this->ui->outputData($d, 'channel-info'); + } + + // }}} + + function doDelete($command, $options, $params) + { + if (count($params) !== 1) { + return $this->raiseError('channel-delete: no channel specified'); + } + + $reg = &$this->config->getRegistry(); + if (!$reg->channelExists($params[0])) { + return $this->raiseError('channel-delete: channel "' . $params[0] . '" does not exist'); + } + + $channel = $reg->channelName($params[0]); + if ($channel == 'pear.php.net') { + return $this->raiseError('Cannot delete the pear.php.net channel'); + } + + if ($channel == 'pecl.php.net') { + return $this->raiseError('Cannot delete the pecl.php.net channel'); + } + + if ($channel == 'doc.php.net') { + return $this->raiseError('Cannot delete the doc.php.net channel'); + } + + if ($channel == '__uri') { + return $this->raiseError('Cannot delete the __uri pseudo-channel'); + } + + if (PEAR::isError($err = $reg->listPackages($channel))) { + return $err; + } + + if (count($err)) { + return $this->raiseError('Channel "' . $channel . + '" has installed packages, cannot delete'); + } + + if (!$reg->deleteChannel($channel)) { + return $this->raiseError('Channel "' . $channel . '" deletion failed'); + } else { + $this->config->deleteChannel($channel); + $this->ui->outputData('Channel "' . $channel . '" deleted', $command); + } + } + + function doAdd($command, $options, $params) + { + if (count($params) !== 1) { + return $this->raiseError('channel-add: no channel file specified'); + } + + if (strpos($params[0], '://')) { + $downloader = &$this->getDownloader(); + $tmpdir = $this->config->get('temp_dir'); + if (!file_exists($tmpdir)) { + require_once 'System.php'; + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $err = System::mkdir(array('-p', $tmpdir)); + PEAR::staticPopErrorHandling(); + if (PEAR::isError($err)) { + return $this->raiseError('channel-add: temp_dir does not exist: "' . + $tmpdir . + '" - You can change this location with "pear config-set temp_dir"'); + } + } + + if (!is_writable($tmpdir)) { + return $this->raiseError('channel-add: temp_dir is not writable: "' . + $tmpdir . + '" - You can change this location with "pear config-set temp_dir"'); + } + + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $loc = $downloader->downloadHttp($params[0], $this->ui, $tmpdir, null, false); + PEAR::staticPopErrorHandling(); + if (PEAR::isError($loc)) { + return $this->raiseError('channel-add: Cannot open "' . $params[0] . + '" (' . $loc->getMessage() . ')'); + } + + list($loc, $lastmodified) = $loc; + $contents = implode('', file($loc)); + } else { + $lastmodified = $fp = false; + if (file_exists($params[0])) { + $fp = fopen($params[0], 'r'); + } + + if (!$fp) { + return $this->raiseError('channel-add: cannot open "' . $params[0] . '"'); + } + + $contents = ''; + while (!feof($fp)) { + $contents .= fread($fp, 1024); + } + fclose($fp); + } + + if (!class_exists('PEAR_ChannelFile')) { + require_once 'PEAR/ChannelFile.php'; + } + + $channel = new PEAR_ChannelFile; + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $result = $channel->fromXmlString($contents); + PEAR::staticPopErrorHandling(); + if (!$result) { + $exit = false; + if (count($errors = $channel->getErrors(true))) { + foreach ($errors as $error) { + $this->ui->outputData(ucfirst($error['level'] . ': ' . $error['message'])); + if (!$exit) { + $exit = $error['level'] == 'error' ? true : false; + } + } + if ($exit) { + return $this->raiseError('channel-add: invalid channel.xml file'); + } + } + } + + $reg = &$this->config->getRegistry(); + if ($reg->channelExists($channel->getName())) { + return $this->raiseError('channel-add: Channel "' . $channel->getName() . + '" exists, use channel-update to update entry', PEAR_COMMAND_CHANNELS_CHANNEL_EXISTS); + } + + $ret = $reg->addChannel($channel, $lastmodified); + if (PEAR::isError($ret)) { + return $ret; + } + + if (!$ret) { + return $this->raiseError('channel-add: adding Channel "' . $channel->getName() . + '" to registry failed'); + } + + $this->config->setChannels($reg->listChannels()); + $this->config->writeConfigFile(); + $this->ui->outputData('Adding Channel "' . $channel->getName() . '" succeeded', $command); + } + + function doUpdate($command, $options, $params) + { + if (count($params) !== 1) { + return $this->raiseError("No channel file specified"); + } + + $tmpdir = $this->config->get('temp_dir'); + if (!file_exists($tmpdir)) { + require_once 'System.php'; + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $err = System::mkdir(array('-p', $tmpdir)); + PEAR::staticPopErrorHandling(); + if (PEAR::isError($err)) { + return $this->raiseError('channel-add: temp_dir does not exist: "' . + $tmpdir . + '" - You can change this location with "pear config-set temp_dir"'); + } + } + + if (!is_writable($tmpdir)) { + return $this->raiseError('channel-add: temp_dir is not writable: "' . + $tmpdir . + '" - You can change this location with "pear config-set temp_dir"'); + } + + $reg = &$this->config->getRegistry(); + $lastmodified = false; + if ((!file_exists($params[0]) || is_dir($params[0])) + && $reg->channelExists(strtolower($params[0]))) { + $c = $reg->getChannel(strtolower($params[0])); + if (PEAR::isError($c)) { + return $this->raiseError($c); + } + + $this->ui->outputData("Updating channel \"$params[0]\"", $command); + $dl = &$this->getDownloader(array()); + // if force is specified, use a timestamp of "1" to force retrieval + $lastmodified = isset($options['force']) ? false : $c->lastModified(); + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $contents = $dl->downloadHttp('http://' . $c->getName() . '/channel.xml', + $this->ui, $tmpdir, null, $lastmodified); + PEAR::staticPopErrorHandling(); + if (PEAR::isError($contents)) { + // Attempt to fall back to https + $this->ui->outputData("Channel \"$params[0]\" is not responding over http://, failed with message: " . $contents->getMessage()); + $this->ui->outputData("Trying channel \"$params[0]\" over https:// instead"); + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $contents = $dl->downloadHttp('https://' . $c->getName() . '/channel.xml', + $this->ui, $tmpdir, null, $lastmodified); + PEAR::staticPopErrorHandling(); + if (PEAR::isError($contents)) { + return $this->raiseError('Cannot retrieve channel.xml for channel "' . + $c->getName() . '" (' . $contents->getMessage() . ')'); + } + } + + list($contents, $lastmodified) = $contents; + if (!$contents) { + $this->ui->outputData("Channel \"$params[0]\" is up to date"); + return; + } + + $contents = implode('', file($contents)); + if (!class_exists('PEAR_ChannelFile')) { + require_once 'PEAR/ChannelFile.php'; + } + + $channel = new PEAR_ChannelFile; + $channel->fromXmlString($contents); + if (!$channel->getErrors()) { + // security check: is the downloaded file for the channel we got it from? + if (strtolower($channel->getName()) != strtolower($c->getName())) { + if (!isset($options['force'])) { + return $this->raiseError('ERROR: downloaded channel definition file' . + ' for channel "' . $channel->getName() . '" from channel "' . + strtolower($c->getName()) . '"'); + } + + $this->ui->log(0, 'WARNING: downloaded channel definition file' . + ' for channel "' . $channel->getName() . '" from channel "' . + strtolower($c->getName()) . '"'); + } + } + } else { + if (strpos($params[0], '://')) { + $dl = &$this->getDownloader(); + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $loc = $dl->downloadHttp($params[0], + $this->ui, $tmpdir, null, $lastmodified); + PEAR::staticPopErrorHandling(); + if (PEAR::isError($loc)) { + return $this->raiseError("Cannot open " . $params[0] . + ' (' . $loc->getMessage() . ')'); + } + + list($loc, $lastmodified) = $loc; + $contents = implode('', file($loc)); + } else { + $fp = false; + if (file_exists($params[0])) { + $fp = fopen($params[0], 'r'); + } + + if (!$fp) { + return $this->raiseError("Cannot open " . $params[0]); + } + + $contents = ''; + while (!feof($fp)) { + $contents .= fread($fp, 1024); + } + fclose($fp); + } + + if (!class_exists('PEAR_ChannelFile')) { + require_once 'PEAR/ChannelFile.php'; + } + + $channel = new PEAR_ChannelFile; + $channel->fromXmlString($contents); + } + + $exit = false; + if (count($errors = $channel->getErrors(true))) { + foreach ($errors as $error) { + $this->ui->outputData(ucfirst($error['level'] . ': ' . $error['message'])); + if (!$exit) { + $exit = $error['level'] == 'error' ? true : false; + } + } + if ($exit) { + return $this->raiseError('Invalid channel.xml file'); + } + } + + if (!$reg->channelExists($channel->getName())) { + return $this->raiseError('Error: Channel "' . $channel->getName() . + '" does not exist, use channel-add to add an entry'); + } + + $ret = $reg->updateChannel($channel, $lastmodified); + if (PEAR::isError($ret)) { + return $ret; + } + + if (!$ret) { + return $this->raiseError('Updating Channel "' . $channel->getName() . + '" in registry failed'); + } + + $this->config->setChannels($reg->listChannels()); + $this->config->writeConfigFile(); + $this->ui->outputData('Update of Channel "' . $channel->getName() . '" succeeded'); + } + + function &getDownloader() + { + if (!class_exists('PEAR_Downloader')) { + require_once 'PEAR/Downloader.php'; + } + $a = new PEAR_Downloader($this->ui, array(), $this->config); + return $a; + } + + function doAlias($command, $options, $params) + { + if (count($params) === 1) { + return $this->raiseError('No channel alias specified'); + } + + if (count($params) !== 2 || (!empty($params[1]) && $params[1]{0} == '-')) { + return $this->raiseError( + 'Invalid format, correct is: channel-alias channel alias'); + } + + $reg = &$this->config->getRegistry(); + if (!$reg->channelExists($params[0], true)) { + $extra = ''; + if ($reg->isAlias($params[0])) { + $extra = ' (use "channel-alias ' . $reg->channelName($params[0]) . ' ' . + strtolower($params[1]) . '")'; + } + + return $this->raiseError('"' . $params[0] . '" is not a valid channel' . $extra); + } + + if ($reg->isAlias($params[1])) { + return $this->raiseError('Channel "' . $reg->channelName($params[1]) . '" is ' . + 'already aliased to "' . strtolower($params[1]) . '", cannot re-alias'); + } + + $chan = &$reg->getChannel($params[0]); + if (PEAR::isError($chan)) { + return $this->raiseError('Corrupt registry? Error retrieving channel "' . $params[0] . + '" information (' . $chan->getMessage() . ')'); + } + + // make it a local alias + if (!$chan->setAlias(strtolower($params[1]), true)) { + return $this->raiseError('Alias "' . strtolower($params[1]) . + '" is not a valid channel alias'); + } + + $reg->updateChannel($chan); + $this->ui->outputData('Channel "' . $chan->getName() . '" aliased successfully to "' . + strtolower($params[1]) . '"'); + } + + /** + * The channel-discover command + * + * @param string $command command name + * @param array $options option_name => value + * @param array $params list of additional parameters. + * $params[0] should contain a string with either: + * - or + * - :@ + * @return null|PEAR_Error + */ + function doDiscover($command, $options, $params) + { + if (count($params) !== 1) { + return $this->raiseError("No channel server specified"); + } + + // Look for the possible input format ":@" + if (preg_match('/^(.+):(.+)@(.+)\\z/', $params[0], $matches)) { + $username = $matches[1]; + $password = $matches[2]; + $channel = $matches[3]; + } else { + $channel = $params[0]; + } + + $reg = &$this->config->getRegistry(); + if ($reg->channelExists($channel)) { + if (!$reg->isAlias($channel)) { + return $this->raiseError("Channel \"$channel\" is already initialized", PEAR_COMMAND_CHANNELS_CHANNEL_EXISTS); + } + + return $this->raiseError("A channel alias named \"$channel\" " . + 'already exists, aliasing channel "' . $reg->channelName($channel) + . '"'); + } + + $this->pushErrorHandling(PEAR_ERROR_RETURN); + $err = $this->doAdd($command, $options, array('http://' . $channel . '/channel.xml')); + $this->popErrorHandling(); + if (PEAR::isError($err)) { + if ($err->getCode() === PEAR_COMMAND_CHANNELS_CHANNEL_EXISTS) { + return $this->raiseError("Discovery of channel \"$channel\" failed (" . + $err->getMessage() . ')'); + } + // Attempt fetch via https + $this->ui->outputData("Discovering channel $channel over http:// failed with message: " . $err->getMessage()); + $this->ui->outputData("Trying to discover channel $channel over https:// instead"); + $this->pushErrorHandling(PEAR_ERROR_RETURN); + $err = $this->doAdd($command, $options, array('https://' . $channel . '/channel.xml')); + $this->popErrorHandling(); + if (PEAR::isError($err)) { + return $this->raiseError("Discovery of channel \"$channel\" failed (" . + $err->getMessage() . ')'); + } + } + + // Store username/password if they were given + // Arguably we should do a logintest on the channel here, but since + // that's awkward on a REST-based channel (even "pear login" doesn't + // do it for those), and XML-RPC is deprecated, it's fairly pointless. + if (isset($username)) { + $this->config->set('username', $username, 'user', $channel); + $this->config->set('password', $password, 'user', $channel); + $this->config->store(); + $this->ui->outputData("Stored login for channel \"$channel\" using username \"$username\"", $command); + } + + $this->ui->outputData("Discovery of channel \"$channel\" succeeded", $command); + } + + /** + * Execute the 'login' command. + * + * @param string $command command name + * @param array $options option_name => value + * @param array $params list of additional parameters + * + * @return bool TRUE on success or + * a PEAR error on failure + * + * @access public + */ + function doLogin($command, $options, $params) + { + $reg = &$this->config->getRegistry(); + + // If a parameter is supplied, use that as the channel to log in to + $channel = isset($params[0]) ? $params[0] : $this->config->get('default_channel'); + + $chan = $reg->getChannel($channel); + if (PEAR::isError($chan)) { + return $this->raiseError($chan); + } + + $server = $this->config->get('preferred_mirror', null, $channel); + $username = $this->config->get('username', null, $channel); + if (empty($username)) { + $username = isset($_ENV['USER']) ? $_ENV['USER'] : null; + } + $this->ui->outputData("Logging in to $server.", $command); + + list($username, $password) = $this->ui->userDialog( + $command, + array('Username', 'Password'), + array('text', 'password'), + array($username, '') + ); + $username = trim($username); + $password = trim($password); + + $ourfile = $this->config->getConfFile('user'); + if (!$ourfile) { + $ourfile = $this->config->getConfFile('system'); + } + + $this->config->set('username', $username, 'user', $channel); + $this->config->set('password', $password, 'user', $channel); + + if ($chan->supportsREST()) { + $ok = true; + } + + if ($ok !== true) { + return $this->raiseError('Login failed!'); + } + + $this->ui->outputData("Logged in.", $command); + // avoid changing any temporary settings changed with -d + $ourconfig = new PEAR_Config($ourfile, $ourfile); + $ourconfig->set('username', $username, 'user', $channel); + $ourconfig->set('password', $password, 'user', $channel); + $ourconfig->store(); + + return true; + } + + /** + * Execute the 'logout' command. + * + * @param string $command command name + * @param array $options option_name => value + * @param array $params list of additional parameters + * + * @return bool TRUE on success or + * a PEAR error on failure + * + * @access public + */ + function doLogout($command, $options, $params) + { + $reg = &$this->config->getRegistry(); + + // If a parameter is supplied, use that as the channel to log in to + $channel = isset($params[0]) ? $params[0] : $this->config->get('default_channel'); + + $chan = $reg->getChannel($channel); + if (PEAR::isError($chan)) { + return $this->raiseError($chan); + } + + $server = $this->config->get('preferred_mirror', null, $channel); + $this->ui->outputData("Logging out from $server.", $command); + $this->config->remove('username', 'user', $channel); + $this->config->remove('password', 'user', $channel); + $this->config->store(); + return true; + } +} \ No newline at end of file diff --git a/library/pear/PEAR/Command/Channels.xml b/library/pear/PEAR/Command/Channels.xml new file mode 100644 index 000000000..47b72066a --- /dev/null +++ b/library/pear/PEAR/Command/Channels.xml @@ -0,0 +1,123 @@ + + + List Available Channels + doList + lc + + +List all available channels for installation. + + + + Update the Channel List + doUpdateAll + uc + + +List all installed packages in all channels. + + + + Remove a Channel From the List + doDelete + cde + + <channel name> +Delete a channel from the registry. You may not +remove any channel that has installed packages. + + + + Add a Channel + doAdd + ca + + <channel.xml> +Add a private channel to the channel list. Note that all +public channels should be synced using "update-channels". +Parameter may be either a local file or remote URL to a +channel.xml. + + + + Update an Existing Channel + doUpdate + cu + + + f + will force download of new channel.xml if an existing channel name is used + + + c + will force download of new channel.xml if an existing channel name is used + CHANNEL + + + [<channel.xml>|<channel name>] +Update a channel in the channel list directly. Note that all +public channels can be synced using "update-channels". +Parameter may be a local or remote channel.xml, or the name of +an existing channel. + + + + Retrieve Information on a Channel + doInfo + ci + + <package> +List the files in an installed package. + + + + Specify an alias to a channel name + doAlias + cha + + <channel> <alias> +Specify a specific alias to use for a channel name. +The alias may not be an existing channel name or +alias. + + + + Initialize a Channel from its server + doDiscover + di + + [<channel.xml>|<channel name>] +Initialize a channel from its server and create a local channel.xml. +If <channel name> is in the format "<username>:<password>@<channel>" then +<username> and <password> will be set as the login username/password for +<channel>. Use caution when passing the username/password in this way, as +it may allow other users on your computer to briefly view your username/ +password via the system's process list. + + + + Connects and authenticates to remote channel server + doLogin + cli + + <channel name> +Log in to a remote channel server. If <channel name> is not supplied, +the default channel is used. To use remote functions in the installer +that require any kind of privileges, you need to log in first. The +username and password you enter here will be stored in your per-user +PEAR configuration (~/.pearrc on Unix-like systems). After logging +in, your username and password will be sent along in subsequent +operations on the remote server. + + + Logs out from the remote channel server + doLogout + clo + + <channel name> +Logs out from a remote channel server. If <channel name> is not supplied, +the default channel is used. This command does not actually connect to the +remote server, it only deletes the stored username and password from your user +configuration. + + \ No newline at end of file diff --git a/library/pear/PEAR/Command/Common.php b/library/pear/PEAR/Command/Common.php new file mode 100644 index 000000000..1c3911fcf --- /dev/null +++ b/library/pear/PEAR/Command/Common.php @@ -0,0 +1,273 @@ + + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Common.php 276383 2009-02-24 23:39:37Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 0.1 + */ + +/** + * base class + */ +require_once 'PEAR.php'; + +/** + * PEAR commands base class + * + * @category pear + * @package PEAR + * @author Stig Bakken + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.1 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 0.1 + */ +class PEAR_Command_Common extends PEAR +{ + /** + * PEAR_Config object used to pass user system and configuration + * on when executing commands + * + * @var PEAR_Config + */ + var $config; + /** + * @var PEAR_Registry + * @access protected + */ + var $_registry; + + /** + * User Interface object, for all interaction with the user. + * @var object + */ + var $ui; + + var $_deps_rel_trans = array( + 'lt' => '<', + 'le' => '<=', + 'eq' => '=', + 'ne' => '!=', + 'gt' => '>', + 'ge' => '>=', + 'has' => '==' + ); + + var $_deps_type_trans = array( + 'pkg' => 'package', + 'ext' => 'extension', + 'php' => 'PHP', + 'prog' => 'external program', + 'ldlib' => 'external library for linking', + 'rtlib' => 'external runtime library', + 'os' => 'operating system', + 'websrv' => 'web server', + 'sapi' => 'SAPI backend' + ); + + /** + * PEAR_Command_Common constructor. + * + * @access public + */ + function PEAR_Command_Common(&$ui, &$config) + { + parent::PEAR(); + $this->config = &$config; + $this->ui = &$ui; + } + + /** + * Return a list of all the commands defined by this class. + * @return array list of commands + * @access public + */ + function getCommands() + { + $ret = array(); + foreach (array_keys($this->commands) as $command) { + $ret[$command] = $this->commands[$command]['summary']; + } + + return $ret; + } + + /** + * Return a list of all the command shortcuts defined by this class. + * @return array shortcut => command + * @access public + */ + function getShortcuts() + { + $ret = array(); + foreach (array_keys($this->commands) as $command) { + if (isset($this->commands[$command]['shortcut'])) { + $ret[$this->commands[$command]['shortcut']] = $command; + } + } + + return $ret; + } + + function getOptions($command) + { + $shortcuts = $this->getShortcuts(); + if (isset($shortcuts[$command])) { + $command = $shortcuts[$command]; + } + + if (isset($this->commands[$command]) && + isset($this->commands[$command]['options'])) { + return $this->commands[$command]['options']; + } + + return null; + } + + function getGetoptArgs($command, &$short_args, &$long_args) + { + $short_args = ''; + $long_args = array(); + if (empty($this->commands[$command]) || empty($this->commands[$command]['options'])) { + return; + } + + reset($this->commands[$command]['options']); + while (list($option, $info) = each($this->commands[$command]['options'])) { + $larg = $sarg = ''; + if (isset($info['arg'])) { + if ($info['arg']{0} == '(') { + $larg = '=='; + $sarg = '::'; + $arg = substr($info['arg'], 1, -1); + } else { + $larg = '='; + $sarg = ':'; + $arg = $info['arg']; + } + } + + if (isset($info['shortopt'])) { + $short_args .= $info['shortopt'] . $sarg; + } + + $long_args[] = $option . $larg; + } + } + + /** + * Returns the help message for the given command + * + * @param string $command The command + * @return mixed A fail string if the command does not have help or + * a two elements array containing [0]=>help string, + * [1]=> help string for the accepted cmd args + */ + function getHelp($command) + { + $config = &PEAR_Config::singleton(); + if (!isset($this->commands[$command])) { + return "No such command \"$command\""; + } + + $help = null; + if (isset($this->commands[$command]['doc'])) { + $help = $this->commands[$command]['doc']; + } + + if (empty($help)) { + // XXX (cox) Fallback to summary if there is no doc (show both?) + if (!isset($this->commands[$command]['summary'])) { + return "No help for command \"$command\""; + } + $help = $this->commands[$command]['summary']; + } + + if (preg_match_all('/{config\s+([^\}]+)}/e', $help, $matches)) { + foreach($matches[0] as $k => $v) { + $help = preg_replace("/$v/", $config->get($matches[1][$k]), $help); + } + } + + return array($help, $this->getHelpArgs($command)); + } + + /** + * Returns the help for the accepted arguments of a command + * + * @param string $command + * @return string The help string + */ + function getHelpArgs($command) + { + if (isset($this->commands[$command]['options']) && + count($this->commands[$command]['options'])) + { + $help = "Options:\n"; + foreach ($this->commands[$command]['options'] as $k => $v) { + if (isset($v['arg'])) { + if ($v['arg'][0] == '(') { + $arg = substr($v['arg'], 1, -1); + $sapp = " [$arg]"; + $lapp = "[=$arg]"; + } else { + $sapp = " $v[arg]"; + $lapp = "=$v[arg]"; + } + } else { + $sapp = $lapp = ""; + } + + if (isset($v['shortopt'])) { + $s = $v['shortopt']; + $help .= " -$s$sapp, --$k$lapp\n"; + } else { + $help .= " --$k$lapp\n"; + } + + $p = " "; + $doc = rtrim(str_replace("\n", "\n$p", $v['doc'])); + $help .= " $doc\n"; + } + + return $help; + } + + return null; + } + + function run($command, $options, $params) + { + if (empty($this->commands[$command]['function'])) { + // look for shortcuts + foreach (array_keys($this->commands) as $cmd) { + if (isset($this->commands[$cmd]['shortcut']) && $this->commands[$cmd]['shortcut'] == $command) { + if (empty($this->commands[$cmd]['function'])) { + return $this->raiseError("unknown command `$command'"); + } else { + $func = $this->commands[$cmd]['function']; + } + $command = $cmd; + + //$command = $this->commands[$cmd]['function']; + break; + } + } + } else { + $func = $this->commands[$command]['function']; + } + + return $this->$func($command, $options, $params); + } +} \ No newline at end of file diff --git a/library/pear/PEAR/Command/Config.php b/library/pear/PEAR/Command/Config.php new file mode 100644 index 000000000..28d7ccc2a --- /dev/null +++ b/library/pear/PEAR/Command/Config.php @@ -0,0 +1,413 @@ + + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Config.php 287554 2009-08-21 21:16:25Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 0.1 + */ + +/** + * base class + */ +require_once 'PEAR/Command/Common.php'; + +/** + * PEAR commands for managing configuration data. + * + * @category pear + * @package PEAR + * @author Stig Bakken + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.1 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 0.1 + */ +class PEAR_Command_Config extends PEAR_Command_Common +{ + var $commands = array( + 'config-show' => array( + 'summary' => 'Show All Settings', + 'function' => 'doConfigShow', + 'shortcut' => 'csh', + 'options' => array( + 'channel' => array( + 'shortopt' => 'c', + 'doc' => 'show configuration variables for another channel', + 'arg' => 'CHAN', + ), +), + 'doc' => '[layer] +Displays all configuration values. An optional argument +may be used to tell which configuration layer to display. Valid +configuration layers are "user", "system" and "default". To display +configurations for different channels, set the default_channel +configuration variable and run config-show again. +', + ), + 'config-get' => array( + 'summary' => 'Show One Setting', + 'function' => 'doConfigGet', + 'shortcut' => 'cg', + 'options' => array( + 'channel' => array( + 'shortopt' => 'c', + 'doc' => 'show configuration variables for another channel', + 'arg' => 'CHAN', + ), +), + 'doc' => ' [layer] +Displays the value of one configuration parameter. The +first argument is the name of the parameter, an optional second argument +may be used to tell which configuration layer to look in. Valid configuration +layers are "user", "system" and "default". If no layer is specified, a value +will be picked from the first layer that defines the parameter, in the order +just specified. The configuration value will be retrieved for the channel +specified by the default_channel configuration variable. +', + ), + 'config-set' => array( + 'summary' => 'Change Setting', + 'function' => 'doConfigSet', + 'shortcut' => 'cs', + 'options' => array( + 'channel' => array( + 'shortopt' => 'c', + 'doc' => 'show configuration variables for another channel', + 'arg' => 'CHAN', + ), +), + 'doc' => ' [layer] +Sets the value of one configuration parameter. The first argument is +the name of the parameter, the second argument is the new value. Some +parameters are subject to validation, and the command will fail with +an error message if the new value does not make sense. An optional +third argument may be used to specify in which layer to set the +configuration parameter. The default layer is "user". The +configuration value will be set for the current channel, which +is controlled by the default_channel configuration variable. +', + ), + 'config-help' => array( + 'summary' => 'Show Information About Setting', + 'function' => 'doConfigHelp', + 'shortcut' => 'ch', + 'options' => array(), + 'doc' => '[parameter] +Displays help for a configuration parameter. Without arguments it +displays help for all configuration parameters. +', + ), + 'config-create' => array( + 'summary' => 'Create a Default configuration file', + 'function' => 'doConfigCreate', + 'shortcut' => 'coc', + 'options' => array( + 'windows' => array( + 'shortopt' => 'w', + 'doc' => 'create a config file for a windows install', + ), + ), + 'doc' => ' +Create a default configuration file with all directory configuration +variables set to subdirectories of , and save it as . +This is useful especially for creating a configuration file for a remote +PEAR installation (using the --remoteconfig option of install, upgrade, +and uninstall). +', + ), + ); + + /** + * PEAR_Command_Config constructor. + * + * @access public + */ + function PEAR_Command_Config(&$ui, &$config) + { + parent::PEAR_Command_Common($ui, $config); + } + + function doConfigShow($command, $options, $params) + { + $layer = null; + if (is_array($params)) { + $layer = isset($params[0]) ? $params[0] : null; + } + + // $params[0] -> the layer + if ($error = $this->_checkLayer($layer)) { + return $this->raiseError("config-show:$error"); + } + + $keys = $this->config->getKeys(); + sort($keys); + $channel = isset($options['channel']) ? $options['channel'] : + $this->config->get('default_channel'); + $reg = &$this->config->getRegistry(); + if (!$reg->channelExists($channel)) { + return $this->raiseError('Channel "' . $channel . '" does not exist'); + } + + $channel = $reg->channelName($channel); + $data = array('caption' => 'Configuration (channel ' . $channel . '):'); + foreach ($keys as $key) { + $type = $this->config->getType($key); + $value = $this->config->get($key, $layer, $channel); + if ($type == 'password' && $value) { + $value = '********'; + } + + if ($value === false) { + $value = 'false'; + } elseif ($value === true) { + $value = 'true'; + } + + $data['data'][$this->config->getGroup($key)][] = array($this->config->getPrompt($key) , $key, $value); + } + + foreach ($this->config->getLayers() as $layer) { + $data['data']['Config Files'][] = array(ucfirst($layer) . ' Configuration File', 'Filename' , $this->config->getConfFile($layer)); + } + + $this->ui->outputData($data, $command); + return true; + } + + function doConfigGet($command, $options, $params) + { + $args_cnt = is_array($params) ? count($params) : 0; + switch ($args_cnt) { + case 1: + $config_key = $params[0]; + $layer = null; + break; + case 2: + $config_key = $params[0]; + $layer = $params[1]; + if ($error = $this->_checkLayer($layer)) { + return $this->raiseError("config-get:$error"); + } + break; + case 0: + default: + return $this->raiseError("config-get expects 1 or 2 parameters"); + } + + $channel = isset($options['channel']) ? $options['channel'] : $this->config->get('default_channel'); + $reg = &$this->config->getRegistry(); + if (!$reg->channelExists($channel)) { + return $this->raiseError('Channel "' . $channel . '" does not exist'); + } + + $this->ui->outputData($this->config->get($config_key, $layer, $channel), $command); + return true; + } + + function doConfigSet($command, $options, $params) + { + // $param[0] -> a parameter to set + // $param[1] -> the value for the parameter + // $param[2] -> the layer + $failmsg = ''; + if (count($params) < 2 || count($params) > 3) { + $failmsg .= "config-set expects 2 or 3 parameters"; + return PEAR::raiseError($failmsg); + } + + if (isset($params[2]) && ($error = $this->_checkLayer($params[2]))) { + $failmsg .= $error; + return PEAR::raiseError("config-set:$failmsg"); + } + + $channel = isset($options['channel']) ? $options['channel'] : $this->config->get('default_channel'); + $reg = &$this->config->getRegistry(); + if (!$reg->channelExists($channel)) { + return $this->raiseError('Channel "' . $channel . '" does not exist'); + } + + $channel = $reg->channelName($channel); + if ($params[0] == 'default_channel' && !$reg->channelExists($params[1])) { + return $this->raiseError('Channel "' . $params[1] . '" does not exist'); + } + + if ($params[0] == 'preferred_mirror' + && ( + !$reg->mirrorExists($channel, $params[1]) && + (!$reg->channelExists($params[1]) || $channel != $params[1]) + ) + ) { + $msg = 'Channel Mirror "' . $params[1] . '" does not exist'; + $msg .= ' in your registry for channel "' . $channel . '".'; + $msg .= "\n" . 'Attempt to run "pear channel-update ' . $channel .'"'; + $msg .= ' if you believe this mirror should exist as you may'; + $msg .= ' have outdated channel information.'; + return $this->raiseError($msg); + } + + if (count($params) == 2) { + array_push($params, 'user'); + $layer = 'user'; + } else { + $layer = $params[2]; + } + + array_push($params, $channel); + if (!call_user_func_array(array(&$this->config, 'set'), $params)) { + array_pop($params); + $failmsg = "config-set (" . implode(", ", $params) . ") failed, channel $channel"; + } else { + $this->config->store($layer); + } + + if ($failmsg) { + return $this->raiseError($failmsg); + } + + $this->ui->outputData('config-set succeeded', $command); + return true; + } + + function doConfigHelp($command, $options, $params) + { + if (empty($params)) { + $params = $this->config->getKeys(); + } + + $data['caption'] = "Config help" . ((count($params) == 1) ? " for $params[0]" : ''); + $data['headline'] = array('Name', 'Type', 'Description'); + $data['border'] = true; + foreach ($params as $name) { + $type = $this->config->getType($name); + $docs = $this->config->getDocs($name); + if ($type == 'set') { + $docs = rtrim($docs) . "\nValid set: " . + implode(' ', $this->config->getSetValues($name)); + } + + $data['data'][] = array($name, $type, $docs); + } + + $this->ui->outputData($data, $command); + } + + function doConfigCreate($command, $options, $params) + { + if (count($params) != 2) { + return PEAR::raiseError('config-create: must have 2 parameters, root path and ' . + 'filename to save as'); + } + + $root = $params[0]; + // Clean up the DIRECTORY_SEPARATOR mess + $ds2 = DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR; + $root = preg_replace(array('!\\\\+!', '!/+!', "!$ds2+!"), + array('/', '/', '/'), + $root); + if ($root{0} != '/') { + if (!isset($options['windows'])) { + return PEAR::raiseError('Root directory must be an absolute path beginning ' . + 'with "/", was: "' . $root . '"'); + } + + if (!preg_match('/^[A-Za-z]:/', $root)) { + return PEAR::raiseError('Root directory must be an absolute path beginning ' . + 'with "\\" or "C:\\", was: "' . $root . '"'); + } + } + + $windows = isset($options['windows']); + if ($windows) { + $root = str_replace('/', '\\', $root); + } + + if (!file_exists($params[1]) && !@touch($params[1])) { + return PEAR::raiseError('Could not create "' . $params[1] . '"'); + } + + $params[1] = realpath($params[1]); + $config = &new PEAR_Config($params[1], '#no#system#config#', false, false); + if ($root{strlen($root) - 1} == '/') { + $root = substr($root, 0, strlen($root) - 1); + } + + $config->noRegistry(); + $config->set('php_dir', $windows ? "$root\\pear\\php" : "$root/pear/php", 'user'); + $config->set('data_dir', $windows ? "$root\\pear\\data" : "$root/pear/data"); + $config->set('www_dir', $windows ? "$root\\pear\\www" : "$root/pear/www"); + $config->set('cfg_dir', $windows ? "$root\\pear\\cfg" : "$root/pear/cfg"); + $config->set('ext_dir', $windows ? "$root\\pear\\ext" : "$root/pear/ext"); + $config->set('doc_dir', $windows ? "$root\\pear\\docs" : "$root/pear/docs"); + $config->set('test_dir', $windows ? "$root\\pear\\tests" : "$root/pear/tests"); + $config->set('cache_dir', $windows ? "$root\\pear\\cache" : "$root/pear/cache"); + $config->set('download_dir', $windows ? "$root\\pear\\download" : "$root/pear/download"); + $config->set('temp_dir', $windows ? "$root\\pear\\temp" : "$root/pear/temp"); + $config->set('bin_dir', $windows ? "$root\\pear" : "$root/pear"); + $config->writeConfigFile(); + $this->_showConfig($config); + $this->ui->outputData('Successfully created default configuration file "' . $params[1] . '"', + $command); + } + + function _showConfig(&$config) + { + $params = array('user'); + $keys = $config->getKeys(); + sort($keys); + $channel = 'pear.php.net'; + $data = array('caption' => 'Configuration (channel ' . $channel . '):'); + foreach ($keys as $key) { + $type = $config->getType($key); + $value = $config->get($key, 'user', $channel); + if ($type == 'password' && $value) { + $value = '********'; + } + + if ($value === false) { + $value = 'false'; + } elseif ($value === true) { + $value = 'true'; + } + $data['data'][$config->getGroup($key)][] = + array($config->getPrompt($key) , $key, $value); + } + + foreach ($config->getLayers() as $layer) { + $data['data']['Config Files'][] = + array(ucfirst($layer) . ' Configuration File', 'Filename' , + $config->getConfFile($layer)); + } + + $this->ui->outputData($data, 'config-show'); + return true; + } + + /** + * Checks if a layer is defined or not + * + * @param string $layer The layer to search for + * @return mixed False on no error or the error message + */ + function _checkLayer($layer = null) + { + if (!empty($layer) && $layer != 'default') { + $layers = $this->config->getLayers(); + if (!in_array($layer, $layers)) { + return " only the layers: \"" . implode('" or "', $layers) . "\" are supported"; + } + } + + return false; + } +} \ No newline at end of file diff --git a/library/pear/PEAR/Command/Config.xml b/library/pear/PEAR/Command/Config.xml new file mode 100644 index 000000000..f64a925f5 --- /dev/null +++ b/library/pear/PEAR/Command/Config.xml @@ -0,0 +1,92 @@ + + + Show All Settings + doConfigShow + csh + + + c + show configuration variables for another channel + CHAN + + + [layer] +Displays all configuration values. An optional argument +may be used to tell which configuration layer to display. Valid +configuration layers are "user", "system" and "default". To display +configurations for different channels, set the default_channel +configuration variable and run config-show again. + + + + Show One Setting + doConfigGet + cg + + + c + show configuration variables for another channel + CHAN + + + <parameter> [layer] +Displays the value of one configuration parameter. The +first argument is the name of the parameter, an optional second argument +may be used to tell which configuration layer to look in. Valid configuration +layers are "user", "system" and "default". If no layer is specified, a value +will be picked from the first layer that defines the parameter, in the order +just specified. The configuration value will be retrieved for the channel +specified by the default_channel configuration variable. + + + + Change Setting + doConfigSet + cs + + + c + show configuration variables for another channel + CHAN + + + <parameter> <value> [layer] +Sets the value of one configuration parameter. The first argument is +the name of the parameter, the second argument is the new value. Some +parameters are subject to validation, and the command will fail with +an error message if the new value does not make sense. An optional +third argument may be used to specify in which layer to set the +configuration parameter. The default layer is "user". The +configuration value will be set for the current channel, which +is controlled by the default_channel configuration variable. + + + + Show Information About Setting + doConfigHelp + ch + + [parameter] +Displays help for a configuration parameter. Without arguments it +displays help for all configuration parameters. + + + + Create a Default configuration file + doConfigCreate + coc + + + w + create a config file for a windows install + + + <root path> <filename> +Create a default configuration file with all directory configuration +variables set to subdirectories of <root path>, and save it as <filename>. +This is useful especially for creating a configuration file for a remote +PEAR installation (using the --remoteconfig option of install, upgrade, +and uninstall). + + + \ No newline at end of file diff --git a/library/pear/PEAR/Command/Install.php b/library/pear/PEAR/Command/Install.php new file mode 100644 index 000000000..474dbf2f4 --- /dev/null +++ b/library/pear/PEAR/Command/Install.php @@ -0,0 +1,1266 @@ + + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Install.php 287477 2009-08-19 14:19:43Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 0.1 + */ + +/** + * base class + */ +require_once 'PEAR/Command/Common.php'; + +/** + * PEAR commands for installation or deinstallation/upgrading of + * packages. + * + * @category pear + * @package PEAR + * @author Stig Bakken + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.1 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 0.1 + */ +class PEAR_Command_Install extends PEAR_Command_Common +{ + // {{{ properties + + var $commands = array( + 'install' => array( + 'summary' => 'Install Package', + 'function' => 'doInstall', + 'shortcut' => 'i', + 'options' => array( + 'force' => array( + 'shortopt' => 'f', + 'doc' => 'will overwrite newer installed packages', + ), + 'loose' => array( + 'shortopt' => 'l', + 'doc' => 'do not check for recommended dependency version', + ), + 'nodeps' => array( + 'shortopt' => 'n', + 'doc' => 'ignore dependencies, install anyway', + ), + 'register-only' => array( + 'shortopt' => 'r', + 'doc' => 'do not install files, only register the package as installed', + ), + 'soft' => array( + 'shortopt' => 's', + 'doc' => 'soft install, fail silently, or upgrade if already installed', + ), + 'nobuild' => array( + 'shortopt' => 'B', + 'doc' => 'don\'t build C extensions', + ), + 'nocompress' => array( + 'shortopt' => 'Z', + 'doc' => 'request uncompressed files when downloading', + ), + 'installroot' => array( + 'shortopt' => 'R', + 'arg' => 'DIR', + 'doc' => 'root directory used when installing files (ala PHP\'s INSTALL_ROOT), use packagingroot for RPM', + ), + 'packagingroot' => array( + 'shortopt' => 'P', + 'arg' => 'DIR', + 'doc' => 'root directory used when packaging files, like RPM packaging', + ), + 'ignore-errors' => array( + 'doc' => 'force install even if there were errors', + ), + 'alldeps' => array( + 'shortopt' => 'a', + 'doc' => 'install all required and optional dependencies', + ), + 'onlyreqdeps' => array( + 'shortopt' => 'o', + 'doc' => 'install all required dependencies', + ), + 'offline' => array( + 'shortopt' => 'O', + 'doc' => 'do not attempt to download any urls or contact channels', + ), + 'pretend' => array( + 'shortopt' => 'p', + 'doc' => 'Only list the packages that would be downloaded', + ), + ), + 'doc' => '[channel/] ... +Installs one or more PEAR packages. You can specify a package to +install in four ways: + +"Package-1.0.tgz" : installs from a local file + +"http://example.com/Package-1.0.tgz" : installs from +anywhere on the net. + +"package.xml" : installs the package described in +package.xml. Useful for testing, or for wrapping a PEAR package in +another package manager such as RPM. + +"Package[-version/state][.tar]" : queries your default channel\'s server +({config master_server}) and downloads the newest package with +the preferred quality/state ({config preferred_state}). + +To retrieve Package version 1.1, use "Package-1.1," to retrieve +Package state beta, use "Package-beta." To retrieve an uncompressed +file, append .tar (make sure there is no file by the same name first) + +To download a package from another channel, prefix with the channel name like +"channel/Package" + +More than one package may be specified at once. It is ok to mix these +four ways of specifying packages. +'), + 'upgrade' => array( + 'summary' => 'Upgrade Package', + 'function' => 'doInstall', + 'shortcut' => 'up', + 'options' => array( + 'channel' => array( + 'shortopt' => 'c', + 'doc' => 'upgrade packages from a specific channel', + 'arg' => 'CHAN', + ), + 'force' => array( + 'shortopt' => 'f', + 'doc' => 'overwrite newer installed packages', + ), + 'loose' => array( + 'shortopt' => 'l', + 'doc' => 'do not check for recommended dependency version', + ), + 'nodeps' => array( + 'shortopt' => 'n', + 'doc' => 'ignore dependencies, upgrade anyway', + ), + 'register-only' => array( + 'shortopt' => 'r', + 'doc' => 'do not install files, only register the package as upgraded', + ), + 'nobuild' => array( + 'shortopt' => 'B', + 'doc' => 'don\'t build C extensions', + ), + 'nocompress' => array( + 'shortopt' => 'Z', + 'doc' => 'request uncompressed files when downloading', + ), + 'installroot' => array( + 'shortopt' => 'R', + 'arg' => 'DIR', + 'doc' => 'root directory used when installing files (ala PHP\'s INSTALL_ROOT)', + ), + 'ignore-errors' => array( + 'doc' => 'force install even if there were errors', + ), + 'alldeps' => array( + 'shortopt' => 'a', + 'doc' => 'install all required and optional dependencies', + ), + 'onlyreqdeps' => array( + 'shortopt' => 'o', + 'doc' => 'install all required dependencies', + ), + 'offline' => array( + 'shortopt' => 'O', + 'doc' => 'do not attempt to download any urls or contact channels', + ), + 'pretend' => array( + 'shortopt' => 'p', + 'doc' => 'Only list the packages that would be downloaded', + ), + ), + 'doc' => ' ... +Upgrades one or more PEAR packages. See documentation for the +"install" command for ways to specify a package. + +When upgrading, your package will be updated if the provided new +package has a higher version number (use the -f option if you need to +upgrade anyway). + +More than one package may be specified at once. +'), + 'upgrade-all' => array( + 'summary' => 'Upgrade All Packages [Deprecated in favor of calling upgrade with no parameters]', + 'function' => 'doUpgradeAll', + 'shortcut' => 'ua', + 'options' => array( + 'channel' => array( + 'shortopt' => 'c', + 'doc' => 'upgrade packages from a specific channel', + 'arg' => 'CHAN', + ), + 'nodeps' => array( + 'shortopt' => 'n', + 'doc' => 'ignore dependencies, upgrade anyway', + ), + 'register-only' => array( + 'shortopt' => 'r', + 'doc' => 'do not install files, only register the package as upgraded', + ), + 'nobuild' => array( + 'shortopt' => 'B', + 'doc' => 'don\'t build C extensions', + ), + 'nocompress' => array( + 'shortopt' => 'Z', + 'doc' => 'request uncompressed files when downloading', + ), + 'installroot' => array( + 'shortopt' => 'R', + 'arg' => 'DIR', + 'doc' => 'root directory used when installing files (ala PHP\'s INSTALL_ROOT), use packagingroot for RPM', + ), + 'ignore-errors' => array( + 'doc' => 'force install even if there were errors', + ), + 'loose' => array( + 'doc' => 'do not check for recommended dependency version', + ), + ), + 'doc' => ' +WARNING: This function is deprecated in favor of using the upgrade command with no params + +Upgrades all packages that have a newer release available. Upgrades are +done only if there is a release available of the state specified in +"preferred_state" (currently {config preferred_state}), or a state considered +more stable. +'), + 'uninstall' => array( + 'summary' => 'Un-install Package', + 'function' => 'doUninstall', + 'shortcut' => 'un', + 'options' => array( + 'nodeps' => array( + 'shortopt' => 'n', + 'doc' => 'ignore dependencies, uninstall anyway', + ), + 'register-only' => array( + 'shortopt' => 'r', + 'doc' => 'do not remove files, only register the packages as not installed', + ), + 'installroot' => array( + 'shortopt' => 'R', + 'arg' => 'DIR', + 'doc' => 'root directory used when installing files (ala PHP\'s INSTALL_ROOT)', + ), + 'ignore-errors' => array( + 'doc' => 'force install even if there were errors', + ), + 'offline' => array( + 'shortopt' => 'O', + 'doc' => 'do not attempt to uninstall remotely', + ), + ), + 'doc' => '[channel/] ... +Uninstalls one or more PEAR packages. More than one package may be +specified at once. Prefix with channel name to uninstall from a +channel not in your default channel ({config default_channel}) +'), + 'bundle' => array( + 'summary' => 'Unpacks a Pecl Package', + 'function' => 'doBundle', + 'shortcut' => 'bun', + 'options' => array( + 'destination' => array( + 'shortopt' => 'd', + 'arg' => 'DIR', + 'doc' => 'Optional destination directory for unpacking (defaults to current path or "ext" if exists)', + ), + 'force' => array( + 'shortopt' => 'f', + 'doc' => 'Force the unpacking even if there were errors in the package', + ), + ), + 'doc' => ' +Unpacks a Pecl Package into the selected location. It will download the +package if needed. +'), + 'run-scripts' => array( + 'summary' => 'Run Post-Install Scripts bundled with a package', + 'function' => 'doRunScripts', + 'shortcut' => 'rs', + 'options' => array( + ), + 'doc' => ' +Run post-installation scripts in package , if any exist. +'), + ); + + // }}} + // {{{ constructor + + /** + * PEAR_Command_Install constructor. + * + * @access public + */ + function PEAR_Command_Install(&$ui, &$config) + { + parent::PEAR_Command_Common($ui, $config); + } + + // }}} + + /** + * For unit testing purposes + */ + function &getDownloader(&$ui, $options, &$config) + { + if (!class_exists('PEAR_Downloader')) { + require_once 'PEAR/Downloader.php'; + } + $a = &new PEAR_Downloader($ui, $options, $config); + return $a; + } + + /** + * For unit testing purposes + */ + function &getInstaller(&$ui) + { + if (!class_exists('PEAR_Installer')) { + require_once 'PEAR/Installer.php'; + } + $a = &new PEAR_Installer($ui); + return $a; + } + + function enableExtension($binaries, $type) + { + if (!($phpini = $this->config->get('php_ini', null, 'pear.php.net'))) { + return PEAR::raiseError('configuration option "php_ini" is not set to php.ini location'); + } + $ini = $this->_parseIni($phpini); + if (PEAR::isError($ini)) { + return $ini; + } + $line = 0; + if ($type == 'extsrc' || $type == 'extbin') { + $search = 'extensions'; + $enable = 'extension'; + } else { + $search = 'zend_extensions'; + ob_start(); + phpinfo(INFO_GENERAL); + $info = ob_get_contents(); + ob_end_clean(); + $debug = function_exists('leak') ? '_debug' : ''; + $ts = preg_match('Thread Safety.+enabled', $info) ? '_ts' : ''; + $enable = 'zend_extension' . $debug . $ts; + } + foreach ($ini[$search] as $line => $extension) { + if (in_array($extension, $binaries, true) || in_array( + $ini['extension_dir'] . DIRECTORY_SEPARATOR . $extension, $binaries, true)) { + // already enabled - assume if one is, all are + return true; + } + } + if ($line) { + $newini = array_slice($ini['all'], 0, $line); + } else { + $newini = array(); + } + foreach ($binaries as $binary) { + if ($ini['extension_dir']) { + $binary = basename($binary); + } + $newini[] = $enable . '="' . $binary . '"' . (OS_UNIX ? "\n" : "\r\n"); + } + $newini = array_merge($newini, array_slice($ini['all'], $line)); + $fp = @fopen($phpini, 'wb'); + if (!$fp) { + return PEAR::raiseError('cannot open php.ini "' . $phpini . '" for writing'); + } + foreach ($newini as $line) { + fwrite($fp, $line); + } + fclose($fp); + return true; + } + + function disableExtension($binaries, $type) + { + if (!($phpini = $this->config->get('php_ini', null, 'pear.php.net'))) { + return PEAR::raiseError('configuration option "php_ini" is not set to php.ini location'); + } + $ini = $this->_parseIni($phpini); + if (PEAR::isError($ini)) { + return $ini; + } + $line = 0; + if ($type == 'extsrc' || $type == 'extbin') { + $search = 'extensions'; + $enable = 'extension'; + } else { + $search = 'zend_extensions'; + ob_start(); + phpinfo(INFO_GENERAL); + $info = ob_get_contents(); + ob_end_clean(); + $debug = function_exists('leak') ? '_debug' : ''; + $ts = preg_match('Thread Safety.+enabled', $info) ? '_ts' : ''; + $enable = 'zend_extension' . $debug . $ts; + } + $found = false; + foreach ($ini[$search] as $line => $extension) { + if (in_array($extension, $binaries, true) || in_array( + $ini['extension_dir'] . DIRECTORY_SEPARATOR . $extension, $binaries, true)) { + $found = true; + break; + } + } + if (!$found) { + // not enabled + return true; + } + $fp = @fopen($phpini, 'wb'); + if (!$fp) { + return PEAR::raiseError('cannot open php.ini "' . $phpini . '" for writing'); + } + if ($line) { + $newini = array_slice($ini['all'], 0, $line); + // delete the enable line + $newini = array_merge($newini, array_slice($ini['all'], $line + 1)); + } else { + $newini = array_slice($ini['all'], 1); + } + foreach ($newini as $line) { + fwrite($fp, $line); + } + fclose($fp); + return true; + } + + function _parseIni($filename) + { + if (!file_exists($filename)) { + return PEAR::raiseError('php.ini "' . $filename . '" does not exist'); + } + + if (filesize($filename) > 300000) { + return PEAR::raiseError('php.ini "' . $filename . '" is too large, aborting'); + } + + ob_start(); + phpinfo(INFO_GENERAL); + $info = ob_get_contents(); + ob_end_clean(); + $debug = function_exists('leak') ? '_debug' : ''; + $ts = preg_match('/Thread Safety.+enabled/', $info) ? '_ts' : ''; + $zend_extension_line = 'zend_extension' . $debug . $ts; + $all = @file($filename); + if (!$all) { + return PEAR::raiseError('php.ini "' . $filename .'" could not be read'); + } + $zend_extensions = $extensions = array(); + // assume this is right, but pull from the php.ini if it is found + $extension_dir = ini_get('extension_dir'); + foreach ($all as $linenum => $line) { + $line = trim($line); + if (!$line) { + continue; + } + if ($line[0] == ';') { + continue; + } + if (strtolower(substr($line, 0, 13)) == 'extension_dir') { + $line = trim(substr($line, 13)); + if ($line[0] == '=') { + $x = trim(substr($line, 1)); + $x = explode(';', $x); + $extension_dir = str_replace('"', '', array_shift($x)); + continue; + } + } + if (strtolower(substr($line, 0, 9)) == 'extension') { + $line = trim(substr($line, 9)); + if ($line[0] == '=') { + $x = trim(substr($line, 1)); + $x = explode(';', $x); + $extensions[$linenum] = str_replace('"', '', array_shift($x)); + continue; + } + } + if (strtolower(substr($line, 0, strlen($zend_extension_line))) == + $zend_extension_line) { + $line = trim(substr($line, strlen($zend_extension_line))); + if ($line[0] == '=') { + $x = trim(substr($line, 1)); + $x = explode(';', $x); + $zend_extensions[$linenum] = str_replace('"', '', array_shift($x)); + continue; + } + } + } + return array( + 'extensions' => $extensions, + 'zend_extensions' => $zend_extensions, + 'extension_dir' => $extension_dir, + 'all' => $all, + ); + } + + // {{{ doInstall() + + function doInstall($command, $options, $params) + { + if (!class_exists('PEAR_PackageFile')) { + require_once 'PEAR/PackageFile.php'; + } + + if (isset($options['installroot']) && isset($options['packagingroot'])) { + return $this->raiseError('ERROR: cannot use both --installroot and --packagingroot'); + } + + $reg = &$this->config->getRegistry(); + $channel = isset($options['channel']) ? $options['channel'] : $this->config->get('default_channel'); + if (!$reg->channelExists($channel)) { + return $this->raiseError('Channel "' . $channel . '" does not exist'); + } + + if (empty($this->installer)) { + $this->installer = &$this->getInstaller($this->ui); + } + + if ($command == 'upgrade' || $command == 'upgrade-all') { + // If people run the upgrade command but pass nothing, emulate a upgrade-all + if ($command == 'upgrade' && empty($params)) { + return $this->doUpgradeAll($command, $options, $params); + } + $options['upgrade'] = true; + } else { + $packages = $params; + } + + $instreg = &$reg; // instreg used to check if package is installed + if (isset($options['packagingroot']) && !isset($options['upgrade'])) { + $packrootphp_dir = $this->installer->_prependPath( + $this->config->get('php_dir', null, 'pear.php.net'), + $options['packagingroot']); + $instreg = new PEAR_Registry($packrootphp_dir); // other instreg! + + if ($this->config->get('verbose') > 2) { + $this->ui->outputData('using package root: ' . $options['packagingroot']); + } + } + + $abstractpackages = $otherpackages = array(); + // parse params + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + + foreach ($params as $param) { + if (strpos($param, 'http://') === 0) { + $otherpackages[] = $param; + continue; + } + + if (strpos($param, 'channel://') === false && @file_exists($param)) { + if (isset($options['force'])) { + $otherpackages[] = $param; + continue; + } + + $pkg = new PEAR_PackageFile($this->config); + $pf = $pkg->fromAnyFile($param, PEAR_VALIDATE_DOWNLOADING); + if (PEAR::isError($pf)) { + $otherpackages[] = $param; + continue; + } + + $exists = $reg->packageExists($pf->getPackage(), $pf->getChannel()); + $pversion = $reg->packageInfo($pf->getPackage(), 'version', $pf->getChannel()); + $version_compare = version_compare($pf->getVersion(), $pversion, '<='); + if ($exists && $version_compare) { + if ($this->config->get('verbose')) { + $this->ui->outputData('Ignoring installed package ' . + $reg->parsedPackageNameToString( + array('package' => $pf->getPackage(), + 'channel' => $pf->getChannel()), true)); + } + continue; + } + $otherpackages[] = $param; + continue; + } + + $e = $reg->parsePackageName($param, $channel); + if (PEAR::isError($e)) { + $otherpackages[] = $param; + } else { + $abstractpackages[] = $e; + } + } + PEAR::staticPopErrorHandling(); + + // if there are any local package .tgz or remote static url, we can't + // filter. The filter only works for abstract packages + if (count($abstractpackages) && !isset($options['force'])) { + // when not being forced, only do necessary upgrades/installs + if (isset($options['upgrade'])) { + $abstractpackages = $this->_filterUptodatePackages($abstractpackages, $command); + } else { + $count = count($abstractpackages); + foreach ($abstractpackages as $i => $package) { + if (isset($package['group'])) { + // do not filter out install groups + continue; + } + + if ($instreg->packageExists($package['package'], $package['channel'])) { + if ($count > 1) { + if ($this->config->get('verbose')) { + $this->ui->outputData('Ignoring installed package ' . + $reg->parsedPackageNameToString($package, true)); + } + unset($abstractpackages[$i]); + } elseif ($count === 1) { + // Lets try to upgrade it since it's already installed + $options['upgrade'] = true; + } + } + } + } + $abstractpackages = + array_map(array($reg, 'parsedPackageNameToString'), $abstractpackages); + } elseif (count($abstractpackages)) { + $abstractpackages = + array_map(array($reg, 'parsedPackageNameToString'), $abstractpackages); + } + + $packages = array_merge($abstractpackages, $otherpackages); + if (!count($packages)) { + $c = ''; + if (isset($options['channel'])){ + $c .= ' in channel "' . $options['channel'] . '"'; + } + $this->ui->outputData('Nothing to ' . $command . $c); + return true; + } + + $this->downloader = &$this->getDownloader($this->ui, $options, $this->config); + $errors = $downloaded = $binaries = array(); + $downloaded = &$this->downloader->download($packages); + if (PEAR::isError($downloaded)) { + return $this->raiseError($downloaded); + } + + $errors = $this->downloader->getErrorMsgs(); + if (count($errors)) { + $err = array(); + $err['data'] = array(); + foreach ($errors as $error) { + if ($error !== null) { + $err['data'][] = array($error); + } + } + + if (!empty($err['data'])) { + $err['headline'] = 'Install Errors'; + $this->ui->outputData($err); + } + + if (!count($downloaded)) { + return $this->raiseError("$command failed"); + } + } + + $data = array( + 'headline' => 'Packages that would be Installed' + ); + + if (isset($options['pretend'])) { + foreach ($downloaded as $package) { + $data['data'][] = array($reg->parsedPackageNameToString($package->getParsedPackage())); + } + $this->ui->outputData($data, 'pretend'); + return true; + } + + $this->installer->setOptions($options); + $this->installer->sortPackagesForInstall($downloaded); + if (PEAR::isError($err = $this->installer->setDownloadedPackages($downloaded))) { + $this->raiseError($err->getMessage()); + return true; + } + + $binaries = $extrainfo = array(); + foreach ($downloaded as $param) { + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $info = $this->installer->install($param, $options); + PEAR::staticPopErrorHandling(); + if (PEAR::isError($info)) { + $oldinfo = $info; + $pkg = &$param->getPackageFile(); + if ($info->getCode() != PEAR_INSTALLER_NOBINARY) { + if (!($info = $pkg->installBinary($this->installer))) { + $this->ui->outputData('ERROR: ' .$oldinfo->getMessage()); + continue; + } + + // we just installed a different package than requested, + // let's change the param and info so that the rest of this works + $param = $info[0]; + $info = $info[1]; + } + } + + if (!is_array($info)) { + return $this->raiseError("$command failed"); + } + + if ($param->getPackageType() == 'extsrc' || + $param->getPackageType() == 'extbin' || + $param->getPackageType() == 'zendextsrc' || + $param->getPackageType() == 'zendextbin') { + $pkg = &$param->getPackageFile(); + if ($instbin = $pkg->getInstalledBinary()) { + $instpkg = &$instreg->getPackage($instbin, $pkg->getChannel()); + } else { + $instpkg = &$instreg->getPackage($pkg->getPackage(), $pkg->getChannel()); + } + + foreach ($instpkg->getFilelist() as $name => $atts) { + $pinfo = pathinfo($atts['installed_as']); + if (!isset($pinfo['extension']) || + in_array($pinfo['extension'], array('c', 'h'))) { + continue; // make sure we don't match php_blah.h + } + + if ((strpos($pinfo['basename'], 'php_') === 0 && + $pinfo['extension'] == 'dll') || + // most unices + $pinfo['extension'] == 'so' || + // hp-ux + $pinfo['extension'] == 'sl') { + $binaries[] = array($atts['installed_as'], $pinfo); + break; + } + } + + if (count($binaries)) { + foreach ($binaries as $pinfo) { + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $ret = $this->enableExtension(array($pinfo[0]), $param->getPackageType()); + PEAR::staticPopErrorHandling(); + if (PEAR::isError($ret)) { + $extrainfo[] = $ret->getMessage(); + if ($param->getPackageType() == 'extsrc' || + $param->getPackageType() == 'extbin') { + $exttype = 'extension'; + } else { + ob_start(); + phpinfo(INFO_GENERAL); + $info = ob_get_contents(); + ob_end_clean(); + $debug = function_exists('leak') ? '_debug' : ''; + $ts = preg_match('Thread Safety.+enabled', $info) ? '_ts' : ''; + $exttype = 'zend_extension' . $debug . $ts; + } + $extrainfo[] = 'You should add "' . $exttype . '=' . + $pinfo[1]['basename'] . '" to php.ini'; + } else { + $extrainfo[] = 'Extension ' . $instpkg->getProvidesExtension() . + ' enabled in php.ini'; + } + } + } + } + + if ($this->config->get('verbose') > 0) { + $chan = $param->getChannel(); + $label = $reg->parsedPackageNameToString( + array( + 'channel' => $chan, + 'package' => $param->getPackage(), + 'version' => $param->getVersion(), + )); + $out = array('data' => "$command ok: $label"); + if (isset($info['release_warnings'])) { + $out['release_warnings'] = $info['release_warnings']; + } + $this->ui->outputData($out, $command); + + if (!isset($options['register-only']) && !isset($options['offline'])) { + if ($this->config->isDefinedLayer('ftp')) { + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $info = $this->installer->ftpInstall($param); + PEAR::staticPopErrorHandling(); + if (PEAR::isError($info)) { + $this->ui->outputData($info->getMessage()); + $this->ui->outputData("remote install failed: $label"); + } else { + $this->ui->outputData("remote install ok: $label"); + } + } + } + } + + $deps = $param->getDeps(); + if ($deps) { + if (isset($deps['group'])) { + $groups = $deps['group']; + if (!isset($groups[0])) { + $groups = array($groups); + } + + foreach ($groups as $group) { + if ($group['attribs']['name'] == 'default') { + // default group is always installed, unless the user + // explicitly chooses to install another group + continue; + } + $extrainfo[] = $param->getPackage() . ': Optional feature ' . + $group['attribs']['name'] . ' available (' . + $group['attribs']['hint'] . ')'; + } + + $extrainfo[] = $param->getPackage() . + ': To install optional features use "pear install ' . + $reg->parsedPackageNameToString( + array('package' => $param->getPackage(), + 'channel' => $param->getChannel()), true) . + '#featurename"'; + } + } + + $pkg = &$instreg->getPackage($param->getPackage(), $param->getChannel()); + // $pkg may be NULL if install is a 'fake' install via --packagingroot + if (is_object($pkg)) { + $pkg->setConfig($this->config); + if ($list = $pkg->listPostinstallScripts()) { + $pn = $reg->parsedPackageNameToString(array('channel' => + $param->getChannel(), 'package' => $param->getPackage()), true); + $extrainfo[] = $pn . ' has post-install scripts:'; + foreach ($list as $file) { + $extrainfo[] = $file; + } + $extrainfo[] = $param->getPackage() . + ': Use "pear run-scripts ' . $pn . '" to finish setup.'; + $extrainfo[] = 'DO NOT RUN SCRIPTS FROM UNTRUSTED SOURCES'; + } + } + } + + if (count($extrainfo)) { + foreach ($extrainfo as $info) { + $this->ui->outputData($info); + } + } + + return true; + } + + // }}} + // {{{ doUpgradeAll() + + function doUpgradeAll($command, $options, $params) + { + $reg = &$this->config->getRegistry(); + $upgrade = array(); + + if (isset($options['channel'])) { + $channels = array($options['channel']); + } else { + $channels = $reg->listChannels(); + } + + foreach ($channels as $channel) { + if ($channel == '__uri') { + continue; + } + + // parse name with channel + foreach ($reg->listPackages($channel) as $name) { + $upgrade[] = $reg->parsedPackageNameToString(array( + 'channel' => $channel, + 'package' => $name + )); + } + } + + $err = $this->doInstall($command, $options, $upgrade); + if (PEAR::isError($err)) { + $this->ui->outputData($err->getMessage(), $command); + } + } + + // }}} + // {{{ doUninstall() + + function doUninstall($command, $options, $params) + { + if (count($params) < 1) { + return $this->raiseError("Please supply the package(s) you want to uninstall"); + } + + if (empty($this->installer)) { + $this->installer = &$this->getInstaller($this->ui); + } + + if (isset($options['remoteconfig'])) { + $e = $this->config->readFTPConfigFile($options['remoteconfig']); + if (!PEAR::isError($e)) { + $this->installer->setConfig($this->config); + } + } + + $reg = &$this->config->getRegistry(); + $newparams = array(); + $binaries = array(); + $badparams = array(); + foreach ($params as $pkg) { + $channel = $this->config->get('default_channel'); + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $parsed = $reg->parsePackageName($pkg, $channel); + PEAR::staticPopErrorHandling(); + if (!$parsed || PEAR::isError($parsed)) { + $badparams[] = $pkg; + continue; + } + $package = $parsed['package']; + $channel = $parsed['channel']; + $info = &$reg->getPackage($package, $channel); + if ($info === null && + ($channel == 'pear.php.net' || $channel == 'pecl.php.net')) { + // make sure this isn't a package that has flipped from pear to pecl but + // used a package.xml 1.0 + $testc = ($channel == 'pear.php.net') ? 'pecl.php.net' : 'pear.php.net'; + $info = &$reg->getPackage($package, $testc); + if ($info !== null) { + $channel = $testc; + } + } + if ($info === null) { + $badparams[] = $pkg; + } else { + $newparams[] = &$info; + // check for binary packages (this is an alias for those packages if so) + if ($installedbinary = $info->getInstalledBinary()) { + $this->ui->log('adding binary package ' . + $reg->parsedPackageNameToString(array('channel' => $channel, + 'package' => $installedbinary), true)); + $newparams[] = &$reg->getPackage($installedbinary, $channel); + } + // add the contents of a dependency group to the list of installed packages + if (isset($parsed['group'])) { + $group = $info->getDependencyGroup($parsed['group']); + if ($group) { + $installed = $reg->getInstalledGroup($group); + if ($installed) { + foreach ($installed as $i => $p) { + $newparams[] = &$installed[$i]; + } + } + } + } + } + } + $err = $this->installer->sortPackagesForUninstall($newparams); + if (PEAR::isError($err)) { + $this->ui->outputData($err->getMessage(), $command); + return true; + } + $params = $newparams; + // twist this to use it to check on whether dependent packages are also being uninstalled + // for circular dependencies like subpackages + $this->installer->setUninstallPackages($newparams); + $params = array_merge($params, $badparams); + $binaries = array(); + foreach ($params as $pkg) { + $this->installer->pushErrorHandling(PEAR_ERROR_RETURN); + if ($err = $this->installer->uninstall($pkg, $options)) { + $this->installer->popErrorHandling(); + if (PEAR::isError($err)) { + $this->ui->outputData($err->getMessage(), $command); + continue; + } + if ($pkg->getPackageType() == 'extsrc' || + $pkg->getPackageType() == 'extbin' || + $pkg->getPackageType() == 'zendextsrc' || + $pkg->getPackageType() == 'zendextbin') { + if ($instbin = $pkg->getInstalledBinary()) { + continue; // this will be uninstalled later + } + + foreach ($pkg->getFilelist() as $name => $atts) { + $pinfo = pathinfo($atts['installed_as']); + if (!isset($pinfo['extension']) || + in_array($pinfo['extension'], array('c', 'h'))) { + continue; // make sure we don't match php_blah.h + } + if ((strpos($pinfo['basename'], 'php_') === 0 && + $pinfo['extension'] == 'dll') || + // most unices + $pinfo['extension'] == 'so' || + // hp-ux + $pinfo['extension'] == 'sl') { + $binaries[] = array($atts['installed_as'], $pinfo); + break; + } + } + if (count($binaries)) { + foreach ($binaries as $pinfo) { + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $ret = $this->disableExtension(array($pinfo[0]), $pkg->getPackageType()); + PEAR::staticPopErrorHandling(); + if (PEAR::isError($ret)) { + $extrainfo[] = $ret->getMessage(); + if ($pkg->getPackageType() == 'extsrc' || + $pkg->getPackageType() == 'extbin') { + $exttype = 'extension'; + } else { + ob_start(); + phpinfo(INFO_GENERAL); + $info = ob_get_contents(); + ob_end_clean(); + $debug = function_exists('leak') ? '_debug' : ''; + $ts = preg_match('Thread Safety.+enabled', $info) ? '_ts' : ''; + $exttype = 'zend_extension' . $debug . $ts; + } + $this->ui->outputData('Unable to remove "' . $exttype . '=' . + $pinfo[1]['basename'] . '" from php.ini', $command); + } else { + $this->ui->outputData('Extension ' . $pkg->getProvidesExtension() . + ' disabled in php.ini', $command); + } + } + } + } + $savepkg = $pkg; + if ($this->config->get('verbose') > 0) { + if (is_object($pkg)) { + $pkg = $reg->parsedPackageNameToString($pkg); + } + $this->ui->outputData("uninstall ok: $pkg", $command); + } + if (!isset($options['offline']) && is_object($savepkg) && + defined('PEAR_REMOTEINSTALL_OK')) { + if ($this->config->isDefinedLayer('ftp')) { + $this->installer->pushErrorHandling(PEAR_ERROR_RETURN); + $info = $this->installer->ftpUninstall($savepkg); + $this->installer->popErrorHandling(); + if (PEAR::isError($info)) { + $this->ui->outputData($info->getMessage()); + $this->ui->outputData("remote uninstall failed: $pkg"); + } else { + $this->ui->outputData("remote uninstall ok: $pkg"); + } + } + } + } else { + $this->installer->popErrorHandling(); + if (!is_object($pkg)) { + return $this->raiseError("uninstall failed: $pkg"); + } + $pkg = $reg->parsedPackageNameToString($pkg); + } + } + + return true; + } + + // }}} + + + // }}} + // {{{ doBundle() + /* + (cox) It just downloads and untars the package, does not do + any check that the PEAR_Installer::_installFile() does. + */ + + function doBundle($command, $options, $params) + { + $opts = array( + 'force' => true, + 'nodeps' => true, + 'soft' => true, + 'downloadonly' => true + ); + $downloader = &$this->getDownloader($this->ui, $opts, $this->config); + $reg = &$this->config->getRegistry(); + if (count($params) < 1) { + return $this->raiseError("Please supply the package you want to bundle"); + } + + if (isset($options['destination'])) { + if (!is_dir($options['destination'])) { + System::mkdir('-p ' . $options['destination']); + } + $dest = realpath($options['destination']); + } else { + $pwd = getcwd(); + $dir = $pwd . DIRECTORY_SEPARATOR . 'ext'; + $dest = is_dir($dir) ? $dir : $pwd; + } + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $err = $downloader->setDownloadDir($dest); + PEAR::staticPopErrorHandling(); + if (PEAR::isError($err)) { + return PEAR::raiseError('download directory "' . $dest . + '" is not writeable.'); + } + $result = &$downloader->download(array($params[0])); + if (PEAR::isError($result)) { + return $result; + } + if (!isset($result[0])) { + return $this->raiseError('unable to unpack ' . $params[0]); + } + $pkgfile = &$result[0]->getPackageFile(); + $pkgname = $pkgfile->getName(); + $pkgversion = $pkgfile->getVersion(); + + // Unpacking ------------------------------------------------- + $dest .= DIRECTORY_SEPARATOR . $pkgname; + $orig = $pkgname . '-' . $pkgversion; + + $tar = &new Archive_Tar($pkgfile->getArchiveFile()); + if (!$tar->extractModify($dest, $orig)) { + return $this->raiseError('unable to unpack ' . $pkgfile->getArchiveFile()); + } + $this->ui->outputData("Package ready at '$dest'"); + // }}} + } + + // }}} + + function doRunScripts($command, $options, $params) + { + if (!isset($params[0])) { + return $this->raiseError('run-scripts expects 1 parameter: a package name'); + } + + $reg = &$this->config->getRegistry(); + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $parsed = $reg->parsePackageName($params[0], $this->config->get('default_channel')); + PEAR::staticPopErrorHandling(); + if (PEAR::isError($parsed)) { + return $this->raiseError($parsed); + } + + $package = &$reg->getPackage($parsed['package'], $parsed['channel']); + if (!is_object($package)) { + return $this->raiseError('Could not retrieve package "' . $params[0] . '" from registry'); + } + + $package->setConfig($this->config); + $package->runPostinstallScripts(); + $this->ui->outputData('Install scripts complete', $command); + return true; + } + + /** + * Given a list of packages, filter out those ones that are already up to date + * + * @param $packages: packages, in parsed array format ! + * @return list of packages that can be upgraded + */ + function _filterUptodatePackages($packages, $command) + { + $reg = &$this->config->getRegistry(); + $latestReleases = array(); + + $ret = array(); + foreach ($packages as $package) { + if (isset($package['group'])) { + $ret[] = $package; + continue; + } + + $channel = $package['channel']; + $name = $package['package']; + if (!$reg->packageExists($name, $channel)) { + $ret[] = $package; + continue; + } + + if (!isset($latestReleases[$channel])) { + // fill in cache for this channel + $chan = &$reg->getChannel($channel); + if (PEAR::isError($chan)) { + return $this->raiseError($chan); + } + + $base2 = false; + $preferred_mirror = $this->config->get('preferred_mirror', null, $channel); + if ($chan->supportsREST($preferred_mirror) && + ( + //($base2 = $chan->getBaseURL('REST1.4', $preferred_mirror)) || + ($base = $chan->getBaseURL('REST1.0', $preferred_mirror)) + ) + ) { + $dorest = true; + } + + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + if (!isset($package['state'])) { + $state = $this->config->get('preferred_state', null, $channel); + } else { + $state = $package['state']; + } + + if ($dorest) { + if ($base2) { + $rest = &$this->config->getREST('1.4', array()); + $base = $base2; + } else { + $rest = &$this->config->getREST('1.0', array()); + } + + $installed = array_flip($reg->listPackages($channel)); + $latest = $rest->listLatestUpgrades($base, $state, $installed, $channel, $reg); + } + + PEAR::staticPopErrorHandling(); + if (PEAR::isError($latest)) { + $this->ui->outputData('Error getting channel info from ' . $channel . + ': ' . $latest->getMessage()); + continue; + } + + $latestReleases[$channel] = array_change_key_case($latest); + } + + // check package for latest release + $name_lower = strtolower($name); + if (isset($latestReleases[$channel][$name_lower])) { + // if not set, up to date + $inst_version = $reg->packageInfo($name, 'version', $channel); + $channel_version = $latestReleases[$channel][$name_lower]['version']; + if (version_compare($channel_version, $inst_version, 'le')) { + // installed version is up-to-date + continue; + } + + // maintain BC + if ($command == 'upgrade-all') { + $this->ui->outputData(array('data' => 'Will upgrade ' . + $reg->parsedPackageNameToString($package)), $command); + } + $ret[] = $package; + } + } + + return $ret; + } +} \ No newline at end of file diff --git a/library/pear/PEAR/Command/Install.xml b/library/pear/PEAR/Command/Install.xml new file mode 100644 index 000000000..1b1e933c2 --- /dev/null +++ b/library/pear/PEAR/Command/Install.xml @@ -0,0 +1,276 @@ + + + Install Package + doInstall + i + + + f + will overwrite newer installed packages + + + l + do not check for recommended dependency version + + + n + ignore dependencies, install anyway + + + r + do not install files, only register the package as installed + + + s + soft install, fail silently, or upgrade if already installed + + + B + don't build C extensions + + + Z + request uncompressed files when downloading + + + R + root directory used when installing files (ala PHP's INSTALL_ROOT), use packagingroot for RPM + DIR + + + P + root directory used when packaging files, like RPM packaging + DIR + + + + force install even if there were errors + + + a + install all required and optional dependencies + + + o + install all required dependencies + + + O + do not attempt to download any urls or contact channels + + + p + Only list the packages that would be downloaded + + + [channel/]<package> ... +Installs one or more PEAR packages. You can specify a package to +install in four ways: + +"Package-1.0.tgz" : installs from a local file + +"http://example.com/Package-1.0.tgz" : installs from +anywhere on the net. + +"package.xml" : installs the package described in +package.xml. Useful for testing, or for wrapping a PEAR package in +another package manager such as RPM. + +"Package[-version/state][.tar]" : queries your default channel's server +({config master_server}) and downloads the newest package with +the preferred quality/state ({config preferred_state}). + +To retrieve Package version 1.1, use "Package-1.1," to retrieve +Package state beta, use "Package-beta." To retrieve an uncompressed +file, append .tar (make sure there is no file by the same name first) + +To download a package from another channel, prefix with the channel name like +"channel/Package" + +More than one package may be specified at once. It is ok to mix these +four ways of specifying packages. + + + + Upgrade Package + doInstall + up + + + c + upgrade packages from a specific channel + CHAN + + + f + overwrite newer installed packages + + + l + do not check for recommended dependency version + + + n + ignore dependencies, upgrade anyway + + + r + do not install files, only register the package as upgraded + + + B + don't build C extensions + + + Z + request uncompressed files when downloading + + + R + root directory used when installing files (ala PHP's INSTALL_ROOT) + DIR + + + + force install even if there were errors + + + a + install all required and optional dependencies + + + o + install all required dependencies + + + O + do not attempt to download any urls or contact channels + + + p + Only list the packages that would be downloaded + + + <package> ... +Upgrades one or more PEAR packages. See documentation for the +"install" command for ways to specify a package. + +When upgrading, your package will be updated if the provided new +package has a higher version number (use the -f option if you need to +upgrade anyway). + +More than one package may be specified at once. + + + + Upgrade All Packages [Deprecated in favor of calling upgrade with no parameters] + doUpgradeAll + ua + + + c + upgrade packages from a specific channel + CHAN + + + n + ignore dependencies, upgrade anyway + + + r + do not install files, only register the package as upgraded + + + B + don't build C extensions + + + Z + request uncompressed files when downloading + + + R + root directory used when installing files (ala PHP's INSTALL_ROOT), use packagingroot for RPM + DIR + + + + force install even if there were errors + + + + do not check for recommended dependency version + + + +WARNING: This function is deprecated in favor of using the upgrade command with no params + +Upgrades all packages that have a newer release available. Upgrades are +done only if there is a release available of the state specified in +"preferred_state" (currently {config preferred_state}), or a state considered +more stable. + + + + Un-install Package + doUninstall + un + + + n + ignore dependencies, uninstall anyway + + + r + do not remove files, only register the packages as not installed + + + R + root directory used when installing files (ala PHP's INSTALL_ROOT) + DIR + + + + force install even if there were errors + + + O + do not attempt to uninstall remotely + + + [channel/]<package> ... +Uninstalls one or more PEAR packages. More than one package may be +specified at once. Prefix with channel name to uninstall from a +channel not in your default channel ({config default_channel}) + + + + Unpacks a Pecl Package + doBundle + bun + + + d + Optional destination directory for unpacking (defaults to current path or "ext" if exists) + DIR + + + f + Force the unpacking even if there were errors in the package + + + <package> +Unpacks a Pecl Package into the selected location. It will download the +package if needed. + + + + Run Post-Install Scripts bundled with a package + doRunScripts + rs + + <package> +Run post-installation scripts in package <package>, if any exist. + + + \ No newline at end of file diff --git a/library/pear/PEAR/Command/Mirror.php b/library/pear/PEAR/Command/Mirror.php new file mode 100644 index 000000000..e20cb3491 --- /dev/null +++ b/library/pear/PEAR/Command/Mirror.php @@ -0,0 +1,139 @@ + + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Mirror.php 276383 2009-02-24 23:39:37Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.2.0 + */ + +/** + * base class + */ +require_once 'PEAR/Command/Common.php'; + +/** + * PEAR commands for providing file mirrors + * + * @category pear + * @package PEAR + * @author Alexander Merz + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.1 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.2.0 + */ +class PEAR_Command_Mirror extends PEAR_Command_Common +{ + var $commands = array( + 'download-all' => array( + 'summary' => 'Downloads each available package from the default channel', + 'function' => 'doDownloadAll', + 'shortcut' => 'da', + 'options' => array( + 'channel' => + array( + 'shortopt' => 'c', + 'doc' => 'specify a channel other than the default channel', + 'arg' => 'CHAN', + ), + ), + 'doc' => ' +Requests a list of available packages from the default channel ({config default_channel}) +and downloads them to current working directory. Note: only +packages within preferred_state ({config preferred_state}) will be downloaded' + ), + ); + + /** + * PEAR_Command_Mirror constructor. + * + * @access public + * @param object PEAR_Frontend a reference to an frontend + * @param object PEAR_Config a reference to the configuration data + */ + function PEAR_Command_Mirror(&$ui, &$config) + { + parent::PEAR_Command_Common($ui, $config); + } + + /** + * For unit-testing + */ + function &factory($a) + { + $a = &PEAR_Command::factory($a, $this->config); + return $a; + } + + /** + * retrieves a list of avaible Packages from master server + * and downloads them + * + * @access public + * @param string $command the command + * @param array $options the command options before the command + * @param array $params the stuff after the command name + * @return bool true if succesful + * @throw PEAR_Error + */ + function doDownloadAll($command, $options, $params) + { + $savechannel = $this->config->get('default_channel'); + $reg = &$this->config->getRegistry(); + $channel = isset($options['channel']) ? $options['channel'] : + $this->config->get('default_channel'); + if (!$reg->channelExists($channel)) { + $this->config->set('default_channel', $savechannel); + return $this->raiseError('Channel "' . $channel . '" does not exist'); + } + $this->config->set('default_channel', $channel); + + $this->ui->outputData('Using Channel ' . $this->config->get('default_channel')); + $chan = $reg->getChannel($channel); + if (PEAR::isError($chan)) { + return $this->raiseError($chan); + } + + if ($chan->supportsREST($this->config->get('preferred_mirror')) && + $base = $chan->getBaseURL('REST1.0', $this->config->get('preferred_mirror'))) { + $rest = &$this->config->getREST('1.0', array()); + $remoteInfo = array_flip($rest->listPackages($base, $channel)); + } + + if (PEAR::isError($remoteInfo)) { + return $remoteInfo; + } + + $cmd = &$this->factory("download"); + if (PEAR::isError($cmd)) { + return $cmd; + } + + $this->ui->outputData('Using Preferred State of ' . + $this->config->get('preferred_state')); + $this->ui->outputData('Gathering release information, please wait...'); + + /** + * Error handling not necessary, because already done by + * the download command + */ + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $err = $cmd->run('download', array('downloadonly' => true), array_keys($remoteInfo)); + PEAR::staticPopErrorHandling(); + $this->config->set('default_channel', $savechannel); + if (PEAR::isError($err)) { + $this->ui->outputData($err->getMessage()); + } + + return true; + } +} \ No newline at end of file diff --git a/library/pear/PEAR/Command/Mirror.xml b/library/pear/PEAR/Command/Mirror.xml new file mode 100644 index 000000000..fe8be9d03 --- /dev/null +++ b/library/pear/PEAR/Command/Mirror.xml @@ -0,0 +1,18 @@ + + + Downloads each available package from the default channel + doDownloadAll + da + + + c + specify a channel other than the default channel + CHAN + + + +Requests a list of available packages from the default channel ({config default_channel}) +and downloads them to current working directory. Note: only +packages within preferred_state ({config preferred_state}) will be downloaded + + \ No newline at end of file diff --git a/library/pear/PEAR/Command/Package.php b/library/pear/PEAR/Command/Package.php new file mode 100644 index 000000000..f3ee5f95c --- /dev/null +++ b/library/pear/PEAR/Command/Package.php @@ -0,0 +1,1119 @@ + + * @author Martin Jansen + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Package.php 288113 2009-09-06 21:11:55Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 0.1 + */ + +/** + * base class + */ +require_once 'PEAR/Command/Common.php'; + +/** + * PEAR commands for login/logout + * + * @category pear + * @package PEAR + * @author Stig Bakken + * @author Martin Jansen + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 0.1 + */ + +class PEAR_Command_Package extends PEAR_Command_Common +{ + var $commands = array( + 'package' => array( + 'summary' => 'Build Package', + 'function' => 'doPackage', + 'shortcut' => 'p', + 'options' => array( + 'nocompress' => array( + 'shortopt' => 'Z', + 'doc' => 'Do not gzip the package file' + ), + 'showname' => array( + 'shortopt' => 'n', + 'doc' => 'Print the name of the packaged file.', + ), + ), + 'doc' => '[descfile] [descfile2] +Creates a PEAR package from its description file (usually called +package.xml). If a second packagefile is passed in, then +the packager will check to make sure that one is a package.xml +version 1.0, and the other is a package.xml version 2.0. The +package.xml version 1.0 will be saved as "package.xml" in the archive, +and the other as "package2.xml" in the archive" +' + ), + 'package-validate' => array( + 'summary' => 'Validate Package Consistency', + 'function' => 'doPackageValidate', + 'shortcut' => 'pv', + 'options' => array(), + 'doc' => ' +', + ), + 'cvsdiff' => array( + 'summary' => 'Run a "cvs diff" for all files in a package', + 'function' => 'doCvsDiff', + 'shortcut' => 'cd', + 'options' => array( + 'quiet' => array( + 'shortopt' => 'q', + 'doc' => 'Be quiet', + ), + 'reallyquiet' => array( + 'shortopt' => 'Q', + 'doc' => 'Be really quiet', + ), + 'date' => array( + 'shortopt' => 'D', + 'doc' => 'Diff against revision of DATE', + 'arg' => 'DATE', + ), + 'release' => array( + 'shortopt' => 'R', + 'doc' => 'Diff against tag for package release REL', + 'arg' => 'REL', + ), + 'revision' => array( + 'shortopt' => 'r', + 'doc' => 'Diff against revision REV', + 'arg' => 'REV', + ), + 'context' => array( + 'shortopt' => 'c', + 'doc' => 'Generate context diff', + ), + 'unified' => array( + 'shortopt' => 'u', + 'doc' => 'Generate unified diff', + ), + 'ignore-case' => array( + 'shortopt' => 'i', + 'doc' => 'Ignore case, consider upper- and lower-case letters equivalent', + ), + 'ignore-whitespace' => array( + 'shortopt' => 'b', + 'doc' => 'Ignore changes in amount of white space', + ), + 'ignore-blank-lines' => array( + 'shortopt' => 'B', + 'doc' => 'Ignore changes that insert or delete blank lines', + ), + 'brief' => array( + 'doc' => 'Report only whether the files differ, no details', + ), + 'dry-run' => array( + 'shortopt' => 'n', + 'doc' => 'Don\'t do anything, just pretend', + ), + ), + 'doc' => ' +Compares all the files in a package. Without any options, this +command will compare the current code with the last checked-in code. +Using the -r or -R option you may compare the current code with that +of a specific release. +', + ), + 'svntag' => array( + 'summary' => 'Set SVN Release Tag', + 'function' => 'doSvnTag', + 'shortcut' => 'sv', + 'options' => array( + 'quiet' => array( + 'shortopt' => 'q', + 'doc' => 'Be quiet', + ), + 'slide' => array( + 'shortopt' => 'F', + 'doc' => 'Move (slide) tag if it exists', + ), + 'delete' => array( + 'shortopt' => 'd', + 'doc' => 'Remove tag', + ), + 'dry-run' => array( + 'shortopt' => 'n', + 'doc' => 'Don\'t do anything, just pretend', + ), + ), + 'doc' => ' [files...] + Sets a SVN tag on all files in a package. Use this command after you have + packaged a distribution tarball with the "package" command to tag what + revisions of what files were in that release. If need to fix something + after running cvstag once, but before the tarball is released to the public, + use the "slide" option to move the release tag. + + to include files (such as a second package.xml, or tests not included in the + release), pass them as additional parameters. + ', + ), + 'cvstag' => array( + 'summary' => 'Set CVS Release Tag', + 'function' => 'doCvsTag', + 'shortcut' => 'ct', + 'options' => array( + 'quiet' => array( + 'shortopt' => 'q', + 'doc' => 'Be quiet', + ), + 'reallyquiet' => array( + 'shortopt' => 'Q', + 'doc' => 'Be really quiet', + ), + 'slide' => array( + 'shortopt' => 'F', + 'doc' => 'Move (slide) tag if it exists', + ), + 'delete' => array( + 'shortopt' => 'd', + 'doc' => 'Remove tag', + ), + 'dry-run' => array( + 'shortopt' => 'n', + 'doc' => 'Don\'t do anything, just pretend', + ), + ), + 'doc' => ' [files...] +Sets a CVS tag on all files in a package. Use this command after you have +packaged a distribution tarball with the "package" command to tag what +revisions of what files were in that release. If need to fix something +after running cvstag once, but before the tarball is released to the public, +use the "slide" option to move the release tag. + +to include files (such as a second package.xml, or tests not included in the +release), pass them as additional parameters. +', + ), + 'package-dependencies' => array( + 'summary' => 'Show package dependencies', + 'function' => 'doPackageDependencies', + 'shortcut' => 'pd', + 'options' => array(), + 'doc' => ' or or +List all dependencies the package has. +Can take a tgz / tar file, package.xml or a package name of an installed package.' + ), + 'sign' => array( + 'summary' => 'Sign a package distribution file', + 'function' => 'doSign', + 'shortcut' => 'si', + 'options' => array( + 'verbose' => array( + 'shortopt' => 'v', + 'doc' => 'Display GnuPG output', + ), + ), + 'doc' => ' +Signs a package distribution (.tar or .tgz) file with GnuPG.', + ), + 'makerpm' => array( + 'summary' => 'Builds an RPM spec file from a PEAR package', + 'function' => 'doMakeRPM', + 'shortcut' => 'rpm', + 'options' => array( + 'spec-template' => array( + 'shortopt' => 't', + 'arg' => 'FILE', + 'doc' => 'Use FILE as RPM spec file template' + ), + 'rpm-pkgname' => array( + 'shortopt' => 'p', + 'arg' => 'FORMAT', + 'doc' => 'Use FORMAT as format string for RPM package name, %s is replaced +by the PEAR package name, defaults to "PEAR::%s".', + ), + ), + 'doc' => ' + +Creates an RPM .spec file for wrapping a PEAR package inside an RPM +package. Intended to be used from the SPECS directory, with the PEAR +package tarball in the SOURCES directory: + +$ pear makerpm ../SOURCES/Net_Socket-1.0.tgz +Wrote RPM spec file PEAR::Net_Geo-1.0.spec +$ rpm -bb PEAR::Net_Socket-1.0.spec +... +Wrote: /usr/src/redhat/RPMS/i386/PEAR::Net_Socket-1.0-1.i386.rpm +', + ), + 'convert' => array( + 'summary' => 'Convert a package.xml 1.0 to package.xml 2.0 format', + 'function' => 'doConvert', + 'shortcut' => 'c2', + 'options' => array( + 'flat' => array( + 'shortopt' => 'f', + 'doc' => 'do not beautify the filelist.', + ), + ), + 'doc' => '[descfile] [descfile2] +Converts a package.xml in 1.0 format into a package.xml +in 2.0 format. The new file will be named package2.xml by default, +and package.xml will be used as the old file by default. +This is not the most intelligent conversion, and should only be +used for automated conversion or learning the format. +' + ), + ); + + var $output; + + /** + * PEAR_Command_Package constructor. + * + * @access public + */ + function PEAR_Command_Package(&$ui, &$config) + { + parent::PEAR_Command_Common($ui, $config); + } + + function _displayValidationResults($err, $warn, $strict = false) + { + foreach ($err as $e) { + $this->output .= "Error: $e\n"; + } + foreach ($warn as $w) { + $this->output .= "Warning: $w\n"; + } + $this->output .= sprintf('Validation: %d error(s), %d warning(s)'."\n", + sizeof($err), sizeof($warn)); + if ($strict && count($err) > 0) { + $this->output .= "Fix these errors and try again."; + return false; + } + return true; + } + + function &getPackager() + { + if (!class_exists('PEAR_Packager')) { + require_once 'PEAR/Packager.php'; + } + $a = &new PEAR_Packager; + return $a; + } + + function &getPackageFile($config, $debug = false, $tmpdir = null) + { + if (!class_exists('PEAR_Common')) { + require_once 'PEAR/Common.php'; + } + if (!class_exists('PEAR_PackageFile')) { + require_once 'PEAR/PackageFile.php'; + } + $a = &new PEAR_PackageFile($config, $debug, $tmpdir); + $common = new PEAR_Common; + $common->ui = $this->ui; + $a->setLogger($common); + return $a; + } + + function doPackage($command, $options, $params) + { + $this->output = ''; + $pkginfofile = isset($params[0]) ? $params[0] : 'package.xml'; + $pkg2 = isset($params[1]) ? $params[1] : null; + if (!$pkg2 && !isset($params[0]) && file_exists('package2.xml')) { + $pkg2 = 'package2.xml'; + } + + $packager = &$this->getPackager(); + $compress = empty($options['nocompress']) ? true : false; + $result = $packager->package($pkginfofile, $compress, $pkg2); + if (PEAR::isError($result)) { + return $this->raiseError($result); + } + + // Don't want output, only the package file name just created + if (isset($options['showname'])) { + $this->output = $result; + } + + if ($this->output) { + $this->ui->outputData($this->output, $command); + } + + return true; + } + + function doPackageValidate($command, $options, $params) + { + $this->output = ''; + if (count($params) < 1) { + $params[0] = 'package.xml'; + } + + $obj = &$this->getPackageFile($this->config, $this->_debug); + $obj->rawReturn(); + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $info = $obj->fromTgzFile($params[0], PEAR_VALIDATE_NORMAL); + if (PEAR::isError($info)) { + $info = $obj->fromPackageFile($params[0], PEAR_VALIDATE_NORMAL); + } else { + $archive = $info->getArchiveFile(); + $tar = &new Archive_Tar($archive); + $tar->extract(dirname($info->getPackageFile())); + $info->setPackageFile(dirname($info->getPackageFile()) . DIRECTORY_SEPARATOR . + $info->getPackage() . '-' . $info->getVersion() . DIRECTORY_SEPARATOR . + basename($info->getPackageFile())); + } + + PEAR::staticPopErrorHandling(); + if (PEAR::isError($info)) { + return $this->raiseError($info); + } + + $valid = false; + if ($info->getPackagexmlVersion() == '2.0') { + if ($valid = $info->validate(PEAR_VALIDATE_NORMAL)) { + $info->flattenFileList(); + $valid = $info->validate(PEAR_VALIDATE_PACKAGING); + } + } else { + $valid = $info->validate(PEAR_VALIDATE_PACKAGING); + } + + $err = $warn = array(); + if ($errors = $info->getValidationWarnings()) { + foreach ($errors as $error) { + if ($error['level'] == 'warning') { + $warn[] = $error['message']; + } else { + $err[] = $error['message']; + } + } + } + + $this->_displayValidationResults($err, $warn); + $this->ui->outputData($this->output, $command); + return true; + } + + function doSvnTag($command, $options, $params) + { + $this->output = ''; + $_cmd = $command; + if (count($params) < 1) { + $help = $this->getHelp($command); + return $this->raiseError("$command: missing parameter: $help[0]"); + } + + $packageFile = realpath($params[0]); + $dir = dirname($packageFile); + $dir = substr($dir, strrpos($dir, '/') + 1); + $obj = &$this->getPackageFile($this->config, $this->_debug); + $info = $obj->fromAnyFile($packageFile, PEAR_VALIDATE_NORMAL); + if (PEAR::isError($info)) { + return $this->raiseError($info); + } + + $err = $warn = array(); + if (!$info->validate()) { + foreach ($info->getValidationWarnings() as $error) { + if ($error['level'] == 'warning') { + $warn[] = $error['message']; + } else { + $err[] = $error['message']; + } + } + } + + if (!$this->_displayValidationResults($err, $warn, true)) { + $this->ui->outputData($this->output, $command); + return $this->raiseError('SVN tag failed'); + } + + $version = $info->getVersion(); + $package = $info->getName(); + $svntag = "$package-$version"; + + if (isset($options['delete'])) { + return $this->_svnRemoveTag($version, $package, $svntag, $packageFile, $options); + } + + $path = $this->_svnFindPath($packageFile); + + // Check if there are any modified files + $fp = popen('svn st --xml ' . dirname($packageFile), "r"); + $out = ''; + while ($line = fgets($fp, 1024)) { + $out .= rtrim($line)."\n"; + } + pclose($fp); + + if (!isset($options['quiet']) && strpos($out, 'item="modified"')) { + $params = array(array( + 'name' => 'modified', + 'type' => 'yesno', + 'default' => 'no', + 'prompt' => 'You have files in your SVN checkout (' . $path['from'] . ') that have been modified but not commited, do you still want to tag ' . $version . '?', + )); + $answers = $this->ui->confirmDialog($params); + + if (!in_array($answers['modified'], array('y', 'yes', 'on', '1'))) { + return true; + } + } + + if (isset($options['slide'])) { + $this->_svnRemoveTag($version, $package, $svntag, $packageFile, $options); + } + + // Check if tag already exists + $releaseTag = $path['local']['base'] . 'tags/' . $svntag; + $existsCommand = 'svn ls ' . $path['base'] . 'tags/'; + + $fp = popen($existsCommand, "r"); + $out = ''; + while ($line = fgets($fp, 1024)) { + $out .= rtrim($line)."\n"; + } + pclose($fp); + + if (in_array($svntag . '/', explode("\n", $out))) { + $this->ui->outputData($this->output, $command); + return $this->raiseError('SVN tag ' . $svntag . ' for ' . $package . ' already exists.'); + } elseif (file_exists($path['local']['base'] . 'tags') === false) { + return $this->raiseError('Can not locate the tags directory at ' . $path['local']['base'] . 'tags'); + } elseif (is_writeable($path['local']['base'] . 'tags') === false) { + return $this->raiseError('Can not write to the tag directory at ' . $path['local']['base'] . 'tags'); + } else { + $makeCommand = 'svn mkdir ' . $releaseTag; + $this->output .= "+ $makeCommand\n"; + if (empty($options['dry-run'])) { + // We need to create the tag dir. + $fp = popen($makeCommand, "r"); + $out = ''; + while ($line = fgets($fp, 1024)) { + $out .= rtrim($line)."\n"; + } + pclose($fp); + $this->output .= "$out\n"; + } + } + + $command = 'svn'; + if (isset($options['quiet'])) { + $command .= ' -q'; + } + + $command .= ' copy --parents '; + + $dir = dirname($packageFile); + $dir = substr($dir, strrpos($dir, '/') + 1); + $files = array_keys($info->getFilelist()); + + array_shift($params); + if (count($params)) { + // add in additional files to be tagged (package files and such) + $files = array_merge($files, $params); + } + + $commands = array(); + foreach ($files as $file) { + if (!file_exists($file)) { + $file = $dir . DIRECTORY_SEPARATOR . $file; + } + $commands[] = $command . ' ' . escapeshellarg($file) . ' ' . + escapeshellarg($releaseTag . DIRECTORY_SEPARATOR . $file); + } + + $this->output .= implode("\n", $commands) . "\n"; + if (empty($options['dry-run'])) { + foreach ($commands as $command) { + $fp = popen($command, "r"); + while ($line = fgets($fp, 1024)) { + $this->output .= rtrim($line)."\n"; + } + pclose($fp); + } + } + + $command = 'svn ci -m "Tagging the ' . $version . ' release" ' . $releaseTag . "\n"; + $this->output .= "+ $command\n"; + if (empty($options['dry-run'])) { + $fp = popen($command, "r"); + while ($line = fgets($fp, 1024)) { + $this->output .= rtrim($line)."\n"; + } + pclose($fp); + } + + $this->ui->outputData($this->output, $_cmd); + return true; + } + + function _svnFindPath($file) + { + $xml = ''; + $command = "svn info --xml $file"; + $fp = popen($command, "r"); + while ($line = fgets($fp, 1024)) { + $xml .= rtrim($line)."\n"; + } + pclose($fp); + $url_tag = strpos($xml, ''); + $url = substr($xml, $url_tag + 5, strpos($xml, '', $url_tag + 5) - ($url_tag + 5)); + + $path = array(); + $path['from'] = substr($url, 0, strrpos($url, '/')); + $path['base'] = substr($path['from'], 0, strrpos($path['from'], '/') + 1); + + // Figure out the local paths + $pos = strpos($file, '/trunk/'); + if ($pos === false) { + $pos = strpos($file, '/branches/'); + } + $path['local']['base'] = substr($file, 0, $pos + 1); + + return $path; + } + + function _svnRemoveTag($version, $package, $tag, $packageFile, $options) + { + $command = 'svn'; + + if (isset($options['quiet'])) { + $command .= ' -q'; + } + + $command .= ' remove'; + $command .= ' -m "Removing tag for the ' . $version . ' release."'; + + $path = $this->_svnFindPath($packageFile); + $command .= ' ' . $path['base'] . 'tags/' . $tag; + + + if ($this->config->get('verbose') > 1) { + $this->output .= "+ $command\n"; + } + + $this->output .= "+ $command\n"; + if (empty($options['dry-run'])) { + $fp = popen($command, "r"); + while ($line = fgets($fp, 1024)) { + $this->output .= rtrim($line)."\n"; + } + pclose($fp); + } + + $this->ui->outputData($this->output, $command); + return true; + } + + function doCvsTag($command, $options, $params) + { + $this->output = ''; + $_cmd = $command; + if (count($params) < 1) { + $help = $this->getHelp($command); + return $this->raiseError("$command: missing parameter: $help[0]"); + } + + $packageFile = realpath($params[0]); + $obj = &$this->getPackageFile($this->config, $this->_debug); + $info = $obj->fromAnyFile($packageFile, PEAR_VALIDATE_NORMAL); + if (PEAR::isError($info)) { + return $this->raiseError($info); + } + + $err = $warn = array(); + if (!$info->validate()) { + foreach ($info->getValidationWarnings() as $error) { + if ($error['level'] == 'warning') { + $warn[] = $error['message']; + } else { + $err[] = $error['message']; + } + } + } + + if (!$this->_displayValidationResults($err, $warn, true)) { + $this->ui->outputData($this->output, $command); + return $this->raiseError('CVS tag failed'); + } + + $version = $info->getVersion(); + $cvsversion = preg_replace('/[^a-z0-9]/i', '_', $version); + $cvstag = "RELEASE_$cvsversion"; + $files = array_keys($info->getFilelist()); + $command = 'cvs'; + if (isset($options['quiet'])) { + $command .= ' -q'; + } + + if (isset($options['reallyquiet'])) { + $command .= ' -Q'; + } + + $command .= ' tag'; + if (isset($options['slide'])) { + $command .= ' -F'; + } + + if (isset($options['delete'])) { + $command .= ' -d'; + } + + $command .= ' ' . $cvstag . ' ' . escapeshellarg($params[0]); + array_shift($params); + if (count($params)) { + // add in additional files to be tagged + $files = array_merge($files, $params); + } + + $dir = dirname($packageFile); + $dir = substr($dir, strrpos($dir, '/') + 1); + foreach ($files as $file) { + if (!file_exists($file)) { + $file = $dir . DIRECTORY_SEPARATOR . $file; + } + $command .= ' ' . escapeshellarg($file); + } + + if ($this->config->get('verbose') > 1) { + $this->output .= "+ $command\n"; + } + + $this->output .= "+ $command\n"; + if (empty($options['dry-run'])) { + $fp = popen($command, "r"); + while ($line = fgets($fp, 1024)) { + $this->output .= rtrim($line)."\n"; + } + pclose($fp); + } + + $this->ui->outputData($this->output, $_cmd); + return true; + } + + function doCvsDiff($command, $options, $params) + { + $this->output = ''; + if (sizeof($params) < 1) { + $help = $this->getHelp($command); + return $this->raiseError("$command: missing parameter: $help[0]"); + } + + $file = realpath($params[0]); + $obj = &$this->getPackageFile($this->config, $this->_debug); + $info = $obj->fromAnyFile($file, PEAR_VALIDATE_NORMAL); + if (PEAR::isError($info)) { + return $this->raiseError($info); + } + + $err = $warn = array(); + if (!$info->validate()) { + foreach ($info->getValidationWarnings() as $error) { + if ($error['level'] == 'warning') { + $warn[] = $error['message']; + } else { + $err[] = $error['message']; + } + } + } + + if (!$this->_displayValidationResults($err, $warn, true)) { + $this->ui->outputData($this->output, $command); + return $this->raiseError('CVS diff failed'); + } + + $info1 = $info->getFilelist(); + $files = $info1; + $cmd = "cvs"; + if (isset($options['quiet'])) { + $cmd .= ' -q'; + unset($options['quiet']); + } + + if (isset($options['reallyquiet'])) { + $cmd .= ' -Q'; + unset($options['reallyquiet']); + } + + if (isset($options['release'])) { + $cvsversion = preg_replace('/[^a-z0-9]/i', '_', $options['release']); + $cvstag = "RELEASE_$cvsversion"; + $options['revision'] = $cvstag; + unset($options['release']); + } + + $execute = true; + if (isset($options['dry-run'])) { + $execute = false; + unset($options['dry-run']); + } + + $cmd .= ' diff'; + // the rest of the options are passed right on to "cvs diff" + foreach ($options as $option => $optarg) { + $arg = $short = false; + if (isset($this->commands[$command]['options'][$option])) { + $arg = $this->commands[$command]['options'][$option]['arg']; + $short = $this->commands[$command]['options'][$option]['shortopt']; + } + $cmd .= $short ? " -$short" : " --$option"; + if ($arg && $optarg) { + $cmd .= ($short ? '' : '=') . escapeshellarg($optarg); + } + } + + foreach ($files as $file) { + $cmd .= ' ' . escapeshellarg($file['name']); + } + + if ($this->config->get('verbose') > 1) { + $this->output .= "+ $cmd\n"; + } + + if ($execute) { + $fp = popen($cmd, "r"); + while ($line = fgets($fp, 1024)) { + $this->output .= rtrim($line)."\n"; + } + pclose($fp); + } + + $this->ui->outputData($this->output, $command); + return true; + } + + function doPackageDependencies($command, $options, $params) + { + // $params[0] -> the PEAR package to list its information + if (count($params) !== 1) { + return $this->raiseError("bad parameter(s), try \"help $command\""); + } + + $obj = &$this->getPackageFile($this->config, $this->_debug); + if (is_file($params[0]) || strpos($params[0], '.xml') > 0) { + $info = $obj->fromAnyFile($params[0], PEAR_VALIDATE_NORMAL); + } else { + $reg = $this->config->getRegistry(); + $info = $obj->fromArray($reg->packageInfo($params[0])); + } + + if (PEAR::isError($info)) { + return $this->raiseError($info); + } + + $deps = $info->getDeps(); + if (is_array($deps)) { + if ($info->getPackagexmlVersion() == '1.0') { + $data = array( + 'caption' => 'Dependencies for pear/' . $info->getPackage(), + 'border' => true, + 'headline' => array("Required?", "Type", "Name", "Relation", "Version"), + ); + + foreach ($deps as $d) { + if (isset($d['optional'])) { + if ($d['optional'] == 'yes') { + $req = 'No'; + } else { + $req = 'Yes'; + } + } else { + $req = 'Yes'; + } + + if (isset($this->_deps_rel_trans[$d['rel']])) { + $rel = $this->_deps_rel_trans[$d['rel']]; + } else { + $rel = $d['rel']; + } + + if (isset($this->_deps_type_trans[$d['type']])) { + $type = ucfirst($this->_deps_type_trans[$d['type']]); + } else { + $type = $d['type']; + } + + if (isset($d['name'])) { + $name = $d['name']; + } else { + $name = ''; + } + + if (isset($d['version'])) { + $version = $d['version']; + } else { + $version = ''; + } + + $data['data'][] = array($req, $type, $name, $rel, $version); + } + } else { // package.xml 2.0 dependencies display + require_once 'PEAR/Dependency2.php'; + $deps = $info->getDependencies(); + $reg = &$this->config->getRegistry(); + if (is_array($deps)) { + $d = new PEAR_Dependency2($this->config, array(), ''); + $data = array( + 'caption' => 'Dependencies for ' . $info->getPackage(), + 'border' => true, + 'headline' => array("Required?", "Type", "Name", 'Versioning', 'Group'), + ); + foreach ($deps as $type => $subd) { + $req = ($type == 'required') ? 'Yes' : 'No'; + if ($type == 'group') { + $group = $subd['attribs']['name']; + } else { + $group = ''; + } + + if (!isset($subd[0])) { + $subd = array($subd); + } + + foreach ($subd as $groupa) { + foreach ($groupa as $deptype => $depinfo) { + if ($deptype == 'attribs') { + continue; + } + + if ($deptype == 'pearinstaller') { + $deptype = 'pear Installer'; + } + + if (!isset($depinfo[0])) { + $depinfo = array($depinfo); + } + + foreach ($depinfo as $inf) { + $name = ''; + if (isset($inf['channel'])) { + $alias = $reg->channelAlias($inf['channel']); + if (!$alias) { + $alias = '(channel?) ' .$inf['channel']; + } + $name = $alias . '/'; + + } + if (isset($inf['name'])) { + $name .= $inf['name']; + } elseif (isset($inf['pattern'])) { + $name .= $inf['pattern']; + } else { + $name .= ''; + } + + if (isset($inf['uri'])) { + $name .= ' [' . $inf['uri'] . ']'; + } + + if (isset($inf['conflicts'])) { + $ver = 'conflicts'; + } else { + $ver = $d->_getExtraString($inf); + } + + $data['data'][] = array($req, ucfirst($deptype), $name, + $ver, $group); + } + } + } + } + } + } + + $this->ui->outputData($data, $command); + return true; + } + + // Fallback + $this->ui->outputData("This package does not have any dependencies.", $command); + } + + function doSign($command, $options, $params) + { + // should move most of this code into PEAR_Packager + // so it'll be easy to implement "pear package --sign" + if (count($params) !== 1) { + return $this->raiseError("bad parameter(s), try \"help $command\""); + } + + require_once 'System.php'; + require_once 'Archive/Tar.php'; + + if (!file_exists($params[0])) { + return $this->raiseError("file does not exist: $params[0]"); + } + + $obj = $this->getPackageFile($this->config, $this->_debug); + $info = $obj->fromTgzFile($params[0], PEAR_VALIDATE_NORMAL); + if (PEAR::isError($info)) { + return $this->raiseError($info); + } + + $tar = new Archive_Tar($params[0]); + $tmpdir = System::mktemp('-d pearsign'); + if (!$tar->extractList('package2.xml package.xml package.sig', $tmpdir)) { + return $this->raiseError("failed to extract tar file"); + } + + if (file_exists("$tmpdir/package.sig")) { + return $this->raiseError("package already signed"); + } + + $packagexml = 'package.xml'; + if (file_exists("$tmpdir/package2.xml")) { + $packagexml = 'package2.xml'; + } + + if (file_exists("$tmpdir/package.sig")) { + unlink("$tmpdir/package.sig"); + } + + if (!file_exists("$tmpdir/$packagexml")) { + return $this->raiseError("Extracted file $tmpdir/$packagexml not found."); + } + + $input = $this->ui->userDialog($command, + array('GnuPG Passphrase'), + array('password')); + if (!isset($input[0])) { + //use empty passphrase + $input[0] = ''; + } + + $devnull = (isset($options['verbose'])) ? '' : ' 2>/dev/null'; + $gpg = popen("gpg --batch --passphrase-fd 0 --armor --detach-sign --output $tmpdir/package.sig $tmpdir/$packagexml" . $devnull, "w"); + if (!$gpg) { + return $this->raiseError("gpg command failed"); + } + + fwrite($gpg, "$input[0]\n"); + if (pclose($gpg) || !file_exists("$tmpdir/package.sig")) { + return $this->raiseError("gpg sign failed"); + } + + if (!$tar->addModify("$tmpdir/package.sig", '', $tmpdir)) { + return $this->raiseError('failed adding signature to file'); + } + + $this->ui->outputData("Package signed.", $command); + return true; + } + + /** + * For unit testing purposes + */ + function &getInstaller(&$ui) + { + if (!class_exists('PEAR_Installer')) { + require_once 'PEAR/Installer.php'; + } + $a = &new PEAR_Installer($ui); + return $a; + } + + /** + * For unit testing purposes + */ + function &getCommandPackaging(&$ui, &$config) + { + if (!class_exists('PEAR_Command_Packaging')) { + if ($fp = @fopen('PEAR/Command/Packaging.php', 'r', true)) { + fclose($fp); + include_once 'PEAR/Command/Packaging.php'; + } + } + + if (class_exists('PEAR_Command_Packaging')) { + $a = &new PEAR_Command_Packaging($ui, $config); + } else { + $a = null; + } + + return $a; + } + + function doMakeRPM($command, $options, $params) + { + + // Check to see if PEAR_Command_Packaging is installed, and + // transparently switch to use the "make-rpm-spec" command from it + // instead, if it does. Otherwise, continue to use the old version + // of "makerpm" supplied with this package (PEAR). + $packaging_cmd = $this->getCommandPackaging($this->ui, $this->config); + if ($packaging_cmd !== null) { + $this->ui->outputData('PEAR_Command_Packaging is installed; using '. + 'newer "make-rpm-spec" command instead'); + return $packaging_cmd->run('make-rpm-spec', $options, $params); + } + + $this->ui->outputData('WARNING: "pear makerpm" is no longer available; an '. + 'improved version is available via "pear make-rpm-spec", which '. + 'is available by installing PEAR_Command_Packaging'); + return true; + } + + function doConvert($command, $options, $params) + { + $packagexml = isset($params[0]) ? $params[0] : 'package.xml'; + $newpackagexml = isset($params[1]) ? $params[1] : dirname($packagexml) . + DIRECTORY_SEPARATOR . 'package2.xml'; + $pkg = &$this->getPackageFile($this->config, $this->_debug); + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $pf = $pkg->fromPackageFile($packagexml, PEAR_VALIDATE_NORMAL); + PEAR::staticPopErrorHandling(); + if (PEAR::isError($pf)) { + if (is_array($pf->getUserInfo())) { + foreach ($pf->getUserInfo() as $warning) { + $this->ui->outputData($warning['message']); + } + } + return $this->raiseError($pf); + } + + if (is_a($pf, 'PEAR_PackageFile_v2')) { + $this->ui->outputData($packagexml . ' is already a package.xml version 2.0'); + return true; + } + + $gen = &$pf->getDefaultGenerator(); + $newpf = &$gen->toV2(); + $newpf->setPackagefile($newpackagexml); + $gen = &$newpf->getDefaultGenerator(); + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $state = (isset($options['flat']) ? PEAR_VALIDATE_PACKAGING : PEAR_VALIDATE_NORMAL); + $saved = $gen->toPackageFile(dirname($newpackagexml), $state, basename($newpackagexml)); + PEAR::staticPopErrorHandling(); + if (PEAR::isError($saved)) { + if (is_array($saved->getUserInfo())) { + foreach ($saved->getUserInfo() as $warning) { + $this->ui->outputData($warning['message']); + } + } + + $this->ui->outputData($saved->getMessage()); + return true; + } + + $this->ui->outputData('Wrote new version 2.0 package.xml to "' . $saved . '"'); + return true; + } +} \ No newline at end of file diff --git a/library/pear/PEAR/Command/Package.xml b/library/pear/PEAR/Command/Package.xml new file mode 100644 index 000000000..9f093ef62 --- /dev/null +++ b/library/pear/PEAR/Command/Package.xml @@ -0,0 +1,237 @@ + + + Build Package + doPackage + p + + + Z + Do not gzip the package file + + + n + Print the name of the packaged file. + + + [descfile] [descfile2] +Creates a PEAR package from its description file (usually called +package.xml). If a second packagefile is passed in, then +the packager will check to make sure that one is a package.xml +version 1.0, and the other is a package.xml version 2.0. The +package.xml version 1.0 will be saved as "package.xml" in the archive, +and the other as "package2.xml" in the archive" + + + + Validate Package Consistency + doPackageValidate + pv + + + + + + Run a "cvs diff" for all files in a package + doCvsDiff + cd + + + q + Be quiet + + + Q + Be really quiet + + + D + Diff against revision of DATE + DATE + + + R + Diff against tag for package release REL + REL + + + r + Diff against revision REV + REV + + + c + Generate context diff + + + u + Generate unified diff + + + i + Ignore case, consider upper- and lower-case letters equivalent + + + b + Ignore changes in amount of white space + + + B + Ignore changes that insert or delete blank lines + + + + Report only whether the files differ, no details + + + n + Don't do anything, just pretend + + + <package.xml> +Compares all the files in a package. Without any options, this +command will compare the current code with the last checked-in code. +Using the -r or -R option you may compare the current code with that +of a specific release. + + + + Set SVN Release Tag + doSvnTag + sv + + + q + Be quiet + + + F + Move (slide) tag if it exists + + + d + Remove tag + + + n + Don't do anything, just pretend + + + <package.xml> [files...] + Sets a SVN tag on all files in a package. Use this command after you have + packaged a distribution tarball with the "package" command to tag what + revisions of what files were in that release. If need to fix something + after running cvstag once, but before the tarball is released to the public, + use the "slide" option to move the release tag. + + to include files (such as a second package.xml, or tests not included in the + release), pass them as additional parameters. + + + + Set CVS Release Tag + doCvsTag + ct + + + q + Be quiet + + + Q + Be really quiet + + + F + Move (slide) tag if it exists + + + d + Remove tag + + + n + Don't do anything, just pretend + + + <package.xml> [files...] +Sets a CVS tag on all files in a package. Use this command after you have +packaged a distribution tarball with the "package" command to tag what +revisions of what files were in that release. If need to fix something +after running cvstag once, but before the tarball is released to the public, +use the "slide" option to move the release tag. + +to include files (such as a second package.xml, or tests not included in the +release), pass them as additional parameters. + + + + Show package dependencies + doPackageDependencies + pd + + <package-file> or <package.xml> or <install-package-name> +List all dependencies the package has. +Can take a tgz / tar file, package.xml or a package name of an installed package. + + + Sign a package distribution file + doSign + si + + + v + Display GnuPG output + + + <package-file> +Signs a package distribution (.tar or .tgz) file with GnuPG. + + + Builds an RPM spec file from a PEAR package + doMakeRPM + rpm + + + t + Use FILE as RPM spec file template + FILE + + + p + Use FORMAT as format string for RPM package name, %s is replaced +by the PEAR package name, defaults to "PEAR::%s". + FORMAT + + + <package-file> + +Creates an RPM .spec file for wrapping a PEAR package inside an RPM +package. Intended to be used from the SPECS directory, with the PEAR +package tarball in the SOURCES directory: + +$ pear makerpm ../SOURCES/Net_Socket-1.0.tgz +Wrote RPM spec file PEAR::Net_Geo-1.0.spec +$ rpm -bb PEAR::Net_Socket-1.0.spec +... +Wrote: /usr/src/redhat/RPMS/i386/PEAR::Net_Socket-1.0-1.i386.rpm + + + + Convert a package.xml 1.0 to package.xml 2.0 format + doConvert + c2 + + + f + do not beautify the filelist. + + + [descfile] [descfile2] +Converts a package.xml in 1.0 format into a package.xml +in 2.0 format. The new file will be named package2.xml by default, +and package.xml will be used as the old file by default. +This is not the most intelligent conversion, and should only be +used for automated conversion or learning the format. + + + \ No newline at end of file diff --git a/library/pear/PEAR/Command/Pickle.php b/library/pear/PEAR/Command/Pickle.php new file mode 100644 index 000000000..2252a9d8a --- /dev/null +++ b/library/pear/PEAR/Command/Pickle.php @@ -0,0 +1,421 @@ + + * @copyright 2005-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Pickle.php 276383 2009-02-24 23:39:37Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.1 + */ + +/** + * base class + */ +require_once 'PEAR/Command/Common.php'; + +/** + * PEAR commands for login/logout + * + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 2005-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.1 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.1 + */ + +class PEAR_Command_Pickle extends PEAR_Command_Common +{ + var $commands = array( + 'pickle' => array( + 'summary' => 'Build PECL Package', + 'function' => 'doPackage', + 'shortcut' => 'pi', + 'options' => array( + 'nocompress' => array( + 'shortopt' => 'Z', + 'doc' => 'Do not gzip the package file' + ), + 'showname' => array( + 'shortopt' => 'n', + 'doc' => 'Print the name of the packaged file.', + ), + ), + 'doc' => '[descfile] +Creates a PECL package from its package2.xml file. + +An automatic conversion will be made to a package.xml 1.0 and written out to +disk in the current directory as "package.xml". Note that +only simple package.xml 2.0 will be converted. package.xml 2.0 with: + + - dependency types other than required/optional PECL package/ext/php/pearinstaller + - more than one extsrcrelease or zendextsrcrelease + - zendextbinrelease, extbinrelease, phprelease, or bundle release type + - dependency groups + - ignore tags in release filelist + - tasks other than replace + - custom roles + +will cause pickle to fail, and output an error message. If your package2.xml +uses any of these features, you are best off using PEAR_PackageFileManager to +generate both package.xml. +' + ), + ); + + /** + * PEAR_Command_Package constructor. + * + * @access public + */ + function PEAR_Command_Pickle(&$ui, &$config) + { + parent::PEAR_Command_Common($ui, $config); + } + + /** + * For unit-testing ease + * + * @return PEAR_Packager + */ + function &getPackager() + { + if (!class_exists('PEAR_Packager')) { + require_once 'PEAR/Packager.php'; + } + + $a = &new PEAR_Packager; + return $a; + } + + /** + * For unit-testing ease + * + * @param PEAR_Config $config + * @param bool $debug + * @param string|null $tmpdir + * @return PEAR_PackageFile + */ + function &getPackageFile($config, $debug = false, $tmpdir = null) + { + if (!class_exists('PEAR_Common')) { + require_once 'PEAR/Common.php'; + } + + if (!class_exists('PEAR_PackageFile')) { + require_once 'PEAR/PackageFile.php'; + } + + $a = &new PEAR_PackageFile($config, $debug, $tmpdir); + $common = new PEAR_Common; + $common->ui = $this->ui; + $a->setLogger($common); + return $a; + } + + function doPackage($command, $options, $params) + { + $this->output = ''; + $pkginfofile = isset($params[0]) ? $params[0] : 'package2.xml'; + $packager = &$this->getPackager(); + if (PEAR::isError($err = $this->_convertPackage($pkginfofile))) { + return $err; + } + + $compress = empty($options['nocompress']) ? true : false; + $result = $packager->package($pkginfofile, $compress, 'package.xml'); + if (PEAR::isError($result)) { + return $this->raiseError($result); + } + + // Don't want output, only the package file name just created + if (isset($options['showname'])) { + $this->ui->outputData($result, $command); + } + + return true; + } + + function _convertPackage($packagexml) + { + $pkg = &$this->getPackageFile($this->config); + $pf2 = &$pkg->fromPackageFile($packagexml, PEAR_VALIDATE_NORMAL); + if (!is_a($pf2, 'PEAR_PackageFile_v2')) { + return $this->raiseError('Cannot process "' . + $packagexml . '", is not a package.xml 2.0'); + } + + require_once 'PEAR/PackageFile/v1.php'; + $pf = new PEAR_PackageFile_v1; + $pf->setConfig($this->config); + if ($pf2->getPackageType() != 'extsrc' && $pf2->getPackageType() != 'zendextsrc') { + return $this->raiseError('Cannot safely convert "' . $packagexml . + '", is not an extension source package. Using a PEAR_PackageFileManager-based ' . + 'script is an option'); + } + + if (is_array($pf2->getUsesRole())) { + return $this->raiseError('Cannot safely convert "' . $packagexml . + '", contains custom roles. Using a PEAR_PackageFileManager-based script or ' . + 'the convert command is an option'); + } + + if (is_array($pf2->getUsesTask())) { + return $this->raiseError('Cannot safely convert "' . $packagexml . + '", contains custom tasks. Using a PEAR_PackageFileManager-based script or ' . + 'the convert command is an option'); + } + + $deps = $pf2->getDependencies(); + if (isset($deps['group'])) { + return $this->raiseError('Cannot safely convert "' . $packagexml . + '", contains dependency groups. Using a PEAR_PackageFileManager-based script ' . + 'or the convert command is an option'); + } + + if (isset($deps['required']['subpackage']) || + isset($deps['optional']['subpackage'])) { + return $this->raiseError('Cannot safely convert "' . $packagexml . + '", contains subpackage dependencies. Using a PEAR_PackageFileManager-based '. + 'script is an option'); + } + + if (isset($deps['required']['os'])) { + return $this->raiseError('Cannot safely convert "' . $packagexml . + '", contains os dependencies. Using a PEAR_PackageFileManager-based '. + 'script is an option'); + } + + if (isset($deps['required']['arch'])) { + return $this->raiseError('Cannot safely convert "' . $packagexml . + '", contains arch dependencies. Using a PEAR_PackageFileManager-based '. + 'script is an option'); + } + + $pf->setPackage($pf2->getPackage()); + $pf->setSummary($pf2->getSummary()); + $pf->setDescription($pf2->getDescription()); + foreach ($pf2->getMaintainers() as $maintainer) { + $pf->addMaintainer($maintainer['role'], $maintainer['handle'], + $maintainer['name'], $maintainer['email']); + } + + $pf->setVersion($pf2->getVersion()); + $pf->setDate($pf2->getDate()); + $pf->setLicense($pf2->getLicense()); + $pf->setState($pf2->getState()); + $pf->setNotes($pf2->getNotes()); + $pf->addPhpDep($deps['required']['php']['min'], 'ge'); + if (isset($deps['required']['php']['max'])) { + $pf->addPhpDep($deps['required']['php']['max'], 'le'); + } + + if (isset($deps['required']['package'])) { + if (!isset($deps['required']['package'][0])) { + $deps['required']['package'] = array($deps['required']['package']); + } + + foreach ($deps['required']['package'] as $dep) { + if (!isset($dep['channel'])) { + return $this->raiseError('Cannot safely convert "' . $packagexml . '"' . + ' contains uri-based dependency on a package. Using a ' . + 'PEAR_PackageFileManager-based script is an option'); + } + + if ($dep['channel'] != 'pear.php.net' + && $dep['channel'] != 'pecl.php.net' + && $dep['channel'] != 'doc.php.net') { + return $this->raiseError('Cannot safely convert "' . $packagexml . '"' . + ' contains dependency on a non-standard channel package. Using a ' . + 'PEAR_PackageFileManager-based script is an option'); + } + + if (isset($dep['conflicts'])) { + return $this->raiseError('Cannot safely convert "' . $packagexml . '"' . + ' contains conflicts dependency. Using a ' . + 'PEAR_PackageFileManager-based script is an option'); + } + + if (isset($dep['exclude'])) { + $this->ui->outputData('WARNING: exclude tags are ignored in conversion'); + } + + if (isset($dep['min'])) { + $pf->addPackageDep($dep['name'], $dep['min'], 'ge'); + } + + if (isset($dep['max'])) { + $pf->addPackageDep($dep['name'], $dep['max'], 'le'); + } + } + } + + if (isset($deps['required']['extension'])) { + if (!isset($deps['required']['extension'][0])) { + $deps['required']['extension'] = array($deps['required']['extension']); + } + + foreach ($deps['required']['extension'] as $dep) { + if (isset($dep['conflicts'])) { + return $this->raiseError('Cannot safely convert "' . $packagexml . '"' . + ' contains conflicts dependency. Using a ' . + 'PEAR_PackageFileManager-based script is an option'); + } + + if (isset($dep['exclude'])) { + $this->ui->outputData('WARNING: exclude tags are ignored in conversion'); + } + + if (isset($dep['min'])) { + $pf->addExtensionDep($dep['name'], $dep['min'], 'ge'); + } + + if (isset($dep['max'])) { + $pf->addExtensionDep($dep['name'], $dep['max'], 'le'); + } + } + } + + if (isset($deps['optional']['package'])) { + if (!isset($deps['optional']['package'][0])) { + $deps['optional']['package'] = array($deps['optional']['package']); + } + + foreach ($deps['optional']['package'] as $dep) { + if (!isset($dep['channel'])) { + return $this->raiseError('Cannot safely convert "' . $packagexml . '"' . + ' contains uri-based dependency on a package. Using a ' . + 'PEAR_PackageFileManager-based script is an option'); + } + + if ($dep['channel'] != 'pear.php.net' + && $dep['channel'] != 'pecl.php.net' + && $dep['channel'] != 'doc.php.net') { + return $this->raiseError('Cannot safely convert "' . $packagexml . '"' . + ' contains dependency on a non-standard channel package. Using a ' . + 'PEAR_PackageFileManager-based script is an option'); + } + + if (isset($dep['exclude'])) { + $this->ui->outputData('WARNING: exclude tags are ignored in conversion'); + } + + if (isset($dep['min'])) { + $pf->addPackageDep($dep['name'], $dep['min'], 'ge', 'yes'); + } + + if (isset($dep['max'])) { + $pf->addPackageDep($dep['name'], $dep['max'], 'le', 'yes'); + } + } + } + + if (isset($deps['optional']['extension'])) { + if (!isset($deps['optional']['extension'][0])) { + $deps['optional']['extension'] = array($deps['optional']['extension']); + } + + foreach ($deps['optional']['extension'] as $dep) { + if (isset($dep['exclude'])) { + $this->ui->outputData('WARNING: exclude tags are ignored in conversion'); + } + + if (isset($dep['min'])) { + $pf->addExtensionDep($dep['name'], $dep['min'], 'ge', 'yes'); + } + + if (isset($dep['max'])) { + $pf->addExtensionDep($dep['name'], $dep['max'], 'le', 'yes'); + } + } + } + + $contents = $pf2->getContents(); + $release = $pf2->getReleases(); + if (isset($releases[0])) { + return $this->raiseError('Cannot safely process "' . $packagexml . '" contains ' + . 'multiple extsrcrelease/zendextsrcrelease tags. Using a PEAR_PackageFileManager-based script ' . + 'or the convert command is an option'); + } + + if ($configoptions = $pf2->getConfigureOptions()) { + foreach ($configoptions as $option) { + $default = isset($option['default']) ? $option['default'] : false; + $pf->addConfigureOption($option['name'], $option['prompt'], $default); + } + } + + if (isset($release['filelist']['ignore'])) { + return $this->raiseError('Cannot safely process "' . $packagexml . '" contains ' + . 'ignore tags. Using a PEAR_PackageFileManager-based script or the convert' . + ' command is an option'); + } + + if (isset($release['filelist']['install']) && + !isset($release['filelist']['install'][0])) { + $release['filelist']['install'] = array($release['filelist']['install']); + } + + if (isset($contents['dir']['attribs']['baseinstalldir'])) { + $baseinstalldir = $contents['dir']['attribs']['baseinstalldir']; + } else { + $baseinstalldir = false; + } + + if (!isset($contents['dir']['file'][0])) { + $contents['dir']['file'] = array($contents['dir']['file']); + } + + foreach ($contents['dir']['file'] as $file) { + if ($baseinstalldir && !isset($file['attribs']['baseinstalldir'])) { + $file['attribs']['baseinstalldir'] = $baseinstalldir; + } + + $processFile = $file; + unset($processFile['attribs']); + if (count($processFile)) { + foreach ($processFile as $name => $task) { + if ($name != $pf2->getTasksNs() . ':replace') { + return $this->raiseError('Cannot safely process "' . $packagexml . + '" contains tasks other than replace. Using a ' . + 'PEAR_PackageFileManager-based script is an option.'); + } + $file['attribs']['replace'][] = $task; + } + } + + if (!in_array($file['attribs']['role'], PEAR_Common::getFileRoles())) { + return $this->raiseError('Cannot safely convert "' . $packagexml . + '", contains custom roles. Using a PEAR_PackageFileManager-based script ' . + 'or the convert command is an option'); + } + + if (isset($release['filelist']['install'])) { + foreach ($release['filelist']['install'] as $installas) { + if ($installas['attribs']['name'] == $file['attribs']['name']) { + $file['attribs']['install-as'] = $installas['attribs']['as']; + } + } + } + + $pf->addFile('/', $file['attribs']['name'], $file['attribs']); + } + + if ($pf2->getChangeLog()) { + $this->ui->outputData('WARNING: changelog is not translated to package.xml ' . + '1.0, use PEAR_PackageFileManager-based script if you need changelog-' . + 'translation for package.xml 1.0'); + } + + $gen = &$pf->getDefaultGenerator(); + $gen->toPackageFile('.'); + } +} \ No newline at end of file diff --git a/library/pear/PEAR/Command/Pickle.xml b/library/pear/PEAR/Command/Pickle.xml new file mode 100644 index 000000000..721ecea99 --- /dev/null +++ b/library/pear/PEAR/Command/Pickle.xml @@ -0,0 +1,36 @@ + + + Build PECL Package + doPackage + pi + + + Z + Do not gzip the package file + + + n + Print the name of the packaged file. + + + [descfile] +Creates a PECL package from its package2.xml file. + +An automatic conversion will be made to a package.xml 1.0 and written out to +disk in the current directory as "package.xml". Note that +only simple package.xml 2.0 will be converted. package.xml 2.0 with: + + - dependency types other than required/optional PECL package/ext/php/pearinstaller + - more than one extsrcrelease or zendextsrcrelease + - zendextbinrelease, extbinrelease, phprelease, or bundle release type + - dependency groups + - ignore tags in release filelist + - tasks other than replace + - custom roles + +will cause pickle to fail, and output an error message. If your package2.xml +uses any of these features, you are best off using PEAR_PackageFileManager to +generate both package.xml. + + + \ No newline at end of file diff --git a/library/pear/PEAR/Command/Registry.php b/library/pear/PEAR/Command/Registry.php new file mode 100644 index 000000000..c56319f2d --- /dev/null +++ b/library/pear/PEAR/Command/Registry.php @@ -0,0 +1,1145 @@ + + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Registry.php 299146 2010-05-08 16:26:13Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 0.1 + */ + +/** + * base class + */ +require_once 'PEAR/Command/Common.php'; + +/** + * PEAR commands for registry manipulation + * + * @category pear + * @package PEAR + * @author Stig Bakken + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.1 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 0.1 + */ +class PEAR_Command_Registry extends PEAR_Command_Common +{ + var $commands = array( + 'list' => array( + 'summary' => 'List Installed Packages In The Default Channel', + 'function' => 'doList', + 'shortcut' => 'l', + 'options' => array( + 'channel' => array( + 'shortopt' => 'c', + 'doc' => 'list installed packages from this channel', + 'arg' => 'CHAN', + ), + 'allchannels' => array( + 'shortopt' => 'a', + 'doc' => 'list installed packages from all channels', + ), + 'channelinfo' => array( + 'shortopt' => 'i', + 'doc' => 'output fully channel-aware data, even on failure', + ), + ), + 'doc' => ' +If invoked without parameters, this command lists the PEAR packages +installed in your php_dir ({config php_dir}). With a parameter, it +lists the files in a package. +', + ), + 'list-files' => array( + 'summary' => 'List Files In Installed Package', + 'function' => 'doFileList', + 'shortcut' => 'fl', + 'options' => array(), + 'doc' => ' +List the files in an installed package. +' + ), + 'shell-test' => array( + 'summary' => 'Shell Script Test', + 'function' => 'doShellTest', + 'shortcut' => 'st', + 'options' => array(), + 'doc' => ' [[relation] version] +Tests if a package is installed in the system. Will exit(1) if it is not. + The version comparison operator. One of: + <, lt, <=, le, >, gt, >=, ge, ==, =, eq, !=, <>, ne + The version to compare with +'), + 'info' => array( + 'summary' => 'Display information about a package', + 'function' => 'doInfo', + 'shortcut' => 'in', + 'options' => array(), + 'doc' => ' +Displays information about a package. The package argument may be a +local package file, an URL to a package file, or the name of an +installed package.' + ) + ); + + /** + * PEAR_Command_Registry constructor. + * + * @access public + */ + function PEAR_Command_Registry(&$ui, &$config) + { + parent::PEAR_Command_Common($ui, $config); + } + + function _sortinfo($a, $b) + { + $apackage = isset($a['package']) ? $a['package'] : $a['name']; + $bpackage = isset($b['package']) ? $b['package'] : $b['name']; + return strcmp($apackage, $bpackage); + } + + function doList($command, $options, $params) + { + $reg = &$this->config->getRegistry(); + $channelinfo = isset($options['channelinfo']); + if (isset($options['allchannels']) && !$channelinfo) { + return $this->doListAll($command, array(), $params); + } + + if (isset($options['allchannels']) && $channelinfo) { + // allchannels with $channelinfo + unset($options['allchannels']); + $channels = $reg->getChannels(); + $errors = array(); + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + foreach ($channels as $channel) { + $options['channel'] = $channel->getName(); + $ret = $this->doList($command, $options, $params); + + if (PEAR::isError($ret)) { + $errors[] = $ret; + } + } + + PEAR::staticPopErrorHandling(); + if (count($errors)) { + // for now, only give first error + return PEAR::raiseError($errors[0]); + } + + return true; + } + + if (count($params) === 1) { + return $this->doFileList($command, $options, $params); + } + + if (isset($options['channel'])) { + if (!$reg->channelExists($options['channel'])) { + return $this->raiseError('Channel "' . $options['channel'] .'" does not exist'); + } + + $channel = $reg->channelName($options['channel']); + } else { + $channel = $this->config->get('default_channel'); + } + + $installed = $reg->packageInfo(null, null, $channel); + usort($installed, array(&$this, '_sortinfo')); + + $data = array( + 'caption' => 'Installed packages, channel ' . + $channel . ':', + 'border' => true, + 'headline' => array('Package', 'Version', 'State'), + 'channel' => $channel, + ); + if ($channelinfo) { + $data['headline'] = array('Channel', 'Package', 'Version', 'State'); + } + + if (count($installed) && !isset($data['data'])) { + $data['data'] = array(); + } + + foreach ($installed as $package) { + $pobj = $reg->getPackage(isset($package['package']) ? + $package['package'] : $package['name'], $channel); + if ($channelinfo) { + $packageinfo = array($pobj->getChannel(), $pobj->getPackage(), $pobj->getVersion(), + $pobj->getState() ? $pobj->getState() : null); + } else { + $packageinfo = array($pobj->getPackage(), $pobj->getVersion(), + $pobj->getState() ? $pobj->getState() : null); + } + $data['data'][] = $packageinfo; + } + + if (count($installed) === 0) { + if (!$channelinfo) { + $data = '(no packages installed from channel ' . $channel . ')'; + } else { + $data = array( + 'caption' => 'Installed packages, channel ' . + $channel . ':', + 'border' => true, + 'channel' => $channel, + 'data' => array(array('(no packages installed)')), + ); + } + } + + $this->ui->outputData($data, $command); + return true; + } + + function doListAll($command, $options, $params) + { + // This duplicate code is deprecated over + // list --channelinfo, which gives identical + // output for list and list --allchannels. + $reg = &$this->config->getRegistry(); + $installed = $reg->packageInfo(null, null, null); + foreach ($installed as $channel => $packages) { + usort($packages, array($this, '_sortinfo')); + $data = array( + 'caption' => 'Installed packages, channel ' . $channel . ':', + 'border' => true, + 'headline' => array('Package', 'Version', 'State'), + 'channel' => $channel + ); + + foreach ($packages as $package) { + $p = isset($package['package']) ? $package['package'] : $package['name']; + $pobj = $reg->getPackage($p, $channel); + $data['data'][] = array($pobj->getPackage(), $pobj->getVersion(), + $pobj->getState() ? $pobj->getState() : null); + } + + // Adds a blank line after each section + $data['data'][] = array(); + + if (count($packages) === 0) { + $data = array( + 'caption' => 'Installed packages, channel ' . $channel . ':', + 'border' => true, + 'data' => array(array('(no packages installed)'), array()), + 'channel' => $channel + ); + } + $this->ui->outputData($data, $command); + } + return true; + } + + function doFileList($command, $options, $params) + { + if (count($params) !== 1) { + return $this->raiseError('list-files expects 1 parameter'); + } + + $reg = &$this->config->getRegistry(); + $fp = false; + if (!is_dir($params[0]) && (file_exists($params[0]) || $fp = @fopen($params[0], 'r'))) { + if ($fp) { + fclose($fp); + } + + if (!class_exists('PEAR_PackageFile')) { + require_once 'PEAR/PackageFile.php'; + } + + $pkg = &new PEAR_PackageFile($this->config, $this->_debug); + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $info = &$pkg->fromAnyFile($params[0], PEAR_VALIDATE_NORMAL); + PEAR::staticPopErrorHandling(); + $headings = array('Package File', 'Install Path'); + $installed = false; + } else { + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $parsed = $reg->parsePackageName($params[0], $this->config->get('default_channel')); + PEAR::staticPopErrorHandling(); + if (PEAR::isError($parsed)) { + return $this->raiseError($parsed); + } + + $info = &$reg->getPackage($parsed['package'], $parsed['channel']); + $headings = array('Type', 'Install Path'); + $installed = true; + } + + if (PEAR::isError($info)) { + return $this->raiseError($info); + } + + if ($info === null) { + return $this->raiseError("`$params[0]' not installed"); + } + + $list = ($info->getPackagexmlVersion() == '1.0' || $installed) ? + $info->getFilelist() : $info->getContents(); + if ($installed) { + $caption = 'Installed Files For ' . $params[0]; + } else { + $caption = 'Contents of ' . basename($params[0]); + } + + $data = array( + 'caption' => $caption, + 'border' => true, + 'headline' => $headings); + if ($info->getPackagexmlVersion() == '1.0' || $installed) { + foreach ($list as $file => $att) { + if ($installed) { + if (empty($att['installed_as'])) { + continue; + } + $data['data'][] = array($att['role'], $att['installed_as']); + } else { + if (isset($att['baseinstalldir']) && !in_array($att['role'], + array('test', 'data', 'doc'))) { + $dest = $att['baseinstalldir'] . DIRECTORY_SEPARATOR . + $file; + } else { + $dest = $file; + } + switch ($att['role']) { + case 'test': + case 'data': + case 'doc': + $role = $att['role']; + if ($role == 'test') { + $role .= 's'; + } + $dest = $this->config->get($role . '_dir') . DIRECTORY_SEPARATOR . + $info->getPackage() . DIRECTORY_SEPARATOR . $dest; + break; + case 'php': + default: + $dest = $this->config->get('php_dir') . DIRECTORY_SEPARATOR . + $dest; + } + $ds2 = DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR; + $dest = preg_replace(array('!\\\\+!', '!/!', "!$ds2+!"), + array(DIRECTORY_SEPARATOR, + DIRECTORY_SEPARATOR, + DIRECTORY_SEPARATOR), + $dest); + $file = preg_replace('!/+!', '/', $file); + $data['data'][] = array($file, $dest); + } + } + } else { // package.xml 2.0, not installed + if (!isset($list['dir']['file'][0])) { + $list['dir']['file'] = array($list['dir']['file']); + } + + foreach ($list['dir']['file'] as $att) { + $att = $att['attribs']; + $file = $att['name']; + $role = &PEAR_Installer_Role::factory($info, $att['role'], $this->config); + $role->setup($this, $info, $att, $file); + if (!$role->isInstallable()) { + $dest = '(not installable)'; + } else { + $dest = $role->processInstallation($info, $att, $file, ''); + if (PEAR::isError($dest)) { + $dest = '(Unknown role "' . $att['role'] . ')'; + } else { + list(,, $dest) = $dest; + } + } + $data['data'][] = array($file, $dest); + } + } + + $this->ui->outputData($data, $command); + return true; + } + + function doShellTest($command, $options, $params) + { + if (count($params) < 1) { + return PEAR::raiseError('ERROR, usage: pear shell-test packagename [[relation] version]'); + } + + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $reg = &$this->config->getRegistry(); + $info = $reg->parsePackageName($params[0], $this->config->get('default_channel')); + if (PEAR::isError($info)) { + exit(1); // invalid package name + } + + $package = $info['package']; + $channel = $info['channel']; + // "pear shell-test Foo" + if (!$reg->packageExists($package, $channel)) { + if ($channel == 'pecl.php.net') { + if ($reg->packageExists($package, 'pear.php.net')) { + $channel = 'pear.php.net'; // magically change channels for extensions + } + } + } + + if (count($params) === 1) { + if (!$reg->packageExists($package, $channel)) { + exit(1); + } + // "pear shell-test Foo 1.0" + } elseif (count($params) === 2) { + $v = $reg->packageInfo($package, 'version', $channel); + if (!$v || !version_compare("$v", "{$params[1]}", "ge")) { + exit(1); + } + // "pear shell-test Foo ge 1.0" + } elseif (count($params) === 3) { + $v = $reg->packageInfo($package, 'version', $channel); + if (!$v || !version_compare("$v", "{$params[2]}", $params[1])) { + exit(1); + } + } else { + PEAR::staticPopErrorHandling(); + $this->raiseError("$command: expects 1 to 3 parameters"); + exit(1); + } + } + + function doInfo($command, $options, $params) + { + if (count($params) !== 1) { + return $this->raiseError('pear info expects 1 parameter'); + } + + $info = $fp = false; + $reg = &$this->config->getRegistry(); + if (is_file($params[0]) && !is_dir($params[0]) && + (file_exists($params[0]) || $fp = @fopen($params[0], 'r')) + ) { + if ($fp) { + fclose($fp); + } + + if (!class_exists('PEAR_PackageFile')) { + require_once 'PEAR/PackageFile.php'; + } + + $pkg = &new PEAR_PackageFile($this->config, $this->_debug); + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $obj = &$pkg->fromAnyFile($params[0], PEAR_VALIDATE_NORMAL); + PEAR::staticPopErrorHandling(); + if (PEAR::isError($obj)) { + $uinfo = $obj->getUserInfo(); + if (is_array($uinfo)) { + foreach ($uinfo as $message) { + if (is_array($message)) { + $message = $message['message']; + } + $this->ui->outputData($message); + } + } + + return $this->raiseError($obj); + } + + if ($obj->getPackagexmlVersion() != '1.0') { + return $this->_doInfo2($command, $options, $params, $obj, false); + } + + $info = $obj->toArray(); + } else { + $parsed = $reg->parsePackageName($params[0], $this->config->get('default_channel')); + if (PEAR::isError($parsed)) { + return $this->raiseError($parsed); + } + + $package = $parsed['package']; + $channel = $parsed['channel']; + $info = $reg->packageInfo($package, null, $channel); + if (isset($info['old'])) { + $obj = $reg->getPackage($package, $channel); + return $this->_doInfo2($command, $options, $params, $obj, true); + } + } + + if (PEAR::isError($info)) { + return $info; + } + + if (empty($info)) { + $this->raiseError("No information found for `$params[0]'"); + return; + } + + unset($info['filelist']); + unset($info['dirtree']); + unset($info['changelog']); + if (isset($info['xsdversion'])) { + $info['package.xml version'] = $info['xsdversion']; + unset($info['xsdversion']); + } + + if (isset($info['packagerversion'])) { + $info['packaged with PEAR version'] = $info['packagerversion']; + unset($info['packagerversion']); + } + + $keys = array_keys($info); + $longtext = array('description', 'summary'); + foreach ($keys as $key) { + if (is_array($info[$key])) { + switch ($key) { + case 'maintainers': { + $i = 0; + $mstr = ''; + foreach ($info[$key] as $m) { + if ($i++ > 0) { + $mstr .= "\n"; + } + $mstr .= $m['name'] . " <"; + if (isset($m['email'])) { + $mstr .= $m['email']; + } else { + $mstr .= $m['handle'] . '@php.net'; + } + $mstr .= "> ($m[role])"; + } + $info[$key] = $mstr; + break; + } + case 'release_deps': { + $i = 0; + $dstr = ''; + foreach ($info[$key] as $d) { + if (isset($this->_deps_rel_trans[$d['rel']])) { + $rel = $this->_deps_rel_trans[$d['rel']]; + } else { + $rel = $d['rel']; + } + if (isset($this->_deps_type_trans[$d['type']])) { + $type = ucfirst($this->_deps_type_trans[$d['type']]); + } else { + $type = $d['type']; + } + if (isset($d['name'])) { + $name = $d['name'] . ' '; + } else { + $name = ''; + } + if (isset($d['version'])) { + $version = $d['version'] . ' '; + } else { + $version = ''; + } + if (isset($d['optional']) && $d['optional'] == 'yes') { + $optional = ' (optional)'; + } else { + $optional = ''; + } + $dstr .= "$type $name$rel $version$optional\n"; + } + $info[$key] = $dstr; + break; + } + case 'provides' : { + $debug = $this->config->get('verbose'); + if ($debug < 2) { + $pstr = 'Classes: '; + } else { + $pstr = ''; + } + $i = 0; + foreach ($info[$key] as $p) { + if ($debug < 2 && $p['type'] != "class") { + continue; + } + // Only print classes when verbosity mode is < 2 + if ($debug < 2) { + if ($i++ > 0) { + $pstr .= ", "; + } + $pstr .= $p['name']; + } else { + if ($i++ > 0) { + $pstr .= "\n"; + } + $pstr .= ucfirst($p['type']) . " " . $p['name']; + if (isset($p['explicit']) && $p['explicit'] == 1) { + $pstr .= " (explicit)"; + } + } + } + $info[$key] = $pstr; + break; + } + case 'configure_options' : { + foreach ($info[$key] as $i => $p) { + $info[$key][$i] = array_map(null, array_keys($p), array_values($p)); + $info[$key][$i] = array_map(create_function('$a', + 'return join(" = ",$a);'), $info[$key][$i]); + $info[$key][$i] = implode(', ', $info[$key][$i]); + } + $info[$key] = implode("\n", $info[$key]); + break; + } + default: { + $info[$key] = implode(", ", $info[$key]); + break; + } + } + } + + if ($key == '_lastmodified') { + $hdate = date('Y-m-d', $info[$key]); + unset($info[$key]); + $info['Last Modified'] = $hdate; + } elseif ($key == '_lastversion') { + $info['Previous Installed Version'] = $info[$key] ? $info[$key] : '- None -'; + unset($info[$key]); + } else { + $info[$key] = trim($info[$key]); + if (in_array($key, $longtext)) { + $info[$key] = preg_replace('/ +/', ' ', $info[$key]); + } + } + } + + $caption = 'About ' . $info['package'] . '-' . $info['version']; + $data = array( + 'caption' => $caption, + 'border' => true); + foreach ($info as $key => $value) { + $key = ucwords(trim(str_replace('_', ' ', $key))); + $data['data'][] = array($key, $value); + } + $data['raw'] = $info; + + $this->ui->outputData($data, 'package-info'); + } + + /** + * @access private + */ + function _doInfo2($command, $options, $params, &$obj, $installed) + { + $reg = &$this->config->getRegistry(); + $caption = 'About ' . $obj->getChannel() . '/' .$obj->getPackage() . '-' . + $obj->getVersion(); + $data = array( + 'caption' => $caption, + 'border' => true); + switch ($obj->getPackageType()) { + case 'php' : + $release = 'PEAR-style PHP-based Package'; + break; + case 'extsrc' : + $release = 'PECL-style PHP extension (source code)'; + break; + case 'zendextsrc' : + $release = 'PECL-style Zend extension (source code)'; + break; + case 'extbin' : + $release = 'PECL-style PHP extension (binary)'; + break; + case 'zendextbin' : + $release = 'PECL-style Zend extension (binary)'; + break; + case 'bundle' : + $release = 'Package bundle (collection of packages)'; + break; + } + $extends = $obj->getExtends(); + $extends = $extends ? + $obj->getPackage() . ' (extends ' . $extends . ')' : $obj->getPackage(); + if ($src = $obj->getSourcePackage()) { + $extends .= ' (source package ' . $src['channel'] . '/' . $src['package'] . ')'; + } + + $info = array( + 'Release Type' => $release, + 'Name' => $extends, + 'Channel' => $obj->getChannel(), + 'Summary' => preg_replace('/ +/', ' ', $obj->getSummary()), + 'Description' => preg_replace('/ +/', ' ', $obj->getDescription()), + ); + $info['Maintainers'] = ''; + foreach (array('lead', 'developer', 'contributor', 'helper') as $role) { + $leads = $obj->{"get{$role}s"}(); + if (!$leads) { + continue; + } + + if (isset($leads['active'])) { + $leads = array($leads); + } + + foreach ($leads as $lead) { + if (!empty($info['Maintainers'])) { + $info['Maintainers'] .= "\n"; + } + + $active = $lead['active'] == 'no' ? ', inactive' : ''; + $info['Maintainers'] .= $lead['name'] . ' <'; + $info['Maintainers'] .= $lead['email'] . "> ($role$active)"; + } + } + + $info['Release Date'] = $obj->getDate(); + if ($time = $obj->getTime()) { + $info['Release Date'] .= ' ' . $time; + } + + $info['Release Version'] = $obj->getVersion() . ' (' . $obj->getState() . ')'; + $info['API Version'] = $obj->getVersion('api') . ' (' . $obj->getState('api') . ')'; + $info['License'] = $obj->getLicense(); + $uri = $obj->getLicenseLocation(); + if ($uri) { + if (isset($uri['uri'])) { + $info['License'] .= ' (' . $uri['uri'] . ')'; + } else { + $extra = $obj->getInstalledLocation($info['filesource']); + if ($extra) { + $info['License'] .= ' (' . $uri['filesource'] . ')'; + } + } + } + + $info['Release Notes'] = $obj->getNotes(); + if ($compat = $obj->getCompatible()) { + if (!isset($compat[0])) { + $compat = array($compat); + } + + $info['Compatible with'] = ''; + foreach ($compat as $package) { + $info['Compatible with'] .= $package['channel'] . '/' . $package['name'] . + "\nVersions >= " . $package['min'] . ', <= ' . $package['max']; + if (isset($package['exclude'])) { + if (is_array($package['exclude'])) { + $package['exclude'] = implode(', ', $package['exclude']); + } + + if (!isset($info['Not Compatible with'])) { + $info['Not Compatible with'] = ''; + } else { + $info['Not Compatible with'] .= "\n"; + } + $info['Not Compatible with'] .= $package['channel'] . '/' . + $package['name'] . "\nVersions " . $package['exclude']; + } + } + } + + $usesrole = $obj->getUsesrole(); + if ($usesrole) { + if (!isset($usesrole[0])) { + $usesrole = array($usesrole); + } + + foreach ($usesrole as $roledata) { + if (isset($info['Uses Custom Roles'])) { + $info['Uses Custom Roles'] .= "\n"; + } else { + $info['Uses Custom Roles'] = ''; + } + + if (isset($roledata['package'])) { + $rolepackage = $reg->parsedPackageNameToString($roledata, true); + } else { + $rolepackage = $roledata['uri']; + } + $info['Uses Custom Roles'] .= $roledata['role'] . ' (' . $rolepackage . ')'; + } + } + + $usestask = $obj->getUsestask(); + if ($usestask) { + if (!isset($usestask[0])) { + $usestask = array($usestask); + } + + foreach ($usestask as $taskdata) { + if (isset($info['Uses Custom Tasks'])) { + $info['Uses Custom Tasks'] .= "\n"; + } else { + $info['Uses Custom Tasks'] = ''; + } + + if (isset($taskdata['package'])) { + $taskpackage = $reg->parsedPackageNameToString($taskdata, true); + } else { + $taskpackage = $taskdata['uri']; + } + $info['Uses Custom Tasks'] .= $taskdata['task'] . ' (' . $taskpackage . ')'; + } + } + + $deps = $obj->getDependencies(); + $info['Required Dependencies'] = 'PHP version ' . $deps['required']['php']['min']; + if (isset($deps['required']['php']['max'])) { + $info['Required Dependencies'] .= '-' . $deps['required']['php']['max'] . "\n"; + } else { + $info['Required Dependencies'] .= "\n"; + } + + if (isset($deps['required']['php']['exclude'])) { + if (!isset($info['Not Compatible with'])) { + $info['Not Compatible with'] = ''; + } else { + $info['Not Compatible with'] .= "\n"; + } + + if (is_array($deps['required']['php']['exclude'])) { + $deps['required']['php']['exclude'] = + implode(', ', $deps['required']['php']['exclude']); + } + $info['Not Compatible with'] .= "PHP versions\n " . + $deps['required']['php']['exclude']; + } + + $info['Required Dependencies'] .= 'PEAR installer version'; + if (isset($deps['required']['pearinstaller']['max'])) { + $info['Required Dependencies'] .= 's ' . + $deps['required']['pearinstaller']['min'] . '-' . + $deps['required']['pearinstaller']['max']; + } else { + $info['Required Dependencies'] .= ' ' . + $deps['required']['pearinstaller']['min'] . ' or newer'; + } + + if (isset($deps['required']['pearinstaller']['exclude'])) { + if (!isset($info['Not Compatible with'])) { + $info['Not Compatible with'] = ''; + } else { + $info['Not Compatible with'] .= "\n"; + } + + if (is_array($deps['required']['pearinstaller']['exclude'])) { + $deps['required']['pearinstaller']['exclude'] = + implode(', ', $deps['required']['pearinstaller']['exclude']); + } + $info['Not Compatible with'] .= "PEAR installer\n Versions " . + $deps['required']['pearinstaller']['exclude']; + } + + foreach (array('Package', 'Extension') as $type) { + $index = strtolower($type); + if (isset($deps['required'][$index])) { + if (isset($deps['required'][$index]['name'])) { + $deps['required'][$index] = array($deps['required'][$index]); + } + + foreach ($deps['required'][$index] as $package) { + if (isset($package['conflicts'])) { + $infoindex = 'Not Compatible with'; + if (!isset($info['Not Compatible with'])) { + $info['Not Compatible with'] = ''; + } else { + $info['Not Compatible with'] .= "\n"; + } + } else { + $infoindex = 'Required Dependencies'; + $info[$infoindex] .= "\n"; + } + + if ($index == 'extension') { + $name = $package['name']; + } else { + if (isset($package['channel'])) { + $name = $package['channel'] . '/' . $package['name']; + } else { + $name = '__uri/' . $package['name'] . ' (static URI)'; + } + } + + $info[$infoindex] .= "$type $name"; + if (isset($package['uri'])) { + $info[$infoindex] .= "\n Download URI: $package[uri]"; + continue; + } + + if (isset($package['max']) && isset($package['min'])) { + $info[$infoindex] .= " \n Versions " . + $package['min'] . '-' . $package['max']; + } elseif (isset($package['min'])) { + $info[$infoindex] .= " \n Version " . + $package['min'] . ' or newer'; + } elseif (isset($package['max'])) { + $info[$infoindex] .= " \n Version " . + $package['max'] . ' or older'; + } + + if (isset($package['recommended'])) { + $info[$infoindex] .= "\n Recommended version: $package[recommended]"; + } + + if (isset($package['exclude'])) { + if (!isset($info['Not Compatible with'])) { + $info['Not Compatible with'] = ''; + } else { + $info['Not Compatible with'] .= "\n"; + } + + if (is_array($package['exclude'])) { + $package['exclude'] = implode(', ', $package['exclude']); + } + + $package['package'] = $package['name']; // for parsedPackageNameToString + if (isset($package['conflicts'])) { + $info['Not Compatible with'] .= '=> except '; + } + $info['Not Compatible with'] .= 'Package ' . + $reg->parsedPackageNameToString($package, true); + $info['Not Compatible with'] .= "\n Versions " . $package['exclude']; + } + } + } + } + + if (isset($deps['required']['os'])) { + if (isset($deps['required']['os']['name'])) { + $dep['required']['os']['name'] = array($dep['required']['os']['name']); + } + + foreach ($dep['required']['os'] as $os) { + if (isset($os['conflicts']) && $os['conflicts'] == 'yes') { + if (!isset($info['Not Compatible with'])) { + $info['Not Compatible with'] = ''; + } else { + $info['Not Compatible with'] .= "\n"; + } + $info['Not Compatible with'] .= "$os[name] Operating System"; + } else { + $info['Required Dependencies'] .= "\n"; + $info['Required Dependencies'] .= "$os[name] Operating System"; + } + } + } + + if (isset($deps['required']['arch'])) { + if (isset($deps['required']['arch']['pattern'])) { + $dep['required']['arch']['pattern'] = array($dep['required']['os']['pattern']); + } + + foreach ($dep['required']['arch'] as $os) { + if (isset($os['conflicts']) && $os['conflicts'] == 'yes') { + if (!isset($info['Not Compatible with'])) { + $info['Not Compatible with'] = ''; + } else { + $info['Not Compatible with'] .= "\n"; + } + $info['Not Compatible with'] .= "OS/Arch matching pattern '/$os[pattern]/'"; + } else { + $info['Required Dependencies'] .= "\n"; + $info['Required Dependencies'] .= "OS/Arch matching pattern '/$os[pattern]/'"; + } + } + } + + if (isset($deps['optional'])) { + foreach (array('Package', 'Extension') as $type) { + $index = strtolower($type); + if (isset($deps['optional'][$index])) { + if (isset($deps['optional'][$index]['name'])) { + $deps['optional'][$index] = array($deps['optional'][$index]); + } + + foreach ($deps['optional'][$index] as $package) { + if (isset($package['conflicts']) && $package['conflicts'] == 'yes') { + $infoindex = 'Not Compatible with'; + if (!isset($info['Not Compatible with'])) { + $info['Not Compatible with'] = ''; + } else { + $info['Not Compatible with'] .= "\n"; + } + } else { + $infoindex = 'Optional Dependencies'; + if (!isset($info['Optional Dependencies'])) { + $info['Optional Dependencies'] = ''; + } else { + $info['Optional Dependencies'] .= "\n"; + } + } + + if ($index == 'extension') { + $name = $package['name']; + } else { + if (isset($package['channel'])) { + $name = $package['channel'] . '/' . $package['name']; + } else { + $name = '__uri/' . $package['name'] . ' (static URI)'; + } + } + + $info[$infoindex] .= "$type $name"; + if (isset($package['uri'])) { + $info[$infoindex] .= "\n Download URI: $package[uri]"; + continue; + } + + if ($infoindex == 'Not Compatible with') { + // conflicts is only used to say that all versions conflict + continue; + } + + if (isset($package['max']) && isset($package['min'])) { + $info[$infoindex] .= " \n Versions " . + $package['min'] . '-' . $package['max']; + } elseif (isset($package['min'])) { + $info[$infoindex] .= " \n Version " . + $package['min'] . ' or newer'; + } elseif (isset($package['max'])) { + $info[$infoindex] .= " \n Version " . + $package['min'] . ' or older'; + } + + if (isset($package['recommended'])) { + $info[$infoindex] .= "\n Recommended version: $package[recommended]"; + } + + if (isset($package['exclude'])) { + if (!isset($info['Not Compatible with'])) { + $info['Not Compatible with'] = ''; + } else { + $info['Not Compatible with'] .= "\n"; + } + + if (is_array($package['exclude'])) { + $package['exclude'] = implode(', ', $package['exclude']); + } + + $info['Not Compatible with'] .= "Package $package\n Versions " . + $package['exclude']; + } + } + } + } + } + + if (isset($deps['group'])) { + if (!isset($deps['group'][0])) { + $deps['group'] = array($deps['group']); + } + + foreach ($deps['group'] as $group) { + $info['Dependency Group ' . $group['attribs']['name']] = $group['attribs']['hint']; + $groupindex = $group['attribs']['name'] . ' Contents'; + $info[$groupindex] = ''; + foreach (array('Package', 'Extension') as $type) { + $index = strtolower($type); + if (isset($group[$index])) { + if (isset($group[$index]['name'])) { + $group[$index] = array($group[$index]); + } + + foreach ($group[$index] as $package) { + if (!empty($info[$groupindex])) { + $info[$groupindex] .= "\n"; + } + + if ($index == 'extension') { + $name = $package['name']; + } else { + if (isset($package['channel'])) { + $name = $package['channel'] . '/' . $package['name']; + } else { + $name = '__uri/' . $package['name'] . ' (static URI)'; + } + } + + if (isset($package['uri'])) { + if (isset($package['conflicts']) && $package['conflicts'] == 'yes') { + $info[$groupindex] .= "Not Compatible with $type $name"; + } else { + $info[$groupindex] .= "$type $name"; + } + + $info[$groupindex] .= "\n Download URI: $package[uri]"; + continue; + } + + if (isset($package['conflicts']) && $package['conflicts'] == 'yes') { + $info[$groupindex] .= "Not Compatible with $type $name"; + continue; + } + + $info[$groupindex] .= "$type $name"; + if (isset($package['max']) && isset($package['min'])) { + $info[$groupindex] .= " \n Versions " . + $package['min'] . '-' . $package['max']; + } elseif (isset($package['min'])) { + $info[$groupindex] .= " \n Version " . + $package['min'] . ' or newer'; + } elseif (isset($package['max'])) { + $info[$groupindex] .= " \n Version " . + $package['min'] . ' or older'; + } + + if (isset($package['recommended'])) { + $info[$groupindex] .= "\n Recommended version: $package[recommended]"; + } + + if (isset($package['exclude'])) { + if (!isset($info['Not Compatible with'])) { + $info['Not Compatible with'] = ''; + } else { + $info[$groupindex] .= "Not Compatible with\n"; + } + + if (is_array($package['exclude'])) { + $package['exclude'] = implode(', ', $package['exclude']); + } + $info[$groupindex] .= " Package $package\n Versions " . + $package['exclude']; + } + } + } + } + } + } + + if ($obj->getPackageType() == 'bundle') { + $info['Bundled Packages'] = ''; + foreach ($obj->getBundledPackages() as $package) { + if (!empty($info['Bundled Packages'])) { + $info['Bundled Packages'] .= "\n"; + } + + if (isset($package['uri'])) { + $info['Bundled Packages'] .= '__uri/' . $package['name']; + $info['Bundled Packages'] .= "\n (URI: $package[uri]"; + } else { + $info['Bundled Packages'] .= $package['channel'] . '/' . $package['name']; + } + } + } + + $info['package.xml version'] = '2.0'; + if ($installed) { + if ($obj->getLastModified()) { + $info['Last Modified'] = date('Y-m-d H:i', $obj->getLastModified()); + } + + $v = $obj->getLastInstalledVersion(); + $info['Previous Installed Version'] = $v ? $v : '- None -'; + } + + foreach ($info as $key => $value) { + $data['data'][] = array($key, $value); + } + + $data['raw'] = $obj->getArray(); // no validation needed + $this->ui->outputData($data, 'package-info'); + } +} \ No newline at end of file diff --git a/library/pear/PEAR/Command/Registry.xml b/library/pear/PEAR/Command/Registry.xml new file mode 100644 index 000000000..9f4e21496 --- /dev/null +++ b/library/pear/PEAR/Command/Registry.xml @@ -0,0 +1,58 @@ + + + List Installed Packages In The Default Channel + doList + l + + + c + list installed packages from this channel + CHAN + + + a + list installed packages from all channels + + + i + output fully channel-aware data, even on failure + + + <package> +If invoked without parameters, this command lists the PEAR packages +installed in your php_dir ({config php_dir}). With a parameter, it +lists the files in a package. + + + + List Files In Installed Package + doFileList + fl + + <package> +List the files in an installed package. + + + + Shell Script Test + doShellTest + st + + <package> [[relation] version] +Tests if a package is installed in the system. Will exit(1) if it is not. + <relation> The version comparison operator. One of: + <, lt, <=, le, >, gt, >=, ge, ==, =, eq, !=, <>, ne + <version> The version to compare with + + + + Display information about a package + doInfo + in + + <package> +Displays information about a package. The package argument may be a +local package file, an URL to a package file, or the name of an +installed package. + + \ No newline at end of file diff --git a/library/pear/PEAR/Command/Remote.php b/library/pear/PEAR/Command/Remote.php new file mode 100644 index 000000000..0bb848034 --- /dev/null +++ b/library/pear/PEAR/Command/Remote.php @@ -0,0 +1,809 @@ + + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Remote.php 287477 2009-08-19 14:19:43Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 0.1 + */ + +/** + * base class + */ +require_once 'PEAR/Command/Common.php'; +require_once 'PEAR/REST.php'; + +/** + * PEAR commands for remote server querying + * + * @category pear + * @package PEAR + * @author Stig Bakken + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.1 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 0.1 + */ +class PEAR_Command_Remote extends PEAR_Command_Common +{ + var $commands = array( + 'remote-info' => array( + 'summary' => 'Information About Remote Packages', + 'function' => 'doRemoteInfo', + 'shortcut' => 'ri', + 'options' => array(), + 'doc' => ' +Get details on a package from the server.', + ), + 'list-upgrades' => array( + 'summary' => 'List Available Upgrades', + 'function' => 'doListUpgrades', + 'shortcut' => 'lu', + 'options' => array( + 'channelinfo' => array( + 'shortopt' => 'i', + 'doc' => 'output fully channel-aware data, even on failure', + ), + ), + 'doc' => '[preferred_state] +List releases on the server of packages you have installed where +a newer version is available with the same release state (stable etc.) +or the state passed as the second parameter.' + ), + 'remote-list' => array( + 'summary' => 'List Remote Packages', + 'function' => 'doRemoteList', + 'shortcut' => 'rl', + 'options' => array( + 'channel' => + array( + 'shortopt' => 'c', + 'doc' => 'specify a channel other than the default channel', + 'arg' => 'CHAN', + ) + ), + 'doc' => ' +Lists the packages available on the configured server along with the +latest stable release of each package.', + ), + 'search' => array( + 'summary' => 'Search remote package database', + 'function' => 'doSearch', + 'shortcut' => 'sp', + 'options' => array( + 'channel' => + array( + 'shortopt' => 'c', + 'doc' => 'specify a channel other than the default channel', + 'arg' => 'CHAN', + ), + 'allchannels' => array( + 'shortopt' => 'a', + 'doc' => 'search packages from all known channels', + ), + 'channelinfo' => array( + 'shortopt' => 'i', + 'doc' => 'output fully channel-aware data, even on failure', + ), + ), + 'doc' => '[packagename] [packageinfo] +Lists all packages which match the search parameters. The first +parameter is a fragment of a packagename. The default channel +will be used unless explicitly overridden. The second parameter +will be used to match any portion of the summary/description', + ), + 'list-all' => array( + 'summary' => 'List All Packages', + 'function' => 'doListAll', + 'shortcut' => 'la', + 'options' => array( + 'channel' => + array( + 'shortopt' => 'c', + 'doc' => 'specify a channel other than the default channel', + 'arg' => 'CHAN', + ), + 'channelinfo' => array( + 'shortopt' => 'i', + 'doc' => 'output fully channel-aware data, even on failure', + ), + ), + 'doc' => ' +Lists the packages available on the configured server along with the +latest stable release of each package.', + ), + 'download' => array( + 'summary' => 'Download Package', + 'function' => 'doDownload', + 'shortcut' => 'd', + 'options' => array( + 'nocompress' => array( + 'shortopt' => 'Z', + 'doc' => 'download an uncompressed (.tar) file', + ), + ), + 'doc' => '... +Download package tarballs. The files will be named as suggested by the +server, for example if you download the DB package and the latest stable +version of DB is 1.6.5, the downloaded file will be DB-1.6.5.tgz.', + ), + 'clear-cache' => array( + 'summary' => 'Clear Web Services Cache', + 'function' => 'doClearCache', + 'shortcut' => 'cc', + 'options' => array(), + 'doc' => ' +Clear the XML-RPC/REST cache. See also the cache_ttl configuration +parameter. +', + ), + ); + + /** + * PEAR_Command_Remote constructor. + * + * @access public + */ + function PEAR_Command_Remote(&$ui, &$config) + { + parent::PEAR_Command_Common($ui, $config); + } + + function _checkChannelForStatus($channel, $chan) + { + if (PEAR::isError($chan)) { + $this->raiseError($chan); + } + if (!is_a($chan, 'PEAR_ChannelFile')) { + return $this->raiseError('Internal corruption error: invalid channel "' . + $channel . '"'); + } + $rest = new PEAR_REST($this->config); + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $mirror = $this->config->get('preferred_mirror', null, + $channel); + $a = $rest->downloadHttp('http://' . $channel . + '/channel.xml', $chan->lastModified()); + PEAR::staticPopErrorHandling(); + if (!PEAR::isError($a) && $a) { + $this->ui->outputData('WARNING: channel "' . $channel . '" has ' . + 'updated its protocols, use "' . PEAR_RUNTYPE . ' channel-update ' . $channel . + '" to update'); + } + } + + function doRemoteInfo($command, $options, $params) + { + if (sizeof($params) != 1) { + return $this->raiseError("$command expects one param: the remote package name"); + } + $savechannel = $channel = $this->config->get('default_channel'); + $reg = &$this->config->getRegistry(); + $package = $params[0]; + $parsed = $reg->parsePackageName($package, $channel); + if (PEAR::isError($parsed)) { + return $this->raiseError('Invalid package name "' . $package . '"'); + } + + $channel = $parsed['channel']; + $this->config->set('default_channel', $channel); + $chan = $reg->getChannel($channel); + if (PEAR::isError($e = $this->_checkChannelForStatus($channel, $chan))) { + return $e; + } + + $mirror = $this->config->get('preferred_mirror'); + if ($chan->supportsREST($mirror) && $base = $chan->getBaseURL('REST1.0', $mirror)) { + $rest = &$this->config->getREST('1.0', array()); + $info = $rest->packageInfo($base, $parsed['package'], $channel); + } + + if (!isset($info)) { + return $this->raiseError('No supported protocol was found'); + } + + if (PEAR::isError($info)) { + $this->config->set('default_channel', $savechannel); + return $this->raiseError($info); + } + + if (!isset($info['name'])) { + return $this->raiseError('No remote package "' . $package . '" was found'); + } + + $installed = $reg->packageInfo($info['name'], null, $channel); + $info['installed'] = $installed['version'] ? $installed['version'] : '- no -'; + if (is_array($info['installed'])) { + $info['installed'] = $info['installed']['release']; + } + + $this->ui->outputData($info, $command); + $this->config->set('default_channel', $savechannel); + + return true; + } + + function doRemoteList($command, $options, $params) + { + $savechannel = $channel = $this->config->get('default_channel'); + $reg = &$this->config->getRegistry(); + if (isset($options['channel'])) { + $channel = $options['channel']; + if (!$reg->channelExists($channel)) { + return $this->raiseError('Channel "' . $channel . '" does not exist'); + } + + $this->config->set('default_channel', $channel); + } + + $chan = $reg->getChannel($channel); + if (PEAR::isError($e = $this->_checkChannelForStatus($channel, $chan))) { + return $e; + } + + $list_options = false; + if ($this->config->get('preferred_state') == 'stable') { + $list_options = true; + } + + $available = array(); + if ($chan->supportsREST($this->config->get('preferred_mirror')) && + $base = $chan->getBaseURL('REST1.1', $this->config->get('preferred_mirror')) + ) { + // use faster list-all if available + $rest = &$this->config->getREST('1.1', array()); + $available = $rest->listAll($base, $list_options, true, false, false, $chan->getName()); + } elseif ($chan->supportsREST($this->config->get('preferred_mirror')) && + $base = $chan->getBaseURL('REST1.0', $this->config->get('preferred_mirror'))) { + $rest = &$this->config->getREST('1.0', array()); + $available = $rest->listAll($base, $list_options, true, false, false, $chan->getName()); + } + + if (PEAR::isError($available)) { + $this->config->set('default_channel', $savechannel); + return $this->raiseError($available); + } + + $i = $j = 0; + $data = array( + 'caption' => 'Channel ' . $channel . ' Available packages:', + 'border' => true, + 'headline' => array('Package', 'Version'), + 'channel' => $channel + ); + + if (count($available) == 0) { + $data = '(no packages available yet)'; + } else { + foreach ($available as $name => $info) { + $version = (isset($info['stable']) && $info['stable']) ? $info['stable'] : '-n/a-'; + $data['data'][] = array($name, $version); + } + } + $this->ui->outputData($data, $command); + $this->config->set('default_channel', $savechannel); + return true; + } + + function doListAll($command, $options, $params) + { + $savechannel = $channel = $this->config->get('default_channel'); + $reg = &$this->config->getRegistry(); + if (isset($options['channel'])) { + $channel = $options['channel']; + if (!$reg->channelExists($channel)) { + return $this->raiseError("Channel \"$channel\" does not exist"); + } + + $this->config->set('default_channel', $channel); + } + + $list_options = false; + if ($this->config->get('preferred_state') == 'stable') { + $list_options = true; + } + + $chan = $reg->getChannel($channel); + if (PEAR::isError($e = $this->_checkChannelForStatus($channel, $chan))) { + return $e; + } + + if ($chan->supportsREST($this->config->get('preferred_mirror')) && + $base = $chan->getBaseURL('REST1.1', $this->config->get('preferred_mirror'))) { + // use faster list-all if available + $rest = &$this->config->getREST('1.1', array()); + $available = $rest->listAll($base, $list_options, false, false, false, $chan->getName()); + } elseif ($chan->supportsREST($this->config->get('preferred_mirror')) && + $base = $chan->getBaseURL('REST1.0', $this->config->get('preferred_mirror'))) { + $rest = &$this->config->getREST('1.0', array()); + $available = $rest->listAll($base, $list_options, false, false, false, $chan->getName()); + } + + if (PEAR::isError($available)) { + $this->config->set('default_channel', $savechannel); + return $this->raiseError('The package list could not be fetched from the remote server. Please try again. (Debug info: "' . $available->getMessage() . '")'); + } + + $data = array( + 'caption' => 'All packages [Channel ' . $channel . ']:', + 'border' => true, + 'headline' => array('Package', 'Latest', 'Local'), + 'channel' => $channel, + ); + + if (isset($options['channelinfo'])) { + // add full channelinfo + $data['caption'] = 'Channel ' . $channel . ' All packages:'; + $data['headline'] = array('Channel', 'Package', 'Latest', 'Local', + 'Description', 'Dependencies'); + } + $local_pkgs = $reg->listPackages($channel); + + foreach ($available as $name => $info) { + $installed = $reg->packageInfo($name, null, $channel); + if (is_array($installed['version'])) { + $installed['version'] = $installed['version']['release']; + } + $desc = $info['summary']; + if (isset($params[$name])) { + $desc .= "\n\n".$info['description']; + } + if (isset($options['mode'])) + { + if ($options['mode'] == 'installed' && !isset($installed['version'])) { + continue; + } + if ($options['mode'] == 'notinstalled' && isset($installed['version'])) { + continue; + } + if ($options['mode'] == 'upgrades' + && (!isset($installed['version']) || version_compare($installed['version'], + $info['stable'], '>='))) { + continue; + } + } + $pos = array_search(strtolower($name), $local_pkgs); + if ($pos !== false) { + unset($local_pkgs[$pos]); + } + + if (isset($info['stable']) && !$info['stable']) { + $info['stable'] = null; + } + + if (isset($options['channelinfo'])) { + // add full channelinfo + if ($info['stable'] === $info['unstable']) { + $state = $info['state']; + } else { + $state = 'stable'; + } + $latest = $info['stable'].' ('.$state.')'; + $local = ''; + if (isset($installed['version'])) { + $inst_state = $reg->packageInfo($name, 'release_state', $channel); + $local = $installed['version'].' ('.$inst_state.')'; + } + + $packageinfo = array( + $channel, + $name, + $latest, + $local, + isset($desc) ? $desc : null, + isset($info['deps']) ? $info['deps'] : null, + ); + } else { + $packageinfo = array( + $reg->channelAlias($channel) . '/' . $name, + isset($info['stable']) ? $info['stable'] : null, + isset($installed['version']) ? $installed['version'] : null, + isset($desc) ? $desc : null, + isset($info['deps']) ? $info['deps'] : null, + ); + } + $data['data'][$info['category']][] = $packageinfo; + } + + if (isset($options['mode']) && in_array($options['mode'], array('notinstalled', 'upgrades'))) { + $this->config->set('default_channel', $savechannel); + $this->ui->outputData($data, $command); + return true; + } + + foreach ($local_pkgs as $name) { + $info = &$reg->getPackage($name, $channel); + $data['data']['Local'][] = array( + $reg->channelAlias($channel) . '/' . $info->getPackage(), + '', + $info->getVersion(), + $info->getSummary(), + $info->getDeps() + ); + } + + $this->config->set('default_channel', $savechannel); + $this->ui->outputData($data, $command); + return true; + } + + function doSearch($command, $options, $params) + { + if ((!isset($params[0]) || empty($params[0])) + && (!isset($params[1]) || empty($params[1]))) + { + return $this->raiseError('no valid search string supplied'); + } + + $channelinfo = isset($options['channelinfo']); + $reg = &$this->config->getRegistry(); + if (isset($options['allchannels'])) { + // search all channels + unset($options['allchannels']); + $channels = $reg->getChannels(); + $errors = array(); + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + foreach ($channels as $channel) { + if ($channel->getName() != '__uri') { + $options['channel'] = $channel->getName(); + $ret = $this->doSearch($command, $options, $params); + if (PEAR::isError($ret)) { + $errors[] = $ret; + } + } + } + + PEAR::staticPopErrorHandling(); + if (count($errors) !== 0) { + // for now, only give first error + return PEAR::raiseError($errors[0]); + } + + return true; + } + + $savechannel = $channel = $this->config->get('default_channel'); + $package = strtolower($params[0]); + $summary = isset($params[1]) ? $params[1] : false; + if (isset($options['channel'])) { + $reg = &$this->config->getRegistry(); + $channel = $options['channel']; + if (!$reg->channelExists($channel)) { + return $this->raiseError('Channel "' . $channel . '" does not exist'); + } + + $this->config->set('default_channel', $channel); + } + + $chan = $reg->getChannel($channel); + if (PEAR::isError($e = $this->_checkChannelForStatus($channel, $chan))) { + return $e; + } + + if ($chan->supportsREST($this->config->get('preferred_mirror')) && + $base = $chan->getBaseURL('REST1.0', $this->config->get('preferred_mirror'))) { + $rest = &$this->config->getREST('1.0', array()); + $available = $rest->listAll($base, false, false, $package, $summary, $chan->getName()); + } + + if (PEAR::isError($available)) { + $this->config->set('default_channel', $savechannel); + return $this->raiseError($available); + } + + if (!$available && !$channelinfo) { + // clean exit when not found, no error ! + $data = 'no packages found that match pattern "' . $package . '", for channel '.$channel.'.'; + $this->ui->outputData($data); + $this->config->set('default_channel', $channel); + return true; + } + + if ($channelinfo) { + $data = array( + 'caption' => 'Matched packages, channel ' . $channel . ':', + 'border' => true, + 'headline' => array('Channel', 'Package', 'Stable/(Latest)', 'Local'), + 'channel' => $channel + ); + } else { + $data = array( + 'caption' => 'Matched packages, channel ' . $channel . ':', + 'border' => true, + 'headline' => array('Package', 'Stable/(Latest)', 'Local'), + 'channel' => $channel + ); + } + + if (!$available && $channelinfo) { + unset($data['headline']); + $data['data'] = 'No packages found that match pattern "' . $package . '".'; + $available = array(); + } + + foreach ($available as $name => $info) { + $installed = $reg->packageInfo($name, null, $channel); + $desc = $info['summary']; + if (isset($params[$name])) + $desc .= "\n\n".$info['description']; + + if (!isset($info['stable']) || !$info['stable']) { + $version_remote = 'none'; + } else { + if ($info['unstable']) { + $version_remote = $info['unstable']; + } else { + $version_remote = $info['stable']; + } + $version_remote .= ' ('.$info['state'].')'; + } + $version = is_array($installed['version']) ? $installed['version']['release'] : + $installed['version']; + if ($channelinfo) { + $packageinfo = array( + $channel, + $name, + $version_remote, + $version, + $desc, + ); + } else { + $packageinfo = array( + $name, + $version_remote, + $version, + $desc, + ); + } + $data['data'][$info['category']][] = $packageinfo; + } + + $this->ui->outputData($data, $command); + $this->config->set('default_channel', $channel); + return true; + } + + function &getDownloader($options) + { + if (!class_exists('PEAR_Downloader')) { + require_once 'PEAR/Downloader.php'; + } + $a = &new PEAR_Downloader($this->ui, $options, $this->config); + return $a; + } + + function doDownload($command, $options, $params) + { + // make certain that dependencies are ignored + $options['downloadonly'] = 1; + + // eliminate error messages for preferred_state-related errors + /* TODO: Should be an option, but until now download does respect + prefered state */ + /* $options['ignorepreferred_state'] = 1; */ + // eliminate error messages for preferred_state-related errors + + $downloader = &$this->getDownloader($options); + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $e = $downloader->setDownloadDir(getcwd()); + PEAR::staticPopErrorHandling(); + if (PEAR::isError($e)) { + return $this->raiseError('Current directory is not writeable, cannot download'); + } + + $errors = array(); + $downloaded = array(); + $err = $downloader->download($params); + if (PEAR::isError($err)) { + return $err; + } + + $errors = $downloader->getErrorMsgs(); + if (count($errors)) { + foreach ($errors as $error) { + if ($error !== null) { + $this->ui->outputData($error); + } + } + + return $this->raiseError("$command failed"); + } + + $downloaded = $downloader->getDownloadedPackages(); + foreach ($downloaded as $pkg) { + $this->ui->outputData("File $pkg[file] downloaded", $command); + } + + return true; + } + + function downloadCallback($msg, $params = null) + { + if ($msg == 'done') { + $this->bytes_downloaded = $params; + } + } + + function doListUpgrades($command, $options, $params) + { + require_once 'PEAR/Common.php'; + if (isset($params[0]) && !is_array(PEAR_Common::betterStates($params[0]))) { + return $this->raiseError($params[0] . ' is not a valid state (stable/beta/alpha/devel/etc.) try "pear help list-upgrades"'); + } + + $savechannel = $channel = $this->config->get('default_channel'); + $reg = &$this->config->getRegistry(); + foreach ($reg->listChannels() as $channel) { + $inst = array_flip($reg->listPackages($channel)); + if (!count($inst)) { + continue; + } + + if ($channel == '__uri') { + continue; + } + + $this->config->set('default_channel', $channel); + $state = empty($params[0]) ? $this->config->get('preferred_state') : $params[0]; + + $caption = $channel . ' Available Upgrades'; + $chan = $reg->getChannel($channel); + if (PEAR::isError($e = $this->_checkChannelForStatus($channel, $chan))) { + return $e; + } + + $latest = array(); + $base2 = false; + $preferred_mirror = $this->config->get('preferred_mirror'); + if ($chan->supportsREST($preferred_mirror) && + ( + //($base2 = $chan->getBaseURL('REST1.4', $preferred_mirror)) || + ($base = $chan->getBaseURL('REST1.0', $preferred_mirror)) + ) + + ) { + if ($base2) { + $rest = &$this->config->getREST('1.4', array()); + $base = $base2; + } else { + $rest = &$this->config->getREST('1.0', array()); + } + + if (empty($state) || $state == 'any') { + $state = false; + } else { + $caption .= ' (' . implode(', ', PEAR_Common::betterStates($state, true)) . ')'; + } + + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $latest = $rest->listLatestUpgrades($base, $state, $inst, $channel, $reg); + PEAR::staticPopErrorHandling(); + } + + if (PEAR::isError($latest)) { + $this->ui->outputData($latest->getMessage()); + continue; + } + + $caption .= ':'; + if (PEAR::isError($latest)) { + $this->config->set('default_channel', $savechannel); + return $latest; + } + + $data = array( + 'caption' => $caption, + 'border' => 1, + 'headline' => array('Channel', 'Package', 'Local', 'Remote', 'Size'), + 'channel' => $channel + ); + + foreach ((array)$latest as $pkg => $info) { + $package = strtolower($pkg); + if (!isset($inst[$package])) { + // skip packages we don't have installed + continue; + } + + extract($info); + $inst_version = $reg->packageInfo($package, 'version', $channel); + $inst_state = $reg->packageInfo($package, 'release_state', $channel); + if (version_compare("$version", "$inst_version", "le")) { + // installed version is up-to-date + continue; + } + + if ($filesize >= 20480) { + $filesize += 1024 - ($filesize % 1024); + $fs = sprintf("%dkB", $filesize / 1024); + } elseif ($filesize > 0) { + $filesize += 103 - ($filesize % 103); + $fs = sprintf("%.1fkB", $filesize / 1024.0); + } else { + $fs = " -"; // XXX center instead + } + + $data['data'][] = array($channel, $pkg, "$inst_version ($inst_state)", "$version ($state)", $fs); + } + + if (isset($options['channelinfo'])) { + if (empty($data['data'])) { + unset($data['headline']); + if (count($inst) == 0) { + $data['data'] = '(no packages installed)'; + } else { + $data['data'] = '(no upgrades available)'; + } + } + $this->ui->outputData($data, $command); + } else { + if (empty($data['data'])) { + $this->ui->outputData('Channel ' . $channel . ': No upgrades available'); + } else { + $this->ui->outputData($data, $command); + } + } + } + + $this->config->set('default_channel', $savechannel); + return true; + } + + function doClearCache($command, $options, $params) + { + $cache_dir = $this->config->get('cache_dir'); + $verbose = $this->config->get('verbose'); + $output = ''; + if (!file_exists($cache_dir) || !is_dir($cache_dir)) { + return $this->raiseError("$cache_dir does not exist or is not a directory"); + } + + if (!($dp = @opendir($cache_dir))) { + return $this->raiseError("opendir($cache_dir) failed: $php_errormsg"); + } + + if ($verbose >= 1) { + $output .= "reading directory $cache_dir\n"; + } + $num = 0; + while ($ent = readdir($dp)) { + if (preg_match('/rest.cache(file|id)\\z/', $ent)) { + $path = $cache_dir . DIRECTORY_SEPARATOR . $ent; + if (file_exists($path)) { + $ok = @unlink($path); + } else { + $ok = false; + $php_errormsg = ''; + } + + if ($ok) { + if ($verbose >= 2) { + $output .= "deleted $path\n"; + } + $num++; + } elseif ($verbose >= 1) { + $output .= "failed to delete $path $php_errormsg\n"; + } + } + } + + closedir($dp); + if ($verbose >= 1) { + $output .= "$num cache entries cleared\n"; + } + + $this->ui->outputData(rtrim($output), $command); + return $num; + } +} \ No newline at end of file diff --git a/library/pear/PEAR/Command/Remote.xml b/library/pear/PEAR/Command/Remote.xml new file mode 100644 index 000000000..b4f6100c7 --- /dev/null +++ b/library/pear/PEAR/Command/Remote.xml @@ -0,0 +1,109 @@ + + + Information About Remote Packages + doRemoteInfo + ri + + <package> +Get details on a package from the server. + + + List Available Upgrades + doListUpgrades + lu + + + i + output fully channel-aware data, even on failure + + + [preferred_state] +List releases on the server of packages you have installed where +a newer version is available with the same release state (stable etc.) +or the state passed as the second parameter. + + + List Remote Packages + doRemoteList + rl + + + c + specify a channel other than the default channel + CHAN + + + +Lists the packages available on the configured server along with the +latest stable release of each package. + + + Search remote package database + doSearch + sp + + + c + specify a channel other than the default channel + CHAN + + + a + search packages from all known channels + + + i + output fully channel-aware data, even on failure + + + [packagename] [packageinfo] +Lists all packages which match the search parameters. The first +parameter is a fragment of a packagename. The default channel +will be used unless explicitly overridden. The second parameter +will be used to match any portion of the summary/description + + + List All Packages + doListAll + la + + + c + specify a channel other than the default channel + CHAN + + + i + output fully channel-aware data, even on failure + + + +Lists the packages available on the configured server along with the +latest stable release of each package. + + + Download Package + doDownload + d + + + Z + download an uncompressed (.tar) file + + + <package>... +Download package tarballs. The files will be named as suggested by the +server, for example if you download the DB package and the latest stable +version of DB is 1.6.5, the downloaded file will be DB-1.6.5.tgz. + + + Clear Web Services Cache + doClearCache + cc + + +Clear the XML-RPC/REST cache. See also the cache_ttl configuration +parameter. + + + \ No newline at end of file diff --git a/library/pear/PEAR/Command/Test.php b/library/pear/PEAR/Command/Test.php new file mode 100644 index 000000000..0df8cb4ef --- /dev/null +++ b/library/pear/PEAR/Command/Test.php @@ -0,0 +1,337 @@ + + * @author Martin Jansen + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Test.php 279072 2009-04-20 19:57:41Z cellog $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 0.1 + */ + +/** + * base class + */ +require_once 'PEAR/Command/Common.php'; + +/** + * PEAR commands for login/logout + * + * @category pear + * @package PEAR + * @author Stig Bakken + * @author Martin Jansen + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.1 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 0.1 + */ + +class PEAR_Command_Test extends PEAR_Command_Common +{ + var $commands = array( + 'run-tests' => array( + 'summary' => 'Run Regression Tests', + 'function' => 'doRunTests', + 'shortcut' => 'rt', + 'options' => array( + 'recur' => array( + 'shortopt' => 'r', + 'doc' => 'Run tests in child directories, recursively. 4 dirs deep maximum', + ), + 'ini' => array( + 'shortopt' => 'i', + 'doc' => 'actual string of settings to pass to php in format " -d setting=blah"', + 'arg' => 'SETTINGS' + ), + 'realtimelog' => array( + 'shortopt' => 'l', + 'doc' => 'Log test runs/results as they are run', + ), + 'quiet' => array( + 'shortopt' => 'q', + 'doc' => 'Only display detail for failed tests', + ), + 'simple' => array( + 'shortopt' => 's', + 'doc' => 'Display simple output for all tests', + ), + 'package' => array( + 'shortopt' => 'p', + 'doc' => 'Treat parameters as installed packages from which to run tests', + ), + 'phpunit' => array( + 'shortopt' => 'u', + 'doc' => 'Search parameters for AllTests.php, and use that to run phpunit-based tests +If none is found, all .phpt tests will be tried instead.', + ), + 'tapoutput' => array( + 'shortopt' => 't', + 'doc' => 'Output run-tests.log in TAP-compliant format', + ), + 'cgi' => array( + 'shortopt' => 'c', + 'doc' => 'CGI php executable (needed for tests with POST/GET section)', + 'arg' => 'PHPCGI', + ), + 'coverage' => array( + 'shortopt' => 'x', + 'doc' => 'Generate a code coverage report (requires Xdebug 2.0.0+)', + ), + ), + 'doc' => '[testfile|dir ...] +Run regression tests with PHP\'s regression testing script (run-tests.php).', + ), + ); + + var $output; + + /** + * PEAR_Command_Test constructor. + * + * @access public + */ + function PEAR_Command_Test(&$ui, &$config) + { + parent::PEAR_Command_Common($ui, $config); + } + + function doRunTests($command, $options, $params) + { + if (isset($options['phpunit']) && isset($options['tapoutput'])) { + return $this->raiseError('ERROR: cannot use both --phpunit and --tapoutput at the same time'); + } + + require_once 'PEAR/Common.php'; + require_once 'System.php'; + $log = new PEAR_Common; + $log->ui = &$this->ui; // slightly hacky, but it will work + $tests = array(); + $depth = isset($options['recur']) ? 14 : 1; + + if (!count($params)) { + $params[] = '.'; + } + + if (isset($options['package'])) { + $oldparams = $params; + $params = array(); + $reg = &$this->config->getRegistry(); + foreach ($oldparams as $param) { + $pname = $reg->parsePackageName($param, $this->config->get('default_channel')); + if (PEAR::isError($pname)) { + return $this->raiseError($pname); + } + + $package = &$reg->getPackage($pname['package'], $pname['channel']); + if (!$package) { + return PEAR::raiseError('Unknown package "' . + $reg->parsedPackageNameToString($pname) . '"'); + } + + $filelist = $package->getFilelist(); + foreach ($filelist as $name => $atts) { + if (isset($atts['role']) && $atts['role'] != 'test') { + continue; + } + + if (isset($options['phpunit']) && preg_match('/AllTests\.php\\z/i', $name)) { + $params[] = $atts['installed_as']; + continue; + } elseif (!preg_match('/\.phpt\\z/', $name)) { + continue; + } + $params[] = $atts['installed_as']; + } + } + } + + foreach ($params as $p) { + if (is_dir($p)) { + if (isset($options['phpunit'])) { + $dir = System::find(array($p, '-type', 'f', + '-maxdepth', $depth, + '-name', 'AllTests.php')); + if (count($dir)) { + foreach ($dir as $p) { + $p = realpath($p); + if (!count($tests) || + (count($tests) && strlen($p) < strlen($tests[0]))) { + // this is in a higher-level directory, use this one instead. + $tests = array($p); + } + } + } + continue; + } + + $args = array($p, '-type', 'f', '-name', '*.phpt'); + } else { + if (isset($options['phpunit'])) { + if (preg_match('/AllTests\.php\\z/i', $p)) { + $p = realpath($p); + if (!count($tests) || + (count($tests) && strlen($p) < strlen($tests[0]))) { + // this is in a higher-level directory, use this one instead. + $tests = array($p); + } + } + continue; + } + + if (file_exists($p) && preg_match('/\.phpt$/', $p)) { + $tests[] = $p; + continue; + } + + if (!preg_match('/\.phpt\\z/', $p)) { + $p .= '.phpt'; + } + + $args = array(dirname($p), '-type', 'f', '-name', $p); + } + + if (!isset($options['recur'])) { + $args[] = '-maxdepth'; + $args[] = 1; + } + + $dir = System::find($args); + $tests = array_merge($tests, $dir); + } + + $ini_settings = ''; + if (isset($options['ini'])) { + $ini_settings .= $options['ini']; + } + + if (isset($_ENV['TEST_PHP_INCLUDE_PATH'])) { + $ini_settings .= " -d include_path={$_ENV['TEST_PHP_INCLUDE_PATH']}"; + } + + if ($ini_settings) { + $this->ui->outputData('Using INI settings: "' . $ini_settings . '"'); + } + + $skipped = $passed = $failed = array(); + $tests_count = count($tests); + $this->ui->outputData('Running ' . $tests_count . ' tests', $command); + $start = time(); + if (isset($options['realtimelog']) && file_exists('run-tests.log')) { + unlink('run-tests.log'); + } + + if (isset($options['tapoutput'])) { + $tap = '1..' . $tests_count . "\n"; + } + + require_once 'PEAR/RunTest.php'; + $run = new PEAR_RunTest($log, $options); + $run->tests_count = $tests_count; + + if (isset($options['coverage']) && extension_loaded('xdebug')){ + $run->xdebug_loaded = true; + } else { + $run->xdebug_loaded = false; + } + + $j = $i = 1; + foreach ($tests as $t) { + if (isset($options['realtimelog'])) { + $fp = @fopen('run-tests.log', 'a'); + if ($fp) { + fwrite($fp, "Running test [$i / $tests_count] $t..."); + fclose($fp); + } + } + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + if (isset($options['phpunit'])) { + $result = $run->runPHPUnit($t, $ini_settings); + } else { + $result = $run->run($t, $ini_settings, $j); + } + PEAR::staticPopErrorHandling(); + if (PEAR::isError($result)) { + $this->ui->log($result->getMessage()); + continue; + } + + if (isset($options['tapoutput'])) { + $tap .= $result[0] . ' ' . $i . $result[1] . "\n"; + continue; + } + + if (isset($options['realtimelog'])) { + $fp = @fopen('run-tests.log', 'a'); + if ($fp) { + fwrite($fp, "$result\n"); + fclose($fp); + } + } + + if ($result == 'FAILED') { + $failed[] = $t; + } + if ($result == 'PASSED') { + $passed[] = $t; + } + if ($result == 'SKIPPED') { + $skipped[] = $t; + } + + $j++; + } + + $total = date('i:s', time() - $start); + if (isset($options['tapoutput'])) { + $fp = @fopen('run-tests.log', 'w'); + if ($fp) { + fwrite($fp, $tap, strlen($tap)); + fclose($fp); + $this->ui->outputData('wrote TAP-format log to "' .realpath('run-tests.log') . + '"', $command); + } + } else { + if (count($failed)) { + $output = "TOTAL TIME: $total\n"; + $output .= count($passed) . " PASSED TESTS\n"; + $output .= count($skipped) . " SKIPPED TESTS\n"; + $output .= count($failed) . " FAILED TESTS:\n"; + foreach ($failed as $failure) { + $output .= $failure . "\n"; + } + + $mode = isset($options['realtimelog']) ? 'a' : 'w'; + $fp = @fopen('run-tests.log', $mode); + + if ($fp) { + fwrite($fp, $output, strlen($output)); + fclose($fp); + $this->ui->outputData('wrote log to "' . realpath('run-tests.log') . '"', $command); + } + } elseif (file_exists('run-tests.log') && !is_dir('run-tests.log')) { + @unlink('run-tests.log'); + } + } + $this->ui->outputData('TOTAL TIME: ' . $total); + $this->ui->outputData(count($passed) . ' PASSED TESTS', $command); + $this->ui->outputData(count($skipped) . ' SKIPPED TESTS', $command); + if (count($failed)) { + $this->ui->outputData(count($failed) . ' FAILED TESTS:', $command); + foreach ($failed as $failure) { + $this->ui->outputData($failure, $command); + } + } + + return true; + } +} \ No newline at end of file diff --git a/library/pear/PEAR/Command/Test.xml b/library/pear/PEAR/Command/Test.xml new file mode 100644 index 000000000..bbe9fcccc --- /dev/null +++ b/library/pear/PEAR/Command/Test.xml @@ -0,0 +1,54 @@ + + + Run Regression Tests + doRunTests + rt + + + r + Run tests in child directories, recursively. 4 dirs deep maximum + + + i + actual string of settings to pass to php in format " -d setting=blah" + SETTINGS + + + l + Log test runs/results as they are run + + + q + Only display detail for failed tests + + + s + Display simple output for all tests + + + p + Treat parameters as installed packages from which to run tests + + + u + Search parameters for AllTests.php, and use that to run phpunit-based tests +If none is found, all .phpt tests will be tried instead. + + + t + Output run-tests.log in TAP-compliant format + + + c + CGI php executable (needed for tests with POST/GET section) + PHPCGI + + + x + Generate a code coverage report (requires Xdebug 2.0.0+) + + + [testfile|dir ...] +Run regression tests with PHP's regression testing script (run-tests.php). + + \ No newline at end of file diff --git a/library/pear/PEAR/Common.php b/library/pear/PEAR/Common.php new file mode 100644 index 000000000..7eaf5d14c --- /dev/null +++ b/library/pear/PEAR/Common.php @@ -0,0 +1,837 @@ + + * @author Tomas V. V. Cox + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Common.php 282969 2009-06-28 23:09:27Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 0.1.0 + * @deprecated File deprecated since Release 1.4.0a1 + */ + +/** + * Include error handling + */ +require_once 'PEAR.php'; + +/** + * PEAR_Common error when an invalid PHP file is passed to PEAR_Common::analyzeSourceCode() + */ +define('PEAR_COMMON_ERROR_INVALIDPHP', 1); +define('_PEAR_COMMON_PACKAGE_NAME_PREG', '[A-Za-z][a-zA-Z0-9_]+'); +define('PEAR_COMMON_PACKAGE_NAME_PREG', '/^' . _PEAR_COMMON_PACKAGE_NAME_PREG . '\\z/'); + +// this should allow: 1, 1.0, 1.0RC1, 1.0dev, 1.0dev123234234234, 1.0a1, 1.0b1, 1.0pl1 +define('_PEAR_COMMON_PACKAGE_VERSION_PREG', '\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?'); +define('PEAR_COMMON_PACKAGE_VERSION_PREG', '/^' . _PEAR_COMMON_PACKAGE_VERSION_PREG . '\\z/i'); + +// XXX far from perfect :-) +define('_PEAR_COMMON_PACKAGE_DOWNLOAD_PREG', '(' . _PEAR_COMMON_PACKAGE_NAME_PREG . + ')(-([.0-9a-zA-Z]+))?'); +define('PEAR_COMMON_PACKAGE_DOWNLOAD_PREG', '/^' . _PEAR_COMMON_PACKAGE_DOWNLOAD_PREG . + '\\z/'); + +define('_PEAR_CHANNELS_NAME_PREG', '[A-Za-z][a-zA-Z0-9\.]+'); +define('PEAR_CHANNELS_NAME_PREG', '/^' . _PEAR_CHANNELS_NAME_PREG . '\\z/'); + +// this should allow any dns or IP address, plus a path - NO UNDERSCORES ALLOWED +define('_PEAR_CHANNELS_SERVER_PREG', '[a-zA-Z0-9\-]+(?:\.[a-zA-Z0-9\-]+)*(\/[a-zA-Z0-9\-]+)*'); +define('PEAR_CHANNELS_SERVER_PREG', '/^' . _PEAR_CHANNELS_SERVER_PREG . '\\z/i'); + +define('_PEAR_CHANNELS_PACKAGE_PREG', '(' ._PEAR_CHANNELS_SERVER_PREG . ')\/(' + . _PEAR_COMMON_PACKAGE_NAME_PREG . ')'); +define('PEAR_CHANNELS_PACKAGE_PREG', '/^' . _PEAR_CHANNELS_PACKAGE_PREG . '\\z/i'); + +define('_PEAR_COMMON_CHANNEL_DOWNLOAD_PREG', '(' . _PEAR_CHANNELS_NAME_PREG . ')::(' + . _PEAR_COMMON_PACKAGE_NAME_PREG . ')(-([.0-9a-zA-Z]+))?'); +define('PEAR_COMMON_CHANNEL_DOWNLOAD_PREG', '/^' . _PEAR_COMMON_CHANNEL_DOWNLOAD_PREG . '\\z/'); + +/** + * List of temporary files and directories registered by + * PEAR_Common::addTempFile(). + * @var array + */ +$GLOBALS['_PEAR_Common_tempfiles'] = array(); + +/** + * Valid maintainer roles + * @var array + */ +$GLOBALS['_PEAR_Common_maintainer_roles'] = array('lead','developer','contributor','helper'); + +/** + * Valid release states + * @var array + */ +$GLOBALS['_PEAR_Common_release_states'] = array('alpha','beta','stable','snapshot','devel'); + +/** + * Valid dependency types + * @var array + */ +$GLOBALS['_PEAR_Common_dependency_types'] = array('pkg','ext','php','prog','ldlib','rtlib','os','websrv','sapi'); + +/** + * Valid dependency relations + * @var array + */ +$GLOBALS['_PEAR_Common_dependency_relations'] = array('has','eq','lt','le','gt','ge','not', 'ne'); + +/** + * Valid file roles + * @var array + */ +$GLOBALS['_PEAR_Common_file_roles'] = array('php','ext','test','doc','data','src','script'); + +/** + * Valid replacement types + * @var array + */ +$GLOBALS['_PEAR_Common_replacement_types'] = array('php-const', 'pear-config', 'package-info'); + +/** + * Valid "provide" types + * @var array + */ +$GLOBALS['_PEAR_Common_provide_types'] = array('ext', 'prog', 'class', 'function', 'feature', 'api'); + +/** + * Valid "provide" types + * @var array + */ +$GLOBALS['_PEAR_Common_script_phases'] = array('pre-install', 'post-install', 'pre-uninstall', 'post-uninstall', 'pre-build', 'post-build', 'pre-configure', 'post-configure', 'pre-setup', 'post-setup'); + +/** + * Class providing common functionality for PEAR administration classes. + * @category pear + * @package PEAR + * @author Stig Bakken + * @author Tomas V. V. Cox + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.1 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + * @deprecated This class will disappear, and its components will be spread + * into smaller classes, like the AT&T breakup, as of Release 1.4.0a1 + */ +class PEAR_Common extends PEAR +{ + /** + * User Interface object (PEAR_Frontend_* class). If null, + * the log() method uses print. + * @var object + */ + var $ui = null; + + /** + * Configuration object (PEAR_Config). + * @var PEAR_Config + */ + var $config = null; + + /** stack of elements, gives some sort of XML context */ + var $element_stack = array(); + + /** name of currently parsed XML element */ + var $current_element; + + /** array of attributes of the currently parsed XML element */ + var $current_attributes = array(); + + /** assoc with information about a package */ + var $pkginfo = array(); + + var $current_path = null; + + /** + * Flag variable used to mark a valid package file + * @var boolean + * @access private + */ + var $_validPackageFile; + + /** + * PEAR_Common constructor + * + * @access public + */ + function PEAR_Common() + { + parent::PEAR(); + $this->config = &PEAR_Config::singleton(); + $this->debug = $this->config->get('verbose'); + } + + /** + * PEAR_Common destructor + * + * @access private + */ + function _PEAR_Common() + { + // doesn't work due to bug #14744 + //$tempfiles = $this->_tempfiles; + $tempfiles =& $GLOBALS['_PEAR_Common_tempfiles']; + while ($file = array_shift($tempfiles)) { + if (@is_dir($file)) { + if (!class_exists('System')) { + require_once 'System.php'; + } + + System::rm(array('-rf', $file)); + } elseif (file_exists($file)) { + unlink($file); + } + } + } + + /** + * Register a temporary file or directory. When the destructor is + * executed, all registered temporary files and directories are + * removed. + * + * @param string $file name of file or directory + * + * @return void + * + * @access public + */ + function addTempFile($file) + { + if (!class_exists('PEAR_Frontend')) { + require_once 'PEAR/Frontend.php'; + } + PEAR_Frontend::addTempFile($file); + } + + /** + * Wrapper to System::mkDir(), creates a directory as well as + * any necessary parent directories. + * + * @param string $dir directory name + * + * @return bool TRUE on success, or a PEAR error + * + * @access public + */ + function mkDirHier($dir) + { + // Only used in Installer - move it there ? + $this->log(2, "+ create dir $dir"); + if (!class_exists('System')) { + require_once 'System.php'; + } + return System::mkDir(array('-p', $dir)); + } + + /** + * Logging method. + * + * @param int $level log level (0 is quiet, higher is noisier) + * @param string $msg message to write to the log + * + * @return void + * + * @access public + * @static + */ + function log($level, $msg, $append_crlf = true) + { + if ($this->debug >= $level) { + if (!class_exists('PEAR_Frontend')) { + require_once 'PEAR/Frontend.php'; + } + + $ui = &PEAR_Frontend::singleton(); + if (is_a($ui, 'PEAR_Frontend')) { + $ui->log($msg, $append_crlf); + } else { + print "$msg\n"; + } + } + } + + /** + * Create and register a temporary directory. + * + * @param string $tmpdir (optional) Directory to use as tmpdir. + * Will use system defaults (for example + * /tmp or c:\windows\temp) if not specified + * + * @return string name of created directory + * + * @access public + */ + function mkTempDir($tmpdir = '') + { + $topt = $tmpdir ? array('-t', $tmpdir) : array(); + $topt = array_merge($topt, array('-d', 'pear')); + if (!class_exists('System')) { + require_once 'System.php'; + } + + if (!$tmpdir = System::mktemp($topt)) { + return false; + } + + $this->addTempFile($tmpdir); + return $tmpdir; + } + + /** + * Set object that represents the frontend to be used. + * + * @param object Reference of the frontend object + * @return void + * @access public + */ + function setFrontendObject(&$ui) + { + $this->ui = &$ui; + } + + /** + * Return an array containing all of the states that are more stable than + * or equal to the passed in state + * + * @param string Release state + * @param boolean Determines whether to include $state in the list + * @return false|array False if $state is not a valid release state + */ + function betterStates($state, $include = false) + { + static $states = array('snapshot', 'devel', 'alpha', 'beta', 'stable'); + $i = array_search($state, $states); + if ($i === false) { + return false; + } + if ($include) { + $i--; + } + return array_slice($states, $i + 1); + } + + /** + * Get the valid roles for a PEAR package maintainer + * + * @return array + * @static + */ + function getUserRoles() + { + return $GLOBALS['_PEAR_Common_maintainer_roles']; + } + + /** + * Get the valid package release states of packages + * + * @return array + * @static + */ + function getReleaseStates() + { + return $GLOBALS['_PEAR_Common_release_states']; + } + + /** + * Get the implemented dependency types (php, ext, pkg etc.) + * + * @return array + * @static + */ + function getDependencyTypes() + { + return $GLOBALS['_PEAR_Common_dependency_types']; + } + + /** + * Get the implemented dependency relations (has, lt, ge etc.) + * + * @return array + * @static + */ + function getDependencyRelations() + { + return $GLOBALS['_PEAR_Common_dependency_relations']; + } + + /** + * Get the implemented file roles + * + * @return array + * @static + */ + function getFileRoles() + { + return $GLOBALS['_PEAR_Common_file_roles']; + } + + /** + * Get the implemented file replacement types in + * + * @return array + * @static + */ + function getReplacementTypes() + { + return $GLOBALS['_PEAR_Common_replacement_types']; + } + + /** + * Get the implemented file replacement types in + * + * @return array + * @static + */ + function getProvideTypes() + { + return $GLOBALS['_PEAR_Common_provide_types']; + } + + /** + * Get the implemented file replacement types in + * + * @return array + * @static + */ + function getScriptPhases() + { + return $GLOBALS['_PEAR_Common_script_phases']; + } + + /** + * Test whether a string contains a valid package name. + * + * @param string $name the package name to test + * + * @return bool + * + * @access public + */ + function validPackageName($name) + { + return (bool)preg_match(PEAR_COMMON_PACKAGE_NAME_PREG, $name); + } + + /** + * Test whether a string contains a valid package version. + * + * @param string $ver the package version to test + * + * @return bool + * + * @access public + */ + function validPackageVersion($ver) + { + return (bool)preg_match(PEAR_COMMON_PACKAGE_VERSION_PREG, $ver); + } + + /** + * @param string $path relative or absolute include path + * @return boolean + * @static + */ + function isIncludeable($path) + { + if (file_exists($path) && is_readable($path)) { + return true; + } + + $ipath = explode(PATH_SEPARATOR, ini_get('include_path')); + foreach ($ipath as $include) { + $test = realpath($include . DIRECTORY_SEPARATOR . $path); + if (file_exists($test) && is_readable($test)) { + return true; + } + } + + return false; + } + + function _postProcessChecks($pf) + { + if (!PEAR::isError($pf)) { + return $this->_postProcessValidPackagexml($pf); + } + + $errs = $pf->getUserinfo(); + if (is_array($errs)) { + foreach ($errs as $error) { + $e = $this->raiseError($error['message'], $error['code'], null, null, $error); + } + } + + return $pf; + } + + /** + * Returns information about a package file. Expects the name of + * a gzipped tar file as input. + * + * @param string $file name of .tgz file + * + * @return array array with package information + * + * @access public + * @deprecated use PEAR_PackageFile->fromTgzFile() instead + * + */ + function infoFromTgzFile($file) + { + $packagefile = &new PEAR_PackageFile($this->config); + $pf = &$packagefile->fromTgzFile($file, PEAR_VALIDATE_NORMAL); + return $this->_postProcessChecks($pf); + } + + /** + * Returns information about a package file. Expects the name of + * a package xml file as input. + * + * @param string $descfile name of package xml file + * + * @return array array with package information + * + * @access public + * @deprecated use PEAR_PackageFile->fromPackageFile() instead + * + */ + function infoFromDescriptionFile($descfile) + { + $packagefile = &new PEAR_PackageFile($this->config); + $pf = &$packagefile->fromPackageFile($descfile, PEAR_VALIDATE_NORMAL); + return $this->_postProcessChecks($pf); + } + + /** + * Returns information about a package file. Expects the contents + * of a package xml file as input. + * + * @param string $data contents of package.xml file + * + * @return array array with package information + * + * @access public + * @deprecated use PEAR_PackageFile->fromXmlstring() instead + * + */ + function infoFromString($data) + { + $packagefile = &new PEAR_PackageFile($this->config); + $pf = &$packagefile->fromXmlString($data, PEAR_VALIDATE_NORMAL, false); + return $this->_postProcessChecks($pf); + } + + /** + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2 + * @return array + */ + function _postProcessValidPackagexml(&$pf) + { + if (!is_a($pf, 'PEAR_PackageFile_v2')) { + $this->pkginfo = $pf->toArray(); + return $this->pkginfo; + } + + // sort of make this into a package.xml 1.0-style array + // changelog is not converted to old format. + $arr = $pf->toArray(true); + $arr = array_merge($arr, $arr['old']); + unset($arr['old'], $arr['xsdversion'], $arr['contents'], $arr['compatible'], + $arr['channel'], $arr['uri'], $arr['dependencies'], $arr['phprelease'], + $arr['extsrcrelease'], $arr['zendextsrcrelease'], $arr['extbinrelease'], + $arr['zendextbinrelease'], $arr['bundle'], $arr['lead'], $arr['developer'], + $arr['helper'], $arr['contributor']); + $arr['filelist'] = $pf->getFilelist(); + $this->pkginfo = $arr; + return $arr; + } + + /** + * Returns package information from different sources + * + * This method is able to extract information about a package + * from a .tgz archive or from a XML package definition file. + * + * @access public + * @param string Filename of the source ('package.xml', '.tgz') + * @return string + * @deprecated use PEAR_PackageFile->fromAnyFile() instead + */ + function infoFromAny($info) + { + if (is_string($info) && file_exists($info)) { + $packagefile = &new PEAR_PackageFile($this->config); + $pf = &$packagefile->fromAnyFile($info, PEAR_VALIDATE_NORMAL); + if (PEAR::isError($pf)) { + $errs = $pf->getUserinfo(); + if (is_array($errs)) { + foreach ($errs as $error) { + $e = $this->raiseError($error['message'], $error['code'], null, null, $error); + } + } + + return $pf; + } + + return $this->_postProcessValidPackagexml($pf); + } + + return $info; + } + + /** + * Return an XML document based on the package info (as returned + * by the PEAR_Common::infoFrom* methods). + * + * @param array $pkginfo package info + * + * @return string XML data + * + * @access public + * @deprecated use a PEAR_PackageFile_v* object's generator instead + */ + function xmlFromInfo($pkginfo) + { + $config = &PEAR_Config::singleton(); + $packagefile = &new PEAR_PackageFile($config); + $pf = &$packagefile->fromArray($pkginfo); + $gen = &$pf->getDefaultGenerator(); + return $gen->toXml(PEAR_VALIDATE_PACKAGING); + } + + /** + * Validate XML package definition file. + * + * @param string $info Filename of the package archive or of the + * package definition file + * @param array $errors Array that will contain the errors + * @param array $warnings Array that will contain the warnings + * @param string $dir_prefix (optional) directory where source files + * may be found, or empty if they are not available + * @access public + * @return boolean + * @deprecated use the validation of PEAR_PackageFile objects + */ + function validatePackageInfo($info, &$errors, &$warnings, $dir_prefix = '') + { + $config = &PEAR_Config::singleton(); + $packagefile = &new PEAR_PackageFile($config); + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + if (strpos($info, 'fromXmlString($info, PEAR_VALIDATE_NORMAL, ''); + } else { + $pf = &$packagefile->fromAnyFile($info, PEAR_VALIDATE_NORMAL); + } + + PEAR::staticPopErrorHandling(); + if (PEAR::isError($pf)) { + $errs = $pf->getUserinfo(); + if (is_array($errs)) { + foreach ($errs as $error) { + if ($error['level'] == 'error') { + $errors[] = $error['message']; + } else { + $warnings[] = $error['message']; + } + } + } + + return false; + } + + return true; + } + + /** + * Build a "provides" array from data returned by + * analyzeSourceCode(). The format of the built array is like + * this: + * + * array( + * 'class;MyClass' => 'array('type' => 'class', 'name' => 'MyClass'), + * ... + * ) + * + * + * @param array $srcinfo array with information about a source file + * as returned by the analyzeSourceCode() method. + * + * @return void + * + * @access public + * + */ + function buildProvidesArray($srcinfo) + { + $file = basename($srcinfo['source_file']); + $pn = ''; + if (isset($this->_packageName)) { + $pn = $this->_packageName; + } + + $pnl = strlen($pn); + foreach ($srcinfo['declared_classes'] as $class) { + $key = "class;$class"; + if (isset($this->pkginfo['provides'][$key])) { + continue; + } + + $this->pkginfo['provides'][$key] = + array('file'=> $file, 'type' => 'class', 'name' => $class); + if (isset($srcinfo['inheritance'][$class])) { + $this->pkginfo['provides'][$key]['extends'] = + $srcinfo['inheritance'][$class]; + } + } + + foreach ($srcinfo['declared_methods'] as $class => $methods) { + foreach ($methods as $method) { + $function = "$class::$method"; + $key = "function;$function"; + if ($method{0} == '_' || !strcasecmp($method, $class) || + isset($this->pkginfo['provides'][$key])) { + continue; + } + + $this->pkginfo['provides'][$key] = + array('file'=> $file, 'type' => 'function', 'name' => $function); + } + } + + foreach ($srcinfo['declared_functions'] as $function) { + $key = "function;$function"; + if ($function{0} == '_' || isset($this->pkginfo['provides'][$key])) { + continue; + } + + if (!strstr($function, '::') && strncasecmp($function, $pn, $pnl)) { + $warnings[] = "in1 " . $file . ": function \"$function\" not prefixed with package name \"$pn\""; + } + + $this->pkginfo['provides'][$key] = + array('file'=> $file, 'type' => 'function', 'name' => $function); + } + } + + /** + * Analyze the source code of the given PHP file + * + * @param string Filename of the PHP file + * @return mixed + * @access public + */ + function analyzeSourceCode($file) + { + if (!class_exists('PEAR_PackageFile_v2_Validator')) { + require_once 'PEAR/PackageFile/v2/Validator.php'; + } + + $a = new PEAR_PackageFile_v2_Validator; + return $a->analyzeSourceCode($file); + } + + function detectDependencies($any, $status_callback = null) + { + if (!function_exists("token_get_all")) { + return false; + } + + if (PEAR::isError($info = $this->infoFromAny($any))) { + return $this->raiseError($info); + } + + if (!is_array($info)) { + return false; + } + + $deps = array(); + $used_c = $decl_c = $decl_f = $decl_m = array(); + foreach ($info['filelist'] as $file => $fa) { + $tmp = $this->analyzeSourceCode($file); + $used_c = @array_merge($used_c, $tmp['used_classes']); + $decl_c = @array_merge($decl_c, $tmp['declared_classes']); + $decl_f = @array_merge($decl_f, $tmp['declared_functions']); + $decl_m = @array_merge($decl_m, $tmp['declared_methods']); + $inheri = @array_merge($inheri, $tmp['inheritance']); + } + + $used_c = array_unique($used_c); + $decl_c = array_unique($decl_c); + $undecl_c = array_diff($used_c, $decl_c); + + return array('used_classes' => $used_c, + 'declared_classes' => $decl_c, + 'declared_methods' => $decl_m, + 'declared_functions' => $decl_f, + 'undeclared_classes' => $undecl_c, + 'inheritance' => $inheri, + ); + } + + /** + * Download a file through HTTP. Considers suggested file name in + * Content-disposition: header and can run a callback function for + * different events. The callback will be called with two + * parameters: the callback type, and parameters. The implemented + * callback types are: + * + * 'setup' called at the very beginning, parameter is a UI object + * that should be used for all output + * 'message' the parameter is a string with an informational message + * 'saveas' may be used to save with a different file name, the + * parameter is the filename that is about to be used. + * If a 'saveas' callback returns a non-empty string, + * that file name will be used as the filename instead. + * Note that $save_dir will not be affected by this, only + * the basename of the file. + * 'start' download is starting, parameter is number of bytes + * that are expected, or -1 if unknown + * 'bytesread' parameter is the number of bytes read so far + * 'done' download is complete, parameter is the total number + * of bytes read + * 'connfailed' if the TCP connection fails, this callback is called + * with array(host,port,errno,errmsg) + * 'writefailed' if writing to disk fails, this callback is called + * with array(destfile,errmsg) + * + * If an HTTP proxy has been configured (http_proxy PEAR_Config + * setting), the proxy will be used. + * + * @param string $url the URL to download + * @param object $ui PEAR_Frontend_* instance + * @param object $config PEAR_Config instance + * @param string $save_dir (optional) directory to save file in + * @param mixed $callback (optional) function/method to call for status + * updates + * + * @return string Returns the full path of the downloaded file or a PEAR + * error on failure. If the error is caused by + * socket-related errors, the error object will + * have the fsockopen error code available through + * getCode(). + * + * @access public + * @deprecated in favor of PEAR_Downloader::downloadHttp() + */ + function downloadHttp($url, &$ui, $save_dir = '.', $callback = null) + { + if (!class_exists('PEAR_Downloader')) { + require_once 'PEAR/Downloader.php'; + } + return PEAR_Downloader::downloadHttp($url, $ui, $save_dir, $callback); + } +} + +require_once 'PEAR/Config.php'; +require_once 'PEAR/PackageFile.php'; \ No newline at end of file diff --git a/library/pear/PEAR/Config.php b/library/pear/PEAR/Config.php new file mode 100644 index 000000000..64abd4367 --- /dev/null +++ b/library/pear/PEAR/Config.php @@ -0,0 +1,2097 @@ + + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Config.php 286480 2009-07-29 02:50:02Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 0.1 + */ + +/** + * Required for error handling + */ +require_once 'PEAR.php'; +require_once 'PEAR/Registry.php'; +require_once 'PEAR/Installer/Role.php'; +require_once 'System.php'; + +/** + * Last created PEAR_Config instance. + * @var object + */ +$GLOBALS['_PEAR_Config_instance'] = null; +if (!defined('PEAR_INSTALL_DIR') || !PEAR_INSTALL_DIR) { + $PEAR_INSTALL_DIR = PHP_LIBDIR . DIRECTORY_SEPARATOR . 'pear'; +} else { + $PEAR_INSTALL_DIR = PEAR_INSTALL_DIR; +} + +// Below we define constants with default values for all configuration +// parameters except username/password. All of them can have their +// defaults set through environment variables. The reason we use the +// PHP_ prefix is for some security, PHP protects environment +// variables starting with PHP_*. + +// default channel and preferred mirror is based on whether we are invoked through +// the "pear" or the "pecl" command +if (!defined('PEAR_RUNTYPE')) { + define('PEAR_RUNTYPE', 'pear'); +} + +if (PEAR_RUNTYPE == 'pear') { + define('PEAR_CONFIG_DEFAULT_CHANNEL', 'pear.php.net'); +} else { + define('PEAR_CONFIG_DEFAULT_CHANNEL', 'pecl.php.net'); +} + +if (getenv('PHP_PEAR_SYSCONF_DIR')) { + define('PEAR_CONFIG_SYSCONFDIR', getenv('PHP_PEAR_SYSCONF_DIR')); +} elseif (getenv('SystemRoot')) { + define('PEAR_CONFIG_SYSCONFDIR', getenv('SystemRoot')); +} else { + define('PEAR_CONFIG_SYSCONFDIR', PHP_SYSCONFDIR); +} + +// Default for master_server +if (getenv('PHP_PEAR_MASTER_SERVER')) { + define('PEAR_CONFIG_DEFAULT_MASTER_SERVER', getenv('PHP_PEAR_MASTER_SERVER')); +} else { + define('PEAR_CONFIG_DEFAULT_MASTER_SERVER', 'pear.php.net'); +} + +// Default for http_proxy +if (getenv('PHP_PEAR_HTTP_PROXY')) { + define('PEAR_CONFIG_DEFAULT_HTTP_PROXY', getenv('PHP_PEAR_HTTP_PROXY')); +} elseif (getenv('http_proxy')) { + define('PEAR_CONFIG_DEFAULT_HTTP_PROXY', getenv('http_proxy')); +} else { + define('PEAR_CONFIG_DEFAULT_HTTP_PROXY', ''); +} + +// Default for php_dir +if (getenv('PHP_PEAR_INSTALL_DIR')) { + define('PEAR_CONFIG_DEFAULT_PHP_DIR', getenv('PHP_PEAR_INSTALL_DIR')); +} else { + if (@file_exists($PEAR_INSTALL_DIR) && is_dir($PEAR_INSTALL_DIR)) { + define('PEAR_CONFIG_DEFAULT_PHP_DIR', $PEAR_INSTALL_DIR); + } else { + define('PEAR_CONFIG_DEFAULT_PHP_DIR', $PEAR_INSTALL_DIR); + } +} + +// Default for ext_dir +if (getenv('PHP_PEAR_EXTENSION_DIR')) { + define('PEAR_CONFIG_DEFAULT_EXT_DIR', getenv('PHP_PEAR_EXTENSION_DIR')); +} else { + if (ini_get('extension_dir')) { + define('PEAR_CONFIG_DEFAULT_EXT_DIR', ini_get('extension_dir')); + } elseif (defined('PEAR_EXTENSION_DIR') && + file_exists(PEAR_EXTENSION_DIR) && is_dir(PEAR_EXTENSION_DIR)) { + define('PEAR_CONFIG_DEFAULT_EXT_DIR', PEAR_EXTENSION_DIR); + } elseif (defined('PHP_EXTENSION_DIR')) { + define('PEAR_CONFIG_DEFAULT_EXT_DIR', PHP_EXTENSION_DIR); + } else { + define('PEAR_CONFIG_DEFAULT_EXT_DIR', '.'); + } +} + +// Default for doc_dir +if (getenv('PHP_PEAR_DOC_DIR')) { + define('PEAR_CONFIG_DEFAULT_DOC_DIR', getenv('PHP_PEAR_DOC_DIR')); +} else { + define('PEAR_CONFIG_DEFAULT_DOC_DIR', + $PEAR_INSTALL_DIR.DIRECTORY_SEPARATOR.'docs'); +} + +// Default for bin_dir +if (getenv('PHP_PEAR_BIN_DIR')) { + define('PEAR_CONFIG_DEFAULT_BIN_DIR', getenv('PHP_PEAR_BIN_DIR')); +} else { + define('PEAR_CONFIG_DEFAULT_BIN_DIR', PHP_BINDIR); +} + +// Default for data_dir +if (getenv('PHP_PEAR_DATA_DIR')) { + define('PEAR_CONFIG_DEFAULT_DATA_DIR', getenv('PHP_PEAR_DATA_DIR')); +} else { + define('PEAR_CONFIG_DEFAULT_DATA_DIR', + $PEAR_INSTALL_DIR.DIRECTORY_SEPARATOR.'data'); +} + +// Default for cfg_dir +if (getenv('PHP_PEAR_CFG_DIR')) { + define('PEAR_CONFIG_DEFAULT_CFG_DIR', getenv('PHP_PEAR_CFG_DIR')); +} else { + define('PEAR_CONFIG_DEFAULT_CFG_DIR', + $PEAR_INSTALL_DIR.DIRECTORY_SEPARATOR.'cfg'); +} + +// Default for www_dir +if (getenv('PHP_PEAR_WWW_DIR')) { + define('PEAR_CONFIG_DEFAULT_WWW_DIR', getenv('PHP_PEAR_WWW_DIR')); +} else { + define('PEAR_CONFIG_DEFAULT_WWW_DIR', + $PEAR_INSTALL_DIR.DIRECTORY_SEPARATOR.'www'); +} + +// Default for test_dir +if (getenv('PHP_PEAR_TEST_DIR')) { + define('PEAR_CONFIG_DEFAULT_TEST_DIR', getenv('PHP_PEAR_TEST_DIR')); +} else { + define('PEAR_CONFIG_DEFAULT_TEST_DIR', + $PEAR_INSTALL_DIR.DIRECTORY_SEPARATOR.'tests'); +} + +// Default for temp_dir +if (getenv('PHP_PEAR_TEMP_DIR')) { + define('PEAR_CONFIG_DEFAULT_TEMP_DIR', getenv('PHP_PEAR_TEMP_DIR')); +} else { + define('PEAR_CONFIG_DEFAULT_TEMP_DIR', + System::tmpdir() . DIRECTORY_SEPARATOR . 'pear' . + DIRECTORY_SEPARATOR . 'temp'); +} + +// Default for cache_dir +if (getenv('PHP_PEAR_CACHE_DIR')) { + define('PEAR_CONFIG_DEFAULT_CACHE_DIR', getenv('PHP_PEAR_CACHE_DIR')); +} else { + define('PEAR_CONFIG_DEFAULT_CACHE_DIR', + System::tmpdir() . DIRECTORY_SEPARATOR . 'pear' . + DIRECTORY_SEPARATOR . 'cache'); +} + +// Default for download_dir +if (getenv('PHP_PEAR_DOWNLOAD_DIR')) { + define('PEAR_CONFIG_DEFAULT_DOWNLOAD_DIR', getenv('PHP_PEAR_DOWNLOAD_DIR')); +} else { + define('PEAR_CONFIG_DEFAULT_DOWNLOAD_DIR', + System::tmpdir() . DIRECTORY_SEPARATOR . 'pear' . + DIRECTORY_SEPARATOR . 'download'); +} + +// Default for php_bin +if (getenv('PHP_PEAR_PHP_BIN')) { + define('PEAR_CONFIG_DEFAULT_PHP_BIN', getenv('PHP_PEAR_PHP_BIN')); +} else { + define('PEAR_CONFIG_DEFAULT_PHP_BIN', PEAR_CONFIG_DEFAULT_BIN_DIR. + DIRECTORY_SEPARATOR.'php'.(OS_WINDOWS ? '.exe' : '')); +} + +// Default for verbose +if (getenv('PHP_PEAR_VERBOSE')) { + define('PEAR_CONFIG_DEFAULT_VERBOSE', getenv('PHP_PEAR_VERBOSE')); +} else { + define('PEAR_CONFIG_DEFAULT_VERBOSE', 1); +} + +// Default for preferred_state +if (getenv('PHP_PEAR_PREFERRED_STATE')) { + define('PEAR_CONFIG_DEFAULT_PREFERRED_STATE', getenv('PHP_PEAR_PREFERRED_STATE')); +} else { + define('PEAR_CONFIG_DEFAULT_PREFERRED_STATE', 'stable'); +} + +// Default for umask +if (getenv('PHP_PEAR_UMASK')) { + define('PEAR_CONFIG_DEFAULT_UMASK', getenv('PHP_PEAR_UMASK')); +} else { + define('PEAR_CONFIG_DEFAULT_UMASK', decoct(umask())); +} + +// Default for cache_ttl +if (getenv('PHP_PEAR_CACHE_TTL')) { + define('PEAR_CONFIG_DEFAULT_CACHE_TTL', getenv('PHP_PEAR_CACHE_TTL')); +} else { + define('PEAR_CONFIG_DEFAULT_CACHE_TTL', 3600); +} + +// Default for sig_type +if (getenv('PHP_PEAR_SIG_TYPE')) { + define('PEAR_CONFIG_DEFAULT_SIG_TYPE', getenv('PHP_PEAR_SIG_TYPE')); +} else { + define('PEAR_CONFIG_DEFAULT_SIG_TYPE', 'gpg'); +} + +// Default for sig_bin +if (getenv('PHP_PEAR_SIG_BIN')) { + define('PEAR_CONFIG_DEFAULT_SIG_BIN', getenv('PHP_PEAR_SIG_BIN')); +} else { + define('PEAR_CONFIG_DEFAULT_SIG_BIN', + System::which( + 'gpg', OS_WINDOWS ? 'c:\gnupg\gpg.exe' : '/usr/local/bin/gpg')); +} + +// Default for sig_keydir +if (getenv('PHP_PEAR_SIG_KEYDIR')) { + define('PEAR_CONFIG_DEFAULT_SIG_KEYDIR', getenv('PHP_PEAR_SIG_KEYDIR')); +} else { + define('PEAR_CONFIG_DEFAULT_SIG_KEYDIR', + PEAR_CONFIG_SYSCONFDIR . DIRECTORY_SEPARATOR . 'pearkeys'); +} + +/** + * This is a class for storing configuration data, keeping track of + * which are system-defined, user-defined or defaulted. + * @category pear + * @package PEAR + * @author Stig Bakken + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.1 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 0.1 + */ +class PEAR_Config extends PEAR +{ + /** + * Array of config files used. + * + * @var array layer => config file + */ + var $files = array( + 'system' => '', + 'user' => '', + ); + + var $layers = array(); + + /** + * Configuration data, two-dimensional array where the first + * dimension is the config layer ('user', 'system' and 'default'), + * and the second dimension is keyname => value. + * + * The order in the first dimension is important! Earlier + * layers will shadow later ones when a config value is + * requested (if a 'user' value exists, it will be returned first, + * then 'system' and finally 'default'). + * + * @var array layer => array(keyname => value, ...) + */ + var $configuration = array( + 'user' => array(), + 'system' => array(), + 'default' => array(), + ); + + /** + * Configuration values that can be set for a channel + * + * All other configuration values can only have a global value + * @var array + * @access private + */ + var $_channelConfigInfo = array( + 'php_dir', 'ext_dir', 'doc_dir', 'bin_dir', 'data_dir', 'cfg_dir', + 'test_dir', 'www_dir', 'php_bin', 'php_prefix', 'php_suffix', 'username', + 'password', 'verbose', 'preferred_state', 'umask', 'preferred_mirror', 'php_ini' + ); + + /** + * Channels that can be accessed + * @see setChannels() + * @var array + * @access private + */ + var $_channels = array('pear.php.net', 'pecl.php.net', '__uri'); + + /** + * This variable is used to control the directory values returned + * @see setInstallRoot(); + * @var string|false + * @access private + */ + var $_installRoot = false; + + /** + * If requested, this will always refer to the registry + * contained in php_dir + * @var PEAR_Registry + */ + var $_registry = array(); + + /** + * @var array + * @access private + */ + var $_regInitialized = array(); + + /** + * @var bool + * @access private + */ + var $_noRegistry = false; + + /** + * amount of errors found while parsing config + * @var integer + * @access private + */ + var $_errorsFound = 0; + var $_lastError = null; + + /** + * Information about the configuration data. Stores the type, + * default value and a documentation string for each configuration + * value. + * + * @var array layer => array(infotype => value, ...) + */ + var $configuration_info = array( + // Channels/Internet Access + 'default_channel' => array( + 'type' => 'string', + 'default' => PEAR_CONFIG_DEFAULT_CHANNEL, + 'doc' => 'the default channel to use for all non explicit commands', + 'prompt' => 'Default Channel', + 'group' => 'Internet Access', + ), + 'preferred_mirror' => array( + 'type' => 'string', + 'default' => PEAR_CONFIG_DEFAULT_CHANNEL, + 'doc' => 'the default server or mirror to use for channel actions', + 'prompt' => 'Default Channel Mirror', + 'group' => 'Internet Access', + ), + 'remote_config' => array( + 'type' => 'password', + 'default' => '', + 'doc' => 'ftp url of remote configuration file to use for synchronized install', + 'prompt' => 'Remote Configuration File', + 'group' => 'Internet Access', + ), + 'auto_discover' => array( + 'type' => 'integer', + 'default' => 0, + 'doc' => 'whether to automatically discover new channels', + 'prompt' => 'Auto-discover new Channels', + 'group' => 'Internet Access', + ), + // Internet Access + 'master_server' => array( + 'type' => 'string', + 'default' => 'pear.php.net', + 'doc' => 'name of the main PEAR server [NOT USED IN THIS VERSION]', + 'prompt' => 'PEAR server [DEPRECATED]', + 'group' => 'Internet Access', + ), + 'http_proxy' => array( + 'type' => 'string', + 'default' => PEAR_CONFIG_DEFAULT_HTTP_PROXY, + 'doc' => 'HTTP proxy (host:port) to use when downloading packages', + 'prompt' => 'HTTP Proxy Server Address', + 'group' => 'Internet Access', + ), + // File Locations + 'php_dir' => array( + 'type' => 'directory', + 'default' => PEAR_CONFIG_DEFAULT_PHP_DIR, + 'doc' => 'directory where .php files are installed', + 'prompt' => 'PEAR directory', + 'group' => 'File Locations', + ), + 'ext_dir' => array( + 'type' => 'directory', + 'default' => PEAR_CONFIG_DEFAULT_EXT_DIR, + 'doc' => 'directory where loadable extensions are installed', + 'prompt' => 'PHP extension directory', + 'group' => 'File Locations', + ), + 'doc_dir' => array( + 'type' => 'directory', + 'default' => PEAR_CONFIG_DEFAULT_DOC_DIR, + 'doc' => 'directory where documentation is installed', + 'prompt' => 'PEAR documentation directory', + 'group' => 'File Locations', + ), + 'bin_dir' => array( + 'type' => 'directory', + 'default' => PEAR_CONFIG_DEFAULT_BIN_DIR, + 'doc' => 'directory where executables are installed', + 'prompt' => 'PEAR executables directory', + 'group' => 'File Locations', + ), + 'data_dir' => array( + 'type' => 'directory', + 'default' => PEAR_CONFIG_DEFAULT_DATA_DIR, + 'doc' => 'directory where data files are installed', + 'prompt' => 'PEAR data directory', + 'group' => 'File Locations (Advanced)', + ), + 'cfg_dir' => array( + 'type' => 'directory', + 'default' => PEAR_CONFIG_DEFAULT_CFG_DIR, + 'doc' => 'directory where modifiable configuration files are installed', + 'prompt' => 'PEAR configuration file directory', + 'group' => 'File Locations (Advanced)', + ), + 'www_dir' => array( + 'type' => 'directory', + 'default' => PEAR_CONFIG_DEFAULT_WWW_DIR, + 'doc' => 'directory where www frontend files (html/js) are installed', + 'prompt' => 'PEAR www files directory', + 'group' => 'File Locations (Advanced)', + ), + 'test_dir' => array( + 'type' => 'directory', + 'default' => PEAR_CONFIG_DEFAULT_TEST_DIR, + 'doc' => 'directory where regression tests are installed', + 'prompt' => 'PEAR test directory', + 'group' => 'File Locations (Advanced)', + ), + 'cache_dir' => array( + 'type' => 'directory', + 'default' => PEAR_CONFIG_DEFAULT_CACHE_DIR, + 'doc' => 'directory which is used for web service cache', + 'prompt' => 'PEAR Installer cache directory', + 'group' => 'File Locations (Advanced)', + ), + 'temp_dir' => array( + 'type' => 'directory', + 'default' => PEAR_CONFIG_DEFAULT_TEMP_DIR, + 'doc' => 'directory which is used for all temp files', + 'prompt' => 'PEAR Installer temp directory', + 'group' => 'File Locations (Advanced)', + ), + 'download_dir' => array( + 'type' => 'directory', + 'default' => PEAR_CONFIG_DEFAULT_DOWNLOAD_DIR, + 'doc' => 'directory which is used for all downloaded files', + 'prompt' => 'PEAR Installer download directory', + 'group' => 'File Locations (Advanced)', + ), + 'php_bin' => array( + 'type' => 'file', + 'default' => PEAR_CONFIG_DEFAULT_PHP_BIN, + 'doc' => 'PHP CLI/CGI binary for executing scripts', + 'prompt' => 'PHP CLI/CGI binary', + 'group' => 'File Locations (Advanced)', + ), + 'php_prefix' => array( + 'type' => 'string', + 'default' => '', + 'doc' => '--program-prefix for php_bin\'s ./configure, used for pecl installs', + 'prompt' => '--program-prefix passed to PHP\'s ./configure', + 'group' => 'File Locations (Advanced)', + ), + 'php_suffix' => array( + 'type' => 'string', + 'default' => '', + 'doc' => '--program-suffix for php_bin\'s ./configure, used for pecl installs', + 'prompt' => '--program-suffix passed to PHP\'s ./configure', + 'group' => 'File Locations (Advanced)', + ), + 'php_ini' => array( + 'type' => 'file', + 'default' => '', + 'doc' => 'location of php.ini in which to enable PECL extensions on install', + 'prompt' => 'php.ini location', + 'group' => 'File Locations (Advanced)', + ), + // Maintainers + 'username' => array( + 'type' => 'string', + 'default' => '', + 'doc' => '(maintainers) your PEAR account name', + 'prompt' => 'PEAR username (for maintainers)', + 'group' => 'Maintainers', + ), + 'password' => array( + 'type' => 'password', + 'default' => '', + 'doc' => '(maintainers) your PEAR account password', + 'prompt' => 'PEAR password (for maintainers)', + 'group' => 'Maintainers', + ), + // Advanced + 'verbose' => array( + 'type' => 'integer', + 'default' => PEAR_CONFIG_DEFAULT_VERBOSE, + 'doc' => 'verbosity level +0: really quiet +1: somewhat quiet +2: verbose +3: debug', + 'prompt' => 'Debug Log Level', + 'group' => 'Advanced', + ), + 'preferred_state' => array( + 'type' => 'set', + 'default' => PEAR_CONFIG_DEFAULT_PREFERRED_STATE, + 'doc' => 'the installer will prefer releases with this state when installing packages without a version or state specified', + 'valid_set' => array( + 'stable', 'beta', 'alpha', 'devel', 'snapshot'), + 'prompt' => 'Preferred Package State', + 'group' => 'Advanced', + ), + 'umask' => array( + 'type' => 'mask', + 'default' => PEAR_CONFIG_DEFAULT_UMASK, + 'doc' => 'umask used when creating files (Unix-like systems only)', + 'prompt' => 'Unix file mask', + 'group' => 'Advanced', + ), + 'cache_ttl' => array( + 'type' => 'integer', + 'default' => PEAR_CONFIG_DEFAULT_CACHE_TTL, + 'doc' => 'amount of secs where the local cache is used and not updated', + 'prompt' => 'Cache TimeToLive', + 'group' => 'Advanced', + ), + 'sig_type' => array( + 'type' => 'set', + 'default' => PEAR_CONFIG_DEFAULT_SIG_TYPE, + 'doc' => 'which package signature mechanism to use', + 'valid_set' => array('gpg'), + 'prompt' => 'Package Signature Type', + 'group' => 'Maintainers', + ), + 'sig_bin' => array( + 'type' => 'string', + 'default' => PEAR_CONFIG_DEFAULT_SIG_BIN, + 'doc' => 'which package signature mechanism to use', + 'prompt' => 'Signature Handling Program', + 'group' => 'Maintainers', + ), + 'sig_keyid' => array( + 'type' => 'string', + 'default' => '', + 'doc' => 'which key to use for signing with', + 'prompt' => 'Signature Key Id', + 'group' => 'Maintainers', + ), + 'sig_keydir' => array( + 'type' => 'directory', + 'default' => PEAR_CONFIG_DEFAULT_SIG_KEYDIR, + 'doc' => 'directory where signature keys are located', + 'prompt' => 'Signature Key Directory', + 'group' => 'Maintainers', + ), + // __channels is reserved - used for channel-specific configuration + ); + + /** + * Constructor. + * + * @param string file to read user-defined options from + * @param string file to read system-wide defaults from + * @param bool determines whether a registry object "follows" + * the value of php_dir (is automatically created + * and moved when php_dir is changed) + * @param bool if true, fails if configuration files cannot be loaded + * + * @access public + * + * @see PEAR_Config::singleton + */ + function PEAR_Config($user_file = '', $system_file = '', $ftp_file = false, + $strict = true) + { + $this->PEAR(); + PEAR_Installer_Role::initializeConfig($this); + $sl = DIRECTORY_SEPARATOR; + if (empty($user_file)) { + if (OS_WINDOWS) { + $user_file = PEAR_CONFIG_SYSCONFDIR . $sl . 'pear.ini'; + } else { + $user_file = getenv('HOME') . $sl . '.pearrc'; + } + } + + if (empty($system_file)) { + $system_file = PEAR_CONFIG_SYSCONFDIR . $sl; + if (OS_WINDOWS) { + $system_file .= 'pearsys.ini'; + } else { + $system_file .= 'pear.conf'; + } + } + + $this->layers = array_keys($this->configuration); + $this->files['user'] = $user_file; + $this->files['system'] = $system_file; + if ($user_file && file_exists($user_file)) { + $this->pushErrorHandling(PEAR_ERROR_RETURN); + $this->readConfigFile($user_file, 'user', $strict); + $this->popErrorHandling(); + if ($this->_errorsFound > 0) { + return; + } + } + + if ($system_file && @file_exists($system_file)) { + $this->mergeConfigFile($system_file, false, 'system', $strict); + if ($this->_errorsFound > 0) { + return; + } + + } + + if (!$ftp_file) { + $ftp_file = $this->get('remote_config'); + } + + if ($ftp_file && defined('PEAR_REMOTEINSTALL_OK')) { + $this->readFTPConfigFile($ftp_file); + } + + foreach ($this->configuration_info as $key => $info) { + $this->configuration['default'][$key] = $info['default']; + } + + $this->_registry['default'] = &new PEAR_Registry($this->configuration['default']['php_dir']); + $this->_registry['default']->setConfig($this, false); + $this->_regInitialized['default'] = false; + //$GLOBALS['_PEAR_Config_instance'] = &$this; + } + + /** + * Return the default locations of user and system configuration files + * @static + */ + function getDefaultConfigFiles() + { + $sl = DIRECTORY_SEPARATOR; + if (OS_WINDOWS) { + return array( + 'user' => PEAR_CONFIG_SYSCONFDIR . $sl . 'pear.ini', + 'system' => PEAR_CONFIG_SYSCONFDIR . $sl . 'pearsys.ini' + ); + } + + return array( + 'user' => getenv('HOME') . $sl . '.pearrc', + 'system' => PEAR_CONFIG_SYSCONFDIR . $sl . 'pear.conf' + ); + } + + /** + * Static singleton method. If you want to keep only one instance + * of this class in use, this method will give you a reference to + * the last created PEAR_Config object if one exists, or create a + * new object. + * + * @param string (optional) file to read user-defined options from + * @param string (optional) file to read system-wide defaults from + * + * @return object an existing or new PEAR_Config instance + * + * @access public + * + * @see PEAR_Config::PEAR_Config + */ + function &singleton($user_file = '', $system_file = '', $strict = true) + { + if (is_object($GLOBALS['_PEAR_Config_instance'])) { + return $GLOBALS['_PEAR_Config_instance']; + } + + $t_conf = &new PEAR_Config($user_file, $system_file, false, $strict); + if ($t_conf->_errorsFound > 0) { + return $t_conf->lastError; + } + + $GLOBALS['_PEAR_Config_instance'] = &$t_conf; + return $GLOBALS['_PEAR_Config_instance']; + } + + /** + * Determine whether any configuration files have been detected, and whether a + * registry object can be retrieved from this configuration. + * @return bool + * @since PEAR 1.4.0a1 + */ + function validConfiguration() + { + if ($this->isDefinedLayer('user') || $this->isDefinedLayer('system')) { + return true; + } + + return false; + } + + /** + * Reads configuration data from a file. All existing values in + * the config layer are discarded and replaced with data from the + * file. + * @param string file to read from, if NULL or not specified, the + * last-used file for the same layer (second param) is used + * @param string config layer to insert data into ('user' or 'system') + * @return bool TRUE on success or a PEAR error on failure + */ + function readConfigFile($file = null, $layer = 'user', $strict = true) + { + if (empty($this->files[$layer])) { + return $this->raiseError("unknown config layer `$layer'"); + } + + if ($file === null) { + $file = $this->files[$layer]; + } + + $data = $this->_readConfigDataFrom($file); + if (PEAR::isError($data)) { + if (!$strict) { + return true; + } + + $this->_errorsFound++; + $this->lastError = $data; + + return $data; + } + + $this->files[$layer] = $file; + $this->_decodeInput($data); + $this->configuration[$layer] = $data; + $this->_setupChannels(); + if (!$this->_noRegistry && ($phpdir = $this->get('php_dir', $layer, 'pear.php.net'))) { + $this->_registry[$layer] = &new PEAR_Registry($phpdir); + $this->_registry[$layer]->setConfig($this, false); + $this->_regInitialized[$layer] = false; + } else { + unset($this->_registry[$layer]); + } + return true; + } + + /** + * @param string url to the remote config file, like ftp://www.example.com/pear/config.ini + * @return true|PEAR_Error + */ + function readFTPConfigFile($path) + { + do { // poor man's try + if (!class_exists('PEAR_FTP')) { + if (!class_exists('PEAR_Common')) { + require_once 'PEAR/Common.php'; + } + if (PEAR_Common::isIncludeable('PEAR/FTP.php')) { + require_once 'PEAR/FTP.php'; + } + } + + if (!class_exists('PEAR_FTP')) { + return PEAR::raiseError('PEAR_RemoteInstaller must be installed to use remote config'); + } + + $this->_ftp = &new PEAR_FTP; + $this->_ftp->pushErrorHandling(PEAR_ERROR_RETURN); + $e = $this->_ftp->init($path); + if (PEAR::isError($e)) { + $this->_ftp->popErrorHandling(); + return $e; + } + + $tmp = System::mktemp('-d'); + PEAR_Common::addTempFile($tmp); + $e = $this->_ftp->get(basename($path), $tmp . DIRECTORY_SEPARATOR . + 'pear.ini', false, FTP_BINARY); + if (PEAR::isError($e)) { + $this->_ftp->popErrorHandling(); + return $e; + } + + PEAR_Common::addTempFile($tmp . DIRECTORY_SEPARATOR . 'pear.ini'); + $this->_ftp->disconnect(); + $this->_ftp->popErrorHandling(); + $this->files['ftp'] = $tmp . DIRECTORY_SEPARATOR . 'pear.ini'; + $e = $this->readConfigFile(null, 'ftp'); + if (PEAR::isError($e)) { + return $e; + } + + $fail = array(); + foreach ($this->configuration_info as $key => $val) { + if (in_array($this->getGroup($key), + array('File Locations', 'File Locations (Advanced)')) && + $this->getType($key) == 'directory') { + // any directory configs must be set for this to work + if (!isset($this->configuration['ftp'][$key])) { + $fail[] = $key; + } + } + } + + if (!count($fail)) { + return true; + } + + $fail = '"' . implode('", "', $fail) . '"'; + unset($this->files['ftp']); + unset($this->configuration['ftp']); + return PEAR::raiseError('ERROR: Ftp configuration file must set all ' . + 'directory configuration variables. These variables were not set: ' . + $fail); + } while (false); // poor man's catch + unset($this->files['ftp']); + return PEAR::raiseError('no remote host specified'); + } + + /** + * Reads the existing configurations and creates the _channels array from it + */ + function _setupChannels() + { + $set = array_flip(array_values($this->_channels)); + foreach ($this->configuration as $layer => $data) { + $i = 1000; + if (isset($data['__channels']) && is_array($data['__channels'])) { + foreach ($data['__channels'] as $channel => $info) { + $set[$channel] = $i++; + } + } + } + $this->_channels = array_values(array_flip($set)); + $this->setChannels($this->_channels); + } + + function deleteChannel($channel) + { + $ch = strtolower($channel); + foreach ($this->configuration as $layer => $data) { + if (isset($data['__channels']) && isset($data['__channels'][$ch])) { + unset($this->configuration[$layer]['__channels'][$ch]); + } + } + + $this->_channels = array_flip($this->_channels); + unset($this->_channels[$ch]); + $this->_channels = array_flip($this->_channels); + } + + /** + * Merges data into a config layer from a file. Does the same + * thing as readConfigFile, except it does not replace all + * existing values in the config layer. + * @param string file to read from + * @param bool whether to overwrite existing data (default TRUE) + * @param string config layer to insert data into ('user' or 'system') + * @param string if true, errors are returned if file opening fails + * @return bool TRUE on success or a PEAR error on failure + */ + function mergeConfigFile($file, $override = true, $layer = 'user', $strict = true) + { + if (empty($this->files[$layer])) { + return $this->raiseError("unknown config layer `$layer'"); + } + + if ($file === null) { + $file = $this->files[$layer]; + } + + $data = $this->_readConfigDataFrom($file); + if (PEAR::isError($data)) { + if (!$strict) { + return true; + } + + $this->_errorsFound++; + $this->lastError = $data; + + return $data; + } + + $this->_decodeInput($data); + if ($override) { + $this->configuration[$layer] = + PEAR_Config::arrayMergeRecursive($this->configuration[$layer], $data); + } else { + $this->configuration[$layer] = + PEAR_Config::arrayMergeRecursive($data, $this->configuration[$layer]); + } + + $this->_setupChannels(); + if (!$this->_noRegistry && ($phpdir = $this->get('php_dir', $layer, 'pear.php.net'))) { + $this->_registry[$layer] = &new PEAR_Registry($phpdir); + $this->_registry[$layer]->setConfig($this, false); + $this->_regInitialized[$layer] = false; + } else { + unset($this->_registry[$layer]); + } + return true; + } + + /** + * @param array + * @param array + * @return array + * @static + */ + function arrayMergeRecursive($arr2, $arr1) + { + $ret = array(); + foreach ($arr2 as $key => $data) { + if (!isset($arr1[$key])) { + $ret[$key] = $data; + unset($arr1[$key]); + continue; + } + if (is_array($data)) { + if (!is_array($arr1[$key])) { + $ret[$key] = $arr1[$key]; + unset($arr1[$key]); + continue; + } + $ret[$key] = PEAR_Config::arrayMergeRecursive($arr1[$key], $arr2[$key]); + unset($arr1[$key]); + } + } + + return array_merge($ret, $arr1); + } + + /** + * Writes data into a config layer from a file. + * + * @param string|null file to read from, or null for default + * @param string config layer to insert data into ('user' or + * 'system') + * @param string|null data to write to config file or null for internal data [DEPRECATED] + * @return bool TRUE on success or a PEAR error on failure + */ + function writeConfigFile($file = null, $layer = 'user', $data = null) + { + $this->_lazyChannelSetup($layer); + if ($layer == 'both' || $layer == 'all') { + foreach ($this->files as $type => $file) { + $err = $this->writeConfigFile($file, $type, $data); + if (PEAR::isError($err)) { + return $err; + } + } + return true; + } + + if (empty($this->files[$layer])) { + return $this->raiseError("unknown config file type `$layer'"); + } + + if ($file === null) { + $file = $this->files[$layer]; + } + + $data = ($data === null) ? $this->configuration[$layer] : $data; + $this->_encodeOutput($data); + $opt = array('-p', dirname($file)); + if (!@System::mkDir($opt)) { + return $this->raiseError("could not create directory: " . dirname($file)); + } + + if (file_exists($file) && is_file($file) && !is_writeable($file)) { + return $this->raiseError("no write access to $file!"); + } + + $fp = @fopen($file, "w"); + if (!$fp) { + return $this->raiseError("PEAR_Config::writeConfigFile fopen('$file','w') failed ($php_errormsg)"); + } + + $contents = "#PEAR_Config 0.9\n" . serialize($data); + if (!@fwrite($fp, $contents)) { + return $this->raiseError("PEAR_Config::writeConfigFile: fwrite failed ($php_errormsg)"); + } + return true; + } + + /** + * Reads configuration data from a file and returns the parsed data + * in an array. + * + * @param string file to read from + * @return array configuration data or a PEAR error on failure + * @access private + */ + function _readConfigDataFrom($file) + { + $fp = false; + if (file_exists($file)) { + $fp = @fopen($file, "r"); + } + + if (!$fp) { + return $this->raiseError("PEAR_Config::readConfigFile fopen('$file','r') failed"); + } + + $size = filesize($file); + $rt = get_magic_quotes_runtime(); + set_magic_quotes_runtime(0); + fclose($fp); + $contents = file_get_contents($file); + if (empty($contents)) { + return $this->raiseError('Configuration file "' . $file . '" is empty'); + } + + set_magic_quotes_runtime($rt); + + $version = false; + if (preg_match('/^#PEAR_Config\s+(\S+)\s+/si', $contents, $matches)) { + $version = $matches[1]; + $contents = substr($contents, strlen($matches[0])); + } else { + // Museum config file + if (substr($contents,0,2) == 'a:') { + $version = '0.1'; + } + } + + if ($version && version_compare("$version", '1', '<')) { + // no '@', it is possible that unserialize + // raises a notice but it seems to block IO to + // STDOUT if a '@' is used and a notice is raise + $data = unserialize($contents); + + if (!is_array($data) && !$data) { + if ($contents == serialize(false)) { + $data = array(); + } else { + $err = $this->raiseError("PEAR_Config: bad data in $file"); + return $err; + } + } + if (!is_array($data)) { + if (strlen(trim($contents)) > 0) { + $error = "PEAR_Config: bad data in $file"; + $err = $this->raiseError($error); + return $err; + } + + $data = array(); + } + // add parsing of newer formats here... + } else { + $err = $this->raiseError("$file: unknown version `$version'"); + return $err; + } + + return $data; + } + + /** + * Gets the file used for storing the config for a layer + * + * @param string $layer 'user' or 'system' + */ + function getConfFile($layer) + { + return $this->files[$layer]; + } + + /** + * @param string Configuration class name, used for detecting duplicate calls + * @param array information on a role as parsed from its xml file + * @return true|PEAR_Error + * @access private + */ + function _addConfigVars($class, $vars) + { + static $called = array(); + if (isset($called[$class])) { + return; + } + + $called[$class] = 1; + if (count($vars) > 3) { + return $this->raiseError('Roles can only define 3 new config variables or less'); + } + + foreach ($vars as $name => $var) { + if (!is_array($var)) { + return $this->raiseError('Configuration information must be an array'); + } + + if (!isset($var['type'])) { + return $this->raiseError('Configuration information must contain a type'); + } elseif (!in_array($var['type'], + array('string', 'mask', 'password', 'directory', 'file', 'set'))) { + return $this->raiseError( + 'Configuration type must be one of directory, file, string, ' . + 'mask, set, or password'); + } + if (!isset($var['default'])) { + return $this->raiseError( + 'Configuration information must contain a default value ("default" index)'); + } + + if (is_array($var['default'])) { + $real_default = ''; + foreach ($var['default'] as $config_var => $val) { + if (strpos($config_var, 'text') === 0) { + $real_default .= $val; + } elseif (strpos($config_var, 'constant') === 0) { + if (!defined($val)) { + return $this->raiseError( + 'Unknown constant "' . $val . '" requested in ' . + 'default value for configuration variable "' . + $name . '"'); + } + + $real_default .= constant($val); + } elseif (isset($this->configuration_info[$config_var])) { + $real_default .= + $this->configuration_info[$config_var]['default']; + } else { + return $this->raiseError( + 'Unknown request for "' . $config_var . '" value in ' . + 'default value for configuration variable "' . + $name . '"'); + } + } + $var['default'] = $real_default; + } + + if ($var['type'] == 'integer') { + $var['default'] = (integer) $var['default']; + } + + if (!isset($var['doc'])) { + return $this->raiseError( + 'Configuration information must contain a summary ("doc" index)'); + } + + if (!isset($var['prompt'])) { + return $this->raiseError( + 'Configuration information must contain a simple prompt ("prompt" index)'); + } + + if (!isset($var['group'])) { + return $this->raiseError( + 'Configuration information must contain a simple group ("group" index)'); + } + + if (isset($this->configuration_info[$name])) { + return $this->raiseError('Configuration variable "' . $name . + '" already exists'); + } + + $this->configuration_info[$name] = $var; + // fix bug #7351: setting custom config variable in a channel fails + $this->_channelConfigInfo[] = $name; + } + + return true; + } + + /** + * Encodes/scrambles configuration data before writing to files. + * Currently, 'password' values will be base64-encoded as to avoid + * that people spot cleartext passwords by accident. + * + * @param array (reference) array to encode values in + * @return bool TRUE on success + * @access private + */ + function _encodeOutput(&$data) + { + foreach ($data as $key => $value) { + if ($key == '__channels') { + foreach ($data['__channels'] as $channel => $blah) { + $this->_encodeOutput($data['__channels'][$channel]); + } + } + + if (!isset($this->configuration_info[$key])) { + continue; + } + + $type = $this->configuration_info[$key]['type']; + switch ($type) { + // we base64-encode passwords so they are at least + // not shown in plain by accident + case 'password': { + $data[$key] = base64_encode($data[$key]); + break; + } + case 'mask': { + $data[$key] = octdec($data[$key]); + break; + } + } + } + + return true; + } + + /** + * Decodes/unscrambles configuration data after reading from files. + * + * @param array (reference) array to encode values in + * @return bool TRUE on success + * @access private + * + * @see PEAR_Config::_encodeOutput + */ + function _decodeInput(&$data) + { + if (!is_array($data)) { + return true; + } + + foreach ($data as $key => $value) { + if ($key == '__channels') { + foreach ($data['__channels'] as $channel => $blah) { + $this->_decodeInput($data['__channels'][$channel]); + } + } + + if (!isset($this->configuration_info[$key])) { + continue; + } + + $type = $this->configuration_info[$key]['type']; + switch ($type) { + case 'password': { + $data[$key] = base64_decode($data[$key]); + break; + } + case 'mask': { + $data[$key] = decoct($data[$key]); + break; + } + } + } + + return true; + } + + /** + * Retrieve the default channel. + * + * On startup, channels are not initialized, so if the default channel is not + * pear.php.net, then initialize the config. + * @param string registry layer + * @return string|false + */ + function getDefaultChannel($layer = null) + { + $ret = false; + if ($layer === null) { + foreach ($this->layers as $layer) { + if (isset($this->configuration[$layer]['default_channel'])) { + $ret = $this->configuration[$layer]['default_channel']; + break; + } + } + } elseif (isset($this->configuration[$layer]['default_channel'])) { + $ret = $this->configuration[$layer]['default_channel']; + } + + if ($ret == 'pear.php.net' && defined('PEAR_RUNTYPE') && PEAR_RUNTYPE == 'pecl') { + $ret = 'pecl.php.net'; + } + + if ($ret) { + if ($ret != 'pear.php.net') { + $this->_lazyChannelSetup(); + } + + return $ret; + } + + return PEAR_CONFIG_DEFAULT_CHANNEL; + } + + /** + * Returns a configuration value, prioritizing layers as per the + * layers property. + * + * @param string config key + * @return mixed the config value, or NULL if not found + * @access public + */ + function get($key, $layer = null, $channel = false) + { + if (!isset($this->configuration_info[$key])) { + return null; + } + + if ($key == '__channels') { + return null; + } + + if ($key == 'default_channel') { + return $this->getDefaultChannel($layer); + } + + if (!$channel) { + $channel = $this->getDefaultChannel(); + } elseif ($channel != 'pear.php.net') { + $this->_lazyChannelSetup(); + } + $channel = strtolower($channel); + + $test = (in_array($key, $this->_channelConfigInfo)) ? + $this->_getChannelValue($key, $layer, $channel) : + null; + if ($test !== null) { + if ($this->_installRoot) { + if (in_array($this->getGroup($key), + array('File Locations', 'File Locations (Advanced)')) && + $this->getType($key) == 'directory') { + return $this->_prependPath($test, $this->_installRoot); + } + } + return $test; + } + + if ($layer === null) { + foreach ($this->layers as $layer) { + if (isset($this->configuration[$layer][$key])) { + $test = $this->configuration[$layer][$key]; + if ($this->_installRoot) { + if (in_array($this->getGroup($key), + array('File Locations', 'File Locations (Advanced)')) && + $this->getType($key) == 'directory') { + return $this->_prependPath($test, $this->_installRoot); + } + } + + if ($key == 'preferred_mirror') { + $reg = &$this->getRegistry(); + if (is_object($reg)) { + $chan = &$reg->getChannel($channel); + if (PEAR::isError($chan)) { + return $channel; + } + + if (!$chan->getMirror($test) && $chan->getName() != $test) { + return $channel; // mirror does not exist + } + } + } + return $test; + } + } + } elseif (isset($this->configuration[$layer][$key])) { + $test = $this->configuration[$layer][$key]; + if ($this->_installRoot) { + if (in_array($this->getGroup($key), + array('File Locations', 'File Locations (Advanced)')) && + $this->getType($key) == 'directory') { + return $this->_prependPath($test, $this->_installRoot); + } + } + + if ($key == 'preferred_mirror') { + $reg = &$this->getRegistry(); + if (is_object($reg)) { + $chan = &$reg->getChannel($channel); + if (PEAR::isError($chan)) { + return $channel; + } + + if (!$chan->getMirror($test) && $chan->getName() != $test) { + return $channel; // mirror does not exist + } + } + } + + return $test; + } + + return null; + } + + /** + * Returns a channel-specific configuration value, prioritizing layers as per the + * layers property. + * + * @param string config key + * @return mixed the config value, or NULL if not found + * @access private + */ + function _getChannelValue($key, $layer, $channel) + { + if ($key == '__channels' || $channel == 'pear.php.net') { + return null; + } + + $ret = null; + if ($layer === null) { + foreach ($this->layers as $ilayer) { + if (isset($this->configuration[$ilayer]['__channels'][$channel][$key])) { + $ret = $this->configuration[$ilayer]['__channels'][$channel][$key]; + break; + } + } + } elseif (isset($this->configuration[$layer]['__channels'][$channel][$key])) { + $ret = $this->configuration[$layer]['__channels'][$channel][$key]; + } + + if ($key != 'preferred_mirror') { + return $ret; + } + + + if ($ret !== null) { + $reg = &$this->getRegistry($layer); + if (is_object($reg)) { + $chan = &$reg->getChannel($channel); + if (PEAR::isError($chan)) { + return $channel; + } + + if (!$chan->getMirror($ret) && $chan->getName() != $ret) { + return $channel; // mirror does not exist + } + } + + return $ret; + } + + if ($channel != $this->getDefaultChannel($layer)) { + return $channel; // we must use the channel name as the preferred mirror + // if the user has not chosen an alternate + } + + return $this->getDefaultChannel($layer); + } + + /** + * Set a config value in a specific layer (defaults to 'user'). + * Enforces the types defined in the configuration_info array. An + * integer config variable will be cast to int, and a set config + * variable will be validated against its legal values. + * + * @param string config key + * @param string config value + * @param string (optional) config layer + * @param string channel to set this value for, or null for global value + * @return bool TRUE on success, FALSE on failure + */ + function set($key, $value, $layer = 'user', $channel = false) + { + if ($key == '__channels') { + return false; + } + + if (!isset($this->configuration[$layer])) { + return false; + } + + if ($key == 'default_channel') { + // can only set this value globally + $channel = 'pear.php.net'; + if ($value != 'pear.php.net') { + $this->_lazyChannelSetup($layer); + } + } + + if ($key == 'preferred_mirror') { + if ($channel == '__uri') { + return false; // can't set the __uri pseudo-channel's mirror + } + + $reg = &$this->getRegistry($layer); + if (is_object($reg)) { + $chan = &$reg->getChannel($channel ? $channel : 'pear.php.net'); + if (PEAR::isError($chan)) { + return false; + } + + if (!$chan->getMirror($value) && $chan->getName() != $value) { + return false; // mirror does not exist + } + } + } + + if (!isset($this->configuration_info[$key])) { + return false; + } + + extract($this->configuration_info[$key]); + switch ($type) { + case 'integer': + $value = (int)$value; + break; + case 'set': { + // If a valid_set is specified, require the value to + // be in the set. If there is no valid_set, accept + // any value. + if ($valid_set) { + reset($valid_set); + if ((key($valid_set) === 0 && !in_array($value, $valid_set)) || + (key($valid_set) !== 0 && empty($valid_set[$value]))) + { + return false; + } + } + break; + } + } + + if (!$channel) { + $channel = $this->get('default_channel', null, 'pear.php.net'); + } + + if (!in_array($channel, $this->_channels)) { + $this->_lazyChannelSetup($layer); + $reg = &$this->getRegistry($layer); + if ($reg) { + $channel = $reg->channelName($channel); + } + + if (!in_array($channel, $this->_channels)) { + return false; + } + } + + if ($channel != 'pear.php.net') { + if (in_array($key, $this->_channelConfigInfo)) { + $this->configuration[$layer]['__channels'][$channel][$key] = $value; + return true; + } + + return false; + } + + if ($key == 'default_channel') { + if (!isset($reg)) { + $reg = &$this->getRegistry($layer); + if (!$reg) { + $reg = &$this->getRegistry(); + } + } + + if ($reg) { + $value = $reg->channelName($value); + } + + if (!$value) { + return false; + } + } + + $this->configuration[$layer][$key] = $value; + if ($key == 'php_dir' && !$this->_noRegistry) { + if (!isset($this->_registry[$layer]) || + $value != $this->_registry[$layer]->install_dir) { + $this->_registry[$layer] = &new PEAR_Registry($value); + $this->_regInitialized[$layer] = false; + $this->_registry[$layer]->setConfig($this, false); + } + } + + return true; + } + + function _lazyChannelSetup($uselayer = false) + { + if ($this->_noRegistry) { + return; + } + + $merge = false; + foreach ($this->_registry as $layer => $p) { + if ($uselayer && $uselayer != $layer) { + continue; + } + + if (!$this->_regInitialized[$layer]) { + if ($layer == 'default' && isset($this->_registry['user']) || + isset($this->_registry['system'])) { + // only use the default registry if there are no alternatives + continue; + } + + if (!is_object($this->_registry[$layer])) { + if ($phpdir = $this->get('php_dir', $layer, 'pear.php.net')) { + $this->_registry[$layer] = &new PEAR_Registry($phpdir); + $this->_registry[$layer]->setConfig($this, false); + $this->_regInitialized[$layer] = false; + } else { + unset($this->_registry[$layer]); + return; + } + } + + $this->setChannels($this->_registry[$layer]->listChannels(), $merge); + $this->_regInitialized[$layer] = true; + $merge = true; + } + } + } + + /** + * Set the list of channels. + * + * This should be set via a call to {@link PEAR_Registry::listChannels()} + * @param array + * @param bool + * @return bool success of operation + */ + function setChannels($channels, $merge = false) + { + if (!is_array($channels)) { + return false; + } + + if ($merge) { + $this->_channels = array_merge($this->_channels, $channels); + } else { + $this->_channels = $channels; + } + + foreach ($channels as $channel) { + $channel = strtolower($channel); + if ($channel == 'pear.php.net') { + continue; + } + + foreach ($this->layers as $layer) { + if (!isset($this->configuration[$layer]['__channels'])) { + $this->configuration[$layer]['__channels'] = array(); + } + if (!isset($this->configuration[$layer]['__channels'][$channel]) + || !is_array($this->configuration[$layer]['__channels'][$channel])) { + $this->configuration[$layer]['__channels'][$channel] = array(); + } + } + } + + return true; + } + + /** + * Get the type of a config value. + * + * @param string config key + * + * @return string type, one of "string", "integer", "file", + * "directory", "set" or "password". + * + * @access public + * + */ + function getType($key) + { + if (isset($this->configuration_info[$key])) { + return $this->configuration_info[$key]['type']; + } + return false; + } + + /** + * Get the documentation for a config value. + * + * @param string config key + * @return string documentation string + * + * @access public + * + */ + function getDocs($key) + { + if (isset($this->configuration_info[$key])) { + return $this->configuration_info[$key]['doc']; + } + + return false; + } + + /** + * Get the short documentation for a config value. + * + * @param string config key + * @return string short documentation string + * + * @access public + * + */ + function getPrompt($key) + { + if (isset($this->configuration_info[$key])) { + return $this->configuration_info[$key]['prompt']; + } + + return false; + } + + /** + * Get the parameter group for a config key. + * + * @param string config key + * @return string parameter group + * + * @access public + * + */ + function getGroup($key) + { + if (isset($this->configuration_info[$key])) { + return $this->configuration_info[$key]['group']; + } + + return false; + } + + /** + * Get the list of parameter groups. + * + * @return array list of parameter groups + * + * @access public + * + */ + function getGroups() + { + $tmp = array(); + foreach ($this->configuration_info as $key => $info) { + $tmp[$info['group']] = 1; + } + + return array_keys($tmp); + } + + /** + * Get the list of the parameters in a group. + * + * @param string $group parameter group + * @return array list of parameters in $group + * + * @access public + * + */ + function getGroupKeys($group) + { + $keys = array(); + foreach ($this->configuration_info as $key => $info) { + if ($info['group'] == $group) { + $keys[] = $key; + } + } + + return $keys; + } + + /** + * Get the list of allowed set values for a config value. Returns + * NULL for config values that are not sets. + * + * @param string config key + * @return array enumerated array of set values, or NULL if the + * config key is unknown or not a set + * + * @access public + * + */ + function getSetValues($key) + { + if (isset($this->configuration_info[$key]) && + isset($this->configuration_info[$key]['type']) && + $this->configuration_info[$key]['type'] == 'set') + { + $valid_set = $this->configuration_info[$key]['valid_set']; + reset($valid_set); + if (key($valid_set) === 0) { + return $valid_set; + } + + return array_keys($valid_set); + } + + return null; + } + + /** + * Get all the current config keys. + * + * @return array simple array of config keys + * + * @access public + */ + function getKeys() + { + $keys = array(); + foreach ($this->layers as $layer) { + $test = $this->configuration[$layer]; + if (isset($test['__channels'])) { + foreach ($test['__channels'] as $channel => $configs) { + $keys = array_merge($keys, $configs); + } + } + + unset($test['__channels']); + $keys = array_merge($keys, $test); + + } + return array_keys($keys); + } + + /** + * Remove the a config key from a specific config layer. + * + * @param string config key + * @param string (optional) config layer + * @param string (optional) channel (defaults to default channel) + * @return bool TRUE on success, FALSE on failure + * + * @access public + */ + function remove($key, $layer = 'user', $channel = null) + { + if ($channel === null) { + $channel = $this->getDefaultChannel(); + } + + if ($channel !== 'pear.php.net') { + if (isset($this->configuration[$layer]['__channels'][$channel][$key])) { + unset($this->configuration[$layer]['__channels'][$channel][$key]); + return true; + } + } + + if (isset($this->configuration[$layer][$key])) { + unset($this->configuration[$layer][$key]); + return true; + } + + return false; + } + + /** + * Temporarily remove an entire config layer. USE WITH CARE! + * + * @param string config key + * @param string (optional) config layer + * @return bool TRUE on success, FALSE on failure + * + * @access public + */ + function removeLayer($layer) + { + if (isset($this->configuration[$layer])) { + $this->configuration[$layer] = array(); + return true; + } + + return false; + } + + /** + * Stores configuration data in a layer. + * + * @param string config layer to store + * @return bool TRUE on success, or PEAR error on failure + * + * @access public + */ + function store($layer = 'user', $data = null) + { + return $this->writeConfigFile(null, $layer, $data); + } + + /** + * Tells what config layer that gets to define a key. + * + * @param string config key + * @param boolean return the defining channel + * + * @return string|array the config layer, or an empty string if not found. + * + * if $returnchannel, the return is an array array('layer' => layername, + * 'channel' => channelname), or an empty string if not found + * + * @access public + */ + function definedBy($key, $returnchannel = false) + { + foreach ($this->layers as $layer) { + $channel = $this->getDefaultChannel(); + if ($channel !== 'pear.php.net') { + if (isset($this->configuration[$layer]['__channels'][$channel][$key])) { + if ($returnchannel) { + return array('layer' => $layer, 'channel' => $channel); + } + return $layer; + } + } + + if (isset($this->configuration[$layer][$key])) { + if ($returnchannel) { + return array('layer' => $layer, 'channel' => 'pear.php.net'); + } + return $layer; + } + } + + return ''; + } + + /** + * Tells whether a given key exists as a config value. + * + * @param string config key + * @return bool whether exists in this object + * + * @access public + */ + function isDefined($key) + { + foreach ($this->layers as $layer) { + if (isset($this->configuration[$layer][$key])) { + return true; + } + } + + return false; + } + + /** + * Tells whether a given config layer exists. + * + * @param string config layer + * @return bool whether exists in this object + * + * @access public + */ + function isDefinedLayer($layer) + { + return isset($this->configuration[$layer]); + } + + /** + * Returns the layers defined (except the 'default' one) + * + * @return array of the defined layers + */ + function getLayers() + { + $cf = $this->configuration; + unset($cf['default']); + return array_keys($cf); + } + + function apiVersion() + { + return '1.1'; + } + + /** + * @return PEAR_Registry + */ + function &getRegistry($use = null) + { + $layer = $use === null ? 'user' : $use; + if (isset($this->_registry[$layer])) { + return $this->_registry[$layer]; + } elseif ($use === null && isset($this->_registry['system'])) { + return $this->_registry['system']; + } elseif ($use === null && isset($this->_registry['default'])) { + return $this->_registry['default']; + } elseif ($use) { + $a = false; + return $a; + } + + // only go here if null was passed in + echo "CRITICAL ERROR: Registry could not be initialized from any value"; + exit(1); + } + + /** + * This is to allow customization like the use of installroot + * @param PEAR_Registry + * @return bool + */ + function setRegistry(&$reg, $layer = 'user') + { + if ($this->_noRegistry) { + return false; + } + + if (!in_array($layer, array('user', 'system'))) { + return false; + } + + $this->_registry[$layer] = &$reg; + if (is_object($reg)) { + $this->_registry[$layer]->setConfig($this, false); + } + + return true; + } + + function noRegistry() + { + $this->_noRegistry = true; + } + + /** + * @return PEAR_REST + */ + function &getREST($version, $options = array()) + { + $version = str_replace('.', '', $version); + if (!class_exists($class = 'PEAR_REST_' . $version)) { + require_once 'PEAR/REST/' . $version . '.php'; + } + + $remote = &new $class($this, $options); + return $remote; + } + + /** + * The ftp server is set in {@link readFTPConfigFile()}. It exists only if a + * remote configuration file has been specified + * @return PEAR_FTP|false + */ + function &getFTP() + { + if (isset($this->_ftp)) { + return $this->_ftp; + } + + $a = false; + return $a; + } + + function _prependPath($path, $prepend) + { + if (strlen($prepend) > 0) { + if (OS_WINDOWS && preg_match('/^[a-z]:/i', $path)) { + if (preg_match('/^[a-z]:/i', $prepend)) { + $prepend = substr($prepend, 2); + } elseif ($prepend{0} != '\\') { + $prepend = "\\$prepend"; + } + $path = substr($path, 0, 2) . $prepend . substr($path, 2); + } else { + $path = $prepend . $path; + } + } + return $path; + } + + /** + * @param string|false installation directory to prepend to all _dir variables, or false to + * disable + */ + function setInstallRoot($root) + { + if (substr($root, -1) == DIRECTORY_SEPARATOR) { + $root = substr($root, 0, -1); + } + $old = $this->_installRoot; + $this->_installRoot = $root; + if (($old != $root) && !$this->_noRegistry) { + foreach (array_keys($this->_registry) as $layer) { + if ($layer == 'ftp' || !isset($this->_registry[$layer])) { + continue; + } + $this->_registry[$layer] = + &new PEAR_Registry($this->get('php_dir', $layer, 'pear.php.net')); + $this->_registry[$layer]->setConfig($this, false); + $this->_regInitialized[$layer] = false; + } + } + } +} diff --git a/library/pear/PEAR/Dependency2.php b/library/pear/PEAR/Dependency2.php new file mode 100644 index 000000000..115a5578c --- /dev/null +++ b/library/pear/PEAR/Dependency2.php @@ -0,0 +1,1358 @@ + + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Dependency2.php 286494 2009-07-29 06:57:11Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ + +/** + * Required for the PEAR_VALIDATE_* constants + */ +require_once 'PEAR/Validate.php'; + +/** + * Dependency check for PEAR packages + * + * This class handles both version 1.0 and 2.0 dependencies + * WARNING: *any* changes to this class must be duplicated in the + * test_PEAR_Dependency2 class found in tests/PEAR_Dependency2/setup.php.inc, + * or unit tests will not actually validate the changes + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.1 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_Dependency2 +{ + /** + * One of the PEAR_VALIDATE_* states + * @see PEAR_VALIDATE_NORMAL + * @var integer + */ + var $_state; + + /** + * Command-line options to install/upgrade/uninstall commands + * @param array + */ + var $_options; + + /** + * @var OS_Guess + */ + var $_os; + + /** + * @var PEAR_Registry + */ + var $_registry; + + /** + * @var PEAR_Config + */ + var $_config; + + /** + * @var PEAR_DependencyDB + */ + var $_dependencydb; + + /** + * Output of PEAR_Registry::parsedPackageName() + * @var array + */ + var $_currentPackage; + + /** + * @param PEAR_Config + * @param array installation options + * @param array format of PEAR_Registry::parsedPackageName() + * @param int installation state (one of PEAR_VALIDATE_*) + */ + function PEAR_Dependency2(&$config, $installoptions, $package, + $state = PEAR_VALIDATE_INSTALLING) + { + $this->_config = &$config; + if (!class_exists('PEAR_DependencyDB')) { + require_once 'PEAR/DependencyDB.php'; + } + + if (isset($installoptions['packagingroot'])) { + // make sure depdb is in the right location + $config->setInstallRoot($installoptions['packagingroot']); + } + + $this->_registry = &$config->getRegistry(); + $this->_dependencydb = &PEAR_DependencyDB::singleton($config); + if (isset($installoptions['packagingroot'])) { + $config->setInstallRoot(false); + } + + $this->_options = $installoptions; + $this->_state = $state; + if (!class_exists('OS_Guess')) { + require_once 'OS/Guess.php'; + } + + $this->_os = new OS_Guess; + $this->_currentPackage = $package; + } + + function _getExtraString($dep) + { + $extra = ' ('; + if (isset($dep['uri'])) { + return ''; + } + + if (isset($dep['recommended'])) { + $extra .= 'recommended version ' . $dep['recommended']; + } else { + if (isset($dep['min'])) { + $extra .= 'version >= ' . $dep['min']; + } + + if (isset($dep['max'])) { + if ($extra != ' (') { + $extra .= ', '; + } + $extra .= 'version <= ' . $dep['max']; + } + + if (isset($dep['exclude'])) { + if (!is_array($dep['exclude'])) { + $dep['exclude'] = array($dep['exclude']); + } + + if ($extra != ' (') { + $extra .= ', '; + } + + $extra .= 'excluded versions: '; + foreach ($dep['exclude'] as $i => $exclude) { + if ($i) { + $extra .= ', '; + } + $extra .= $exclude; + } + } + } + + $extra .= ')'; + if ($extra == ' ()') { + $extra = ''; + } + + return $extra; + } + + /** + * This makes unit-testing a heck of a lot easier + */ + function getPHP_OS() + { + return PHP_OS; + } + + /** + * This makes unit-testing a heck of a lot easier + */ + function getsysname() + { + return $this->_os->getSysname(); + } + + /** + * Specify a dependency on an OS. Use arch for detailed os/processor information + * + * There are two generic OS dependencies that will be the most common, unix and windows. + * Other options are linux, freebsd, darwin (OS X), sunos, irix, hpux, aix + */ + function validateOsDependency($dep) + { + if ($this->_state != PEAR_VALIDATE_INSTALLING && $this->_state != PEAR_VALIDATE_DOWNLOADING) { + return true; + } + + if ($dep['name'] == '*') { + return true; + } + + $not = isset($dep['conflicts']) ? true : false; + switch (strtolower($dep['name'])) { + case 'windows' : + if ($not) { + if (strtolower(substr($this->getPHP_OS(), 0, 3)) == 'win') { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError("Cannot install %s on Windows"); + } + + return $this->warning("warning: Cannot install %s on Windows"); + } + } else { + if (strtolower(substr($this->getPHP_OS(), 0, 3)) != 'win') { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError("Can only install %s on Windows"); + } + + return $this->warning("warning: Can only install %s on Windows"); + } + } + break; + case 'unix' : + $unices = array('linux', 'freebsd', 'darwin', 'sunos', 'irix', 'hpux', 'aix'); + if ($not) { + if (in_array($this->getSysname(), $unices)) { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError("Cannot install %s on any Unix system"); + } + + return $this->warning( "warning: Cannot install %s on any Unix system"); + } + } else { + if (!in_array($this->getSysname(), $unices)) { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError("Can only install %s on a Unix system"); + } + + return $this->warning("warning: Can only install %s on a Unix system"); + } + } + break; + default : + if ($not) { + if (strtolower($dep['name']) == strtolower($this->getSysname())) { + if (!isset($this->_options['nodeps']) && + !isset($this->_options['force'])) { + return $this->raiseError('Cannot install %s on ' . $dep['name'] . + ' operating system'); + } + + return $this->warning('warning: Cannot install %s on ' . + $dep['name'] . ' operating system'); + } + } else { + if (strtolower($dep['name']) != strtolower($this->getSysname())) { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('Cannot install %s on ' . + $this->getSysname() . + ' operating system, can only install on ' . $dep['name']); + } + + return $this->warning('warning: Cannot install %s on ' . + $this->getSysname() . + ' operating system, can only install on ' . $dep['name']); + } + } + } + return true; + } + + /** + * This makes unit-testing a heck of a lot easier + */ + function matchSignature($pattern) + { + return $this->_os->matchSignature($pattern); + } + + /** + * Specify a complex dependency on an OS/processor/kernel version, + * Use OS for simple operating system dependency. + * + * This is the only dependency that accepts an eregable pattern. The pattern + * will be matched against the php_uname() output parsed by OS_Guess + */ + function validateArchDependency($dep) + { + if ($this->_state != PEAR_VALIDATE_INSTALLING) { + return true; + } + + $not = isset($dep['conflicts']) ? true : false; + if (!$this->matchSignature($dep['pattern'])) { + if (!$not) { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s Architecture dependency failed, does not ' . + 'match "' . $dep['pattern'] . '"'); + } + + return $this->warning('warning: %s Architecture dependency failed, does ' . + 'not match "' . $dep['pattern'] . '"'); + } + + return true; + } + + if ($not) { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s Architecture dependency failed, required "' . + $dep['pattern'] . '"'); + } + + return $this->warning('warning: %s Architecture dependency failed, ' . + 'required "' . $dep['pattern'] . '"'); + } + + return true; + } + + /** + * This makes unit-testing a heck of a lot easier + */ + function extension_loaded($name) + { + return extension_loaded($name); + } + + /** + * This makes unit-testing a heck of a lot easier + */ + function phpversion($name = null) + { + if ($name !== null) { + return phpversion($name); + } + + return phpversion(); + } + + function validateExtensionDependency($dep, $required = true) + { + if ($this->_state != PEAR_VALIDATE_INSTALLING && + $this->_state != PEAR_VALIDATE_DOWNLOADING) { + return true; + } + + $loaded = $this->extension_loaded($dep['name']); + $extra = $this->_getExtraString($dep); + if (isset($dep['exclude'])) { + if (!is_array($dep['exclude'])) { + $dep['exclude'] = array($dep['exclude']); + } + } + + if (!isset($dep['min']) && !isset($dep['max']) && + !isset($dep['recommended']) && !isset($dep['exclude']) + ) { + if ($loaded) { + if (isset($dep['conflicts'])) { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s conflicts with PHP extension "' . + $dep['name'] . '"' . $extra); + } + + return $this->warning('warning: %s conflicts with PHP extension "' . + $dep['name'] . '"' . $extra); + } + + return true; + } + + if (isset($dep['conflicts'])) { + return true; + } + + if ($required) { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s requires PHP extension "' . + $dep['name'] . '"' . $extra); + } + + return $this->warning('warning: %s requires PHP extension "' . + $dep['name'] . '"' . $extra); + } + + return $this->warning('%s can optionally use PHP extension "' . + $dep['name'] . '"' . $extra); + } + + if (!$loaded) { + if (isset($dep['conflicts'])) { + return true; + } + + if (!$required) { + return $this->warning('%s can optionally use PHP extension "' . + $dep['name'] . '"' . $extra); + } + + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s requires PHP extension "' . $dep['name'] . + '"' . $extra); + } + + return $this->warning('warning: %s requires PHP extension "' . $dep['name'] . + '"' . $extra); + } + + $version = (string) $this->phpversion($dep['name']); + if (empty($version)) { + $version = '0'; + } + + $fail = false; + if (isset($dep['min']) && !version_compare($version, $dep['min'], '>=')) { + $fail = true; + } + + if (isset($dep['max']) && !version_compare($version, $dep['max'], '<=')) { + $fail = true; + } + + if ($fail && !isset($dep['conflicts'])) { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s requires PHP extension "' . $dep['name'] . + '"' . $extra . ', installed version is ' . $version); + } + + return $this->warning('warning: %s requires PHP extension "' . $dep['name'] . + '"' . $extra . ', installed version is ' . $version); + } elseif ((isset($dep['min']) || isset($dep['max'])) && !$fail && isset($dep['conflicts'])) { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s conflicts with PHP extension "' . + $dep['name'] . '"' . $extra . ', installed version is ' . $version); + } + + return $this->warning('warning: %s conflicts with PHP extension "' . + $dep['name'] . '"' . $extra . ', installed version is ' . $version); + } + + if (isset($dep['exclude'])) { + foreach ($dep['exclude'] as $exclude) { + if (version_compare($version, $exclude, '==')) { + if (isset($dep['conflicts'])) { + continue; + } + + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s is not compatible with PHP extension "' . + $dep['name'] . '" version ' . + $exclude); + } + + return $this->warning('warning: %s is not compatible with PHP extension "' . + $dep['name'] . '" version ' . + $exclude); + } elseif (version_compare($version, $exclude, '!=') && isset($dep['conflicts'])) { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s conflicts with PHP extension "' . + $dep['name'] . '"' . $extra . ', installed version is ' . $version); + } + + return $this->warning('warning: %s conflicts with PHP extension "' . + $dep['name'] . '"' . $extra . ', installed version is ' . $version); + } + } + } + + if (isset($dep['recommended'])) { + if (version_compare($version, $dep['recommended'], '==')) { + return true; + } + + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s dependency: PHP extension ' . $dep['name'] . + ' version "' . $version . '"' . + ' is not the recommended version "' . $dep['recommended'] . + '", but may be compatible, use --force to install'); + } + + return $this->warning('warning: %s dependency: PHP extension ' . + $dep['name'] . ' version "' . $version . '"' . + ' is not the recommended version "' . $dep['recommended'].'"'); + } + + return true; + } + + function validatePhpDependency($dep) + { + if ($this->_state != PEAR_VALIDATE_INSTALLING && + $this->_state != PEAR_VALIDATE_DOWNLOADING) { + return true; + } + + $version = $this->phpversion(); + $extra = $this->_getExtraString($dep); + if (isset($dep['exclude'])) { + if (!is_array($dep['exclude'])) { + $dep['exclude'] = array($dep['exclude']); + } + } + + if (isset($dep['min'])) { + if (!version_compare($version, $dep['min'], '>=')) { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s requires PHP' . + $extra . ', installed version is ' . $version); + } + + return $this->warning('warning: %s requires PHP' . + $extra . ', installed version is ' . $version); + } + } + + if (isset($dep['max'])) { + if (!version_compare($version, $dep['max'], '<=')) { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s requires PHP' . + $extra . ', installed version is ' . $version); + } + + return $this->warning('warning: %s requires PHP' . + $extra . ', installed version is ' . $version); + } + } + + if (isset($dep['exclude'])) { + foreach ($dep['exclude'] as $exclude) { + if (version_compare($version, $exclude, '==')) { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s is not compatible with PHP version ' . + $exclude); + } + + return $this->warning( + 'warning: %s is not compatible with PHP version ' . + $exclude); + } + } + } + + return true; + } + + /** + * This makes unit-testing a heck of a lot easier + */ + function getPEARVersion() + { + return '1.9.1'; + } + + function validatePearinstallerDependency($dep) + { + $pearversion = $this->getPEARVersion(); + $extra = $this->_getExtraString($dep); + if (isset($dep['exclude'])) { + if (!is_array($dep['exclude'])) { + $dep['exclude'] = array($dep['exclude']); + } + } + + if (version_compare($pearversion, $dep['min'], '<')) { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s requires PEAR Installer' . $extra . + ', installed version is ' . $pearversion); + } + + return $this->warning('warning: %s requires PEAR Installer' . $extra . + ', installed version is ' . $pearversion); + } + + if (isset($dep['max'])) { + if (version_compare($pearversion, $dep['max'], '>')) { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s requires PEAR Installer' . $extra . + ', installed version is ' . $pearversion); + } + + return $this->warning('warning: %s requires PEAR Installer' . $extra . + ', installed version is ' . $pearversion); + } + } + + if (isset($dep['exclude'])) { + if (!isset($dep['exclude'][0])) { + $dep['exclude'] = array($dep['exclude']); + } + + foreach ($dep['exclude'] as $exclude) { + if (version_compare($exclude, $pearversion, '==')) { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s is not compatible with PEAR Installer ' . + 'version ' . $exclude); + } + + return $this->warning('warning: %s is not compatible with PEAR ' . + 'Installer version ' . $exclude); + } + } + } + + return true; + } + + function validateSubpackageDependency($dep, $required, $params) + { + return $this->validatePackageDependency($dep, $required, $params); + } + + /** + * @param array dependency information (2.0 format) + * @param boolean whether this is a required dependency + * @param array a list of downloaded packages to be installed, if any + * @param boolean if true, then deps on pear.php.net that fail will also check + * against pecl.php.net packages to accomodate extensions that have + * moved to pecl.php.net from pear.php.net + */ + function validatePackageDependency($dep, $required, $params, $depv1 = false) + { + if ($this->_state != PEAR_VALIDATE_INSTALLING && + $this->_state != PEAR_VALIDATE_DOWNLOADING) { + return true; + } + + if (isset($dep['providesextension'])) { + if ($this->extension_loaded($dep['providesextension'])) { + $save = $dep; + $subdep = $dep; + $subdep['name'] = $subdep['providesextension']; + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $ret = $this->validateExtensionDependency($subdep, $required); + PEAR::popErrorHandling(); + if (!PEAR::isError($ret)) { + return true; + } + } + } + + if ($this->_state == PEAR_VALIDATE_INSTALLING) { + return $this->_validatePackageInstall($dep, $required, $depv1); + } + + if ($this->_state == PEAR_VALIDATE_DOWNLOADING) { + return $this->_validatePackageDownload($dep, $required, $params, $depv1); + } + } + + function _validatePackageDownload($dep, $required, $params, $depv1 = false) + { + $dep['package'] = $dep['name']; + if (isset($dep['uri'])) { + $dep['channel'] = '__uri'; + } + + $depname = $this->_registry->parsedPackageNameToString($dep, true); + $found = false; + foreach ($params as $param) { + if ($param->isEqual( + array('package' => $dep['name'], + 'channel' => $dep['channel']))) { + $found = true; + break; + } + + if ($depv1 && $dep['channel'] == 'pear.php.net') { + if ($param->isEqual( + array('package' => $dep['name'], + 'channel' => 'pecl.php.net'))) { + $found = true; + break; + } + } + } + + if (!$found && isset($dep['providesextension'])) { + foreach ($params as $param) { + if ($param->isExtension($dep['providesextension'])) { + $found = true; + break; + } + } + } + + if ($found) { + $version = $param->getVersion(); + $installed = false; + $downloaded = true; + } else { + if ($this->_registry->packageExists($dep['name'], $dep['channel'])) { + $installed = true; + $downloaded = false; + $version = $this->_registry->packageinfo($dep['name'], 'version', + $dep['channel']); + } else { + if ($dep['channel'] == 'pecl.php.net' && $this->_registry->packageExists($dep['name'], + 'pear.php.net')) { + $installed = true; + $downloaded = false; + $version = $this->_registry->packageinfo($dep['name'], 'version', + 'pear.php.net'); + } else { + $version = 'not installed or downloaded'; + $installed = false; + $downloaded = false; + } + } + } + + $extra = $this->_getExtraString($dep); + if (isset($dep['exclude']) && !is_array($dep['exclude'])) { + $dep['exclude'] = array($dep['exclude']); + } + + if (!isset($dep['min']) && !isset($dep['max']) && + !isset($dep['recommended']) && !isset($dep['exclude']) + ) { + if ($installed || $downloaded) { + $installed = $installed ? 'installed' : 'downloaded'; + if (isset($dep['conflicts'])) { + $rest = ''; + if ($version) { + $rest = ", $installed version is " . $version; + } + + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s conflicts with package "' . $depname . '"' . $extra . $rest); + } + + return $this->warning('warning: %s conflicts with package "' . $depname . '"' . $extra . $rest); + } + + return true; + } + + if (isset($dep['conflicts'])) { + return true; + } + + if ($required) { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s requires package "' . $depname . '"' . $extra); + } + + return $this->warning('warning: %s requires package "' . $depname . '"' . $extra); + } + + return $this->warning('%s can optionally use package "' . $depname . '"' . $extra); + } + + if (!$installed && !$downloaded) { + if (isset($dep['conflicts'])) { + return true; + } + + if ($required) { + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s requires package "' . $depname . '"' . $extra); + } + + return $this->warning('warning: %s requires package "' . $depname . '"' . $extra); + } + + return $this->warning('%s can optionally use package "' . $depname . '"' . $extra); + } + + $fail = false; + if (isset($dep['min']) && version_compare($version, $dep['min'], '<')) { + $fail = true; + } + + if (isset($dep['max']) && version_compare($version, $dep['max'], '>')) { + $fail = true; + } + + if ($fail && !isset($dep['conflicts'])) { + $installed = $installed ? 'installed' : 'downloaded'; + $dep['package'] = $dep['name']; + $dep = $this->_registry->parsedPackageNameToString($dep, true); + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s requires package "' . $depname . '"' . + $extra . ", $installed version is " . $version); + } + + return $this->warning('warning: %s requires package "' . $depname . '"' . + $extra . ", $installed version is " . $version); + } elseif ((isset($dep['min']) || isset($dep['max'])) && !$fail && + isset($dep['conflicts']) && !isset($dep['exclude'])) { + $installed = $installed ? 'installed' : 'downloaded'; + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s conflicts with package "' . $depname . '"' . $extra . + ", $installed version is " . $version); + } + + return $this->warning('warning: %s conflicts with package "' . $depname . '"' . + $extra . ", $installed version is " . $version); + } + + if (isset($dep['exclude'])) { + $installed = $installed ? 'installed' : 'downloaded'; + foreach ($dep['exclude'] as $exclude) { + if (version_compare($version, $exclude, '==') && !isset($dep['conflicts'])) { + if (!isset($this->_options['nodeps']) && + !isset($this->_options['force']) + ) { + return $this->raiseError('%s is not compatible with ' . + $installed . ' package "' . + $depname . '" version ' . + $exclude); + } + + return $this->warning('warning: %s is not compatible with ' . + $installed . ' package "' . + $depname . '" version ' . + $exclude); + } elseif (version_compare($version, $exclude, '!=') && isset($dep['conflicts'])) { + $installed = $installed ? 'installed' : 'downloaded'; + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('%s conflicts with package "' . $depname . '"' . + $extra . ", $installed version is " . $version); + } + + return $this->warning('warning: %s conflicts with package "' . $depname . '"' . + $extra . ", $installed version is " . $version); + } + } + } + + if (isset($dep['recommended'])) { + $installed = $installed ? 'installed' : 'downloaded'; + if (version_compare($version, $dep['recommended'], '==')) { + return true; + } + + if (!$found && $installed) { + $param = $this->_registry->getPackage($dep['name'], $dep['channel']); + } + + if ($param) { + $found = false; + foreach ($params as $parent) { + if ($parent->isEqual($this->_currentPackage)) { + $found = true; + break; + } + } + + if ($found) { + if ($param->isCompatible($parent)) { + return true; + } + } else { // this is for validPackage() calls + $parent = $this->_registry->getPackage($this->_currentPackage['package'], + $this->_currentPackage['channel']); + if ($parent !== null && $param->isCompatible($parent)) { + return true; + } + } + } + + if (!isset($this->_options['nodeps']) && !isset($this->_options['force']) && + !isset($this->_options['loose']) + ) { + return $this->raiseError('%s dependency package "' . $depname . + '" ' . $installed . ' version ' . $version . + ' is not the recommended version ' . $dep['recommended'] . + ', but may be compatible, use --force to install'); + } + + return $this->warning('warning: %s dependency package "' . $depname . + '" ' . $installed . ' version ' . $version . + ' is not the recommended version ' . $dep['recommended']); + } + + return true; + } + + function _validatePackageInstall($dep, $required, $depv1 = false) + { + return $this->_validatePackageDownload($dep, $required, array(), $depv1); + } + + /** + * Verify that uninstalling packages passed in to command line is OK. + * + * @param PEAR_Installer $dl + * @return PEAR_Error|true + */ + function validatePackageUninstall(&$dl) + { + if (PEAR::isError($this->_dependencydb)) { + return $this->_dependencydb; + } + + $params = array(); + // construct an array of "downloaded" packages to fool the package dependency checker + // into using these to validate uninstalls of circular dependencies + $downloaded = &$dl->getUninstallPackages(); + foreach ($downloaded as $i => $pf) { + if (!class_exists('PEAR_Downloader_Package')) { + require_once 'PEAR/Downloader/Package.php'; + } + $dp = &new PEAR_Downloader_Package($dl); + $dp->setPackageFile($downloaded[$i]); + $params[$i] = &$dp; + } + + // check cache + $memyselfandI = strtolower($this->_currentPackage['channel']) . '/' . + strtolower($this->_currentPackage['package']); + if (isset($dl->___uninstall_package_cache)) { + $badpackages = $dl->___uninstall_package_cache; + if (isset($badpackages[$memyselfandI]['warnings'])) { + foreach ($badpackages[$memyselfandI]['warnings'] as $warning) { + $dl->log(0, $warning[0]); + } + } + + if (isset($badpackages[$memyselfandI]['errors'])) { + foreach ($badpackages[$memyselfandI]['errors'] as $error) { + if (is_array($error)) { + $dl->log(0, $error[0]); + } else { + $dl->log(0, $error->getMessage()); + } + } + + if (isset($this->_options['nodeps']) || isset($this->_options['force'])) { + return $this->warning( + 'warning: %s should not be uninstalled, other installed packages depend ' . + 'on this package'); + } + + return $this->raiseError( + '%s cannot be uninstalled, other installed packages depend on this package'); + } + + return true; + } + + // first, list the immediate parents of each package to be uninstalled + $perpackagelist = array(); + $allparents = array(); + foreach ($params as $i => $param) { + $a = array( + 'channel' => strtolower($param->getChannel()), + 'package' => strtolower($param->getPackage()) + ); + + $deps = $this->_dependencydb->getDependentPackages($a); + if ($deps) { + foreach ($deps as $d) { + $pardeps = $this->_dependencydb->getDependencies($d); + foreach ($pardeps as $dep) { + if (strtolower($dep['dep']['channel']) == $a['channel'] && + strtolower($dep['dep']['name']) == $a['package']) { + if (!isset($perpackagelist[$a['channel'] . '/' . $a['package']])) { + $perpackagelist[$a['channel'] . '/' . $a['package']] = array(); + } + $perpackagelist[$a['channel'] . '/' . $a['package']][] + = array($d['channel'] . '/' . $d['package'], $dep); + if (!isset($allparents[$d['channel'] . '/' . $d['package']])) { + $allparents[$d['channel'] . '/' . $d['package']] = array(); + } + if (!isset($allparents[$d['channel'] . '/' . $d['package']][$a['channel'] . '/' . $a['package']])) { + $allparents[$d['channel'] . '/' . $d['package']][$a['channel'] . '/' . $a['package']] = array(); + } + $allparents[$d['channel'] . '/' . $d['package']] + [$a['channel'] . '/' . $a['package']][] + = array($d, $dep); + } + } + } + } + } + + // next, remove any packages from the parents list that are not installed + $remove = array(); + foreach ($allparents as $parent => $d1) { + foreach ($d1 as $d) { + if ($this->_registry->packageExists($d[0][0]['package'], $d[0][0]['channel'])) { + continue; + } + $remove[$parent] = true; + } + } + + // next remove any packages from the parents list that are not passed in for + // uninstallation + foreach ($allparents as $parent => $d1) { + foreach ($d1 as $d) { + foreach ($params as $param) { + if (strtolower($param->getChannel()) == $d[0][0]['channel'] && + strtolower($param->getPackage()) == $d[0][0]['package']) { + // found it + continue 3; + } + } + $remove[$parent] = true; + } + } + + // remove all packages whose dependencies fail + // save which ones failed for error reporting + $badchildren = array(); + do { + $fail = false; + foreach ($remove as $package => $unused) { + if (!isset($allparents[$package])) { + continue; + } + + foreach ($allparents[$package] as $kid => $d1) { + foreach ($d1 as $depinfo) { + if ($depinfo[1]['type'] != 'optional') { + if (isset($badchildren[$kid])) { + continue; + } + $badchildren[$kid] = true; + $remove[$kid] = true; + $fail = true; + continue 2; + } + } + } + if ($fail) { + // start over, we removed some children + continue 2; + } + } + } while ($fail); + + // next, construct the list of packages that can't be uninstalled + $badpackages = array(); + $save = $this->_currentPackage; + foreach ($perpackagelist as $package => $packagedeps) { + foreach ($packagedeps as $parent) { + if (!isset($remove[$parent[0]])) { + continue; + } + + $packagename = $this->_registry->parsePackageName($parent[0]); + $packagename['channel'] = $this->_registry->channelAlias($packagename['channel']); + $pa = $this->_registry->getPackage($packagename['package'], $packagename['channel']); + $packagename['package'] = $pa->getPackage(); + $this->_currentPackage = $packagename; + // parent is not present in uninstall list, make sure we can actually + // uninstall it (parent dep is optional) + $parentname['channel'] = $this->_registry->channelAlias($parent[1]['dep']['channel']); + $pa = $this->_registry->getPackage($parent[1]['dep']['name'], $parent[1]['dep']['channel']); + $parentname['package'] = $pa->getPackage(); + $parent[1]['dep']['package'] = $parentname['package']; + $parent[1]['dep']['channel'] = $parentname['channel']; + if ($parent[1]['type'] == 'optional') { + $test = $this->_validatePackageUninstall($parent[1]['dep'], false, $dl); + if ($test !== true) { + $badpackages[$package]['warnings'][] = $test; + } + } else { + $test = $this->_validatePackageUninstall($parent[1]['dep'], true, $dl); + if ($test !== true) { + $badpackages[$package]['errors'][] = $test; + } + } + } + } + + $this->_currentPackage = $save; + $dl->___uninstall_package_cache = $badpackages; + if (isset($badpackages[$memyselfandI])) { + if (isset($badpackages[$memyselfandI]['warnings'])) { + foreach ($badpackages[$memyselfandI]['warnings'] as $warning) { + $dl->log(0, $warning[0]); + } + } + + if (isset($badpackages[$memyselfandI]['errors'])) { + foreach ($badpackages[$memyselfandI]['errors'] as $error) { + if (is_array($error)) { + $dl->log(0, $error[0]); + } else { + $dl->log(0, $error->getMessage()); + } + } + + if (isset($this->_options['nodeps']) || isset($this->_options['force'])) { + return $this->warning( + 'warning: %s should not be uninstalled, other installed packages depend ' . + 'on this package'); + } + + return $this->raiseError( + '%s cannot be uninstalled, other installed packages depend on this package'); + } + } + + return true; + } + + function _validatePackageUninstall($dep, $required, $dl) + { + $depname = $this->_registry->parsedPackageNameToString($dep, true); + $version = $this->_registry->packageinfo($dep['package'], 'version', $dep['channel']); + if (!$version) { + return true; + } + + $extra = $this->_getExtraString($dep); + if (isset($dep['exclude']) && !is_array($dep['exclude'])) { + $dep['exclude'] = array($dep['exclude']); + } + + if (isset($dep['conflicts'])) { + return true; // uninstall OK - these packages conflict (probably installed with --force) + } + + if (!isset($dep['min']) && !isset($dep['max'])) { + if (!$required) { + return $this->warning('"' . $depname . '" can be optionally used by ' . + 'installed package %s' . $extra); + } + + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError('"' . $depname . '" is required by ' . + 'installed package %s' . $extra); + } + + return $this->warning('warning: "' . $depname . '" is required by ' . + 'installed package %s' . $extra); + } + + $fail = false; + if (isset($dep['min']) && version_compare($version, $dep['min'], '>=')) { + $fail = true; + } + + if (isset($dep['max']) && version_compare($version, $dep['max'], '<=')) { + $fail = true; + } + + // we re-use this variable, preserve the original value + $saverequired = $required; + if (!$required) { + return $this->warning($depname . $extra . ' can be optionally used by installed package' . + ' "%s"'); + } + + if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) { + return $this->raiseError($depname . $extra . ' is required by installed package' . + ' "%s"'); + } + + return $this->raiseError('warning: ' . $depname . $extra . + ' is required by installed package "%s"'); + } + + /** + * validate a downloaded package against installed packages + * + * As of PEAR 1.4.3, this will only validate + * + * @param array|PEAR_Downloader_Package|PEAR_PackageFile_v1|PEAR_PackageFile_v2 + * $pkg package identifier (either + * array('package' => blah, 'channel' => blah) or an array with + * index 'info' referencing an object) + * @param PEAR_Downloader $dl + * @param array $params full list of packages to install + * @return true|PEAR_Error + */ + function validatePackage($pkg, &$dl, $params = array()) + { + if (is_array($pkg) && isset($pkg['info'])) { + $deps = $this->_dependencydb->getDependentPackageDependencies($pkg['info']); + } else { + $deps = $this->_dependencydb->getDependentPackageDependencies($pkg); + } + + $fail = false; + if ($deps) { + if (!class_exists('PEAR_Downloader_Package')) { + require_once 'PEAR/Downloader/Package.php'; + } + + $dp = &new PEAR_Downloader_Package($dl); + if (is_object($pkg)) { + $dp->setPackageFile($pkg); + } else { + $dp->setDownloadURL($pkg); + } + + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + foreach ($deps as $channel => $info) { + foreach ($info as $package => $ds) { + foreach ($params as $packd) { + if (strtolower($packd->getPackage()) == strtolower($package) && + $packd->getChannel() == $channel) { + $dl->log(3, 'skipping installed package check of "' . + $this->_registry->parsedPackageNameToString( + array('channel' => $channel, 'package' => $package), + true) . + '", version "' . $packd->getVersion() . '" will be ' . + 'downloaded and installed'); + continue 2; // jump to next package + } + } + + foreach ($ds as $d) { + $checker = &new PEAR_Dependency2($this->_config, $this->_options, + array('channel' => $channel, 'package' => $package), $this->_state); + $dep = $d['dep']; + $required = $d['type'] == 'required'; + $ret = $checker->_validatePackageDownload($dep, $required, array(&$dp)); + if (is_array($ret)) { + $dl->log(0, $ret[0]); + } elseif (PEAR::isError($ret)) { + $dl->log(0, $ret->getMessage()); + $fail = true; + } + } + } + } + PEAR::popErrorHandling(); + } + + if ($fail) { + return $this->raiseError( + '%s cannot be installed, conflicts with installed packages'); + } + + return true; + } + + /** + * validate a package.xml 1.0 dependency + */ + function validateDependency1($dep, $params = array()) + { + if (!isset($dep['optional'])) { + $dep['optional'] = 'no'; + } + + list($newdep, $type) = $this->normalizeDep($dep); + if (!$newdep) { + return $this->raiseError("Invalid Dependency"); + } + + if (method_exists($this, "validate{$type}Dependency")) { + return $this->{"validate{$type}Dependency"}($newdep, $dep['optional'] == 'no', + $params, true); + } + } + + /** + * Convert a 1.0 dep into a 2.0 dep + */ + function normalizeDep($dep) + { + $types = array( + 'pkg' => 'Package', + 'ext' => 'Extension', + 'os' => 'Os', + 'php' => 'Php' + ); + + if (!isset($types[$dep['type']])) { + return array(false, false); + } + + $type = $types[$dep['type']]; + + $newdep = array(); + switch ($type) { + case 'Package' : + $newdep['channel'] = 'pear.php.net'; + case 'Extension' : + case 'Os' : + $newdep['name'] = $dep['name']; + break; + } + + $dep['rel'] = PEAR_Dependency2::signOperator($dep['rel']); + switch ($dep['rel']) { + case 'has' : + return array($newdep, $type); + break; + case 'not' : + $newdep['conflicts'] = true; + break; + case '>=' : + case '>' : + $newdep['min'] = $dep['version']; + if ($dep['rel'] == '>') { + $newdep['exclude'] = $dep['version']; + } + break; + case '<=' : + case '<' : + $newdep['max'] = $dep['version']; + if ($dep['rel'] == '<') { + $newdep['exclude'] = $dep['version']; + } + break; + case 'ne' : + case '!=' : + $newdep['min'] = '0'; + $newdep['max'] = '100000'; + $newdep['exclude'] = $dep['version']; + break; + case '==' : + $newdep['min'] = $dep['version']; + $newdep['max'] = $dep['version']; + break; + } + if ($type == 'Php') { + if (!isset($newdep['min'])) { + $newdep['min'] = '4.4.0'; + } + + if (!isset($newdep['max'])) { + $newdep['max'] = '6.0.0'; + } + } + return array($newdep, $type); + } + + /** + * Converts text comparing operators to them sign equivalents + * + * Example: 'ge' to '>=' + * + * @access public + * @param string Operator + * @return string Sign equivalent + */ + function signOperator($operator) + { + switch($operator) { + case 'lt': return '<'; + case 'le': return '<='; + case 'gt': return '>'; + case 'ge': return '>='; + case 'eq': return '=='; + case 'ne': return '!='; + default: + return $operator; + } + } + + function raiseError($msg) + { + if (isset($this->_options['ignore-errors'])) { + return $this->warning($msg); + } + + return PEAR::raiseError(sprintf($msg, $this->_registry->parsedPackageNameToString( + $this->_currentPackage, true))); + } + + function warning($msg) + { + return array(sprintf($msg, $this->_registry->parsedPackageNameToString( + $this->_currentPackage, true))); + } +} \ No newline at end of file diff --git a/library/pear/PEAR/DependencyDB.php b/library/pear/PEAR/DependencyDB.php new file mode 100644 index 000000000..d85d26009 --- /dev/null +++ b/library/pear/PEAR/DependencyDB.php @@ -0,0 +1,769 @@ + + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: DependencyDB.php 286686 2009-08-02 17:38:57Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ + +/** + * Needed for error handling + */ +require_once 'PEAR.php'; +require_once 'PEAR/Config.php'; + +$GLOBALS['_PEAR_DEPENDENCYDB_INSTANCE'] = array(); +/** + * Track dependency relationships between installed packages + * @category pear + * @package PEAR + * @author Greg Beaver + * @author Tomas V.V.Cox + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.1 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_DependencyDB +{ + // {{{ properties + + /** + * This is initialized by {@link setConfig()} + * @var PEAR_Config + * @access private + */ + var $_config; + /** + * This is initialized by {@link setConfig()} + * @var PEAR_Registry + * @access private + */ + var $_registry; + /** + * Filename of the dependency DB (usually .depdb) + * @var string + * @access private + */ + var $_depdb = false; + /** + * File name of the lockfile (usually .depdblock) + * @var string + * @access private + */ + var $_lockfile = false; + /** + * Open file resource for locking the lockfile + * @var resource|false + * @access private + */ + var $_lockFp = false; + /** + * API version of this class, used to validate a file on-disk + * @var string + * @access private + */ + var $_version = '1.0'; + /** + * Cached dependency database file + * @var array|null + * @access private + */ + var $_cache; + + // }}} + // {{{ & singleton() + + /** + * Get a raw dependency database. Calls setConfig() and assertDepsDB() + * @param PEAR_Config + * @param string|false full path to the dependency database, or false to use default + * @return PEAR_DependencyDB|PEAR_Error + * @static + */ + function &singleton(&$config, $depdb = false) + { + $phpdir = $config->get('php_dir', null, 'pear.php.net'); + if (!isset($GLOBALS['_PEAR_DEPENDENCYDB_INSTANCE'][$phpdir])) { + $a = new PEAR_DependencyDB; + $GLOBALS['_PEAR_DEPENDENCYDB_INSTANCE'][$phpdir] = &$a; + $a->setConfig($config, $depdb); + $e = $a->assertDepsDB(); + if (PEAR::isError($e)) { + return $e; + } + } + + return $GLOBALS['_PEAR_DEPENDENCYDB_INSTANCE'][$phpdir]; + } + + /** + * Set up the registry/location of dependency DB + * @param PEAR_Config|false + * @param string|false full path to the dependency database, or false to use default + */ + function setConfig(&$config, $depdb = false) + { + if (!$config) { + $this->_config = &PEAR_Config::singleton(); + } else { + $this->_config = &$config; + } + + $this->_registry = &$this->_config->getRegistry(); + if (!$depdb) { + $this->_depdb = $this->_config->get('php_dir', null, 'pear.php.net') . + DIRECTORY_SEPARATOR . '.depdb'; + } else { + $this->_depdb = $depdb; + } + + $this->_lockfile = dirname($this->_depdb) . DIRECTORY_SEPARATOR . '.depdblock'; + } + // }}} + + function hasWriteAccess() + { + if (!file_exists($this->_depdb)) { + $dir = $this->_depdb; + while ($dir && $dir != '.') { + $dir = dirname($dir); // cd .. + if ($dir != '.' && file_exists($dir)) { + if (is_writeable($dir)) { + return true; + } + + return false; + } + } + + return false; + } + + return is_writeable($this->_depdb); + } + + // {{{ assertDepsDB() + + /** + * Create the dependency database, if it doesn't exist. Error if the database is + * newer than the code reading it. + * @return void|PEAR_Error + */ + function assertDepsDB() + { + if (!is_file($this->_depdb)) { + $this->rebuildDB(); + return; + } + + $depdb = $this->_getDepDB(); + // Datatype format has been changed, rebuild the Deps DB + if ($depdb['_version'] < $this->_version) { + $this->rebuildDB(); + } + + if ($depdb['_version']{0} > $this->_version{0}) { + return PEAR::raiseError('Dependency database is version ' . + $depdb['_version'] . ', and we are version ' . + $this->_version . ', cannot continue'); + } + } + + /** + * Get a list of installed packages that depend on this package + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2|array + * @return array|false + */ + function getDependentPackages(&$pkg) + { + $data = $this->_getDepDB(); + if (is_object($pkg)) { + $channel = strtolower($pkg->getChannel()); + $package = strtolower($pkg->getPackage()); + } else { + $channel = strtolower($pkg['channel']); + $package = strtolower($pkg['package']); + } + + if (isset($data['packages'][$channel][$package])) { + return $data['packages'][$channel][$package]; + } + + return false; + } + + /** + * Get a list of the actual dependencies of installed packages that depend on + * a package. + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2|array + * @return array|false + */ + function getDependentPackageDependencies(&$pkg) + { + $data = $this->_getDepDB(); + if (is_object($pkg)) { + $channel = strtolower($pkg->getChannel()); + $package = strtolower($pkg->getPackage()); + } else { + $channel = strtolower($pkg['channel']); + $package = strtolower($pkg['package']); + } + + $depend = $this->getDependentPackages($pkg); + if (!$depend) { + return false; + } + + $dependencies = array(); + foreach ($depend as $info) { + $temp = $this->getDependencies($info); + foreach ($temp as $dep) { + if ( + isset($dep['dep'], $dep['dep']['channel'], $dep['dep']['name']) && + strtolower($dep['dep']['channel']) == $channel && + strtolower($dep['dep']['name']) == $package + ) { + if (!isset($dependencies[$info['channel']])) { + $dependencies[$info['channel']] = array(); + } + + if (!isset($dependencies[$info['channel']][$info['package']])) { + $dependencies[$info['channel']][$info['package']] = array(); + } + $dependencies[$info['channel']][$info['package']][] = $dep; + } + } + } + + return $dependencies; + } + + /** + * Get a list of dependencies of this installed package + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2|array + * @return array|false + */ + function getDependencies(&$pkg) + { + if (is_object($pkg)) { + $channel = strtolower($pkg->getChannel()); + $package = strtolower($pkg->getPackage()); + } else { + $channel = strtolower($pkg['channel']); + $package = strtolower($pkg['package']); + } + + $data = $this->_getDepDB(); + if (isset($data['dependencies'][$channel][$package])) { + return $data['dependencies'][$channel][$package]; + } + + return false; + } + + /** + * Determine whether $parent depends on $child, near or deep + * @param array|PEAR_PackageFile_v2|PEAR_PackageFile_v2 + * @param array|PEAR_PackageFile_v2|PEAR_PackageFile_v2 + */ + function dependsOn($parent, $child) + { + $c = array(); + $this->_getDepDB(); + return $this->_dependsOn($parent, $child, $c); + } + + function _dependsOn($parent, $child, &$checked) + { + if (is_object($parent)) { + $channel = strtolower($parent->getChannel()); + $package = strtolower($parent->getPackage()); + } else { + $channel = strtolower($parent['channel']); + $package = strtolower($parent['package']); + } + + if (is_object($child)) { + $depchannel = strtolower($child->getChannel()); + $deppackage = strtolower($child->getPackage()); + } else { + $depchannel = strtolower($child['channel']); + $deppackage = strtolower($child['package']); + } + + if (isset($checked[$channel][$package][$depchannel][$deppackage])) { + return false; // avoid endless recursion + } + + $checked[$channel][$package][$depchannel][$deppackage] = true; + if (!isset($this->_cache['dependencies'][$channel][$package])) { + return false; + } + + foreach ($this->_cache['dependencies'][$channel][$package] as $info) { + if (isset($info['dep']['uri'])) { + if (is_object($child)) { + if ($info['dep']['uri'] == $child->getURI()) { + return true; + } + } elseif (isset($child['uri'])) { + if ($info['dep']['uri'] == $child['uri']) { + return true; + } + } + return false; + } + + if (strtolower($info['dep']['channel']) == $depchannel && + strtolower($info['dep']['name']) == $deppackage) { + return true; + } + } + + foreach ($this->_cache['dependencies'][$channel][$package] as $info) { + if (isset($info['dep']['uri'])) { + if ($this->_dependsOn(array( + 'uri' => $info['dep']['uri'], + 'package' => $info['dep']['name']), $child, $checked)) { + return true; + } + } else { + if ($this->_dependsOn(array( + 'channel' => $info['dep']['channel'], + 'package' => $info['dep']['name']), $child, $checked)) { + return true; + } + } + } + + return false; + } + + /** + * Register dependencies of a package that is being installed or upgraded + * @param PEAR_PackageFile_v2|PEAR_PackageFile_v2 + */ + function installPackage(&$package) + { + $data = $this->_getDepDB(); + unset($this->_cache); + $this->_setPackageDeps($data, $package); + $this->_writeDepDB($data); + } + + /** + * Remove dependencies of a package that is being uninstalled, or upgraded. + * + * Upgraded packages first uninstall, then install + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2|array If an array, then it must have + * indices 'channel' and 'package' + */ + function uninstallPackage(&$pkg) + { + $data = $this->_getDepDB(); + unset($this->_cache); + if (is_object($pkg)) { + $channel = strtolower($pkg->getChannel()); + $package = strtolower($pkg->getPackage()); + } else { + $channel = strtolower($pkg['channel']); + $package = strtolower($pkg['package']); + } + + if (!isset($data['dependencies'][$channel][$package])) { + return true; + } + + foreach ($data['dependencies'][$channel][$package] as $dep) { + $found = false; + $depchannel = isset($dep['dep']['uri']) ? '__uri' : strtolower($dep['dep']['channel']); + $depname = strtolower($dep['dep']['name']); + if (isset($data['packages'][$depchannel][$depname])) { + foreach ($data['packages'][$depchannel][$depname] as $i => $info) { + if ($info['channel'] == $channel && $info['package'] == $package) { + $found = true; + break; + } + } + } + + if ($found) { + unset($data['packages'][$depchannel][$depname][$i]); + if (!count($data['packages'][$depchannel][$depname])) { + unset($data['packages'][$depchannel][$depname]); + if (!count($data['packages'][$depchannel])) { + unset($data['packages'][$depchannel]); + } + } else { + $data['packages'][$depchannel][$depname] = + array_values($data['packages'][$depchannel][$depname]); + } + } + } + + unset($data['dependencies'][$channel][$package]); + if (!count($data['dependencies'][$channel])) { + unset($data['dependencies'][$channel]); + } + + if (!count($data['dependencies'])) { + unset($data['dependencies']); + } + + if (!count($data['packages'])) { + unset($data['packages']); + } + + $this->_writeDepDB($data); + } + + /** + * Rebuild the dependency DB by reading registry entries. + * @return true|PEAR_Error + */ + function rebuildDB() + { + $depdb = array('_version' => $this->_version); + if (!$this->hasWriteAccess()) { + // allow startup for read-only with older Registry + return $depdb; + } + + $packages = $this->_registry->listAllPackages(); + if (PEAR::isError($packages)) { + return $packages; + } + + foreach ($packages as $channel => $ps) { + foreach ($ps as $package) { + $package = $this->_registry->getPackage($package, $channel); + if (PEAR::isError($package)) { + return $package; + } + $this->_setPackageDeps($depdb, $package); + } + } + + $error = $this->_writeDepDB($depdb); + if (PEAR::isError($error)) { + return $error; + } + + $this->_cache = $depdb; + return true; + } + + /** + * Register usage of the dependency DB to prevent race conditions + * @param int one of the LOCK_* constants + * @return true|PEAR_Error + * @access private + */ + function _lock($mode = LOCK_EX) + { + if (stristr(php_uname(), 'Windows 9')) { + return true; + } + + if ($mode != LOCK_UN && is_resource($this->_lockFp)) { + // XXX does not check type of lock (LOCK_SH/LOCK_EX) + return true; + } + + $open_mode = 'w'; + // XXX People reported problems with LOCK_SH and 'w' + if ($mode === LOCK_SH) { + if (!file_exists($this->_lockfile)) { + touch($this->_lockfile); + } elseif (!is_file($this->_lockfile)) { + return PEAR::raiseError('could not create Dependency lock file, ' . + 'it exists and is not a regular file'); + } + $open_mode = 'r'; + } + + if (!is_resource($this->_lockFp)) { + $this->_lockFp = @fopen($this->_lockfile, $open_mode); + } + + if (!is_resource($this->_lockFp)) { + return PEAR::raiseError("could not create Dependency lock file" . + (isset($php_errormsg) ? ": " . $php_errormsg : "")); + } + + if (!(int)flock($this->_lockFp, $mode)) { + switch ($mode) { + case LOCK_SH: $str = 'shared'; break; + case LOCK_EX: $str = 'exclusive'; break; + case LOCK_UN: $str = 'unlock'; break; + default: $str = 'unknown'; break; + } + + return PEAR::raiseError("could not acquire $str lock ($this->_lockfile)"); + } + + return true; + } + + /** + * Release usage of dependency DB + * @return true|PEAR_Error + * @access private + */ + function _unlock() + { + $ret = $this->_lock(LOCK_UN); + if (is_resource($this->_lockFp)) { + fclose($this->_lockFp); + } + $this->_lockFp = null; + return $ret; + } + + /** + * Load the dependency database from disk, or return the cache + * @return array|PEAR_Error + */ + function _getDepDB() + { + if (!$this->hasWriteAccess()) { + return array('_version' => $this->_version); + } + + if (isset($this->_cache)) { + return $this->_cache; + } + + if (!$fp = fopen($this->_depdb, 'r')) { + $err = PEAR::raiseError("Could not open dependencies file `".$this->_depdb."'"); + return $err; + } + + $rt = get_magic_quotes_runtime(); + set_magic_quotes_runtime(0); + clearstatcache(); + fclose($fp); + $data = unserialize(file_get_contents($this->_depdb)); + set_magic_quotes_runtime($rt); + $this->_cache = $data; + return $data; + } + + /** + * Write out the dependency database to disk + * @param array the database + * @return true|PEAR_Error + * @access private + */ + function _writeDepDB(&$deps) + { + if (PEAR::isError($e = $this->_lock(LOCK_EX))) { + return $e; + } + + if (!$fp = fopen($this->_depdb, 'wb')) { + $this->_unlock(); + return PEAR::raiseError("Could not open dependencies file `".$this->_depdb."' for writing"); + } + + $rt = get_magic_quotes_runtime(); + set_magic_quotes_runtime(0); + fwrite($fp, serialize($deps)); + set_magic_quotes_runtime($rt); + fclose($fp); + $this->_unlock(); + $this->_cache = $deps; + return true; + } + + /** + * Register all dependencies from a package in the dependencies database, in essence + * "installing" the package's dependency information + * @param array the database + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2 + * @access private + */ + function _setPackageDeps(&$data, &$pkg) + { + $pkg->setConfig($this->_config); + if ($pkg->getPackagexmlVersion() == '1.0') { + $gen = &$pkg->getDefaultGenerator(); + $deps = $gen->dependenciesToV2(); + } else { + $deps = $pkg->getDeps(true); + } + + if (!$deps) { + return; + } + + if (!is_array($data)) { + $data = array(); + } + + if (!isset($data['dependencies'])) { + $data['dependencies'] = array(); + } + + $channel = strtolower($pkg->getChannel()); + $package = strtolower($pkg->getPackage()); + + if (!isset($data['dependencies'][$channel])) { + $data['dependencies'][$channel] = array(); + } + + $data['dependencies'][$channel][$package] = array(); + if (isset($deps['required']['package'])) { + if (!isset($deps['required']['package'][0])) { + $deps['required']['package'] = array($deps['required']['package']); + } + + foreach ($deps['required']['package'] as $dep) { + $this->_registerDep($data, $pkg, $dep, 'required'); + } + } + + if (isset($deps['optional']['package'])) { + if (!isset($deps['optional']['package'][0])) { + $deps['optional']['package'] = array($deps['optional']['package']); + } + + foreach ($deps['optional']['package'] as $dep) { + $this->_registerDep($data, $pkg, $dep, 'optional'); + } + } + + if (isset($deps['required']['subpackage'])) { + if (!isset($deps['required']['subpackage'][0])) { + $deps['required']['subpackage'] = array($deps['required']['subpackage']); + } + + foreach ($deps['required']['subpackage'] as $dep) { + $this->_registerDep($data, $pkg, $dep, 'required'); + } + } + + if (isset($deps['optional']['subpackage'])) { + if (!isset($deps['optional']['subpackage'][0])) { + $deps['optional']['subpackage'] = array($deps['optional']['subpackage']); + } + + foreach ($deps['optional']['subpackage'] as $dep) { + $this->_registerDep($data, $pkg, $dep, 'optional'); + } + } + + if (isset($deps['group'])) { + if (!isset($deps['group'][0])) { + $deps['group'] = array($deps['group']); + } + + foreach ($deps['group'] as $group) { + if (isset($group['package'])) { + if (!isset($group['package'][0])) { + $group['package'] = array($group['package']); + } + + foreach ($group['package'] as $dep) { + $this->_registerDep($data, $pkg, $dep, 'optional', + $group['attribs']['name']); + } + } + + if (isset($group['subpackage'])) { + if (!isset($group['subpackage'][0])) { + $group['subpackage'] = array($group['subpackage']); + } + + foreach ($group['subpackage'] as $dep) { + $this->_registerDep($data, $pkg, $dep, 'optional', + $group['attribs']['name']); + } + } + } + } + + if ($data['dependencies'][$channel][$package] == array()) { + unset($data['dependencies'][$channel][$package]); + if (!count($data['dependencies'][$channel])) { + unset($data['dependencies'][$channel]); + } + } + } + + /** + * @param array the database + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2 + * @param array the specific dependency + * @param required|optional whether this is a required or an optional dep + * @param string|false dependency group this dependency is from, or false for ordinary dep + */ + function _registerDep(&$data, &$pkg, $dep, $type, $group = false) + { + $info = array( + 'dep' => $dep, + 'type' => $type, + 'group' => $group + ); + + $dep = array_map('strtolower', $dep); + $depchannel = isset($dep['channel']) ? $dep['channel'] : '__uri'; + if (!isset($data['dependencies'])) { + $data['dependencies'] = array(); + } + + $channel = strtolower($pkg->getChannel()); + $package = strtolower($pkg->getPackage()); + + if (!isset($data['dependencies'][$channel])) { + $data['dependencies'][$channel] = array(); + } + + if (!isset($data['dependencies'][$channel][$package])) { + $data['dependencies'][$channel][$package] = array(); + } + + $data['dependencies'][$channel][$package][] = $info; + if (isset($data['packages'][$depchannel][$dep['name']])) { + $found = false; + foreach ($data['packages'][$depchannel][$dep['name']] as $i => $p) { + if ($p['channel'] == $channel && $p['package'] == $package) { + $found = true; + break; + } + } + } else { + if (!isset($data['packages'])) { + $data['packages'] = array(); + } + + if (!isset($data['packages'][$depchannel])) { + $data['packages'][$depchannel] = array(); + } + + if (!isset($data['packages'][$depchannel][$dep['name']])) { + $data['packages'][$depchannel][$dep['name']] = array(); + } + + $found = false; + } + + if (!$found) { + $data['packages'][$depchannel][$dep['name']][] = array( + 'channel' => $channel, + 'package' => $package + ); + } + } +} \ No newline at end of file diff --git a/library/pear/PEAR/Downloader.php b/library/pear/PEAR/Downloader.php new file mode 100644 index 000000000..298a5f403 --- /dev/null +++ b/library/pear/PEAR/Downloader.php @@ -0,0 +1,1762 @@ + + * @author Stig Bakken + * @author Tomas V. V. Cox + * @author Martin Jansen + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Downloader.php 296767 2010-03-25 00:58:33Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.3.0 + */ + +/** + * Needed for constants, extending + */ +require_once 'PEAR/Common.php'; + +define('PEAR_INSTALLER_OK', 1); +define('PEAR_INSTALLER_FAILED', 0); +define('PEAR_INSTALLER_SKIPPED', -1); +define('PEAR_INSTALLER_ERROR_NO_PREF_STATE', 2); + +/** + * Administration class used to download anything from the internet (PEAR Packages, + * static URLs, xml files) + * + * @category pear + * @package PEAR + * @author Greg Beaver + * @author Stig Bakken + * @author Tomas V. V. Cox + * @author Martin Jansen + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.1 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.3.0 + */ +class PEAR_Downloader extends PEAR_Common +{ + /** + * @var PEAR_Registry + * @access private + */ + var $_registry; + + /** + * Preferred Installation State (snapshot, devel, alpha, beta, stable) + * @var string|null + * @access private + */ + var $_preferredState; + + /** + * Options from command-line passed to Install. + * + * Recognized options:
    + * - onlyreqdeps : install all required dependencies as well + * - alldeps : install all dependencies, including optional + * - installroot : base relative path to install files in + * - force : force a download even if warnings would prevent it + * - nocompress : download uncompressed tarballs + * @see PEAR_Command_Install + * @access private + * @var array + */ + var $_options; + + /** + * Downloaded Packages after a call to download(). + * + * Format of each entry: + * + * + * array('pkg' => 'package_name', 'file' => '/path/to/local/file', + * 'info' => array() // parsed package.xml + * ); + * + * @access private + * @var array + */ + var $_downloadedPackages = array(); + + /** + * Packages slated for download. + * + * This is used to prevent downloading a package more than once should it be a dependency + * for two packages to be installed. + * Format of each entry: + * + *
    +     * array('package_name1' => parsed package.xml, 'package_name2' => parsed package.xml,
    +     * );
    +     * 
    + * @access private + * @var array + */ + var $_toDownload = array(); + + /** + * Array of every package installed, with names lower-cased. + * + * Format: + * + * array('package1' => 0, 'package2' => 1, ); + * + * @var array + */ + var $_installed = array(); + + /** + * @var array + * @access private + */ + var $_errorStack = array(); + + /** + * @var boolean + * @access private + */ + var $_internalDownload = false; + + /** + * Temporary variable used in sorting packages by dependency in {@link sortPkgDeps()} + * @var array + * @access private + */ + var $_packageSortTree; + + /** + * Temporary directory, or configuration value where downloads will occur + * @var string + */ + var $_downloadDir; + + /** + * @param PEAR_Frontend_* + * @param array + * @param PEAR_Config + */ + function PEAR_Downloader(&$ui, $options, &$config) + { + parent::PEAR_Common(); + $this->_options = $options; + $this->config = &$config; + $this->_preferredState = $this->config->get('preferred_state'); + $this->ui = &$ui; + if (!$this->_preferredState) { + // don't inadvertantly use a non-set preferred_state + $this->_preferredState = null; + } + + if (isset($this->_options['installroot'])) { + $this->config->setInstallRoot($this->_options['installroot']); + } + $this->_registry = &$config->getRegistry(); + + if (isset($this->_options['alldeps']) || isset($this->_options['onlyreqdeps'])) { + $this->_installed = $this->_registry->listAllPackages(); + foreach ($this->_installed as $key => $unused) { + if (!count($unused)) { + continue; + } + $strtolower = create_function('$a','return strtolower($a);'); + array_walk($this->_installed[$key], $strtolower); + } + } + } + + /** + * Attempt to discover a channel's remote capabilities from + * its server name + * @param string + * @return boolean + */ + function discover($channel) + { + $this->log(1, 'Attempting to discover channel "' . $channel . '"...'); + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $callback = $this->ui ? array(&$this, '_downloadCallback') : null; + if (!class_exists('System')) { + require_once 'System.php'; + } + + $tmp = System::mktemp(array('-d')); + $a = $this->downloadHttp('http://' . $channel . '/channel.xml', $this->ui, $tmp, $callback, false); + PEAR::popErrorHandling(); + if (PEAR::isError($a)) { + // Attempt to fallback to https automatically. + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $this->log(1, 'Attempting fallback to https instead of http on channel "' . $channel . '"...'); + $a = $this->downloadHttp('https://' . $channel . '/channel.xml', $this->ui, $tmp, $callback, false); + PEAR::popErrorHandling(); + if (PEAR::isError($a)) { + return false; + } + } + + list($a, $lastmodified) = $a; + if (!class_exists('PEAR_ChannelFile')) { + require_once 'PEAR/ChannelFile.php'; + } + + $b = new PEAR_ChannelFile; + if ($b->fromXmlFile($a)) { + unlink($a); + if ($this->config->get('auto_discover')) { + $this->_registry->addChannel($b, $lastmodified); + $alias = $b->getName(); + if ($b->getName() == $this->_registry->channelName($b->getAlias())) { + $alias = $b->getAlias(); + } + + $this->log(1, 'Auto-discovered channel "' . $channel . + '", alias "' . $alias . '", adding to registry'); + } + + return true; + } + + unlink($a); + return false; + } + + /** + * For simpler unit-testing + * @param PEAR_Downloader + * @return PEAR_Downloader_Package + */ + function &newDownloaderPackage(&$t) + { + if (!class_exists('PEAR_Downloader_Package')) { + require_once 'PEAR/Downloader/Package.php'; + } + $a = &new PEAR_Downloader_Package($t); + return $a; + } + + /** + * For simpler unit-testing + * @param PEAR_Config + * @param array + * @param array + * @param int + */ + function &getDependency2Object(&$c, $i, $p, $s) + { + if (!class_exists('PEAR_Dependency2')) { + require_once 'PEAR/Dependency2.php'; + } + $z = &new PEAR_Dependency2($c, $i, $p, $s); + return $z; + } + + function &download($params) + { + if (!count($params)) { + $a = array(); + return $a; + } + + if (!isset($this->_registry)) { + $this->_registry = &$this->config->getRegistry(); + } + + $channelschecked = array(); + // convert all parameters into PEAR_Downloader_Package objects + foreach ($params as $i => $param) { + $params[$i] = &$this->newDownloaderPackage($this); + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $err = $params[$i]->initialize($param); + PEAR::staticPopErrorHandling(); + if (!$err) { + // skip parameters that were missed by preferred_state + continue; + } + + if (PEAR::isError($err)) { + if (!isset($this->_options['soft']) && $err->getMessage() !== '') { + $this->log(0, $err->getMessage()); + } + + $params[$i] = false; + if (is_object($param)) { + $param = $param->getChannel() . '/' . $param->getPackage(); + } + + if (!isset($this->_options['soft'])) { + $this->log(2, 'Package "' . $param . '" is not valid'); + } + + // Message logged above in a specific verbose mode, passing null to not show up on CLI + $this->pushError(null, PEAR_INSTALLER_SKIPPED); + } else { + do { + if ($params[$i] && $params[$i]->getType() == 'local') { + // bug #7090 skip channel.xml check for local packages + break; + } + + if ($params[$i] && !isset($channelschecked[$params[$i]->getChannel()]) && + !isset($this->_options['offline']) + ) { + $channelschecked[$params[$i]->getChannel()] = true; + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + if (!class_exists('System')) { + require_once 'System.php'; + } + + $curchannel = &$this->_registry->getChannel($params[$i]->getChannel()); + if (PEAR::isError($curchannel)) { + PEAR::staticPopErrorHandling(); + return $this->raiseError($curchannel); + } + + if (PEAR::isError($dir = $this->getDownloadDir())) { + PEAR::staticPopErrorHandling(); + break; + } + + $mirror = $this->config->get('preferred_mirror', null, $params[$i]->getChannel()); + $url = 'http://' . $mirror . '/channel.xml'; + $a = $this->downloadHttp($url, $this->ui, $dir, null, $curchannel->lastModified()); + + PEAR::staticPopErrorHandling(); + if (PEAR::isError($a) || !$a) { + // Attempt fallback to https automatically + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $a = $this->downloadHttp('https://' . $mirror . + '/channel.xml', $this->ui, $dir, null, $curchannel->lastModified()); + + PEAR::staticPopErrorHandling(); + if (PEAR::isError($a) || !$a) { + break; + } + } + $this->log(0, 'WARNING: channel "' . $params[$i]->getChannel() . '" has ' . + 'updated its protocols, use "' . PEAR_RUNTYPE . ' channel-update ' . $params[$i]->getChannel() . + '" to update'); + } + } while (false); + + if ($params[$i] && !isset($this->_options['downloadonly'])) { + if (isset($this->_options['packagingroot'])) { + $checkdir = $this->_prependPath( + $this->config->get('php_dir', null, $params[$i]->getChannel()), + $this->_options['packagingroot']); + } else { + $checkdir = $this->config->get('php_dir', + null, $params[$i]->getChannel()); + } + + while ($checkdir && $checkdir != '/' && !file_exists($checkdir)) { + $checkdir = dirname($checkdir); + } + + if ($checkdir == '.') { + $checkdir = '/'; + } + + if (!is_writeable($checkdir)) { + return PEAR::raiseError('Cannot install, php_dir for channel "' . + $params[$i]->getChannel() . '" is not writeable by the current user'); + } + } + } + } + + unset($channelschecked); + PEAR_Downloader_Package::removeDuplicates($params); + if (!count($params)) { + $a = array(); + return $a; + } + + if (!isset($this->_options['nodeps']) && !isset($this->_options['offline'])) { + $reverify = true; + while ($reverify) { + $reverify = false; + foreach ($params as $i => $param) { + //PHP Bug 40768 / PEAR Bug #10944 + //Nested foreaches fail in PHP 5.2.1 + key($params); + $ret = $params[$i]->detectDependencies($params); + if (PEAR::isError($ret)) { + $reverify = true; + $params[$i] = false; + PEAR_Downloader_Package::removeDuplicates($params); + if (!isset($this->_options['soft'])) { + $this->log(0, $ret->getMessage()); + } + continue 2; + } + } + } + } + + if (isset($this->_options['offline'])) { + $this->log(3, 'Skipping dependency download check, --offline specified'); + } + + if (!count($params)) { + $a = array(); + return $a; + } + + while (PEAR_Downloader_Package::mergeDependencies($params)); + PEAR_Downloader_Package::removeDuplicates($params, true); + $errorparams = array(); + if (PEAR_Downloader_Package::detectStupidDuplicates($params, $errorparams)) { + if (count($errorparams)) { + foreach ($errorparams as $param) { + $name = $this->_registry->parsedPackageNameToString($param->getParsedPackage()); + $this->pushError('Duplicate package ' . $name . ' found', PEAR_INSTALLER_FAILED); + } + $a = array(); + return $a; + } + } + + PEAR_Downloader_Package::removeInstalled($params); + if (!count($params)) { + $this->pushError('No valid packages found', PEAR_INSTALLER_FAILED); + $a = array(); + return $a; + } + + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $err = $this->analyzeDependencies($params); + PEAR::popErrorHandling(); + if (!count($params)) { + $this->pushError('No valid packages found', PEAR_INSTALLER_FAILED); + $a = array(); + return $a; + } + + $ret = array(); + $newparams = array(); + if (isset($this->_options['pretend'])) { + return $params; + } + + $somefailed = false; + foreach ($params as $i => $package) { + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $pf = &$params[$i]->download(); + PEAR::staticPopErrorHandling(); + if (PEAR::isError($pf)) { + if (!isset($this->_options['soft'])) { + $this->log(1, $pf->getMessage()); + $this->log(0, 'Error: cannot download "' . + $this->_registry->parsedPackageNameToString($package->getParsedPackage(), + true) . + '"'); + } + $somefailed = true; + continue; + } + + $newparams[] = &$params[$i]; + $ret[] = array( + 'file' => $pf->getArchiveFile(), + 'info' => &$pf, + 'pkg' => $pf->getPackage() + ); + } + + if ($somefailed) { + // remove params that did not download successfully + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $err = $this->analyzeDependencies($newparams, true); + PEAR::popErrorHandling(); + if (!count($newparams)) { + $this->pushError('Download failed', PEAR_INSTALLER_FAILED); + $a = array(); + return $a; + } + } + + $this->_downloadedPackages = $ret; + return $newparams; + } + + /** + * @param array all packages to be installed + */ + function analyzeDependencies(&$params, $force = false) + { + $hasfailed = $failed = false; + if (isset($this->_options['downloadonly'])) { + return; + } + + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $redo = true; + $reset = false; + while ($redo) { + $redo = false; + foreach ($params as $i => $param) { + $deps = $param->getDeps(); + if (!$deps) { + $depchecker = &$this->getDependency2Object($this->config, $this->getOptions(), + $param->getParsedPackage(), PEAR_VALIDATE_DOWNLOADING); + $send = $param->getPackageFile(); + + $installcheck = $depchecker->validatePackage($send, $this, $params); + if (PEAR::isError($installcheck)) { + if (!isset($this->_options['soft'])) { + $this->log(0, $installcheck->getMessage()); + } + $hasfailed = true; + $params[$i] = false; + $reset = true; + $redo = true; + $failed = false; + PEAR_Downloader_Package::removeDuplicates($params); + continue 2; + } + continue; + } + + if (!$reset && $param->alreadyValidated() && !$force) { + continue; + } + + if (count($deps)) { + $depchecker = &$this->getDependency2Object($this->config, $this->getOptions(), + $param->getParsedPackage(), PEAR_VALIDATE_DOWNLOADING); + $send = $param->getPackageFile(); + if ($send === null) { + $send = $param->getDownloadURL(); + } + + $installcheck = $depchecker->validatePackage($send, $this, $params); + if (PEAR::isError($installcheck)) { + if (!isset($this->_options['soft'])) { + $this->log(0, $installcheck->getMessage()); + } + $hasfailed = true; + $params[$i] = false; + $reset = true; + $redo = true; + $failed = false; + PEAR_Downloader_Package::removeDuplicates($params); + continue 2; + } + + $failed = false; + if (isset($deps['required'])) { + foreach ($deps['required'] as $type => $dep) { + // note: Dependency2 will never return a PEAR_Error if ignore-errors + // is specified, so soft is needed to turn off logging + if (!isset($dep[0])) { + if (PEAR::isError($e = $depchecker->{"validate{$type}Dependency"}($dep, + true, $params))) { + $failed = true; + if (!isset($this->_options['soft'])) { + $this->log(0, $e->getMessage()); + } + } elseif (is_array($e) && !$param->alreadyValidated()) { + if (!isset($this->_options['soft'])) { + $this->log(0, $e[0]); + } + } + } else { + foreach ($dep as $d) { + if (PEAR::isError($e = + $depchecker->{"validate{$type}Dependency"}($d, + true, $params))) { + $failed = true; + if (!isset($this->_options['soft'])) { + $this->log(0, $e->getMessage()); + } + } elseif (is_array($e) && !$param->alreadyValidated()) { + if (!isset($this->_options['soft'])) { + $this->log(0, $e[0]); + } + } + } + } + } + + if (isset($deps['optional'])) { + foreach ($deps['optional'] as $type => $dep) { + if (!isset($dep[0])) { + if (PEAR::isError($e = + $depchecker->{"validate{$type}Dependency"}($dep, + false, $params))) { + $failed = true; + if (!isset($this->_options['soft'])) { + $this->log(0, $e->getMessage()); + } + } elseif (is_array($e) && !$param->alreadyValidated()) { + if (!isset($this->_options['soft'])) { + $this->log(0, $e[0]); + } + } + } else { + foreach ($dep as $d) { + if (PEAR::isError($e = + $depchecker->{"validate{$type}Dependency"}($d, + false, $params))) { + $failed = true; + if (!isset($this->_options['soft'])) { + $this->log(0, $e->getMessage()); + } + } elseif (is_array($e) && !$param->alreadyValidated()) { + if (!isset($this->_options['soft'])) { + $this->log(0, $e[0]); + } + } + } + } + } + } + + $groupname = $param->getGroup(); + if (isset($deps['group']) && $groupname) { + if (!isset($deps['group'][0])) { + $deps['group'] = array($deps['group']); + } + + $found = false; + foreach ($deps['group'] as $group) { + if ($group['attribs']['name'] == $groupname) { + $found = true; + break; + } + } + + if ($found) { + unset($group['attribs']); + foreach ($group as $type => $dep) { + if (!isset($dep[0])) { + if (PEAR::isError($e = + $depchecker->{"validate{$type}Dependency"}($dep, + false, $params))) { + $failed = true; + if (!isset($this->_options['soft'])) { + $this->log(0, $e->getMessage()); + } + } elseif (is_array($e) && !$param->alreadyValidated()) { + if (!isset($this->_options['soft'])) { + $this->log(0, $e[0]); + } + } + } else { + foreach ($dep as $d) { + if (PEAR::isError($e = + $depchecker->{"validate{$type}Dependency"}($d, + false, $params))) { + $failed = true; + if (!isset($this->_options['soft'])) { + $this->log(0, $e->getMessage()); + } + } elseif (is_array($e) && !$param->alreadyValidated()) { + if (!isset($this->_options['soft'])) { + $this->log(0, $e[0]); + } + } + } + } + } + } + } + } else { + foreach ($deps as $dep) { + if (PEAR::isError($e = $depchecker->validateDependency1($dep, $params))) { + $failed = true; + if (!isset($this->_options['soft'])) { + $this->log(0, $e->getMessage()); + } + } elseif (is_array($e) && !$param->alreadyValidated()) { + if (!isset($this->_options['soft'])) { + $this->log(0, $e[0]); + } + } + } + } + $params[$i]->setValidated(); + } + + if ($failed) { + $hasfailed = true; + $params[$i] = false; + $reset = true; + $redo = true; + $failed = false; + PEAR_Downloader_Package::removeDuplicates($params); + continue 2; + } + } + } + PEAR::staticPopErrorHandling(); + if ($hasfailed && (isset($this->_options['ignore-errors']) || + isset($this->_options['nodeps']))) { + // this is probably not needed, but just in case + if (!isset($this->_options['soft'])) { + $this->log(0, 'WARNING: dependencies failed'); + } + } + } + + /** + * Retrieve the directory that downloads will happen in + * @access private + * @return string + */ + function getDownloadDir() + { + if (isset($this->_downloadDir)) { + return $this->_downloadDir; + } + $downloaddir = $this->config->get('download_dir'); + if (empty($downloaddir) || (is_dir($downloaddir) && !is_writable($downloaddir))) { + if (is_dir($downloaddir) && !is_writable($downloaddir)) { + $this->log(0, 'WARNING: configuration download directory "' . $downloaddir . + '" is not writeable. Change download_dir config variable to ' . + 'a writeable dir to avoid this warning'); + } + if (!class_exists('System')) { + require_once 'System.php'; + } + if (PEAR::isError($downloaddir = System::mktemp('-d'))) { + return $downloaddir; + } + $this->log(3, '+ tmp dir created at ' . $downloaddir); + } + if (!is_writable($downloaddir)) { + if (PEAR::isError(System::mkdir(array('-p', $downloaddir))) || + !is_writable($downloaddir)) { + return PEAR::raiseError('download directory "' . $downloaddir . + '" is not writeable. Change download_dir config variable to ' . + 'a writeable dir'); + } + } + return $this->_downloadDir = $downloaddir; + } + + function setDownloadDir($dir) + { + if (!@is_writable($dir)) { + if (PEAR::isError(System::mkdir(array('-p', $dir)))) { + return PEAR::raiseError('download directory "' . $dir . + '" is not writeable. Change download_dir config variable to ' . + 'a writeable dir'); + } + } + $this->_downloadDir = $dir; + } + + function configSet($key, $value, $layer = 'user', $channel = false) + { + $this->config->set($key, $value, $layer, $channel); + $this->_preferredState = $this->config->get('preferred_state', null, $channel); + if (!$this->_preferredState) { + // don't inadvertantly use a non-set preferred_state + $this->_preferredState = null; + } + } + + function setOptions($options) + { + $this->_options = $options; + } + + // }}} + // {{{ setOptions() + function getOptions() + { + return $this->_options; + } + + /** + * For simpler unit-testing + * @param PEAR_Config + * @param int + * @param string + */ + function &getPackagefileObject(&$c, $d, $t = false) + { + if (!class_exists('PEAR_PackageFile')) { + require_once 'PEAR/PackageFile.php'; + } + $a = &new PEAR_PackageFile($c, $d, $t); + return $a; + } + + /** + * @param array output of {@link parsePackageName()} + * @access private + */ + function _getPackageDownloadUrl($parr) + { + $curchannel = $this->config->get('default_channel'); + $this->configSet('default_channel', $parr['channel']); + // getDownloadURL returns an array. On error, it only contains information + // on the latest release as array(version, info). On success it contains + // array(version, info, download url string) + $state = isset($parr['state']) ? $parr['state'] : $this->config->get('preferred_state'); + if (!$this->_registry->channelExists($parr['channel'])) { + do { + if ($this->config->get('auto_discover') && $this->discover($parr['channel'])) { + break; + } + + $this->configSet('default_channel', $curchannel); + return PEAR::raiseError('Unknown remote channel: ' . $parr['channel']); + } while (false); + } + + $chan = &$this->_registry->getChannel($parr['channel']); + if (PEAR::isError($chan)) { + return $chan; + } + + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $version = $this->_registry->packageInfo($parr['package'], 'version', $parr['channel']); + $stability = $this->_registry->packageInfo($parr['package'], 'stability', $parr['channel']); + // package is installed - use the installed release stability level + if (!isset($parr['state']) && $stability !== null) { + $state = $stability['release']; + } + PEAR::staticPopErrorHandling(); + $base2 = false; + + $preferred_mirror = $this->config->get('preferred_mirror'); + if (!$chan->supportsREST($preferred_mirror) || + ( + !($base2 = $chan->getBaseURL('REST1.3', $preferred_mirror)) + && + !($base = $chan->getBaseURL('REST1.0', $preferred_mirror)) + ) + ) { + return $this->raiseError($parr['channel'] . ' is using a unsupported protocol - This should never happen.'); + } + + if ($base2) { + $rest = &$this->config->getREST('1.3', $this->_options); + $base = $base2; + } else { + $rest = &$this->config->getREST('1.0', $this->_options); + } + + $downloadVersion = false; + if (!isset($parr['version']) && !isset($parr['state']) && $version + && !PEAR::isError($version) + && !isset($this->_options['downloadonly']) + ) { + $downloadVersion = $version; + } + + $url = $rest->getDownloadURL($base, $parr, $state, $downloadVersion, $chan->getName()); + if (PEAR::isError($url)) { + $this->configSet('default_channel', $curchannel); + return $url; + } + + if ($parr['channel'] != $curchannel) { + $this->configSet('default_channel', $curchannel); + } + + if (!is_array($url)) { + return $url; + } + + $url['raw'] = false; // no checking is necessary for REST + if (!is_array($url['info'])) { + return PEAR::raiseError('Invalid remote dependencies retrieved from REST - ' . + 'this should never happen'); + } + + if (!isset($this->_options['force']) && + !isset($this->_options['downloadonly']) && + $version && + !PEAR::isError($version) && + !isset($parr['group']) + ) { + if (version_compare($version, $url['version'], '=')) { + return PEAR::raiseError($this->_registry->parsedPackageNameToString( + $parr, true) . ' is already installed and is the same as the ' . + 'released version ' . $url['version'], -976); + } + + if (version_compare($version, $url['version'], '>')) { + return PEAR::raiseError($this->_registry->parsedPackageNameToString( + $parr, true) . ' is already installed and is newer than detected ' . + 'released version ' . $url['version'], -976); + } + } + + if (isset($url['info']['required']) || $url['compatible']) { + require_once 'PEAR/PackageFile/v2.php'; + $pf = new PEAR_PackageFile_v2; + $pf->setRawChannel($parr['channel']); + if ($url['compatible']) { + $pf->setRawCompatible($url['compatible']); + } + } else { + require_once 'PEAR/PackageFile/v1.php'; + $pf = new PEAR_PackageFile_v1; + } + + $pf->setRawPackage($url['package']); + $pf->setDeps($url['info']); + if ($url['compatible']) { + $pf->setCompatible($url['compatible']); + } + + $pf->setRawState($url['stability']); + $url['info'] = &$pf; + if (!extension_loaded("zlib") || isset($this->_options['nocompress'])) { + $ext = '.tar'; + } else { + $ext = '.tgz'; + } + + if (is_array($url) && isset($url['url'])) { + $url['url'] .= $ext; + } + + return $url; + } + + /** + * @param array dependency array + * @access private + */ + function _getDepPackageDownloadUrl($dep, $parr) + { + $xsdversion = isset($dep['rel']) ? '1.0' : '2.0'; + $curchannel = $this->config->get('default_channel'); + if (isset($dep['uri'])) { + $xsdversion = '2.0'; + $chan = &$this->_registry->getChannel('__uri'); + if (PEAR::isError($chan)) { + return $chan; + } + + $version = $this->_registry->packageInfo($dep['name'], 'version', '__uri'); + $this->configSet('default_channel', '__uri'); + } else { + if (isset($dep['channel'])) { + $remotechannel = $dep['channel']; + } else { + $remotechannel = 'pear.php.net'; + } + + if (!$this->_registry->channelExists($remotechannel)) { + do { + if ($this->config->get('auto_discover')) { + if ($this->discover($remotechannel)) { + break; + } + } + return PEAR::raiseError('Unknown remote channel: ' . $remotechannel); + } while (false); + } + + $chan = &$this->_registry->getChannel($remotechannel); + if (PEAR::isError($chan)) { + return $chan; + } + + $version = $this->_registry->packageInfo($dep['name'], 'version', $remotechannel); + $this->configSet('default_channel', $remotechannel); + } + + $state = isset($parr['state']) ? $parr['state'] : $this->config->get('preferred_state'); + if (isset($parr['state']) && isset($parr['version'])) { + unset($parr['state']); + } + + if (isset($dep['uri'])) { + $info = &$this->newDownloaderPackage($this); + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $err = $info->initialize($dep); + PEAR::staticPopErrorHandling(); + if (!$err) { + // skip parameters that were missed by preferred_state + return PEAR::raiseError('Cannot initialize dependency'); + } + + if (PEAR::isError($err)) { + if (!isset($this->_options['soft'])) { + $this->log(0, $err->getMessage()); + } + + if (is_object($info)) { + $param = $info->getChannel() . '/' . $info->getPackage(); + } + return PEAR::raiseError('Package "' . $param . '" is not valid'); + } + return $info; + } elseif ($chan->supportsREST($this->config->get('preferred_mirror')) + && $base = $chan->getBaseURL('REST1.0', $this->config->get('preferred_mirror')) + ) { + $rest = &$this->config->getREST('1.0', $this->_options); + $url = $rest->getDepDownloadURL($base, $xsdversion, $dep, $parr, + $state, $version, $chan->getName()); + if (PEAR::isError($url)) { + return $url; + } + + if ($parr['channel'] != $curchannel) { + $this->configSet('default_channel', $curchannel); + } + + if (!is_array($url)) { + return $url; + } + + $url['raw'] = false; // no checking is necessary for REST + if (!is_array($url['info'])) { + return PEAR::raiseError('Invalid remote dependencies retrieved from REST - ' . + 'this should never happen'); + } + + if (isset($url['info']['required'])) { + if (!class_exists('PEAR_PackageFile_v2')) { + require_once 'PEAR/PackageFile/v2.php'; + } + $pf = new PEAR_PackageFile_v2; + $pf->setRawChannel($remotechannel); + } else { + if (!class_exists('PEAR_PackageFile_v1')) { + require_once 'PEAR/PackageFile/v1.php'; + } + $pf = new PEAR_PackageFile_v1; + + } + $pf->setRawPackage($url['package']); + $pf->setDeps($url['info']); + if ($url['compatible']) { + $pf->setCompatible($url['compatible']); + } + + $pf->setRawState($url['stability']); + $url['info'] = &$pf; + if (!extension_loaded("zlib") || isset($this->_options['nocompress'])) { + $ext = '.tar'; + } else { + $ext = '.tgz'; + } + + if (is_array($url) && isset($url['url'])) { + $url['url'] .= $ext; + } + + return $url; + } + + return $this->raiseError($parr['channel'] . ' is using a unsupported protocol - This should never happen.'); + } + + /** + * @deprecated in favor of _getPackageDownloadUrl + */ + function getPackageDownloadUrl($package, $version = null, $channel = false) + { + if ($version) { + $package .= "-$version"; + } + if ($this === null || $this->_registry === null) { + $package = "http://pear.php.net/get/$package"; + } else { + $chan = $this->_registry->getChannel($channel); + if (PEAR::isError($chan)) { + return ''; + } + $package = "http://" . $chan->getServer() . "/get/$package"; + } + if (!extension_loaded("zlib")) { + $package .= '?uncompress=yes'; + } + return $package; + } + + /** + * Retrieve a list of downloaded packages after a call to {@link download()}. + * + * Also resets the list of downloaded packages. + * @return array + */ + function getDownloadedPackages() + { + $ret = $this->_downloadedPackages; + $this->_downloadedPackages = array(); + $this->_toDownload = array(); + return $ret; + } + + function _downloadCallback($msg, $params = null) + { + switch ($msg) { + case 'saveas': + $this->log(1, "downloading $params ..."); + break; + case 'done': + $this->log(1, '...done: ' . number_format($params, 0, '', ',') . ' bytes'); + break; + case 'bytesread': + static $bytes; + if (empty($bytes)) { + $bytes = 0; + } + if (!($bytes % 10240)) { + $this->log(1, '.', false); + } + $bytes += $params; + break; + case 'start': + if($params[1] == -1) { + $length = "Unknown size"; + } else { + $length = number_format($params[1], 0, '', ',')." bytes"; + } + $this->log(1, "Starting to download {$params[0]} ($length)"); + break; + } + if (method_exists($this->ui, '_downloadCallback')) + $this->ui->_downloadCallback($msg, $params); + } + + function _prependPath($path, $prepend) + { + if (strlen($prepend) > 0) { + if (OS_WINDOWS && preg_match('/^[a-z]:/i', $path)) { + if (preg_match('/^[a-z]:/i', $prepend)) { + $prepend = substr($prepend, 2); + } elseif ($prepend{0} != '\\') { + $prepend = "\\$prepend"; + } + $path = substr($path, 0, 2) . $prepend . substr($path, 2); + } else { + $path = $prepend . $path; + } + } + return $path; + } + + /** + * @param string + * @param integer + */ + function pushError($errmsg, $code = -1) + { + array_push($this->_errorStack, array($errmsg, $code)); + } + + function getErrorMsgs() + { + $msgs = array(); + $errs = $this->_errorStack; + foreach ($errs as $err) { + $msgs[] = $err[0]; + } + $this->_errorStack = array(); + return $msgs; + } + + /** + * for BC + * + * @deprecated + */ + function sortPkgDeps(&$packages, $uninstall = false) + { + $uninstall ? + $this->sortPackagesForUninstall($packages) : + $this->sortPackagesForInstall($packages); + } + + /** + * Sort a list of arrays of array(downloaded packagefilename) by dependency. + * + * This uses the topological sort method from graph theory, and the + * Structures_Graph package to properly sort dependencies for installation. + * @param array an array of downloaded PEAR_Downloader_Packages + * @return array array of array(packagefilename, package.xml contents) + */ + function sortPackagesForInstall(&$packages) + { + require_once 'Structures/Graph.php'; + require_once 'Structures/Graph/Node.php'; + require_once 'Structures/Graph/Manipulator/TopologicalSorter.php'; + $depgraph = new Structures_Graph(true); + $nodes = array(); + $reg = &$this->config->getRegistry(); + foreach ($packages as $i => $package) { + $pname = $reg->parsedPackageNameToString( + array( + 'channel' => $package->getChannel(), + 'package' => strtolower($package->getPackage()), + )); + $nodes[$pname] = new Structures_Graph_Node; + $nodes[$pname]->setData($packages[$i]); + $depgraph->addNode($nodes[$pname]); + } + + $deplinks = array(); + foreach ($nodes as $package => $node) { + $pf = &$node->getData(); + $pdeps = $pf->getDeps(true); + if (!$pdeps) { + continue; + } + + if ($pf->getPackagexmlVersion() == '1.0') { + foreach ($pdeps as $dep) { + if ($dep['type'] != 'pkg' || + (isset($dep['optional']) && $dep['optional'] == 'yes')) { + continue; + } + + $dname = $reg->parsedPackageNameToString( + array( + 'channel' => 'pear.php.net', + 'package' => strtolower($dep['name']), + )); + + if (isset($nodes[$dname])) { + if (!isset($deplinks[$dname])) { + $deplinks[$dname] = array(); + } + + $deplinks[$dname][$package] = 1; + // dependency is in installed packages + continue; + } + + $dname = $reg->parsedPackageNameToString( + array( + 'channel' => 'pecl.php.net', + 'package' => strtolower($dep['name']), + )); + + if (isset($nodes[$dname])) { + if (!isset($deplinks[$dname])) { + $deplinks[$dname] = array(); + } + + $deplinks[$dname][$package] = 1; + // dependency is in installed packages + continue; + } + } + } else { + // the only ordering we care about is: + // 1) subpackages must be installed before packages that depend on them + // 2) required deps must be installed before packages that depend on them + if (isset($pdeps['required']['subpackage'])) { + $t = $pdeps['required']['subpackage']; + if (!isset($t[0])) { + $t = array($t); + } + + $this->_setupGraph($t, $reg, $deplinks, $nodes, $package); + } + + if (isset($pdeps['group'])) { + if (!isset($pdeps['group'][0])) { + $pdeps['group'] = array($pdeps['group']); + } + + foreach ($pdeps['group'] as $group) { + if (isset($group['subpackage'])) { + $t = $group['subpackage']; + if (!isset($t[0])) { + $t = array($t); + } + + $this->_setupGraph($t, $reg, $deplinks, $nodes, $package); + } + } + } + + if (isset($pdeps['optional']['subpackage'])) { + $t = $pdeps['optional']['subpackage']; + if (!isset($t[0])) { + $t = array($t); + } + + $this->_setupGraph($t, $reg, $deplinks, $nodes, $package); + } + + if (isset($pdeps['required']['package'])) { + $t = $pdeps['required']['package']; + if (!isset($t[0])) { + $t = array($t); + } + + $this->_setupGraph($t, $reg, $deplinks, $nodes, $package); + } + + if (isset($pdeps['group'])) { + if (!isset($pdeps['group'][0])) { + $pdeps['group'] = array($pdeps['group']); + } + + foreach ($pdeps['group'] as $group) { + if (isset($group['package'])) { + $t = $group['package']; + if (!isset($t[0])) { + $t = array($t); + } + + $this->_setupGraph($t, $reg, $deplinks, $nodes, $package); + } + } + } + } + } + + $this->_detectDepCycle($deplinks); + foreach ($deplinks as $dependent => $parents) { + foreach ($parents as $parent => $unused) { + $nodes[$dependent]->connectTo($nodes[$parent]); + } + } + + $installOrder = Structures_Graph_Manipulator_TopologicalSorter::sort($depgraph); + $ret = array(); + for ($i = 0, $count = count($installOrder); $i < $count; $i++) { + foreach ($installOrder[$i] as $index => $sortedpackage) { + $data = &$installOrder[$i][$index]->getData(); + $ret[] = &$nodes[$reg->parsedPackageNameToString( + array( + 'channel' => $data->getChannel(), + 'package' => strtolower($data->getPackage()), + ))]->getData(); + } + } + + $packages = $ret; + return; + } + + /** + * Detect recursive links between dependencies and break the cycles + * + * @param array + * @access private + */ + function _detectDepCycle(&$deplinks) + { + do { + $keepgoing = false; + foreach ($deplinks as $dep => $parents) { + foreach ($parents as $parent => $unused) { + // reset the parent cycle detector + $this->_testCycle(null, null, null); + if ($this->_testCycle($dep, $deplinks, $parent)) { + $keepgoing = true; + unset($deplinks[$dep][$parent]); + if (count($deplinks[$dep]) == 0) { + unset($deplinks[$dep]); + } + + continue 3; + } + } + } + } while ($keepgoing); + } + + function _testCycle($test, $deplinks, $dep) + { + static $visited = array(); + if ($test === null) { + $visited = array(); + return; + } + + // this happens when a parent has a dep cycle on another dependency + // but the child is not part of the cycle + if (isset($visited[$dep])) { + return false; + } + + $visited[$dep] = 1; + if ($test == $dep) { + return true; + } + + if (isset($deplinks[$dep])) { + if (in_array($test, array_keys($deplinks[$dep]), true)) { + return true; + } + + foreach ($deplinks[$dep] as $parent => $unused) { + if ($this->_testCycle($test, $deplinks, $parent)) { + return true; + } + } + } + + return false; + } + + /** + * Set up the dependency for installation parsing + * + * @param array $t dependency information + * @param PEAR_Registry $reg + * @param array $deplinks list of dependency links already established + * @param array $nodes all existing package nodes + * @param string $package parent package name + * @access private + */ + function _setupGraph($t, $reg, &$deplinks, &$nodes, $package) + { + foreach ($t as $dep) { + $depchannel = !isset($dep['channel']) ? '__uri': $dep['channel']; + $dname = $reg->parsedPackageNameToString( + array( + 'channel' => $depchannel, + 'package' => strtolower($dep['name']), + )); + + if (isset($nodes[$dname])) { + if (!isset($deplinks[$dname])) { + $deplinks[$dname] = array(); + } + $deplinks[$dname][$package] = 1; + } + } + } + + function _dependsOn($a, $b) + { + return $this->_checkDepTree(strtolower($a->getChannel()), strtolower($a->getPackage()), $b); + } + + function _checkDepTree($channel, $package, $b, $checked = array()) + { + $checked[$channel][$package] = true; + if (!isset($this->_depTree[$channel][$package])) { + return false; + } + + if (isset($this->_depTree[$channel][$package][strtolower($b->getChannel())] + [strtolower($b->getPackage())])) { + return true; + } + + foreach ($this->_depTree[$channel][$package] as $ch => $packages) { + foreach ($packages as $pa => $true) { + if ($this->_checkDepTree($ch, $pa, $b, $checked)) { + return true; + } + } + } + + return false; + } + + function _sortInstall($a, $b) + { + if (!$a->getDeps() && !$b->getDeps()) { + return 0; // neither package has dependencies, order is insignificant + } + if ($a->getDeps() && !$b->getDeps()) { + return 1; // $a must be installed after $b because $a has dependencies + } + if (!$a->getDeps() && $b->getDeps()) { + return -1; // $b must be installed after $a because $b has dependencies + } + // both packages have dependencies + if ($this->_dependsOn($a, $b)) { + return 1; + } + if ($this->_dependsOn($b, $a)) { + return -1; + } + return 0; + } + + /** + * Download a file through HTTP. Considers suggested file name in + * Content-disposition: header and can run a callback function for + * different events. The callback will be called with two + * parameters: the callback type, and parameters. The implemented + * callback types are: + * + * 'setup' called at the very beginning, parameter is a UI object + * that should be used for all output + * 'message' the parameter is a string with an informational message + * 'saveas' may be used to save with a different file name, the + * parameter is the filename that is about to be used. + * If a 'saveas' callback returns a non-empty string, + * that file name will be used as the filename instead. + * Note that $save_dir will not be affected by this, only + * the basename of the file. + * 'start' download is starting, parameter is number of bytes + * that are expected, or -1 if unknown + * 'bytesread' parameter is the number of bytes read so far + * 'done' download is complete, parameter is the total number + * of bytes read + * 'connfailed' if the TCP/SSL connection fails, this callback is called + * with array(host,port,errno,errmsg) + * 'writefailed' if writing to disk fails, this callback is called + * with array(destfile,errmsg) + * + * If an HTTP proxy has been configured (http_proxy PEAR_Config + * setting), the proxy will be used. + * + * @param string $url the URL to download + * @param object $ui PEAR_Frontend_* instance + * @param object $config PEAR_Config instance + * @param string $save_dir directory to save file in + * @param mixed $callback function/method to call for status + * updates + * @param false|string|array $lastmodified header values to check against for caching + * use false to return the header values from this download + * @param false|array $accept Accept headers to send + * @param false|string $channel Channel to use for retrieving authentication + * @return string|array Returns the full path of the downloaded file or a PEAR + * error on failure. If the error is caused by + * socket-related errors, the error object will + * have the fsockopen error code available through + * getCode(). If caching is requested, then return the header + * values. + * + * @access public + */ + function downloadHttp($url, &$ui, $save_dir = '.', $callback = null, $lastmodified = null, + $accept = false, $channel = false) + { + static $redirect = 0; + // always reset , so we are clean case of error + $wasredirect = $redirect; + $redirect = 0; + if ($callback) { + call_user_func($callback, 'setup', array(&$ui)); + } + + $info = parse_url($url); + if (!isset($info['scheme']) || !in_array($info['scheme'], array('http', 'https'))) { + return PEAR::raiseError('Cannot download non-http URL "' . $url . '"'); + } + + if (!isset($info['host'])) { + return PEAR::raiseError('Cannot download from non-URL "' . $url . '"'); + } + + $host = isset($info['host']) ? $info['host'] : null; + $port = isset($info['port']) ? $info['port'] : null; + $path = isset($info['path']) ? $info['path'] : null; + + if (isset($this)) { + $config = &$this->config; + } else { + $config = &PEAR_Config::singleton(); + } + + $proxy_host = $proxy_port = $proxy_user = $proxy_pass = ''; + if ($config->get('http_proxy') && + $proxy = parse_url($config->get('http_proxy'))) { + $proxy_host = isset($proxy['host']) ? $proxy['host'] : null; + if (isset($proxy['scheme']) && $proxy['scheme'] == 'https') { + $proxy_host = 'ssl://' . $proxy_host; + } + $proxy_port = isset($proxy['port']) ? $proxy['port'] : 8080; + $proxy_user = isset($proxy['user']) ? urldecode($proxy['user']) : null; + $proxy_pass = isset($proxy['pass']) ? urldecode($proxy['pass']) : null; + + if ($callback) { + call_user_func($callback, 'message', "Using HTTP proxy $host:$port"); + } + } + + if (empty($port)) { + $port = (isset($info['scheme']) && $info['scheme'] == 'https') ? 443 : 80; + } + + $scheme = (isset($info['scheme']) && $info['scheme'] == 'https') ? 'https' : 'http'; + + if ($proxy_host != '') { + $fp = @fsockopen($proxy_host, $proxy_port, $errno, $errstr); + if (!$fp) { + if ($callback) { + call_user_func($callback, 'connfailed', array($proxy_host, $proxy_port, + $errno, $errstr)); + } + return PEAR::raiseError("Connection to `$proxy_host:$proxy_port' failed: $errstr", $errno); + } + + if ($lastmodified === false || $lastmodified) { + $request = "GET $url HTTP/1.1\r\n"; + $request .= "Host: $host\r\n"; + } else { + $request = "GET $url HTTP/1.0\r\n"; + $request .= "Host: $host\r\n"; + } + } else { + $network_host = $host; + if (isset($info['scheme']) && $info['scheme'] == 'https') { + $network_host = 'ssl://' . $host; + } + + $fp = @fsockopen($network_host, $port, $errno, $errstr); + if (!$fp) { + if ($callback) { + call_user_func($callback, 'connfailed', array($host, $port, + $errno, $errstr)); + } + return PEAR::raiseError("Connection to `$host:$port' failed: $errstr", $errno); + } + + if ($lastmodified === false || $lastmodified) { + $request = "GET $path HTTP/1.1\r\n"; + $request .= "Host: $host\r\n"; + } else { + $request = "GET $path HTTP/1.0\r\n"; + $request .= "Host: $host\r\n"; + } + } + + $ifmodifiedsince = ''; + if (is_array($lastmodified)) { + if (isset($lastmodified['Last-Modified'])) { + $ifmodifiedsince = 'If-Modified-Since: ' . $lastmodified['Last-Modified'] . "\r\n"; + } + + if (isset($lastmodified['ETag'])) { + $ifmodifiedsince .= "If-None-Match: $lastmodified[ETag]\r\n"; + } + } else { + $ifmodifiedsince = ($lastmodified ? "If-Modified-Since: $lastmodified\r\n" : ''); + } + + $request .= $ifmodifiedsince . + "User-Agent: PEAR/1.9.1/PHP/" . PHP_VERSION . "\r\n"; + + if (isset($this)) { // only pass in authentication for non-static calls + $username = $config->get('username', null, $channel); + $password = $config->get('password', null, $channel); + if ($username && $password) { + $tmp = base64_encode("$username:$password"); + $request .= "Authorization: Basic $tmp\r\n"; + } + } + + if ($proxy_host != '' && $proxy_user != '') { + $request .= 'Proxy-Authorization: Basic ' . + base64_encode($proxy_user . ':' . $proxy_pass) . "\r\n"; + } + + if ($accept) { + $request .= 'Accept: ' . implode(', ', $accept) . "\r\n"; + } + + $request .= "Connection: close\r\n"; + $request .= "\r\n"; + fwrite($fp, $request); + $headers = array(); + $reply = 0; + while (trim($line = fgets($fp, 1024))) { + if (preg_match('/^([^:]+):\s+(.*)\s*\\z/', $line, $matches)) { + $headers[strtolower($matches[1])] = trim($matches[2]); + } elseif (preg_match('|^HTTP/1.[01] ([0-9]{3}) |', $line, $matches)) { + $reply = (int)$matches[1]; + if ($reply == 304 && ($lastmodified || ($lastmodified === false))) { + return false; + } + + if (!in_array($reply, array(200, 301, 302, 303, 305, 307))) { + return PEAR::raiseError("File $scheme://$host:$port$path not valid (received: $line)"); + } + } + } + + if ($reply != 200) { + if (!isset($headers['location'])) { + return PEAR::raiseError("File $scheme://$host:$port$path not valid (redirected but no location)"); + } + + if ($wasredirect > 4) { + return PEAR::raiseError("File $scheme://$host:$port$path not valid (redirection looped more than 5 times)"); + } + + $redirect = $wasredirect + 1; + return $this->downloadHttp($headers['location'], + $ui, $save_dir, $callback, $lastmodified, $accept); + } + + if (isset($headers['content-disposition']) && + preg_match('/\sfilename=\"([^;]*\S)\"\s*(;|\\z)/', $headers['content-disposition'], $matches)) { + $save_as = basename($matches[1]); + } else { + $save_as = basename($url); + } + + if ($callback) { + $tmp = call_user_func($callback, 'saveas', $save_as); + if ($tmp) { + $save_as = $tmp; + } + } + + $dest_file = $save_dir . DIRECTORY_SEPARATOR . $save_as; + if (!$wp = @fopen($dest_file, 'wb')) { + fclose($fp); + if ($callback) { + call_user_func($callback, 'writefailed', array($dest_file, $php_errormsg)); + } + return PEAR::raiseError("could not open $dest_file for writing"); + } + + $length = isset($headers['content-length']) ? $headers['content-length'] : -1; + + $bytes = 0; + if ($callback) { + call_user_func($callback, 'start', array(basename($dest_file), $length)); + } + + while ($data = fread($fp, 1024)) { + $bytes += strlen($data); + if ($callback) { + call_user_func($callback, 'bytesread', $bytes); + } + if (!@fwrite($wp, $data)) { + fclose($fp); + if ($callback) { + call_user_func($callback, 'writefailed', array($dest_file, $php_errormsg)); + } + return PEAR::raiseError("$dest_file: write failed ($php_errormsg)"); + } + } + + fclose($fp); + fclose($wp); + if ($callback) { + call_user_func($callback, 'done', $bytes); + } + + if ($lastmodified === false || $lastmodified) { + if (isset($headers['etag'])) { + $lastmodified = array('ETag' => $headers['etag']); + } + + if (isset($headers['last-modified'])) { + if (is_array($lastmodified)) { + $lastmodified['Last-Modified'] = $headers['last-modified']; + } else { + $lastmodified = $headers['last-modified']; + } + } + return array($dest_file, $lastmodified, $headers); + } + return $dest_file; + } +} +// }}} \ No newline at end of file diff --git a/library/pear/PEAR/Downloader/Package.php b/library/pear/PEAR/Downloader/Package.php new file mode 100644 index 000000000..726753e8e --- /dev/null +++ b/library/pear/PEAR/Downloader/Package.php @@ -0,0 +1,2004 @@ + + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Package.php 287560 2009-08-21 22:36:18Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ + +/** + * Error code when parameter initialization fails because no releases + * exist within preferred_state, but releases do exist + */ +define('PEAR_DOWNLOADER_PACKAGE_STATE', -1003); +/** + * Error code when parameter initialization fails because no releases + * exist that will work with the existing PHP version + */ +define('PEAR_DOWNLOADER_PACKAGE_PHPVERSION', -1004); + +/** + * Coordinates download parameters and manages their dependencies + * prior to downloading them. + * + * Input can come from three sources: + * + * - local files (archives or package.xml) + * - remote files (downloadable urls) + * - abstract package names + * + * The first two elements are handled cleanly by PEAR_PackageFile, but the third requires + * accessing pearweb's xml-rpc interface to determine necessary dependencies, and the + * format returned of dependencies is slightly different from that used in package.xml. + * + * This class hides the differences between these elements, and makes automatic + * dependency resolution a piece of cake. It also manages conflicts when + * two classes depend on incompatible dependencies, or differing versions of the same + * package dependency. In addition, download will not be attempted if the php version is + * not supported, PEAR installer version is not supported, or non-PECL extensions are not + * installed. + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.1 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_Downloader_Package +{ + /** + * @var PEAR_Downloader + */ + var $_downloader; + /** + * @var PEAR_Config + */ + var $_config; + /** + * @var PEAR_Registry + */ + var $_registry; + /** + * Used to implement packagingroot properly + * @var PEAR_Registry + */ + var $_installRegistry; + /** + * @var PEAR_PackageFile_v1|PEAR_PackageFile|v2 + */ + var $_packagefile; + /** + * @var array + */ + var $_parsedname; + /** + * @var array + */ + var $_downloadURL; + /** + * @var array + */ + var $_downloadDeps = array(); + /** + * @var boolean + */ + var $_valid = false; + /** + * @var boolean + */ + var $_analyzed = false; + /** + * if this or a parent package was invoked with Package-state, this is set to the + * state variable. + * + * This allows temporary reassignment of preferred_state for a parent package and all of + * its dependencies. + * @var string|false + */ + var $_explicitState = false; + /** + * If this package is invoked with Package#group, this variable will be true + */ + var $_explicitGroup = false; + /** + * Package type local|url + * @var string + */ + var $_type; + /** + * Contents of package.xml, if downloaded from a remote channel + * @var string|false + * @access private + */ + var $_rawpackagefile; + /** + * @var boolean + * @access private + */ + var $_validated = false; + + /** + * @param PEAR_Downloader + */ + function PEAR_Downloader_Package(&$downloader) + { + $this->_downloader = &$downloader; + $this->_config = &$this->_downloader->config; + $this->_registry = &$this->_config->getRegistry(); + $options = $downloader->getOptions(); + if (isset($options['packagingroot'])) { + $this->_config->setInstallRoot($options['packagingroot']); + $this->_installRegistry = &$this->_config->getRegistry(); + $this->_config->setInstallRoot(false); + } else { + $this->_installRegistry = &$this->_registry; + } + $this->_valid = $this->_analyzed = false; + } + + /** + * Parse the input and determine whether this is a local file, a remote uri, or an + * abstract package name. + * + * This is the heart of the PEAR_Downloader_Package(), and is used in + * {@link PEAR_Downloader::download()} + * @param string + * @return bool|PEAR_Error + */ + function initialize($param) + { + $origErr = $this->_fromFile($param); + if ($this->_valid) { + return true; + } + + $options = $this->_downloader->getOptions(); + if (isset($options['offline'])) { + if (PEAR::isError($origErr) && !isset($options['soft'])) { + foreach ($origErr->getUserInfo() as $userInfo) { + if (isset($userInfo['message'])) { + $this->_downloader->log(0, $userInfo['message']); + } + } + + $this->_downloader->log(0, $origErr->getMessage()); + } + + return PEAR::raiseError('Cannot download non-local package "' . $param . '"'); + } + + $err = $this->_fromUrl($param); + if (PEAR::isError($err) || !$this->_valid) { + if ($this->_type == 'url') { + if (PEAR::isError($err) && !isset($options['soft'])) { + $this->_downloader->log(0, $err->getMessage()); + } + + return PEAR::raiseError("Invalid or missing remote package file"); + } + + $err = $this->_fromString($param); + if (PEAR::isError($err) || !$this->_valid) { + if (PEAR::isError($err) && $err->getCode() == PEAR_DOWNLOADER_PACKAGE_STATE) { + return false; // instruct the downloader to silently skip + } + + if (isset($this->_type) && $this->_type == 'local' && PEAR::isError($origErr)) { + if (is_array($origErr->getUserInfo())) { + foreach ($origErr->getUserInfo() as $err) { + if (is_array($err)) { + $err = $err['message']; + } + + if (!isset($options['soft'])) { + $this->_downloader->log(0, $err); + } + } + } + + if (!isset($options['soft'])) { + $this->_downloader->log(0, $origErr->getMessage()); + } + + if (is_array($param)) { + $param = $this->_registry->parsedPackageNameToString($param, true); + } + + if (!isset($options['soft'])) { + $this->_downloader->log(2, "Cannot initialize '$param', invalid or missing package file"); + } + + // Passing no message back - already logged above + return PEAR::raiseError(); + } + + if (PEAR::isError($err) && !isset($options['soft'])) { + $this->_downloader->log(0, $err->getMessage()); + } + + if (is_array($param)) { + $param = $this->_registry->parsedPackageNameToString($param, true); + } + + if (!isset($options['soft'])) { + $this->_downloader->log(2, "Cannot initialize '$param', invalid or missing package file"); + } + + // Passing no message back - already logged above + return PEAR::raiseError(); + } + } + + return true; + } + + /** + * Retrieve any non-local packages + * @return PEAR_PackageFile_v1|PEAR_PackageFile_v2|PEAR_Error + */ + function &download() + { + if (isset($this->_packagefile)) { + return $this->_packagefile; + } + + if (isset($this->_downloadURL['url'])) { + $this->_isvalid = false; + $info = $this->getParsedPackage(); + foreach ($info as $i => $p) { + $info[$i] = strtolower($p); + } + + $err = $this->_fromUrl($this->_downloadURL['url'], + $this->_registry->parsedPackageNameToString($this->_parsedname, true)); + $newinfo = $this->getParsedPackage(); + foreach ($newinfo as $i => $p) { + $newinfo[$i] = strtolower($p); + } + + if ($info != $newinfo) { + do { + if ($info['channel'] == 'pecl.php.net' && $newinfo['channel'] == 'pear.php.net') { + $info['channel'] = 'pear.php.net'; + if ($info == $newinfo) { + // skip the channel check if a pecl package says it's a PEAR package + break; + } + } + if ($info['channel'] == 'pear.php.net' && $newinfo['channel'] == 'pecl.php.net') { + $info['channel'] = 'pecl.php.net'; + if ($info == $newinfo) { + // skip the channel check if a pecl package says it's a PEAR package + break; + } + } + + return PEAR::raiseError('CRITICAL ERROR: We are ' . + $this->_registry->parsedPackageNameToString($info) . ', but the file ' . + 'downloaded claims to be ' . + $this->_registry->parsedPackageNameToString($this->getParsedPackage())); + } while (false); + } + + if (PEAR::isError($err) || !$this->_valid) { + return $err; + } + } + + $this->_type = 'local'; + return $this->_packagefile; + } + + function &getPackageFile() + { + return $this->_packagefile; + } + + function &getDownloader() + { + return $this->_downloader; + } + + function getType() + { + return $this->_type; + } + + /** + * Like {@link initialize()}, but operates on a dependency + */ + function fromDepURL($dep) + { + $this->_downloadURL = $dep; + if (isset($dep['uri'])) { + $options = $this->_downloader->getOptions(); + if (!extension_loaded("zlib") || isset($options['nocompress'])) { + $ext = '.tar'; + } else { + $ext = '.tgz'; + } + + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $err = $this->_fromUrl($dep['uri'] . $ext); + PEAR::popErrorHandling(); + if (PEAR::isError($err)) { + if (!isset($options['soft'])) { + $this->_downloader->log(0, $err->getMessage()); + } + + return PEAR::raiseError('Invalid uri dependency "' . $dep['uri'] . $ext . '", ' . + 'cannot download'); + } + } else { + $this->_parsedname = + array( + 'package' => $dep['info']->getPackage(), + 'channel' => $dep['info']->getChannel(), + 'version' => $dep['version'] + ); + if (!isset($dep['nodefault'])) { + $this->_parsedname['group'] = 'default'; // download the default dependency group + $this->_explicitGroup = false; + } + + $this->_rawpackagefile = $dep['raw']; + } + } + + function detectDependencies($params) + { + $options = $this->_downloader->getOptions(); + if (isset($options['downloadonly'])) { + return; + } + + if (isset($options['offline'])) { + $this->_downloader->log(3, 'Skipping dependency download check, --offline specified'); + return; + } + + $pname = $this->getParsedPackage(); + if (!$pname) { + return; + } + + $deps = $this->getDeps(); + if (!$deps) { + return; + } + + if (isset($deps['required'])) { // package.xml 2.0 + return $this->_detect2($deps, $pname, $options, $params); + } + + return $this->_detect1($deps, $pname, $options, $params); + } + + function setValidated() + { + $this->_validated = true; + } + + function alreadyValidated() + { + return $this->_validated; + } + + /** + * Remove packages to be downloaded that are already installed + * @param array of PEAR_Downloader_Package objects + * @static + */ + function removeInstalled(&$params) + { + if (!isset($params[0])) { + return; + } + + $options = $params[0]->_downloader->getOptions(); + if (!isset($options['downloadonly'])) { + foreach ($params as $i => $param) { + $package = $param->getPackage(); + $channel = $param->getChannel(); + // remove self if already installed with this version + // this does not need any pecl magic - we only remove exact matches + if ($param->_installRegistry->packageExists($package, $channel)) { + $packageVersion = $param->_installRegistry->packageInfo($package, 'version', $channel); + if (version_compare($packageVersion, $param->getVersion(), '==')) { + if (!isset($options['force'])) { + $info = $param->getParsedPackage(); + unset($info['version']); + unset($info['state']); + if (!isset($options['soft'])) { + $param->_downloader->log(1, 'Skipping package "' . + $param->getShortName() . + '", already installed as version ' . $packageVersion); + } + $params[$i] = false; + } + } elseif (!isset($options['force']) && !isset($options['upgrade']) && + !isset($options['soft'])) { + $info = $param->getParsedPackage(); + $param->_downloader->log(1, 'Skipping package "' . + $param->getShortName() . + '", already installed as version ' . $packageVersion); + $params[$i] = false; + } + } + } + } + + PEAR_Downloader_Package::removeDuplicates($params); + } + + function _detect2($deps, $pname, $options, $params) + { + $this->_downloadDeps = array(); + $groupnotfound = false; + foreach (array('package', 'subpackage') as $packagetype) { + // get required dependency group + if (isset($deps['required'][$packagetype])) { + if (isset($deps['required'][$packagetype][0])) { + foreach ($deps['required'][$packagetype] as $dep) { + if (isset($dep['conflicts'])) { + // skip any package that this package conflicts with + continue; + } + $ret = $this->_detect2Dep($dep, $pname, 'required', $params); + if (is_array($ret)) { + $this->_downloadDeps[] = $ret; + } elseif (PEAR::isError($ret) && !isset($options['soft'])) { + $this->_downloader->log(0, $ret->getMessage()); + } + } + } else { + $dep = $deps['required'][$packagetype]; + if (!isset($dep['conflicts'])) { + // skip any package that this package conflicts with + $ret = $this->_detect2Dep($dep, $pname, 'required', $params); + if (is_array($ret)) { + $this->_downloadDeps[] = $ret; + } elseif (PEAR::isError($ret) && !isset($options['soft'])) { + $this->_downloader->log(0, $ret->getMessage()); + } + } + } + } + + // get optional dependency group, if any + if (isset($deps['optional'][$packagetype])) { + $skipnames = array(); + if (!isset($deps['optional'][$packagetype][0])) { + $deps['optional'][$packagetype] = array($deps['optional'][$packagetype]); + } + + foreach ($deps['optional'][$packagetype] as $dep) { + $skip = false; + if (!isset($options['alldeps'])) { + $dep['package'] = $dep['name']; + if (!isset($options['soft'])) { + $this->_downloader->log(3, 'Notice: package "' . + $this->_registry->parsedPackageNameToString($this->getParsedPackage(), + true) . '" optional dependency "' . + $this->_registry->parsedPackageNameToString(array('package' => + $dep['name'], 'channel' => 'pear.php.net'), true) . + '" will not be automatically downloaded'); + } + $skipnames[] = $this->_registry->parsedPackageNameToString($dep, true); + $skip = true; + unset($dep['package']); + } + + $ret = $this->_detect2Dep($dep, $pname, 'optional', $params); + if (PEAR::isError($ret) && !isset($options['soft'])) { + $this->_downloader->log(0, $ret->getMessage()); + } + + if (!$ret) { + $dep['package'] = $dep['name']; + $skip = count($skipnames) ? + $skipnames[count($skipnames) - 1] : ''; + if ($skip == + $this->_registry->parsedPackageNameToString($dep, true)) { + array_pop($skipnames); + } + } + + if (!$skip && is_array($ret)) { + $this->_downloadDeps[] = $ret; + } + } + + if (count($skipnames)) { + if (!isset($options['soft'])) { + $this->_downloader->log(1, 'Did not download optional dependencies: ' . + implode(', ', $skipnames) . + ', use --alldeps to download automatically'); + } + } + } + + // get requested dependency group, if any + $groupname = $this->getGroup(); + $explicit = $this->_explicitGroup; + if (!$groupname) { + if (!$this->canDefault()) { + continue; + } + + $groupname = 'default'; // try the default dependency group + } + + if ($groupnotfound) { + continue; + } + + if (isset($deps['group'])) { + if (isset($deps['group']['attribs'])) { + if (strtolower($deps['group']['attribs']['name']) == strtolower($groupname)) { + $group = $deps['group']; + } elseif ($explicit) { + if (!isset($options['soft'])) { + $this->_downloader->log(0, 'Warning: package "' . + $this->_registry->parsedPackageNameToString($pname, true) . + '" has no dependency ' . 'group named "' . $groupname . '"'); + } + + $groupnotfound = true; + continue; + } + } else { + $found = false; + foreach ($deps['group'] as $group) { + if (strtolower($group['attribs']['name']) == strtolower($groupname)) { + $found = true; + break; + } + } + + if (!$found) { + if ($explicit) { + if (!isset($options['soft'])) { + $this->_downloader->log(0, 'Warning: package "' . + $this->_registry->parsedPackageNameToString($pname, true) . + '" has no dependency ' . 'group named "' . $groupname . '"'); + } + } + + $groupnotfound = true; + continue; + } + } + } + + if (isset($group) && isset($group[$packagetype])) { + if (isset($group[$packagetype][0])) { + foreach ($group[$packagetype] as $dep) { + $ret = $this->_detect2Dep($dep, $pname, 'dependency group "' . + $group['attribs']['name'] . '"', $params); + if (is_array($ret)) { + $this->_downloadDeps[] = $ret; + } elseif (PEAR::isError($ret) && !isset($options['soft'])) { + $this->_downloader->log(0, $ret->getMessage()); + } + } + } else { + $ret = $this->_detect2Dep($group[$packagetype], $pname, + 'dependency group "' . + $group['attribs']['name'] . '"', $params); + if (is_array($ret)) { + $this->_downloadDeps[] = $ret; + } elseif (PEAR::isError($ret) && !isset($options['soft'])) { + $this->_downloader->log(0, $ret->getMessage()); + } + } + } + } + } + + function _detect2Dep($dep, $pname, $group, $params) + { + if (isset($dep['conflicts'])) { + return true; + } + + $options = $this->_downloader->getOptions(); + if (isset($dep['uri'])) { + return array('uri' => $dep['uri'], 'dep' => $dep);; + } + + $testdep = $dep; + $testdep['package'] = $dep['name']; + if (PEAR_Downloader_Package::willDownload($testdep, $params)) { + $dep['package'] = $dep['name']; + if (!isset($options['soft'])) { + $this->_downloader->log(2, $this->getShortName() . ': Skipping ' . $group . + ' dependency "' . + $this->_registry->parsedPackageNameToString($dep, true) . + '", will be installed'); + } + return false; + } + + $options = $this->_downloader->getOptions(); + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + if ($this->_explicitState) { + $pname['state'] = $this->_explicitState; + } + + $url = $this->_downloader->_getDepPackageDownloadUrl($dep, $pname); + if (PEAR::isError($url)) { + PEAR::popErrorHandling(); + return $url; + } + + $dep['package'] = $dep['name']; + $ret = $this->_analyzeDownloadURL($url, 'dependency', $dep, $params, $group == 'optional' && + !isset($options['alldeps']), true); + PEAR::popErrorHandling(); + if (PEAR::isError($ret)) { + if (!isset($options['soft'])) { + $this->_downloader->log(0, $ret->getMessage()); + } + + return false; + } + + // check to see if a dep is already installed and is the same or newer + if (!isset($dep['min']) && !isset($dep['max']) && !isset($dep['recommended'])) { + $oper = 'has'; + } else { + $oper = 'gt'; + } + + // do not try to move this before getDepPackageDownloadURL + // we can't determine whether upgrade is necessary until we know what + // version would be downloaded + if (!isset($options['force']) && $this->isInstalled($ret, $oper)) { + $version = $this->_installRegistry->packageInfo($dep['name'], 'version', $dep['channel']); + $dep['package'] = $dep['name']; + if (!isset($options['soft'])) { + $this->_downloader->log(3, $this->getShortName() . ': Skipping ' . $group . + ' dependency "' . + $this->_registry->parsedPackageNameToString($dep, true) . + '" version ' . $url['version'] . ', already installed as version ' . + $version); + } + + return false; + } + + if (isset($dep['nodefault'])) { + $ret['nodefault'] = true; + } + + return $ret; + } + + function _detect1($deps, $pname, $options, $params) + { + $this->_downloadDeps = array(); + $skipnames = array(); + foreach ($deps as $dep) { + $nodownload = false; + if (isset ($dep['type']) && $dep['type'] === 'pkg') { + $dep['channel'] = 'pear.php.net'; + $dep['package'] = $dep['name']; + switch ($dep['rel']) { + case 'not' : + continue 2; + case 'ge' : + case 'eq' : + case 'gt' : + case 'has' : + $group = (!isset($dep['optional']) || $dep['optional'] == 'no') ? + 'required' : + 'optional'; + if (PEAR_Downloader_Package::willDownload($dep, $params)) { + $this->_downloader->log(2, $this->getShortName() . ': Skipping ' . $group + . ' dependency "' . + $this->_registry->parsedPackageNameToString($dep, true) . + '", will be installed'); + continue 2; + } + $fakedp = new PEAR_PackageFile_v1; + $fakedp->setPackage($dep['name']); + // skip internet check if we are not upgrading (bug #5810) + if (!isset($options['upgrade']) && $this->isInstalled( + $fakedp, $dep['rel'])) { + $this->_downloader->log(2, $this->getShortName() . ': Skipping ' . $group + . ' dependency "' . + $this->_registry->parsedPackageNameToString($dep, true) . + '", is already installed'); + continue 2; + } + } + + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + if ($this->_explicitState) { + $pname['state'] = $this->_explicitState; + } + + $url = $this->_downloader->_getDepPackageDownloadUrl($dep, $pname); + $chan = 'pear.php.net'; + if (PEAR::isError($url)) { + // check to see if this is a pecl package that has jumped + // from pear.php.net to pecl.php.net channel + if (!class_exists('PEAR_Dependency2')) { + require_once 'PEAR/Dependency2.php'; + } + + $newdep = PEAR_Dependency2::normalizeDep($dep); + $newdep = $newdep[0]; + $newdep['channel'] = 'pecl.php.net'; + $chan = 'pecl.php.net'; + $url = $this->_downloader->_getDepPackageDownloadUrl($newdep, $pname); + $obj = &$this->_installRegistry->getPackage($dep['name']); + if (PEAR::isError($url)) { + PEAR::popErrorHandling(); + if ($obj !== null && $this->isInstalled($obj, $dep['rel'])) { + $group = (!isset($dep['optional']) || $dep['optional'] == 'no') ? + 'required' : + 'optional'; + $dep['package'] = $dep['name']; + if (!isset($options['soft'])) { + $this->_downloader->log(3, $this->getShortName() . + ': Skipping ' . $group . ' dependency "' . + $this->_registry->parsedPackageNameToString($dep, true) . + '", already installed as version ' . $obj->getVersion()); + } + $skip = count($skipnames) ? + $skipnames[count($skipnames) - 1] : ''; + if ($skip == + $this->_registry->parsedPackageNameToString($dep, true)) { + array_pop($skipnames); + } + continue; + } else { + if (isset($dep['optional']) && $dep['optional'] == 'yes') { + $this->_downloader->log(2, $this->getShortName() . + ': Skipping optional dependency "' . + $this->_registry->parsedPackageNameToString($dep, true) . + '", no releases exist'); + continue; + } else { + return $url; + } + } + } + } + + PEAR::popErrorHandling(); + if (!isset($options['alldeps'])) { + if (isset($dep['optional']) && $dep['optional'] == 'yes') { + if (!isset($options['soft'])) { + $this->_downloader->log(3, 'Notice: package "' . + $this->getShortName() . + '" optional dependency "' . + $this->_registry->parsedPackageNameToString( + array('channel' => $chan, 'package' => + $dep['name']), true) . + '" will not be automatically downloaded'); + } + $skipnames[] = $this->_registry->parsedPackageNameToString( + array('channel' => $chan, 'package' => + $dep['name']), true); + $nodownload = true; + } + } + + if (!isset($options['alldeps']) && !isset($options['onlyreqdeps'])) { + if (!isset($dep['optional']) || $dep['optional'] == 'no') { + if (!isset($options['soft'])) { + $this->_downloader->log(3, 'Notice: package "' . + $this->getShortName() . + '" required dependency "' . + $this->_registry->parsedPackageNameToString( + array('channel' => $chan, 'package' => + $dep['name']), true) . + '" will not be automatically downloaded'); + } + $skipnames[] = $this->_registry->parsedPackageNameToString( + array('channel' => $chan, 'package' => + $dep['name']), true); + $nodownload = true; + } + } + + // check to see if a dep is already installed + // do not try to move this before getDepPackageDownloadURL + // we can't determine whether upgrade is necessary until we know what + // version would be downloaded + if (!isset($options['force']) && $this->isInstalled( + $url, $dep['rel'])) { + $group = (!isset($dep['optional']) || $dep['optional'] == 'no') ? + 'required' : + 'optional'; + $dep['package'] = $dep['name']; + if (isset($newdep)) { + $version = $this->_installRegistry->packageInfo($newdep['name'], 'version', $newdep['channel']); + } else { + $version = $this->_installRegistry->packageInfo($dep['name'], 'version'); + } + + $dep['version'] = $url['version']; + if (!isset($options['soft'])) { + $this->_downloader->log(3, $this->getShortName() . ': Skipping ' . $group . + ' dependency "' . + $this->_registry->parsedPackageNameToString($dep, true) . + '", already installed as version ' . $version); + } + + $skip = count($skipnames) ? + $skipnames[count($skipnames) - 1] : ''; + if ($skip == + $this->_registry->parsedPackageNameToString($dep, true)) { + array_pop($skipnames); + } + + continue; + } + + if ($nodownload) { + continue; + } + + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + if (isset($newdep)) { + $dep = $newdep; + } + + $dep['package'] = $dep['name']; + $ret = $this->_analyzeDownloadURL($url, 'dependency', $dep, $params, + isset($dep['optional']) && $dep['optional'] == 'yes' && + !isset($options['alldeps']), true); + PEAR::popErrorHandling(); + if (PEAR::isError($ret)) { + if (!isset($options['soft'])) { + $this->_downloader->log(0, $ret->getMessage()); + } + continue; + } + + $this->_downloadDeps[] = $ret; + } + } + + if (count($skipnames)) { + if (!isset($options['soft'])) { + $this->_downloader->log(1, 'Did not download dependencies: ' . + implode(', ', $skipnames) . + ', use --alldeps or --onlyreqdeps to download automatically'); + } + } + } + + function setDownloadURL($pkg) + { + $this->_downloadURL = $pkg; + } + + /** + * Set the package.xml object for this downloaded package + * + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2 $pkg + */ + function setPackageFile(&$pkg) + { + $this->_packagefile = &$pkg; + } + + function getShortName() + { + return $this->_registry->parsedPackageNameToString(array('channel' => $this->getChannel(), + 'package' => $this->getPackage()), true); + } + + function getParsedPackage() + { + if (isset($this->_packagefile) || isset($this->_parsedname)) { + return array('channel' => $this->getChannel(), + 'package' => $this->getPackage(), + 'version' => $this->getVersion()); + } + + return false; + } + + function getDownloadURL() + { + return $this->_downloadURL; + } + + function canDefault() + { + if (isset($this->_downloadURL) && isset($this->_downloadURL['nodefault'])) { + return false; + } + + return true; + } + + function getPackage() + { + if (isset($this->_packagefile)) { + return $this->_packagefile->getPackage(); + } elseif (isset($this->_downloadURL['info'])) { + return $this->_downloadURL['info']->getPackage(); + } + + return false; + } + + /** + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2 + */ + function isSubpackage(&$pf) + { + if (isset($this->_packagefile)) { + return $this->_packagefile->isSubpackage($pf); + } elseif (isset($this->_downloadURL['info'])) { + return $this->_downloadURL['info']->isSubpackage($pf); + } + + return false; + } + + function getPackageType() + { + if (isset($this->_packagefile)) { + return $this->_packagefile->getPackageType(); + } elseif (isset($this->_downloadURL['info'])) { + return $this->_downloadURL['info']->getPackageType(); + } + + return false; + } + + function isBundle() + { + if (isset($this->_packagefile)) { + return $this->_packagefile->getPackageType() == 'bundle'; + } + + return false; + } + + function getPackageXmlVersion() + { + if (isset($this->_packagefile)) { + return $this->_packagefile->getPackagexmlVersion(); + } elseif (isset($this->_downloadURL['info'])) { + return $this->_downloadURL['info']->getPackagexmlVersion(); + } + + return '1.0'; + } + + function getChannel() + { + if (isset($this->_packagefile)) { + return $this->_packagefile->getChannel(); + } elseif (isset($this->_downloadURL['info'])) { + return $this->_downloadURL['info']->getChannel(); + } + + return false; + } + + function getURI() + { + if (isset($this->_packagefile)) { + return $this->_packagefile->getURI(); + } elseif (isset($this->_downloadURL['info'])) { + return $this->_downloadURL['info']->getURI(); + } + + return false; + } + + function getVersion() + { + if (isset($this->_packagefile)) { + return $this->_packagefile->getVersion(); + } elseif (isset($this->_downloadURL['version'])) { + return $this->_downloadURL['version']; + } + + return false; + } + + function isCompatible($pf) + { + if (isset($this->_packagefile)) { + return $this->_packagefile->isCompatible($pf); + } elseif (isset($this->_downloadURL['info'])) { + return $this->_downloadURL['info']->isCompatible($pf); + } + + return true; + } + + function setGroup($group) + { + $this->_parsedname['group'] = $group; + } + + function getGroup() + { + if (isset($this->_parsedname['group'])) { + return $this->_parsedname['group']; + } + + return ''; + } + + function isExtension($name) + { + if (isset($this->_packagefile)) { + return $this->_packagefile->isExtension($name); + } elseif (isset($this->_downloadURL['info'])) { + if ($this->_downloadURL['info']->getPackagexmlVersion() == '2.0') { + return $this->_downloadURL['info']->getProvidesExtension() == $name; + } + + return false; + } + + return false; + } + + function getDeps() + { + if (isset($this->_packagefile)) { + $ver = $this->_packagefile->getPackagexmlVersion(); + if (version_compare($ver, '2.0', '>=')) { + return $this->_packagefile->getDeps(true); + } + + return $this->_packagefile->getDeps(); + } elseif (isset($this->_downloadURL['info'])) { + $ver = $this->_downloadURL['info']->getPackagexmlVersion(); + if (version_compare($ver, '2.0', '>=')) { + return $this->_downloadURL['info']->getDeps(true); + } + + return $this->_downloadURL['info']->getDeps(); + } + + return array(); + } + + /** + * @param array Parsed array from {@link PEAR_Registry::parsePackageName()} or a dependency + * returned from getDepDownloadURL() + */ + function isEqual($param) + { + if (is_object($param)) { + $channel = $param->getChannel(); + $package = $param->getPackage(); + if ($param->getURI()) { + $param = array( + 'channel' => $param->getChannel(), + 'package' => $param->getPackage(), + 'version' => $param->getVersion(), + 'uri' => $param->getURI(), + ); + } else { + $param = array( + 'channel' => $param->getChannel(), + 'package' => $param->getPackage(), + 'version' => $param->getVersion(), + ); + } + } else { + if (isset($param['uri'])) { + if ($this->getChannel() != '__uri') { + return false; + } + return $param['uri'] == $this->getURI(); + } + + $package = isset($param['package']) ? $param['package'] : $param['info']->getPackage(); + $channel = isset($param['channel']) ? $param['channel'] : $param['info']->getChannel(); + if (isset($param['rel'])) { + if (!class_exists('PEAR_Dependency2')) { + require_once 'PEAR/Dependency2.php'; + } + + $newdep = PEAR_Dependency2::normalizeDep($param); + $newdep = $newdep[0]; + } elseif (isset($param['min'])) { + $newdep = $param; + } + } + + if (isset($newdep)) { + if (!isset($newdep['min'])) { + $newdep['min'] = '0'; + } + + if (!isset($newdep['max'])) { + $newdep['max'] = '100000000000000000000'; + } + + // use magic to support pecl packages suddenly jumping to the pecl channel + // we need to support both dependency possibilities + if ($channel == 'pear.php.net' && $this->getChannel() == 'pecl.php.net') { + if ($package == $this->getPackage()) { + $channel = 'pecl.php.net'; + } + } + if ($channel == 'pecl.php.net' && $this->getChannel() == 'pear.php.net') { + if ($package == $this->getPackage()) { + $channel = 'pear.php.net'; + } + } + + return (strtolower($package) == strtolower($this->getPackage()) && + $channel == $this->getChannel() && + version_compare($newdep['min'], $this->getVersion(), '<=') && + version_compare($newdep['max'], $this->getVersion(), '>=')); + } + + // use magic to support pecl packages suddenly jumping to the pecl channel + if ($channel == 'pecl.php.net' && $this->getChannel() == 'pear.php.net') { + if (strtolower($package) == strtolower($this->getPackage())) { + $channel = 'pear.php.net'; + } + } + + if (isset($param['version'])) { + return (strtolower($package) == strtolower($this->getPackage()) && + $channel == $this->getChannel() && + $param['version'] == $this->getVersion()); + } + + return strtolower($package) == strtolower($this->getPackage()) && + $channel == $this->getChannel(); + } + + function isInstalled($dep, $oper = '==') + { + if (!$dep) { + return false; + } + + if ($oper != 'ge' && $oper != 'gt' && $oper != 'has' && $oper != '==') { + return false; + } + + if (is_object($dep)) { + $package = $dep->getPackage(); + $channel = $dep->getChannel(); + if ($dep->getURI()) { + $dep = array( + 'uri' => $dep->getURI(), + 'version' => $dep->getVersion(), + ); + } else { + $dep = array( + 'version' => $dep->getVersion(), + ); + } + } else { + if (isset($dep['uri'])) { + $channel = '__uri'; + $package = $dep['dep']['name']; + } else { + $channel = $dep['info']->getChannel(); + $package = $dep['info']->getPackage(); + } + } + + $options = $this->_downloader->getOptions(); + $test = $this->_installRegistry->packageExists($package, $channel); + if (!$test && $channel == 'pecl.php.net') { + // do magic to allow upgrading from old pecl packages to new ones + $test = $this->_installRegistry->packageExists($package, 'pear.php.net'); + $channel = 'pear.php.net'; + } + + if ($test) { + if (isset($dep['uri'])) { + if ($this->_installRegistry->packageInfo($package, 'uri', '__uri') == $dep['uri']) { + return true; + } + } + + if (isset($options['upgrade'])) { + $packageVersion = $this->_installRegistry->packageInfo($package, 'version', $channel); + if (version_compare($packageVersion, $dep['version'], '>=')) { + return true; + } + + return false; + } + + return true; + } + + return false; + } + + /** + * Detect duplicate package names with differing versions + * + * If a user requests to install Date 1.4.6 and Date 1.4.7, + * for instance, this is a logic error. This method + * detects this situation. + * + * @param array $params array of PEAR_Downloader_Package objects + * @param array $errorparams empty array + * @return array array of stupid duplicated packages in PEAR_Downloader_Package obejcts + */ + function detectStupidDuplicates($params, &$errorparams) + { + $existing = array(); + foreach ($params as $i => $param) { + $package = $param->getPackage(); + $channel = $param->getChannel(); + $group = $param->getGroup(); + if (!isset($existing[$channel . '/' . $package])) { + $existing[$channel . '/' . $package] = array(); + } + + if (!isset($existing[$channel . '/' . $package][$group])) { + $existing[$channel . '/' . $package][$group] = array(); + } + + $existing[$channel . '/' . $package][$group][] = $i; + } + + $indices = array(); + foreach ($existing as $package => $groups) { + foreach ($groups as $group => $dupes) { + if (count($dupes) > 1) { + $indices = $indices + $dupes; + } + } + } + + $indices = array_unique($indices); + foreach ($indices as $index) { + $errorparams[] = $params[$index]; + } + + return count($errorparams); + } + + /** + * @param array + * @param bool ignore install groups - for final removal of dupe packages + * @static + */ + function removeDuplicates(&$params, $ignoreGroups = false) + { + $pnames = array(); + foreach ($params as $i => $param) { + if (!$param) { + continue; + } + + if ($param->getPackage()) { + $group = $ignoreGroups ? '' : $param->getGroup(); + $pnames[$i] = $param->getChannel() . '/' . + $param->getPackage() . '-' . $param->getVersion() . '#' . $group; + } + } + + $pnames = array_unique($pnames); + $unset = array_diff(array_keys($params), array_keys($pnames)); + $testp = array_flip($pnames); + foreach ($params as $i => $param) { + if (!$param) { + $unset[] = $i; + continue; + } + + if (!is_a($param, 'PEAR_Downloader_Package')) { + $unset[] = $i; + continue; + } + + $group = $ignoreGroups ? '' : $param->getGroup(); + if (!isset($testp[$param->getChannel() . '/' . $param->getPackage() . '-' . + $param->getVersion() . '#' . $group])) { + $unset[] = $i; + } + } + + foreach ($unset as $i) { + unset($params[$i]); + } + + $ret = array(); + foreach ($params as $i => $param) { + $ret[] = &$params[$i]; + } + + $params = array(); + foreach ($ret as $i => $param) { + $params[] = &$ret[$i]; + } + } + + function explicitState() + { + return $this->_explicitState; + } + + function setExplicitState($s) + { + $this->_explicitState = $s; + } + + /** + * @static + */ + function mergeDependencies(&$params) + { + $bundles = $newparams = array(); + foreach ($params as $i => $param) { + if (!$param->isBundle()) { + continue; + } + + $bundles[] = $i; + $pf = &$param->getPackageFile(); + $newdeps = array(); + $contents = $pf->getBundledPackages(); + if (!is_array($contents)) { + $contents = array($contents); + } + + foreach ($contents as $file) { + $filecontents = $pf->getFileContents($file); + $dl = &$param->getDownloader(); + $options = $dl->getOptions(); + if (PEAR::isError($dir = $dl->getDownloadDir())) { + return $dir; + } + + $fp = @fopen($dir . DIRECTORY_SEPARATOR . $file, 'wb'); + if (!$fp) { + continue; + } + + fwrite($fp, $filecontents, strlen($filecontents)); + fclose($fp); + if ($s = $params[$i]->explicitState()) { + $obj->setExplicitState($s); + } + + $obj = &new PEAR_Downloader_Package($params[$i]->getDownloader()); + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + if (PEAR::isError($dir = $dl->getDownloadDir())) { + PEAR::popErrorHandling(); + return $dir; + } + + $e = $obj->_fromFile($a = $dir . DIRECTORY_SEPARATOR . $file); + PEAR::popErrorHandling(); + if (PEAR::isError($e)) { + if (!isset($options['soft'])) { + $dl->log(0, $e->getMessage()); + } + continue; + } + + $j = &$obj; + if (!PEAR_Downloader_Package::willDownload($j, + array_merge($params, $newparams)) && !$param->isInstalled($j)) { + $newparams[] = &$j; + } + } + } + + foreach ($bundles as $i) { + unset($params[$i]); // remove bundles - only their contents matter for installation + } + + PEAR_Downloader_Package::removeDuplicates($params); // strip any unset indices + if (count($newparams)) { // add in bundled packages for install + foreach ($newparams as $i => $unused) { + $params[] = &$newparams[$i]; + } + $newparams = array(); + } + + foreach ($params as $i => $param) { + $newdeps = array(); + foreach ($param->_downloadDeps as $dep) { + $merge = array_merge($params, $newparams); + if (!PEAR_Downloader_Package::willDownload($dep, $merge) + && !$param->isInstalled($dep) + ) { + $newdeps[] = $dep; + } else { + //var_dump($dep); + // detect versioning conflicts here + } + } + + // convert the dependencies into PEAR_Downloader_Package objects for the next time around + $params[$i]->_downloadDeps = array(); + foreach ($newdeps as $dep) { + $obj = &new PEAR_Downloader_Package($params[$i]->getDownloader()); + if ($s = $params[$i]->explicitState()) { + $obj->setExplicitState($s); + } + + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $e = $obj->fromDepURL($dep); + PEAR::popErrorHandling(); + if (PEAR::isError($e)) { + if (!isset($options['soft'])) { + $obj->_downloader->log(0, $e->getMessage()); + } + continue; + } + + $e = $obj->detectDependencies($params); + if (PEAR::isError($e)) { + if (!isset($options['soft'])) { + $obj->_downloader->log(0, $e->getMessage()); + } + } + + $j = &$obj; + $newparams[] = &$j; + } + } + + if (count($newparams)) { + foreach ($newparams as $i => $unused) { + $params[] = &$newparams[$i]; + } + return true; + } + + return false; + } + + + /** + * @static + */ + function willDownload($param, $params) + { + if (!is_array($params)) { + return false; + } + + foreach ($params as $obj) { + if ($obj->isEqual($param)) { + return true; + } + } + + return false; + } + + /** + * For simpler unit-testing + * @param PEAR_Config + * @param int + * @param string + */ + function &getPackagefileObject(&$c, $d, $t = false) + { + $a = &new PEAR_PackageFile($c, $d, $t); + return $a; + } + + + /** + * This will retrieve from a local file if possible, and parse out + * a group name as well. The original parameter will be modified to reflect this. + * @param string|array can be a parsed package name as well + * @access private + */ + function _fromFile(&$param) + { + $saveparam = $param; + if (is_string($param)) { + if (!@file_exists($param)) { + $test = explode('#', $param); + $group = array_pop($test); + if (@file_exists(implode('#', $test))) { + $this->setGroup($group); + $param = implode('#', $test); + $this->_explicitGroup = true; + } + } + + if (@is_file($param)) { + $this->_type = 'local'; + $options = $this->_downloader->getOptions(); + if (isset($options['downloadonly'])) { + $pkg = &$this->getPackagefileObject($this->_config, + $this->_downloader->_debug); + } else { + if (PEAR::isError($dir = $this->_downloader->getDownloadDir())) { + return $dir; + } + $pkg = &$this->getPackagefileObject($this->_config, + $this->_downloader->_debug, $dir); + } + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $pf = &$pkg->fromAnyFile($param, PEAR_VALIDATE_INSTALLING); + PEAR::popErrorHandling(); + if (PEAR::isError($pf)) { + $this->_valid = false; + $param = $saveparam; + return $pf; + } + $this->_packagefile = &$pf; + if (!$this->getGroup()) { + $this->setGroup('default'); // install the default dependency group + } + return $this->_valid = true; + } + } + $param = $saveparam; + return $this->_valid = false; + } + + function _fromUrl($param, $saveparam = '') + { + if (!is_array($param) && (preg_match('#^(http|https|ftp)://#', $param))) { + $options = $this->_downloader->getOptions(); + $this->_type = 'url'; + $callback = $this->_downloader->ui ? + array(&$this->_downloader, '_downloadCallback') : null; + $this->_downloader->pushErrorHandling(PEAR_ERROR_RETURN); + if (PEAR::isError($dir = $this->_downloader->getDownloadDir())) { + $this->_downloader->popErrorHandling(); + return $dir; + } + + $this->_downloader->log(3, 'Downloading "' . $param . '"'); + $file = $this->_downloader->downloadHttp($param, $this->_downloader->ui, + $dir, $callback, null, false, $this->getChannel()); + $this->_downloader->popErrorHandling(); + if (PEAR::isError($file)) { + if (!empty($saveparam)) { + $saveparam = ", cannot download \"$saveparam\""; + } + $err = PEAR::raiseError('Could not download from "' . $param . + '"' . $saveparam . ' (' . $file->getMessage() . ')'); + return $err; + } + + if ($this->_rawpackagefile) { + require_once 'Archive/Tar.php'; + $tar = &new Archive_Tar($file); + $packagexml = $tar->extractInString('package2.xml'); + if (!$packagexml) { + $packagexml = $tar->extractInString('package.xml'); + } + + if (str_replace(array("\n", "\r"), array('',''), $packagexml) != + str_replace(array("\n", "\r"), array('',''), $this->_rawpackagefile)) { + if ($this->getChannel() != 'pear.php.net') { + return PEAR::raiseError('CRITICAL ERROR: package.xml downloaded does ' . + 'not match value returned from xml-rpc'); + } + + // be more lax for the existing PEAR packages that have not-ok + // characters in their package.xml + $this->_downloader->log(0, 'CRITICAL WARNING: The "' . + $this->getPackage() . '" package has invalid characters in its ' . + 'package.xml. The next version of PEAR may not be able to install ' . + 'this package for security reasons. Please open a bug report at ' . + 'http://pear.php.net/package/' . $this->getPackage() . '/bugs'); + } + } + + // whew, download worked! + if (isset($options['downloadonly'])) { + $pkg = &$this->getPackagefileObject($this->_config, $this->_downloader->debug); + } else { + $dir = $this->_downloader->getDownloadDir(); + if (PEAR::isError($dir)) { + return $dir; + } + $pkg = &$this->getPackagefileObject($this->_config, $this->_downloader->debug, $dir); + } + + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $pf = &$pkg->fromAnyFile($file, PEAR_VALIDATE_INSTALLING); + PEAR::popErrorHandling(); + if (PEAR::isError($pf)) { + if (is_array($pf->getUserInfo())) { + foreach ($pf->getUserInfo() as $err) { + if (is_array($err)) { + $err = $err['message']; + } + + if (!isset($options['soft'])) { + $this->_downloader->log(0, "Validation Error: $err"); + } + } + } + + if (!isset($options['soft'])) { + $this->_downloader->log(0, $pf->getMessage()); + } + + ///FIXME need to pass back some error code that we can use to match with to cancel all further operations + /// At least stop all deps of this package from being installed + $out = $saveparam ? $saveparam : $param; + $err = PEAR::raiseError('Download of "' . $out . '" succeeded, but it is not a valid package archive'); + $this->_valid = false; + return $err; + } + + $this->_packagefile = &$pf; + $this->setGroup('default'); // install the default dependency group + return $this->_valid = true; + } + + return $this->_valid = false; + } + + /** + * + * @param string|array pass in an array of format + * array( + * 'package' => 'pname', + * ['channel' => 'channame',] + * ['version' => 'version',] + * ['state' => 'state',]) + * or a string of format [channame/]pname[-version|-state] + */ + function _fromString($param) + { + $options = $this->_downloader->getOptions(); + $channel = $this->_config->get('default_channel'); + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $pname = $this->_registry->parsePackageName($param, $channel); + PEAR::popErrorHandling(); + if (PEAR::isError($pname)) { + if ($pname->getCode() == 'invalid') { + $this->_valid = false; + return false; + } + + if ($pname->getCode() == 'channel') { + $parsed = $pname->getUserInfo(); + if ($this->_downloader->discover($parsed['channel'])) { + if ($this->_config->get('auto_discover')) { + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $pname = $this->_registry->parsePackageName($param, $channel); + PEAR::popErrorHandling(); + } else { + if (!isset($options['soft'])) { + $this->_downloader->log(0, 'Channel "' . $parsed['channel'] . + '" is not initialized, use ' . + '"pear channel-discover ' . $parsed['channel'] . '" to initialize' . + 'or pear config-set auto_discover 1'); + } + } + } + + if (PEAR::isError($pname)) { + if (!isset($options['soft'])) { + $this->_downloader->log(0, $pname->getMessage()); + } + + if (is_array($param)) { + $param = $this->_registry->parsedPackageNameToString($param); + } + + $err = PEAR::raiseError('invalid package name/package file "' . $param . '"'); + $this->_valid = false; + return $err; + } + } else { + if (!isset($options['soft'])) { + $this->_downloader->log(0, $pname->getMessage()); + } + + $err = PEAR::raiseError('invalid package name/package file "' . $param . '"'); + $this->_valid = false; + return $err; + } + } + + if (!isset($this->_type)) { + $this->_type = 'rest'; + } + + $this->_parsedname = $pname; + $this->_explicitState = isset($pname['state']) ? $pname['state'] : false; + $this->_explicitGroup = isset($pname['group']) ? true : false; + + $info = $this->_downloader->_getPackageDownloadUrl($pname); + if (PEAR::isError($info)) { + if ($info->getCode() != -976 && $pname['channel'] == 'pear.php.net') { + // try pecl + $pname['channel'] = 'pecl.php.net'; + if ($test = $this->_downloader->_getPackageDownloadUrl($pname)) { + if (!PEAR::isError($test)) { + $info = PEAR::raiseError($info->getMessage() . ' - package ' . + $this->_registry->parsedPackageNameToString($pname, true) . + ' can be installed with "pecl install ' . $pname['package'] . + '"'); + } else { + $pname['channel'] = 'pear.php.net'; + } + } else { + $pname['channel'] = 'pear.php.net'; + } + } + + return $info; + } + + $this->_rawpackagefile = $info['raw']; + $ret = $this->_analyzeDownloadURL($info, $param, $pname); + if (PEAR::isError($ret)) { + return $ret; + } + + if ($ret) { + $this->_downloadURL = $ret; + return $this->_valid = (bool) $ret; + } + } + + /** + * @param array output of package.getDownloadURL + * @param string|array|object information for detecting packages to be downloaded, and + * for errors + * @param array name information of the package + * @param array|null packages to be downloaded + * @param bool is this an optional dependency? + * @param bool is this any kind of dependency? + * @access private + */ + function _analyzeDownloadURL($info, $param, $pname, $params = null, $optional = false, + $isdependency = false) + { + if (!is_string($param) && PEAR_Downloader_Package::willDownload($param, $params)) { + return false; + } + + if ($info === false) { + $saveparam = !is_string($param) ? ", cannot download \"$param\"" : ''; + + // no releases exist + return PEAR::raiseError('No releases for package "' . + $this->_registry->parsedPackageNameToString($pname, true) . '" exist' . $saveparam); + } + + if (strtolower($info['info']->getChannel()) != strtolower($pname['channel'])) { + $err = false; + if ($pname['channel'] == 'pecl.php.net') { + if ($info['info']->getChannel() != 'pear.php.net') { + $err = true; + } + } elseif ($info['info']->getChannel() == 'pecl.php.net') { + if ($pname['channel'] != 'pear.php.net') { + $err = true; + } + } else { + $err = true; + } + + if ($err) { + return PEAR::raiseError('SECURITY ERROR: package in channel "' . $pname['channel'] . + '" retrieved another channel\'s name for download! ("' . + $info['info']->getChannel() . '")'); + } + } + + $preferred_state = $this->_config->get('preferred_state'); + if (!isset($info['url'])) { + $package_version = $this->_registry->packageInfo($info['info']->getPackage(), + 'version', $info['info']->getChannel()); + if ($this->isInstalled($info)) { + if ($isdependency && version_compare($info['version'], $package_version, '<=')) { + // ignore bogus errors of "failed to download dependency" + // if it is already installed and the one that would be + // downloaded is older or the same version (Bug #7219) + return false; + } + } + + if ($info['version'] === $package_version) { + if (!isset($options['soft'])) { + $this->_downloader->log(1, 'WARNING: failed to download ' . $pname['channel'] . + '/' . $pname['package'] . '-' . $package_version. ', additionally the suggested version' . + ' (' . $package_version . ') is the same as the locally installed one.'); + } + + return false; + } + + if (version_compare($info['version'], $package_version, '<=')) { + if (!isset($options['soft'])) { + $this->_downloader->log(1, 'WARNING: failed to download ' . $pname['channel'] . + '/' . $pname['package'] . '-' . $package_version . ', additionally the suggested version' . + ' (' . $info['version'] . ') is a lower version than the locally installed one (' . $package_version . ').'); + } + + return false; + } + + $instead = ', will instead download version ' . $info['version'] . + ', stability "' . $info['info']->getState() . '"'; + // releases exist, but we failed to get any + if (isset($this->_downloader->_options['force'])) { + if (isset($pname['version'])) { + $vs = ', version "' . $pname['version'] . '"'; + } elseif (isset($pname['state'])) { + $vs = ', stability "' . $pname['state'] . '"'; + } elseif ($param == 'dependency') { + if (!class_exists('PEAR_Common')) { + require_once 'PEAR/Common.php'; + } + + if (!in_array($info['info']->getState(), + PEAR_Common::betterStates($preferred_state, true))) { + if ($optional) { + // don't spit out confusing error message + return $this->_downloader->_getPackageDownloadUrl( + array('package' => $pname['package'], + 'channel' => $pname['channel'], + 'version' => $info['version'])); + } + $vs = ' within preferred state "' . $preferred_state . + '"'; + } else { + if (!class_exists('PEAR_Dependency2')) { + require_once 'PEAR/Dependency2.php'; + } + + if ($optional) { + // don't spit out confusing error message + return $this->_downloader->_getPackageDownloadUrl( + array('package' => $pname['package'], + 'channel' => $pname['channel'], + 'version' => $info['version'])); + } + $vs = PEAR_Dependency2::_getExtraString($pname); + $instead = ''; + } + } else { + $vs = ' within preferred state "' . $preferred_state . '"'; + } + + if (!isset($options['soft'])) { + $this->_downloader->log(1, 'WARNING: failed to download ' . $pname['channel'] . + '/' . $pname['package'] . $vs . $instead); + } + + // download the latest release + return $this->_downloader->_getPackageDownloadUrl( + array('package' => $pname['package'], + 'channel' => $pname['channel'], + 'version' => $info['version'])); + } else { + if (isset($info['php']) && $info['php']) { + $err = PEAR::raiseError('Failed to download ' . + $this->_registry->parsedPackageNameToString( + array('channel' => $pname['channel'], + 'package' => $pname['package']), + true) . + ', latest release is version ' . $info['php']['v'] . + ', but it requires PHP version "' . + $info['php']['m'] . '", use "' . + $this->_registry->parsedPackageNameToString( + array('channel' => $pname['channel'], 'package' => $pname['package'], + 'version' => $info['php']['v'])) . '" to install', + PEAR_DOWNLOADER_PACKAGE_PHPVERSION); + return $err; + } + + // construct helpful error message + if (isset($pname['version'])) { + $vs = ', version "' . $pname['version'] . '"'; + } elseif (isset($pname['state'])) { + $vs = ', stability "' . $pname['state'] . '"'; + } elseif ($param == 'dependency') { + if (!class_exists('PEAR_Common')) { + require_once 'PEAR/Common.php'; + } + + if (!in_array($info['info']->getState(), + PEAR_Common::betterStates($preferred_state, true))) { + if ($optional) { + // don't spit out confusing error message, and don't die on + // optional dep failure! + return $this->_downloader->_getPackageDownloadUrl( + array('package' => $pname['package'], + 'channel' => $pname['channel'], + 'version' => $info['version'])); + } + $vs = ' within preferred state "' . $preferred_state . '"'; + } else { + if (!class_exists('PEAR_Dependency2')) { + require_once 'PEAR/Dependency2.php'; + } + + if ($optional) { + // don't spit out confusing error message, and don't die on + // optional dep failure! + return $this->_downloader->_getPackageDownloadUrl( + array('package' => $pname['package'], + 'channel' => $pname['channel'], + 'version' => $info['version'])); + } + $vs = PEAR_Dependency2::_getExtraString($pname); + } + } else { + $vs = ' within preferred state "' . $this->_downloader->config->get('preferred_state') . '"'; + } + + $options = $this->_downloader->getOptions(); + // this is only set by the "download-all" command + if (isset($options['ignorepreferred_state'])) { + $err = PEAR::raiseError( + 'Failed to download ' . $this->_registry->parsedPackageNameToString( + array('channel' => $pname['channel'], 'package' => $pname['package']), + true) + . $vs . + ', latest release is version ' . $info['version'] . + ', stability "' . $info['info']->getState() . '", use "' . + $this->_registry->parsedPackageNameToString( + array('channel' => $pname['channel'], 'package' => $pname['package'], + 'version' => $info['version'])) . '" to install', + PEAR_DOWNLOADER_PACKAGE_STATE); + return $err; + } + + // Checks if the user has a package installed already and checks the release against + // the state against the installed package, this allows upgrades for packages + // with lower stability than the preferred_state + $stability = $this->_registry->packageInfo($pname['package'], 'stability', $pname['channel']); + if (!$this->isInstalled($info) + || !in_array($info['info']->getState(), PEAR_Common::betterStates($stability['release'], true)) + ) { + $err = PEAR::raiseError( + 'Failed to download ' . $this->_registry->parsedPackageNameToString( + array('channel' => $pname['channel'], 'package' => $pname['package']), + true) + . $vs . + ', latest release is version ' . $info['version'] . + ', stability "' . $info['info']->getState() . '", use "' . + $this->_registry->parsedPackageNameToString( + array('channel' => $pname['channel'], 'package' => $pname['package'], + 'version' => $info['version'])) . '" to install'); + return $err; + } + } + } + + if (isset($info['deprecated']) && $info['deprecated']) { + $this->_downloader->log(0, + 'WARNING: "' . + $this->_registry->parsedPackageNameToString( + array('channel' => $info['info']->getChannel(), + 'package' => $info['info']->getPackage()), true) . + '" is deprecated in favor of "' . + $this->_registry->parsedPackageNameToString($info['deprecated'], true) . + '"'); + } + + return $info; + } +} \ No newline at end of file diff --git a/library/pear/PEAR/ErrorStack.php b/library/pear/PEAR/ErrorStack.php new file mode 100644 index 000000000..fd6af2063 --- /dev/null +++ b/library/pear/PEAR/ErrorStack.php @@ -0,0 +1,985 @@ + + * @copyright 2004-2008 Greg Beaver + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: ErrorStack.php 276383 2009-02-24 23:39:37Z dufuz $ + * @link http://pear.php.net/package/PEAR_ErrorStack + */ + +/** + * Singleton storage + * + * Format: + *
    + * array(
    + *  'package1' => PEAR_ErrorStack object,
    + *  'package2' => PEAR_ErrorStack object,
    + *  ...
    + * )
    + * 
    + * @access private + * @global array $GLOBALS['_PEAR_ERRORSTACK_SINGLETON'] + */ +$GLOBALS['_PEAR_ERRORSTACK_SINGLETON'] = array(); + +/** + * Global error callback (default) + * + * This is only used if set to non-false. * is the default callback for + * all packages, whereas specific packages may set a default callback + * for all instances, regardless of whether they are a singleton or not. + * + * To exclude non-singletons, only set the local callback for the singleton + * @see PEAR_ErrorStack::setDefaultCallback() + * @access private + * @global array $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_CALLBACK'] + */ +$GLOBALS['_PEAR_ERRORSTACK_DEFAULT_CALLBACK'] = array( + '*' => false, +); + +/** + * Global Log object (default) + * + * This is only used if set to non-false. Use to set a default log object for + * all stacks, regardless of instantiation order or location + * @see PEAR_ErrorStack::setDefaultLogger() + * @access private + * @global array $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER'] + */ +$GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER'] = false; + +/** + * Global Overriding Callback + * + * This callback will override any error callbacks that specific loggers have set. + * Use with EXTREME caution + * @see PEAR_ErrorStack::staticPushCallback() + * @access private + * @global array $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER'] + */ +$GLOBALS['_PEAR_ERRORSTACK_OVERRIDE_CALLBACK'] = array(); + +/**#@+ + * One of four possible return values from the error Callback + * @see PEAR_ErrorStack::_errorCallback() + */ +/** + * If this is returned, then the error will be both pushed onto the stack + * and logged. + */ +define('PEAR_ERRORSTACK_PUSHANDLOG', 1); +/** + * If this is returned, then the error will only be pushed onto the stack, + * and not logged. + */ +define('PEAR_ERRORSTACK_PUSH', 2); +/** + * If this is returned, then the error will only be logged, but not pushed + * onto the error stack. + */ +define('PEAR_ERRORSTACK_LOG', 3); +/** + * If this is returned, then the error is completely ignored. + */ +define('PEAR_ERRORSTACK_IGNORE', 4); +/** + * If this is returned, then the error is logged and die() is called. + */ +define('PEAR_ERRORSTACK_DIE', 5); +/**#@-*/ + +/** + * Error code for an attempt to instantiate a non-class as a PEAR_ErrorStack in + * the singleton method. + */ +define('PEAR_ERRORSTACK_ERR_NONCLASS', 1); + +/** + * Error code for an attempt to pass an object into {@link PEAR_ErrorStack::getMessage()} + * that has no __toString() method + */ +define('PEAR_ERRORSTACK_ERR_OBJTOSTRING', 2); +/** + * Error Stack Implementation + * + * Usage: + * + * // global error stack + * $global_stack = &PEAR_ErrorStack::singleton('MyPackage'); + * // local error stack + * $local_stack = new PEAR_ErrorStack('MyPackage'); + * + * @author Greg Beaver + * @version 1.9.1 + * @package PEAR_ErrorStack + * @category Debugging + * @copyright 2004-2008 Greg Beaver + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: ErrorStack.php 276383 2009-02-24 23:39:37Z dufuz $ + * @link http://pear.php.net/package/PEAR_ErrorStack + */ +class PEAR_ErrorStack { + /** + * Errors are stored in the order that they are pushed on the stack. + * @since 0.4alpha Errors are no longer organized by error level. + * This renders pop() nearly unusable, and levels could be more easily + * handled in a callback anyway + * @var array + * @access private + */ + var $_errors = array(); + + /** + * Storage of errors by level. + * + * Allows easy retrieval and deletion of only errors from a particular level + * @since PEAR 1.4.0dev + * @var array + * @access private + */ + var $_errorsByLevel = array(); + + /** + * Package name this error stack represents + * @var string + * @access protected + */ + var $_package; + + /** + * Determines whether a PEAR_Error is thrown upon every error addition + * @var boolean + * @access private + */ + var $_compat = false; + + /** + * If set to a valid callback, this will be used to generate the error + * message from the error code, otherwise the message passed in will be + * used + * @var false|string|array + * @access private + */ + var $_msgCallback = false; + + /** + * If set to a valid callback, this will be used to generate the error + * context for an error. For PHP-related errors, this will be a file + * and line number as retrieved from debug_backtrace(), but can be + * customized for other purposes. The error might actually be in a separate + * configuration file, or in a database query. + * @var false|string|array + * @access protected + */ + var $_contextCallback = false; + + /** + * If set to a valid callback, this will be called every time an error + * is pushed onto the stack. The return value will be used to determine + * whether to allow an error to be pushed or logged. + * + * The return value must be one an PEAR_ERRORSTACK_* constant + * @see PEAR_ERRORSTACK_PUSHANDLOG, PEAR_ERRORSTACK_PUSH, PEAR_ERRORSTACK_LOG + * @var false|string|array + * @access protected + */ + var $_errorCallback = array(); + + /** + * PEAR::Log object for logging errors + * @var false|Log + * @access protected + */ + var $_logger = false; + + /** + * Error messages - designed to be overridden + * @var array + * @abstract + */ + var $_errorMsgs = array(); + + /** + * Set up a new error stack + * + * @param string $package name of the package this error stack represents + * @param callback $msgCallback callback used for error message generation + * @param callback $contextCallback callback used for context generation, + * defaults to {@link getFileLine()} + * @param boolean $throwPEAR_Error + */ + function PEAR_ErrorStack($package, $msgCallback = false, $contextCallback = false, + $throwPEAR_Error = false) + { + $this->_package = $package; + $this->setMessageCallback($msgCallback); + $this->setContextCallback($contextCallback); + $this->_compat = $throwPEAR_Error; + } + + /** + * Return a single error stack for this package. + * + * Note that all parameters are ignored if the stack for package $package + * has already been instantiated + * @param string $package name of the package this error stack represents + * @param callback $msgCallback callback used for error message generation + * @param callback $contextCallback callback used for context generation, + * defaults to {@link getFileLine()} + * @param boolean $throwPEAR_Error + * @param string $stackClass class to instantiate + * @static + * @return PEAR_ErrorStack + */ + function &singleton($package, $msgCallback = false, $contextCallback = false, + $throwPEAR_Error = false, $stackClass = 'PEAR_ErrorStack') + { + if (isset($GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package])) { + return $GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package]; + } + if (!class_exists($stackClass)) { + if (function_exists('debug_backtrace')) { + $trace = debug_backtrace(); + } + PEAR_ErrorStack::staticPush('PEAR_ErrorStack', PEAR_ERRORSTACK_ERR_NONCLASS, + 'exception', array('stackclass' => $stackClass), + 'stack class "%stackclass%" is not a valid class name (should be like PEAR_ErrorStack)', + false, $trace); + } + $GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package] = + new $stackClass($package, $msgCallback, $contextCallback, $throwPEAR_Error); + + return $GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package]; + } + + /** + * Internal error handler for PEAR_ErrorStack class + * + * Dies if the error is an exception (and would have died anyway) + * @access private + */ + function _handleError($err) + { + if ($err['level'] == 'exception') { + $message = $err['message']; + if (isset($_SERVER['REQUEST_URI'])) { + echo '
    '; + } else { + echo "\n"; + } + var_dump($err['context']); + die($message); + } + } + + /** + * Set up a PEAR::Log object for all error stacks that don't have one + * @param Log $log + * @static + */ + function setDefaultLogger(&$log) + { + if (is_object($log) && method_exists($log, 'log') ) { + $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER'] = &$log; + } elseif (is_callable($log)) { + $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER'] = &$log; + } + } + + /** + * Set up a PEAR::Log object for this error stack + * @param Log $log + */ + function setLogger(&$log) + { + if (is_object($log) && method_exists($log, 'log') ) { + $this->_logger = &$log; + } elseif (is_callable($log)) { + $this->_logger = &$log; + } + } + + /** + * Set an error code => error message mapping callback + * + * This method sets the callback that can be used to generate error + * messages for any instance + * @param array|string Callback function/method + */ + function setMessageCallback($msgCallback) + { + if (!$msgCallback) { + $this->_msgCallback = array(&$this, 'getErrorMessage'); + } else { + if (is_callable($msgCallback)) { + $this->_msgCallback = $msgCallback; + } + } + } + + /** + * Get an error code => error message mapping callback + * + * This method returns the current callback that can be used to generate error + * messages + * @return array|string|false Callback function/method or false if none + */ + function getMessageCallback() + { + return $this->_msgCallback; + } + + /** + * Sets a default callback to be used by all error stacks + * + * This method sets the callback that can be used to generate error + * messages for a singleton + * @param array|string Callback function/method + * @param string Package name, or false for all packages + * @static + */ + function setDefaultCallback($callback = false, $package = false) + { + if (!is_callable($callback)) { + $callback = false; + } + $package = $package ? $package : '*'; + $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_CALLBACK'][$package] = $callback; + } + + /** + * Set a callback that generates context information (location of error) for an error stack + * + * This method sets the callback that can be used to generate context + * information for an error. Passing in NULL will disable context generation + * and remove the expensive call to debug_backtrace() + * @param array|string|null Callback function/method + */ + function setContextCallback($contextCallback) + { + if ($contextCallback === null) { + return $this->_contextCallback = false; + } + if (!$contextCallback) { + $this->_contextCallback = array(&$this, 'getFileLine'); + } else { + if (is_callable($contextCallback)) { + $this->_contextCallback = $contextCallback; + } + } + } + + /** + * Set an error Callback + * If set to a valid callback, this will be called every time an error + * is pushed onto the stack. The return value will be used to determine + * whether to allow an error to be pushed or logged. + * + * The return value must be one of the ERRORSTACK_* constants. + * + * This functionality can be used to emulate PEAR's pushErrorHandling, and + * the PEAR_ERROR_CALLBACK mode, without affecting the integrity of + * the error stack or logging + * @see PEAR_ERRORSTACK_PUSHANDLOG, PEAR_ERRORSTACK_PUSH, PEAR_ERRORSTACK_LOG + * @see popCallback() + * @param string|array $cb + */ + function pushCallback($cb) + { + array_push($this->_errorCallback, $cb); + } + + /** + * Remove a callback from the error callback stack + * @see pushCallback() + * @return array|string|false + */ + function popCallback() + { + if (!count($this->_errorCallback)) { + return false; + } + return array_pop($this->_errorCallback); + } + + /** + * Set a temporary overriding error callback for every package error stack + * + * Use this to temporarily disable all existing callbacks (can be used + * to emulate the @ operator, for instance) + * @see PEAR_ERRORSTACK_PUSHANDLOG, PEAR_ERRORSTACK_PUSH, PEAR_ERRORSTACK_LOG + * @see staticPopCallback(), pushCallback() + * @param string|array $cb + * @static + */ + function staticPushCallback($cb) + { + array_push($GLOBALS['_PEAR_ERRORSTACK_OVERRIDE_CALLBACK'], $cb); + } + + /** + * Remove a temporary overriding error callback + * @see staticPushCallback() + * @return array|string|false + * @static + */ + function staticPopCallback() + { + $ret = array_pop($GLOBALS['_PEAR_ERRORSTACK_OVERRIDE_CALLBACK']); + if (!is_array($GLOBALS['_PEAR_ERRORSTACK_OVERRIDE_CALLBACK'])) { + $GLOBALS['_PEAR_ERRORSTACK_OVERRIDE_CALLBACK'] = array(); + } + return $ret; + } + + /** + * Add an error to the stack + * + * If the message generator exists, it is called with 2 parameters. + * - the current Error Stack object + * - an array that is in the same format as an error. Available indices + * are 'code', 'package', 'time', 'params', 'level', and 'context' + * + * Next, if the error should contain context information, this is + * handled by the context grabbing method. + * Finally, the error is pushed onto the proper error stack + * @param int $code Package-specific error code + * @param string $level Error level. This is NOT spell-checked + * @param array $params associative array of error parameters + * @param string $msg Error message, or a portion of it if the message + * is to be generated + * @param array $repackage If this error re-packages an error pushed by + * another package, place the array returned from + * {@link pop()} in this parameter + * @param array $backtrace Protected parameter: use this to pass in the + * {@link debug_backtrace()} that should be used + * to find error context + * @return PEAR_Error|array if compatibility mode is on, a PEAR_Error is also + * thrown. If a PEAR_Error is returned, the userinfo + * property is set to the following array: + * + * + * array( + * 'code' => $code, + * 'params' => $params, + * 'package' => $this->_package, + * 'level' => $level, + * 'time' => time(), + * 'context' => $context, + * 'message' => $msg, + * //['repackage' => $err] repackaged error array/Exception class + * ); + * + * + * Normally, the previous array is returned. + */ + function push($code, $level = 'error', $params = array(), $msg = false, + $repackage = false, $backtrace = false) + { + $context = false; + // grab error context + if ($this->_contextCallback) { + if (!$backtrace) { + $backtrace = debug_backtrace(); + } + $context = call_user_func($this->_contextCallback, $code, $params, $backtrace); + } + + // save error + $time = explode(' ', microtime()); + $time = $time[1] + $time[0]; + $err = array( + 'code' => $code, + 'params' => $params, + 'package' => $this->_package, + 'level' => $level, + 'time' => $time, + 'context' => $context, + 'message' => $msg, + ); + + if ($repackage) { + $err['repackage'] = $repackage; + } + + // set up the error message, if necessary + if ($this->_msgCallback) { + $msg = call_user_func_array($this->_msgCallback, + array(&$this, $err)); + $err['message'] = $msg; + } + $push = $log = true; + $die = false; + // try the overriding callback first + $callback = $this->staticPopCallback(); + if ($callback) { + $this->staticPushCallback($callback); + } + if (!is_callable($callback)) { + // try the local callback next + $callback = $this->popCallback(); + if (is_callable($callback)) { + $this->pushCallback($callback); + } else { + // try the default callback + $callback = isset($GLOBALS['_PEAR_ERRORSTACK_DEFAULT_CALLBACK'][$this->_package]) ? + $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_CALLBACK'][$this->_package] : + $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_CALLBACK']['*']; + } + } + if (is_callable($callback)) { + switch(call_user_func($callback, $err)){ + case PEAR_ERRORSTACK_IGNORE: + return $err; + break; + case PEAR_ERRORSTACK_PUSH: + $log = false; + break; + case PEAR_ERRORSTACK_LOG: + $push = false; + break; + case PEAR_ERRORSTACK_DIE: + $die = true; + break; + // anything else returned has the same effect as pushandlog + } + } + if ($push) { + array_unshift($this->_errors, $err); + if (!isset($this->_errorsByLevel[$err['level']])) { + $this->_errorsByLevel[$err['level']] = array(); + } + $this->_errorsByLevel[$err['level']][] = &$this->_errors[0]; + } + if ($log) { + if ($this->_logger || $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER']) { + $this->_log($err); + } + } + if ($die) { + die(); + } + if ($this->_compat && $push) { + return $this->raiseError($msg, $code, null, null, $err); + } + return $err; + } + + /** + * Static version of {@link push()} + * + * @param string $package Package name this error belongs to + * @param int $code Package-specific error code + * @param string $level Error level. This is NOT spell-checked + * @param array $params associative array of error parameters + * @param string $msg Error message, or a portion of it if the message + * is to be generated + * @param array $repackage If this error re-packages an error pushed by + * another package, place the array returned from + * {@link pop()} in this parameter + * @param array $backtrace Protected parameter: use this to pass in the + * {@link debug_backtrace()} that should be used + * to find error context + * @return PEAR_Error|array if compatibility mode is on, a PEAR_Error is also + * thrown. see docs for {@link push()} + * @static + */ + function staticPush($package, $code, $level = 'error', $params = array(), + $msg = false, $repackage = false, $backtrace = false) + { + $s = &PEAR_ErrorStack::singleton($package); + if ($s->_contextCallback) { + if (!$backtrace) { + if (function_exists('debug_backtrace')) { + $backtrace = debug_backtrace(); + } + } + } + return $s->push($code, $level, $params, $msg, $repackage, $backtrace); + } + + /** + * Log an error using PEAR::Log + * @param array $err Error array + * @param array $levels Error level => Log constant map + * @access protected + */ + function _log($err) + { + if ($this->_logger) { + $logger = &$this->_logger; + } else { + $logger = &$GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER']; + } + if (is_a($logger, 'Log')) { + $levels = array( + 'exception' => PEAR_LOG_CRIT, + 'alert' => PEAR_LOG_ALERT, + 'critical' => PEAR_LOG_CRIT, + 'error' => PEAR_LOG_ERR, + 'warning' => PEAR_LOG_WARNING, + 'notice' => PEAR_LOG_NOTICE, + 'info' => PEAR_LOG_INFO, + 'debug' => PEAR_LOG_DEBUG); + if (isset($levels[$err['level']])) { + $level = $levels[$err['level']]; + } else { + $level = PEAR_LOG_INFO; + } + $logger->log($err['message'], $level, $err); + } else { // support non-standard logs + call_user_func($logger, $err); + } + } + + + /** + * Pop an error off of the error stack + * + * @return false|array + * @since 0.4alpha it is no longer possible to specify a specific error + * level to return - the last error pushed will be returned, instead + */ + function pop() + { + $err = @array_shift($this->_errors); + if (!is_null($err)) { + @array_pop($this->_errorsByLevel[$err['level']]); + if (!count($this->_errorsByLevel[$err['level']])) { + unset($this->_errorsByLevel[$err['level']]); + } + } + return $err; + } + + /** + * Pop an error off of the error stack, static method + * + * @param string package name + * @return boolean + * @since PEAR1.5.0a1 + */ + function staticPop($package) + { + if ($package) { + if (!isset($GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package])) { + return false; + } + return $GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package]->pop(); + } + } + + /** + * Determine whether there are any errors on the stack + * @param string|array Level name. Use to determine if any errors + * of level (string), or levels (array) have been pushed + * @return boolean + */ + function hasErrors($level = false) + { + if ($level) { + return isset($this->_errorsByLevel[$level]); + } + return count($this->_errors); + } + + /** + * Retrieve all errors since last purge + * + * @param boolean set in order to empty the error stack + * @param string level name, to return only errors of a particular severity + * @return array + */ + function getErrors($purge = false, $level = false) + { + if (!$purge) { + if ($level) { + if (!isset($this->_errorsByLevel[$level])) { + return array(); + } else { + return $this->_errorsByLevel[$level]; + } + } else { + return $this->_errors; + } + } + if ($level) { + $ret = $this->_errorsByLevel[$level]; + foreach ($this->_errorsByLevel[$level] as $i => $unused) { + // entries are references to the $_errors array + $this->_errorsByLevel[$level][$i] = false; + } + // array_filter removes all entries === false + $this->_errors = array_filter($this->_errors); + unset($this->_errorsByLevel[$level]); + return $ret; + } + $ret = $this->_errors; + $this->_errors = array(); + $this->_errorsByLevel = array(); + return $ret; + } + + /** + * Determine whether there are any errors on a single error stack, or on any error stack + * + * The optional parameter can be used to test the existence of any errors without the need of + * singleton instantiation + * @param string|false Package name to check for errors + * @param string Level name to check for a particular severity + * @return boolean + * @static + */ + function staticHasErrors($package = false, $level = false) + { + if ($package) { + if (!isset($GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package])) { + return false; + } + return $GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package]->hasErrors($level); + } + foreach ($GLOBALS['_PEAR_ERRORSTACK_SINGLETON'] as $package => $obj) { + if ($obj->hasErrors($level)) { + return true; + } + } + return false; + } + + /** + * Get a list of all errors since last purge, organized by package + * @since PEAR 1.4.0dev BC break! $level is now in the place $merge used to be + * @param boolean $purge Set to purge the error stack of existing errors + * @param string $level Set to a level name in order to retrieve only errors of a particular level + * @param boolean $merge Set to return a flat array, not organized by package + * @param array $sortfunc Function used to sort a merged array - default + * sorts by time, and should be good for most cases + * @static + * @return array + */ + function staticGetErrors($purge = false, $level = false, $merge = false, + $sortfunc = array('PEAR_ErrorStack', '_sortErrors')) + { + $ret = array(); + if (!is_callable($sortfunc)) { + $sortfunc = array('PEAR_ErrorStack', '_sortErrors'); + } + foreach ($GLOBALS['_PEAR_ERRORSTACK_SINGLETON'] as $package => $obj) { + $test = $GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package]->getErrors($purge, $level); + if ($test) { + if ($merge) { + $ret = array_merge($ret, $test); + } else { + $ret[$package] = $test; + } + } + } + if ($merge) { + usort($ret, $sortfunc); + } + return $ret; + } + + /** + * Error sorting function, sorts by time + * @access private + */ + function _sortErrors($a, $b) + { + if ($a['time'] == $b['time']) { + return 0; + } + if ($a['time'] < $b['time']) { + return 1; + } + return -1; + } + + /** + * Standard file/line number/function/class context callback + * + * This function uses a backtrace generated from {@link debug_backtrace()} + * and so will not work at all in PHP < 4.3.0. The frame should + * reference the frame that contains the source of the error. + * @return array|false either array('file' => file, 'line' => line, + * 'function' => function name, 'class' => class name) or + * if this doesn't work, then false + * @param unused + * @param integer backtrace frame. + * @param array Results of debug_backtrace() + * @static + */ + function getFileLine($code, $params, $backtrace = null) + { + if ($backtrace === null) { + return false; + } + $frame = 0; + $functionframe = 1; + if (!isset($backtrace[1])) { + $functionframe = 0; + } else { + while (isset($backtrace[$functionframe]['function']) && + $backtrace[$functionframe]['function'] == 'eval' && + isset($backtrace[$functionframe + 1])) { + $functionframe++; + } + } + if (isset($backtrace[$frame])) { + if (!isset($backtrace[$frame]['file'])) { + $frame++; + } + $funcbacktrace = $backtrace[$functionframe]; + $filebacktrace = $backtrace[$frame]; + $ret = array('file' => $filebacktrace['file'], + 'line' => $filebacktrace['line']); + // rearrange for eval'd code or create function errors + if (strpos($filebacktrace['file'], '(') && + preg_match(';^(.*?)\((\d+)\) : (.*?)\\z;', $filebacktrace['file'], + $matches)) { + $ret['file'] = $matches[1]; + $ret['line'] = $matches[2] + 0; + } + if (isset($funcbacktrace['function']) && isset($backtrace[1])) { + if ($funcbacktrace['function'] != 'eval') { + if ($funcbacktrace['function'] == '__lambda_func') { + $ret['function'] = 'create_function() code'; + } else { + $ret['function'] = $funcbacktrace['function']; + } + } + } + if (isset($funcbacktrace['class']) && isset($backtrace[1])) { + $ret['class'] = $funcbacktrace['class']; + } + return $ret; + } + return false; + } + + /** + * Standard error message generation callback + * + * This method may also be called by a custom error message generator + * to fill in template values from the params array, simply + * set the third parameter to the error message template string to use + * + * The special variable %__msg% is reserved: use it only to specify + * where a message passed in by the user should be placed in the template, + * like so: + * + * Error message: %msg% - internal error + * + * If the message passed like so: + * + * + * $stack->push(ERROR_CODE, 'error', array(), 'server error 500'); + * + * + * The returned error message will be "Error message: server error 500 - + * internal error" + * @param PEAR_ErrorStack + * @param array + * @param string|false Pre-generated error message template + * @static + * @return string + */ + function getErrorMessage(&$stack, $err, $template = false) + { + if ($template) { + $mainmsg = $template; + } else { + $mainmsg = $stack->getErrorMessageTemplate($err['code']); + } + $mainmsg = str_replace('%__msg%', $err['message'], $mainmsg); + if (is_array($err['params']) && count($err['params'])) { + foreach ($err['params'] as $name => $val) { + if (is_array($val)) { + // @ is needed in case $val is a multi-dimensional array + $val = @implode(', ', $val); + } + if (is_object($val)) { + if (method_exists($val, '__toString')) { + $val = $val->__toString(); + } else { + PEAR_ErrorStack::staticPush('PEAR_ErrorStack', PEAR_ERRORSTACK_ERR_OBJTOSTRING, + 'warning', array('obj' => get_class($val)), + 'object %obj% passed into getErrorMessage, but has no __toString() method'); + $val = 'Object'; + } + } + $mainmsg = str_replace('%' . $name . '%', $val, $mainmsg); + } + } + return $mainmsg; + } + + /** + * Standard Error Message Template generator from code + * @return string + */ + function getErrorMessageTemplate($code) + { + if (!isset($this->_errorMsgs[$code])) { + return '%__msg%'; + } + return $this->_errorMsgs[$code]; + } + + /** + * Set the Error Message Template array + * + * The array format must be: + *
    +     * array(error code => 'message template',...)
    +     * 
    + * + * Error message parameters passed into {@link push()} will be used as input + * for the error message. If the template is 'message %foo% was %bar%', and the + * parameters are array('foo' => 'one', 'bar' => 'six'), the error message returned will + * be 'message one was six' + * @return string + */ + function setErrorMessageTemplate($template) + { + $this->_errorMsgs = $template; + } + + + /** + * emulate PEAR::raiseError() + * + * @return PEAR_Error + */ + function raiseError() + { + require_once 'PEAR.php'; + $args = func_get_args(); + return call_user_func_array(array('PEAR', 'raiseError'), $args); + } +} +$stack = &PEAR_ErrorStack::singleton('PEAR_ErrorStack'); +$stack->pushCallback(array('PEAR_ErrorStack', '_handleError')); +?> diff --git a/library/pear/PEAR/Exception.php b/library/pear/PEAR/Exception.php new file mode 100644 index 000000000..77743ef21 --- /dev/null +++ b/library/pear/PEAR/Exception.php @@ -0,0 +1,389 @@ + + * @author Hans Lellelid + * @author Bertrand Mansion + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Exception.php 296939 2010-03-27 16:24:43Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.3.3 + */ + + +/** + * Base PEAR_Exception Class + * + * 1) Features: + * + * - Nestable exceptions (throw new PEAR_Exception($msg, $prev_exception)) + * - Definable triggers, shot when exceptions occur + * - Pretty and informative error messages + * - Added more context info available (like class, method or cause) + * - cause can be a PEAR_Exception or an array of mixed + * PEAR_Exceptions/PEAR_ErrorStack warnings + * - callbacks for specific exception classes and their children + * + * 2) Ideas: + * + * - Maybe a way to define a 'template' for the output + * + * 3) Inherited properties from PHP Exception Class: + * + * protected $message + * protected $code + * protected $line + * protected $file + * private $trace + * + * 4) Inherited methods from PHP Exception Class: + * + * __clone + * __construct + * getMessage + * getCode + * getFile + * getLine + * getTraceSafe + * getTraceSafeAsString + * __toString + * + * 5) Usage example + * + * + * require_once 'PEAR/Exception.php'; + * + * class Test { + * function foo() { + * throw new PEAR_Exception('Error Message', ERROR_CODE); + * } + * } + * + * function myLogger($pear_exception) { + * echo $pear_exception->getMessage(); + * } + * // each time a exception is thrown the 'myLogger' will be called + * // (its use is completely optional) + * PEAR_Exception::addObserver('myLogger'); + * $test = new Test; + * try { + * $test->foo(); + * } catch (PEAR_Exception $e) { + * print $e; + * } + * + * + * @category pear + * @package PEAR + * @author Tomas V.V.Cox + * @author Hans Lellelid + * @author Bertrand Mansion + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.1 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.3.3 + * + */ +class PEAR_Exception extends Exception +{ + const OBSERVER_PRINT = -2; + const OBSERVER_TRIGGER = -4; + const OBSERVER_DIE = -8; + protected $cause; + private static $_observers = array(); + private static $_uniqueid = 0; + private $_trace; + + /** + * Supported signatures: + * - PEAR_Exception(string $message); + * - PEAR_Exception(string $message, int $code); + * - PEAR_Exception(string $message, Exception $cause); + * - PEAR_Exception(string $message, Exception $cause, int $code); + * - PEAR_Exception(string $message, PEAR_Error $cause); + * - PEAR_Exception(string $message, PEAR_Error $cause, int $code); + * - PEAR_Exception(string $message, array $causes); + * - PEAR_Exception(string $message, array $causes, int $code); + * @param string exception message + * @param int|Exception|PEAR_Error|array|null exception cause + * @param int|null exception code or null + */ + public function __construct($message, $p2 = null, $p3 = null) + { + if (is_int($p2)) { + $code = $p2; + $this->cause = null; + } elseif (is_object($p2) || is_array($p2)) { + // using is_object allows both Exception and PEAR_Error + if (is_object($p2) && !($p2 instanceof Exception)) { + if (!class_exists('PEAR_Error') || !($p2 instanceof PEAR_Error)) { + throw new PEAR_Exception('exception cause must be Exception, ' . + 'array, or PEAR_Error'); + } + } + $code = $p3; + if (is_array($p2) && isset($p2['message'])) { + // fix potential problem of passing in a single warning + $p2 = array($p2); + } + $this->cause = $p2; + } else { + $code = null; + $this->cause = null; + } + parent::__construct($message, $code); + $this->signal(); + } + + /** + * @param mixed $callback - A valid php callback, see php func is_callable() + * - A PEAR_Exception::OBSERVER_* constant + * - An array(const PEAR_Exception::OBSERVER_*, + * mixed $options) + * @param string $label The name of the observer. Use this if you want + * to remove it later with removeObserver() + */ + public static function addObserver($callback, $label = 'default') + { + self::$_observers[$label] = $callback; + } + + public static function removeObserver($label = 'default') + { + unset(self::$_observers[$label]); + } + + /** + * @return int unique identifier for an observer + */ + public static function getUniqueId() + { + return self::$_uniqueid++; + } + + private function signal() + { + foreach (self::$_observers as $func) { + if (is_callable($func)) { + call_user_func($func, $this); + continue; + } + settype($func, 'array'); + switch ($func[0]) { + case self::OBSERVER_PRINT : + $f = (isset($func[1])) ? $func[1] : '%s'; + printf($f, $this->getMessage()); + break; + case self::OBSERVER_TRIGGER : + $f = (isset($func[1])) ? $func[1] : E_USER_NOTICE; + trigger_error($this->getMessage(), $f); + break; + case self::OBSERVER_DIE : + $f = (isset($func[1])) ? $func[1] : '%s'; + die(printf($f, $this->getMessage())); + break; + default: + trigger_error('invalid observer type', E_USER_WARNING); + } + } + } + + /** + * Return specific error information that can be used for more detailed + * error messages or translation. + * + * This method may be overridden in child exception classes in order + * to add functionality not present in PEAR_Exception and is a placeholder + * to define API + * + * The returned array must be an associative array of parameter => value like so: + *
    +     * array('name' => $name, 'context' => array(...))
    +     * 
    + * @return array + */ + public function getErrorData() + { + return array(); + } + + /** + * Returns the exception that caused this exception to be thrown + * @access public + * @return Exception|array The context of the exception + */ + public function getCause() + { + return $this->cause; + } + + /** + * Function must be public to call on caused exceptions + * @param array + */ + public function getCauseMessage(&$causes) + { + $trace = $this->getTraceSafe(); + $cause = array('class' => get_class($this), + 'message' => $this->message, + 'file' => 'unknown', + 'line' => 'unknown'); + if (isset($trace[0])) { + if (isset($trace[0]['file'])) { + $cause['file'] = $trace[0]['file']; + $cause['line'] = $trace[0]['line']; + } + } + $causes[] = $cause; + if ($this->cause instanceof PEAR_Exception) { + $this->cause->getCauseMessage($causes); + } elseif ($this->cause instanceof Exception) { + $causes[] = array('class' => get_class($this->cause), + 'message' => $this->cause->getMessage(), + 'file' => $this->cause->getFile(), + 'line' => $this->cause->getLine()); + } elseif (class_exists('PEAR_Error') && $this->cause instanceof PEAR_Error) { + $causes[] = array('class' => get_class($this->cause), + 'message' => $this->cause->getMessage(), + 'file' => 'unknown', + 'line' => 'unknown'); + } elseif (is_array($this->cause)) { + foreach ($this->cause as $cause) { + if ($cause instanceof PEAR_Exception) { + $cause->getCauseMessage($causes); + } elseif ($cause instanceof Exception) { + $causes[] = array('class' => get_class($cause), + 'message' => $cause->getMessage(), + 'file' => $cause->getFile(), + 'line' => $cause->getLine()); + } elseif (class_exists('PEAR_Error') && $cause instanceof PEAR_Error) { + $causes[] = array('class' => get_class($cause), + 'message' => $cause->getMessage(), + 'file' => 'unknown', + 'line' => 'unknown'); + } elseif (is_array($cause) && isset($cause['message'])) { + // PEAR_ErrorStack warning + $causes[] = array( + 'class' => $cause['package'], + 'message' => $cause['message'], + 'file' => isset($cause['context']['file']) ? + $cause['context']['file'] : + 'unknown', + 'line' => isset($cause['context']['line']) ? + $cause['context']['line'] : + 'unknown', + ); + } + } + } + } + + public function getTraceSafe() + { + if (!isset($this->_trace)) { + $this->_trace = $this->getTrace(); + if (empty($this->_trace)) { + $backtrace = debug_backtrace(); + $this->_trace = array($backtrace[count($backtrace)-1]); + } + } + return $this->_trace; + } + + public function getErrorClass() + { + $trace = $this->getTraceSafe(); + return $trace[0]['class']; + } + + public function getErrorMethod() + { + $trace = $this->getTraceSafe(); + return $trace[0]['function']; + } + + public function __toString() + { + if (isset($_SERVER['REQUEST_URI'])) { + return $this->toHtml(); + } + return $this->toText(); + } + + public function toHtml() + { + $trace = $this->getTraceSafe(); + $causes = array(); + $this->getCauseMessage($causes); + $html = '' . "\n"; + foreach ($causes as $i => $cause) { + $html .= '\n"; + } + $html .= '' . "\n" + . '' + . '' + . '' . "\n"; + + foreach ($trace as $k => $v) { + $html .= '' + . '' + . '' . "\n"; + } + $html .= '' + . '' + . '' . "\n" + . '
    ' + . str_repeat('-', $i) . ' ' . $cause['class'] . ': ' + . htmlspecialchars($cause['message']) . ' in ' . $cause['file'] . ' ' + . 'on line ' . $cause['line'] . '' + . "
    Exception trace
    #FunctionLocation
    ' . $k . ''; + if (!empty($v['class'])) { + $html .= $v['class'] . $v['type']; + } + $html .= $v['function']; + $args = array(); + if (!empty($v['args'])) { + foreach ($v['args'] as $arg) { + if (is_null($arg)) $args[] = 'null'; + elseif (is_array($arg)) $args[] = 'Array'; + elseif (is_object($arg)) $args[] = 'Object('.get_class($arg).')'; + elseif (is_bool($arg)) $args[] = $arg ? 'true' : 'false'; + elseif (is_int($arg) || is_double($arg)) $args[] = $arg; + else { + $arg = (string)$arg; + $str = htmlspecialchars(substr($arg, 0, 16)); + if (strlen($arg) > 16) $str .= '…'; + $args[] = "'" . $str . "'"; + } + } + } + $html .= '(' . implode(', ',$args) . ')' + . '' . (isset($v['file']) ? $v['file'] : 'unknown') + . ':' . (isset($v['line']) ? $v['line'] : 'unknown') + . '
    ' . ($k+1) . '{main} 
    '; + return $html; + } + + public function toText() + { + $causes = array(); + $this->getCauseMessage($causes); + $causeMsg = ''; + foreach ($causes as $i => $cause) { + $causeMsg .= str_repeat(' ', $i) . $cause['class'] . ': ' + . $cause['message'] . ' in ' . $cause['file'] + . ' on line ' . $cause['line'] . "\n"; + } + return $causeMsg . $this->getTraceAsString(); + } +} \ No newline at end of file diff --git a/library/pear/PEAR/FixPHP5PEARWarnings.php b/library/pear/PEAR/FixPHP5PEARWarnings.php new file mode 100644 index 000000000..be5dc3ce7 --- /dev/null +++ b/library/pear/PEAR/FixPHP5PEARWarnings.php @@ -0,0 +1,7 @@ + \ No newline at end of file diff --git a/library/pear/PEAR/Frontend.php b/library/pear/PEAR/Frontend.php new file mode 100644 index 000000000..e8d54bc41 --- /dev/null +++ b/library/pear/PEAR/Frontend.php @@ -0,0 +1,228 @@ + + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Frontend.php 276383 2009-02-24 23:39:37Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ + +/** + * Include error handling + */ +//require_once 'PEAR.php'; + +/** + * Which user interface class is being used. + * @var string class name + */ +$GLOBALS['_PEAR_FRONTEND_CLASS'] = 'PEAR_Frontend_CLI'; + +/** + * Instance of $_PEAR_Command_uiclass. + * @var object + */ +$GLOBALS['_PEAR_FRONTEND_SINGLETON'] = null; + +/** + * Singleton-based frontend for PEAR user input/output + * + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.1 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_Frontend extends PEAR +{ + /** + * Retrieve the frontend object + * @return PEAR_Frontend_CLI|PEAR_Frontend_Web|PEAR_Frontend_Gtk + * @static + */ + function &singleton($type = null) + { + if ($type === null) { + if (!isset($GLOBALS['_PEAR_FRONTEND_SINGLETON'])) { + $a = false; + return $a; + } + return $GLOBALS['_PEAR_FRONTEND_SINGLETON']; + } + + $a = PEAR_Frontend::setFrontendClass($type); + return $a; + } + + /** + * Set the frontend class that will be used by calls to {@link singleton()} + * + * Frontends are expected to conform to the PEAR naming standard of + * _ => DIRECTORY_SEPARATOR (PEAR_Frontend_CLI is in PEAR/Frontend/CLI.php) + * @param string $uiclass full class name + * @return PEAR_Frontend + * @static + */ + function &setFrontendClass($uiclass) + { + if (is_object($GLOBALS['_PEAR_FRONTEND_SINGLETON']) && + is_a($GLOBALS['_PEAR_FRONTEND_SINGLETON'], $uiclass)) { + return $GLOBALS['_PEAR_FRONTEND_SINGLETON']; + } + + if (!class_exists($uiclass)) { + $file = str_replace('_', '/', $uiclass) . '.php'; + if (PEAR_Frontend::isIncludeable($file)) { + include_once $file; + } + } + + if (class_exists($uiclass)) { + $obj = &new $uiclass; + // quick test to see if this class implements a few of the most + // important frontend methods + if (is_a($obj, 'PEAR_Frontend')) { + $GLOBALS['_PEAR_FRONTEND_SINGLETON'] = &$obj; + $GLOBALS['_PEAR_FRONTEND_CLASS'] = $uiclass; + return $obj; + } + + $err = PEAR::raiseError("not a frontend class: $uiclass"); + return $err; + } + + $err = PEAR::raiseError("no such class: $uiclass"); + return $err; + } + + /** + * Set the frontend class that will be used by calls to {@link singleton()} + * + * Frontends are expected to be a descendant of PEAR_Frontend + * @param PEAR_Frontend + * @return PEAR_Frontend + * @static + */ + function &setFrontendObject($uiobject) + { + if (is_object($GLOBALS['_PEAR_FRONTEND_SINGLETON']) && + is_a($GLOBALS['_PEAR_FRONTEND_SINGLETON'], get_class($uiobject))) { + return $GLOBALS['_PEAR_FRONTEND_SINGLETON']; + } + + if (!is_a($uiobject, 'PEAR_Frontend')) { + $err = PEAR::raiseError('not a valid frontend class: (' . + get_class($uiobject) . ')'); + return $err; + } + + $GLOBALS['_PEAR_FRONTEND_SINGLETON'] = &$uiobject; + $GLOBALS['_PEAR_FRONTEND_CLASS'] = get_class($uiobject); + return $uiobject; + } + + /** + * @param string $path relative or absolute include path + * @return boolean + * @static + */ + function isIncludeable($path) + { + if (file_exists($path) && is_readable($path)) { + return true; + } + + $fp = @fopen($path, 'r', true); + if ($fp) { + fclose($fp); + return true; + } + + return false; + } + + /** + * @param PEAR_Config + */ + function setConfig(&$config) + { + } + + /** + * This can be overridden to allow session-based temporary file management + * + * By default, all files are deleted at the end of a session. The web installer + * needs to be able to sustain a list over many sessions in order to support + * user interaction with install scripts + */ + function addTempFile($file) + { + $GLOBALS['_PEAR_Common_tempfiles'][] = $file; + } + + /** + * Log an action + * + * @param string $msg the message to log + * @param boolean $append_crlf + * @return boolean true + * @abstract + */ + function log($msg, $append_crlf = true) + { + } + + /** + * Run a post-installation script + * + * @param array $scripts array of post-install scripts + * @abstract + */ + function runPostinstallScripts(&$scripts) + { + } + + /** + * Display human-friendly output formatted depending on the + * $command parameter. + * + * This should be able to handle basic output data with no command + * @param mixed $data data structure containing the information to display + * @param string $command command from which this method was called + * @abstract + */ + function outputData($data, $command = '_default') + { + } + + /** + * Display a modal form dialog and return the given input + * + * A frontend that requires multiple requests to retrieve and process + * data must take these needs into account, and implement the request + * handling code. + * @param string $command command from which this method was called + * @param array $prompts associative array. keys are the input field names + * and values are the description + * @param array $types array of input field types (text, password, + * etc.) keys have to be the same like in $prompts + * @param array $defaults array of default values. again keys have + * to be the same like in $prompts. Do not depend + * on a default value being set. + * @return array input sent by the user + * @abstract + */ + function userDialog($command, $prompts, $types = array(), $defaults = array()) + { + } +} \ No newline at end of file diff --git a/library/pear/PEAR/Frontend/CLI.php b/library/pear/PEAR/Frontend/CLI.php new file mode 100644 index 000000000..62453cf34 --- /dev/null +++ b/library/pear/PEAR/Frontend/CLI.php @@ -0,0 +1,736 @@ + + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: CLI.php 296938 2010-03-27 16:16:25Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 0.1 + */ +/** + * base class + */ +require_once 'PEAR/Frontend.php'; + +/** + * Command-line Frontend for the PEAR Installer + * @category pear + * @package PEAR + * @author Stig Bakken + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.1 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 0.1 + */ +class PEAR_Frontend_CLI extends PEAR_Frontend +{ + /** + * What type of user interface this frontend is for. + * @var string + * @access public + */ + var $type = 'CLI'; + var $lp = ''; // line prefix + + var $params = array(); + var $term = array( + 'bold' => '', + 'normal' => '', + ); + + function PEAR_Frontend_CLI() + { + parent::PEAR(); + $term = getenv('TERM'); //(cox) $_ENV is empty for me in 4.1.1 + if (function_exists('posix_isatty') && !posix_isatty(1)) { + // output is being redirected to a file or through a pipe + } elseif ($term) { + if (preg_match('/^(xterm|vt220|linux)/', $term)) { + $this->term['bold'] = sprintf("%c%c%c%c", 27, 91, 49, 109); + $this->term['normal'] = sprintf("%c%c%c", 27, 91, 109); + } elseif (preg_match('/^vt100/', $term)) { + $this->term['bold'] = sprintf("%c%c%c%c%c%c", 27, 91, 49, 109, 0, 0); + $this->term['normal'] = sprintf("%c%c%c%c%c", 27, 91, 109, 0, 0); + } + } elseif (OS_WINDOWS) { + // XXX add ANSI codes here + } + } + + /** + * @param object PEAR_Error object + */ + function displayError($e) + { + return $this->_displayLine($e->getMessage()); + } + + /** + * @param object PEAR_Error object + */ + function displayFatalError($eobj) + { + $this->displayError($eobj); + if (class_exists('PEAR_Config')) { + $config = &PEAR_Config::singleton(); + if ($config->get('verbose') > 5) { + if (function_exists('debug_print_backtrace')) { + debug_print_backtrace(); + exit(1); + } + + $raised = false; + foreach (debug_backtrace() as $i => $frame) { + if (!$raised) { + if (isset($frame['class']) + && strtolower($frame['class']) == 'pear' + && strtolower($frame['function']) == 'raiseerror' + ) { + $raised = true; + } else { + continue; + } + } + + $frame['class'] = !isset($frame['class']) ? '' : $frame['class']; + $frame['type'] = !isset($frame['type']) ? '' : $frame['type']; + $frame['function'] = !isset($frame['function']) ? '' : $frame['function']; + $frame['line'] = !isset($frame['line']) ? '' : $frame['line']; + $this->_displayLine("#$i: $frame[class]$frame[type]$frame[function] $frame[line]"); + } + } + } + + exit(1); + } + + /** + * Instruct the runInstallScript method to skip a paramgroup that matches the + * id value passed in. + * + * This method is useful for dynamically configuring which sections of a post-install script + * will be run based on the user's setup, which is very useful for making flexible + * post-install scripts without losing the cross-Frontend ability to retrieve user input + * @param string + */ + function skipParamgroup($id) + { + $this->_skipSections[$id] = true; + } + + function runPostinstallScripts(&$scripts) + { + foreach ($scripts as $i => $script) { + $this->runInstallScript($scripts[$i]->_params, $scripts[$i]->_obj); + } + } + + /** + * @param array $xml contents of postinstallscript tag + * @param object $script post-installation script + * @param string install|upgrade + */ + function runInstallScript($xml, &$script) + { + $this->_skipSections = array(); + if (!is_array($xml) || !isset($xml['paramgroup'])) { + $script->run(array(), '_default'); + return; + } + + $completedPhases = array(); + if (!isset($xml['paramgroup'][0])) { + $xml['paramgroup'] = array($xml['paramgroup']); + } + + foreach ($xml['paramgroup'] as $group) { + if (isset($this->_skipSections[$group['id']])) { + // the post-install script chose to skip this section dynamically + continue; + } + + if (isset($group['name'])) { + $paramname = explode('::', $group['name']); + if ($lastgroup['id'] != $paramname[0]) { + continue; + } + + $group['name'] = $paramname[1]; + if (!isset($answers)) { + return; + } + + if (isset($answers[$group['name']])) { + switch ($group['conditiontype']) { + case '=' : + if ($answers[$group['name']] != $group['value']) { + continue 2; + } + break; + case '!=' : + if ($answers[$group['name']] == $group['value']) { + continue 2; + } + break; + case 'preg_match' : + if (!@preg_match('/' . $group['value'] . '/', + $answers[$group['name']])) { + continue 2; + } + break; + default : + return; + } + } + } + + $lastgroup = $group; + if (isset($group['instructions'])) { + $this->_display($group['instructions']); + } + + if (!isset($group['param'][0])) { + $group['param'] = array($group['param']); + } + + if (isset($group['param'])) { + if (method_exists($script, 'postProcessPrompts')) { + $prompts = $script->postProcessPrompts($group['param'], $group['id']); + if (!is_array($prompts) || count($prompts) != count($group['param'])) { + $this->outputData('postinstall', 'Error: post-install script did not ' . + 'return proper post-processed prompts'); + $prompts = $group['param']; + } else { + foreach ($prompts as $i => $var) { + if (!is_array($var) || !isset($var['prompt']) || + !isset($var['name']) || + ($var['name'] != $group['param'][$i]['name']) || + ($var['type'] != $group['param'][$i]['type']) + ) { + $this->outputData('postinstall', 'Error: post-install script ' . + 'modified the variables or prompts, severe security risk. ' . + 'Will instead use the defaults from the package.xml'); + $prompts = $group['param']; + } + } + } + + $answers = $this->confirmDialog($prompts); + } else { + $answers = $this->confirmDialog($group['param']); + } + } + + if ((isset($answers) && $answers) || !isset($group['param'])) { + if (!isset($answers)) { + $answers = array(); + } + + array_unshift($completedPhases, $group['id']); + if (!$script->run($answers, $group['id'])) { + $script->run($completedPhases, '_undoOnError'); + return; + } + } else { + $script->run($completedPhases, '_undoOnError'); + return; + } + } + } + + /** + * Ask for user input, confirm the answers and continue until the user is satisfied + * @param array an array of arrays, format array('name' => 'paramname', 'prompt' => + * 'text to display', 'type' => 'string'[, default => 'default value']) + * @return array + */ + function confirmDialog($params) + { + $answers = $prompts = $types = array(); + foreach ($params as $param) { + $prompts[$param['name']] = $param['prompt']; + $types[$param['name']] = $param['type']; + $answers[$param['name']] = isset($param['default']) ? $param['default'] : ''; + } + + $tried = false; + do { + if ($tried) { + $i = 1; + foreach ($answers as $var => $value) { + if (!strlen($value)) { + echo $this->bold("* Enter an answer for #" . $i . ": ({$prompts[$var]})\n"); + } + $i++; + } + } + + $answers = $this->userDialog('', $prompts, $types, $answers); + $tried = true; + } while (is_array($answers) && count(array_filter($answers)) != count($prompts)); + + return $answers; + } + + function userDialog($command, $prompts, $types = array(), $defaults = array(), $screensize = 20) + { + if (!is_array($prompts)) { + return array(); + } + + $testprompts = array_keys($prompts); + $result = $defaults; + + reset($prompts); + if (count($prompts) === 1) { + foreach ($prompts as $key => $prompt) { + $type = $types[$key]; + $default = @$defaults[$key]; + print "$prompt "; + if ($default) { + print "[$default] "; + } + print ": "; + + $line = fgets(STDIN, 2048); + $result[$key] = ($default && trim($line) == '') ? $default : trim($line); + } + + return $result; + } + + $first_run = true; + while (true) { + $descLength = max(array_map('strlen', $prompts)); + $descFormat = "%-{$descLength}s"; + $last = count($prompts); + + $i = 0; + foreach ($prompts as $n => $var) { + $res = isset($result[$n]) ? $result[$n] : null; + printf("%2d. $descFormat : %s\n", ++$i, $prompts[$n], $res); + } + print "\n1-$last, 'all', 'abort', or Enter to continue: "; + + $tmp = trim(fgets(STDIN, 1024)); + if (empty($tmp)) { + break; + } + + if ($tmp == 'abort') { + return false; + } + + if (isset($testprompts[(int)$tmp - 1])) { + $var = $testprompts[(int)$tmp - 1]; + $desc = $prompts[$var]; + $current = @$result[$var]; + print "$desc [$current] : "; + $tmp = trim(fgets(STDIN, 1024)); + if ($tmp !== '') { + $result[$var] = $tmp; + } + } elseif ($tmp == 'all') { + foreach ($prompts as $var => $desc) { + $current = $result[$var]; + print "$desc [$current] : "; + $tmp = trim(fgets(STDIN, 1024)); + if (trim($tmp) !== '') { + $result[$var] = trim($tmp); + } + } + } + + $first_run = false; + } + + return $result; + } + + function userConfirm($prompt, $default = 'yes') + { + trigger_error("PEAR_Frontend_CLI::userConfirm not yet converted", E_USER_ERROR); + static $positives = array('y', 'yes', 'on', '1'); + static $negatives = array('n', 'no', 'off', '0'); + print "$this->lp$prompt [$default] : "; + $fp = fopen("php://stdin", "r"); + $line = fgets($fp, 2048); + fclose($fp); + $answer = strtolower(trim($line)); + if (empty($answer)) { + $answer = $default; + } + if (in_array($answer, $positives)) { + return true; + } + if (in_array($answer, $negatives)) { + return false; + } + if (in_array($default, $positives)) { + return true; + } + return false; + } + + function outputData($data, $command = '_default') + { + switch ($command) { + case 'channel-info': + foreach ($data as $type => $section) { + if ($type == 'main') { + $section['data'] = array_values($section['data']); + } + + $this->outputData($section); + } + break; + case 'install': + case 'upgrade': + case 'upgrade-all': + if (isset($data['release_warnings'])) { + $this->_displayLine(''); + $this->_startTable(array( + 'border' => false, + 'caption' => 'Release Warnings' + )); + $this->_tableRow(array($data['release_warnings']), null, array(1 => array('wrap' => 55))); + $this->_endTable(); + $this->_displayLine(''); + } + + $this->_displayLine($data['data']); + break; + case 'search': + $this->_startTable($data); + if (isset($data['headline']) && is_array($data['headline'])) { + $this->_tableRow($data['headline'], array('bold' => true), array(1 => array('wrap' => 55))); + } + + foreach($data['data'] as $category) { + foreach($category as $pkg) { + $this->_tableRow($pkg, null, array(1 => array('wrap' => 55))); + } + } + + $this->_endTable(); + break; + case 'list-all': + if (!isset($data['data'])) { + $this->_displayLine('No packages in channel'); + break; + } + + $this->_startTable($data); + if (isset($data['headline']) && is_array($data['headline'])) { + $this->_tableRow($data['headline'], array('bold' => true), array(1 => array('wrap' => 55))); + } + + foreach($data['data'] as $category) { + foreach($category as $pkg) { + unset($pkg[4], $pkg[5]); + $this->_tableRow($pkg, null, array(1 => array('wrap' => 55))); + } + } + + $this->_endTable(); + break; + case 'config-show': + $data['border'] = false; + $opts = array( + 0 => array('wrap' => 30), + 1 => array('wrap' => 20), + 2 => array('wrap' => 35) + ); + + $this->_startTable($data); + if (isset($data['headline']) && is_array($data['headline'])) { + $this->_tableRow($data['headline'], array('bold' => true), $opts); + } + + foreach ($data['data'] as $group) { + foreach ($group as $value) { + if ($value[2] == '') { + $value[2] = ""; + } + + $this->_tableRow($value, null, $opts); + } + } + + $this->_endTable(); + break; + case 'remote-info': + $d = $data; + $data = array( + 'caption' => 'Package details:', + 'border' => false, + 'data' => array( + array("Latest", $data['stable']), + array("Installed", $data['installed']), + array("Package", $data['name']), + array("License", $data['license']), + array("Category", $data['category']), + array("Summary", $data['summary']), + array("Description", $data['description']), + ), + ); + + if (isset($d['deprecated']) && $d['deprecated']) { + $conf = &PEAR_Config::singleton(); + $reg = $conf->getRegistry(); + $name = $reg->parsedPackageNameToString($d['deprecated'], true); + $data['data'][] = array('Deprecated! use', $name); + } + default: { + if (is_array($data)) { + $this->_startTable($data); + $count = count($data['data'][0]); + if ($count == 2) { + $opts = array(0 => array('wrap' => 25), + 1 => array('wrap' => 48) + ); + } elseif ($count == 3) { + $opts = array(0 => array('wrap' => 30), + 1 => array('wrap' => 20), + 2 => array('wrap' => 35) + ); + } else { + $opts = null; + } + if (isset($data['headline']) && is_array($data['headline'])) { + $this->_tableRow($data['headline'], + array('bold' => true), + $opts); + } + + if (is_array($data['data'])) { + foreach($data['data'] as $row) { + $this->_tableRow($row, null, $opts); + } + } else { + $this->_tableRow(array($data['data']), null, $opts); + } + $this->_endTable(); + } else { + $this->_displayLine($data); + } + } + } + } + + function log($text, $append_crlf = true) + { + if ($append_crlf) { + return $this->_displayLine($text); + } + + return $this->_display($text); + } + + function bold($text) + { + if (empty($this->term['bold'])) { + return strtoupper($text); + } + + return $this->term['bold'] . $text . $this->term['normal']; + } + + function _displayHeading($title) + { + print $this->lp.$this->bold($title)."\n"; + print $this->lp.str_repeat("=", strlen($title))."\n"; + } + + function _startTable($params = array()) + { + $params['table_data'] = array(); + $params['widest'] = array(); // indexed by column + $params['highest'] = array(); // indexed by row + $params['ncols'] = 0; + $this->params = $params; + } + + function _tableRow($columns, $rowparams = array(), $colparams = array()) + { + $highest = 1; + for ($i = 0; $i < count($columns); $i++) { + $col = &$columns[$i]; + if (isset($colparams[$i]) && !empty($colparams[$i]['wrap'])) { + $col = wordwrap($col, $colparams[$i]['wrap']); + } + + if (strpos($col, "\n") !== false) { + $multiline = explode("\n", $col); + $w = 0; + foreach ($multiline as $n => $line) { + $len = strlen($line); + if ($len > $w) { + $w = $len; + } + } + $lines = count($multiline); + } else { + $w = strlen($col); + } + + if (isset($this->params['widest'][$i])) { + if ($w > $this->params['widest'][$i]) { + $this->params['widest'][$i] = $w; + } + } else { + $this->params['widest'][$i] = $w; + } + + $tmp = count_chars($columns[$i], 1); + // handle unix, mac and windows formats + $lines = (isset($tmp[10]) ? $tmp[10] : (isset($tmp[13]) ? $tmp[13] : 0)) + 1; + if ($lines > $highest) { + $highest = $lines; + } + } + + if (count($columns) > $this->params['ncols']) { + $this->params['ncols'] = count($columns); + } + + $new_row = array( + 'data' => $columns, + 'height' => $highest, + 'rowparams' => $rowparams, + 'colparams' => $colparams, + ); + $this->params['table_data'][] = $new_row; + } + + function _endTable() + { + extract($this->params); + if (!empty($caption)) { + $this->_displayHeading($caption); + } + + if (count($table_data) === 0) { + return; + } + + if (!isset($width)) { + $width = $widest; + } else { + for ($i = 0; $i < $ncols; $i++) { + if (!isset($width[$i])) { + $width[$i] = $widest[$i]; + } + } + } + + $border = false; + if (empty($border)) { + $cellstart = ''; + $cellend = ' '; + $rowend = ''; + $padrowend = false; + $borderline = ''; + } else { + $cellstart = '| '; + $cellend = ' '; + $rowend = '|'; + $padrowend = true; + $borderline = '+'; + foreach ($width as $w) { + $borderline .= str_repeat('-', $w + strlen($cellstart) + strlen($cellend) - 1); + $borderline .= '+'; + } + } + + if ($borderline) { + $this->_displayLine($borderline); + } + + for ($i = 0; $i < count($table_data); $i++) { + extract($table_data[$i]); + if (!is_array($rowparams)) { + $rowparams = array(); + } + + if (!is_array($colparams)) { + $colparams = array(); + } + + $rowlines = array(); + if ($height > 1) { + for ($c = 0; $c < count($data); $c++) { + $rowlines[$c] = preg_split('/(\r?\n|\r)/', $data[$c]); + if (count($rowlines[$c]) < $height) { + $rowlines[$c] = array_pad($rowlines[$c], $height, ''); + } + } + } else { + for ($c = 0; $c < count($data); $c++) { + $rowlines[$c] = array($data[$c]); + } + } + + for ($r = 0; $r < $height; $r++) { + $rowtext = ''; + for ($c = 0; $c < count($data); $c++) { + if (isset($colparams[$c])) { + $attribs = array_merge($rowparams, $colparams); + } else { + $attribs = $rowparams; + } + + $w = isset($width[$c]) ? $width[$c] : 0; + //$cell = $data[$c]; + $cell = $rowlines[$c][$r]; + $l = strlen($cell); + if ($l > $w) { + $cell = substr($cell, 0, $w); + } + + if (isset($attribs['bold'])) { + $cell = $this->bold($cell); + } + + if ($l < $w) { + // not using str_pad here because we may + // add bold escape characters to $cell + $cell .= str_repeat(' ', $w - $l); + } + + $rowtext .= $cellstart . $cell . $cellend; + } + + if (!$border) { + $rowtext = rtrim($rowtext); + } + + $rowtext .= $rowend; + $this->_displayLine($rowtext); + } + } + + if ($borderline) { + $this->_displayLine($borderline); + } + } + + function _displayLine($text) + { + print "$this->lp$text\n"; + } + + function _display($text) + { + print $text; + } +} \ No newline at end of file diff --git a/library/pear/PEAR/Installer.php b/library/pear/PEAR/Installer.php new file mode 100644 index 000000000..71ada0e6c --- /dev/null +++ b/library/pear/PEAR/Installer.php @@ -0,0 +1,1823 @@ + + * @author Tomas V.V. Cox + * @author Martin Jansen + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Installer.php 287446 2009-08-18 11:45:05Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 0.1 + */ + +/** + * Used for installation groups in package.xml 2.0 and platform exceptions + */ +require_once 'OS/Guess.php'; +require_once 'PEAR/Downloader.php'; + +define('PEAR_INSTALLER_NOBINARY', -240); +/** + * Administration class used to install PEAR packages and maintain the + * installed package database. + * + * @category pear + * @package PEAR + * @author Stig Bakken + * @author Tomas V.V. Cox + * @author Martin Jansen + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.1 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 0.1 + */ +class PEAR_Installer extends PEAR_Downloader +{ + // {{{ properties + + /** name of the package directory, for example Foo-1.0 + * @var string + */ + var $pkgdir; + + /** directory where PHP code files go + * @var string + */ + var $phpdir; + + /** directory where PHP extension files go + * @var string + */ + var $extdir; + + /** directory where documentation goes + * @var string + */ + var $docdir; + + /** installation root directory (ala PHP's INSTALL_ROOT or + * automake's DESTDIR + * @var string + */ + var $installroot = ''; + + /** debug level + * @var int + */ + var $debug = 1; + + /** temporary directory + * @var string + */ + var $tmpdir; + + /** + * PEAR_Registry object used by the installer + * @var PEAR_Registry + */ + var $registry; + + /** + * array of PEAR_Downloader_Packages + * @var array + */ + var $_downloadedPackages; + + /** List of file transactions queued for an install/upgrade/uninstall. + * + * Format: + * array( + * 0 => array("rename => array("from-file", "to-file")), + * 1 => array("delete" => array("file-to-delete")), + * ... + * ) + * + * @var array + */ + var $file_operations = array(); + + // }}} + + // {{{ constructor + + /** + * PEAR_Installer constructor. + * + * @param object $ui user interface object (instance of PEAR_Frontend_*) + * + * @access public + */ + function PEAR_Installer(&$ui) + { + parent::PEAR_Common(); + $this->setFrontendObject($ui); + $this->debug = $this->config->get('verbose'); + } + + function setOptions($options) + { + $this->_options = $options; + } + + function setConfig(&$config) + { + $this->config = &$config; + $this->_registry = &$config->getRegistry(); + } + + // }}} + + function _removeBackups($files) + { + foreach ($files as $path) { + $this->addFileOperation('removebackup', array($path)); + } + } + + // {{{ _deletePackageFiles() + + /** + * Delete a package's installed files, does not remove empty directories. + * + * @param string package name + * @param string channel name + * @param bool if true, then files are backed up first + * @return bool TRUE on success, or a PEAR error on failure + * @access protected + */ + function _deletePackageFiles($package, $channel = false, $backup = false) + { + if (!$channel) { + $channel = 'pear.php.net'; + } + + if (!strlen($package)) { + return $this->raiseError("No package to uninstall given"); + } + + if (strtolower($package) == 'pear' && $channel == 'pear.php.net') { + // to avoid race conditions, include all possible needed files + require_once 'PEAR/Task/Common.php'; + require_once 'PEAR/Task/Replace.php'; + require_once 'PEAR/Task/Unixeol.php'; + require_once 'PEAR/Task/Windowseol.php'; + require_once 'PEAR/PackageFile/v1.php'; + require_once 'PEAR/PackageFile/v2.php'; + require_once 'PEAR/PackageFile/Generator/v1.php'; + require_once 'PEAR/PackageFile/Generator/v2.php'; + } + + $filelist = $this->_registry->packageInfo($package, 'filelist', $channel); + if ($filelist == null) { + return $this->raiseError("$channel/$package not installed"); + } + + $ret = array(); + foreach ($filelist as $file => $props) { + if (empty($props['installed_as'])) { + continue; + } + + $path = $props['installed_as']; + if ($backup) { + $this->addFileOperation('backup', array($path)); + $ret[] = $path; + } + + $this->addFileOperation('delete', array($path)); + } + + if ($backup) { + return $ret; + } + + return true; + } + + // }}} + // {{{ _installFile() + + /** + * @param string filename + * @param array attributes from tag in package.xml + * @param string path to install the file in + * @param array options from command-line + * @access private + */ + function _installFile($file, $atts, $tmp_path, $options) + { + // {{{ return if this file is meant for another platform + static $os; + if (!isset($this->_registry)) { + $this->_registry = &$this->config->getRegistry(); + } + + if (isset($atts['platform'])) { + if (empty($os)) { + $os = new OS_Guess(); + } + + if (strlen($atts['platform']) && $atts['platform']{0} == '!') { + $negate = true; + $platform = substr($atts['platform'], 1); + } else { + $negate = false; + $platform = $atts['platform']; + } + + if ((bool) $os->matchSignature($platform) === $negate) { + $this->log(3, "skipped $file (meant for $atts[platform], we are ".$os->getSignature().")"); + return PEAR_INSTALLER_SKIPPED; + } + } + // }}} + + $channel = $this->pkginfo->getChannel(); + // {{{ assemble the destination paths + switch ($atts['role']) { + case 'src': + case 'extsrc': + $this->source_files++; + return; + case 'doc': + case 'data': + case 'test': + $dest_dir = $this->config->get($atts['role'] . '_dir', null, $channel) . + DIRECTORY_SEPARATOR . $this->pkginfo->getPackage(); + unset($atts['baseinstalldir']); + break; + case 'ext': + case 'php': + $dest_dir = $this->config->get($atts['role'] . '_dir', null, $channel); + break; + case 'script': + $dest_dir = $this->config->get('bin_dir', null, $channel); + break; + default: + return $this->raiseError("Invalid role `$atts[role]' for file $file"); + } + + $save_destdir = $dest_dir; + if (!empty($atts['baseinstalldir'])) { + $dest_dir .= DIRECTORY_SEPARATOR . $atts['baseinstalldir']; + } + + if (dirname($file) != '.' && empty($atts['install-as'])) { + $dest_dir .= DIRECTORY_SEPARATOR . dirname($file); + } + + if (empty($atts['install-as'])) { + $dest_file = $dest_dir . DIRECTORY_SEPARATOR . basename($file); + } else { + $dest_file = $dest_dir . DIRECTORY_SEPARATOR . $atts['install-as']; + } + $orig_file = $tmp_path . DIRECTORY_SEPARATOR . $file; + + // Clean up the DIRECTORY_SEPARATOR mess + $ds2 = DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR; + list($dest_file, $orig_file) = preg_replace(array('!\\\\+!', '!/!', "!$ds2+!"), + array(DIRECTORY_SEPARATOR, + DIRECTORY_SEPARATOR, + DIRECTORY_SEPARATOR), + array($dest_file, $orig_file)); + $final_dest_file = $installed_as = $dest_file; + if (isset($this->_options['packagingroot'])) { + $installedas_dest_dir = dirname($final_dest_file); + $installedas_dest_file = $dest_dir . DIRECTORY_SEPARATOR . '.tmp' . basename($final_dest_file); + $final_dest_file = $this->_prependPath($final_dest_file, $this->_options['packagingroot']); + } else { + $installedas_dest_dir = dirname($final_dest_file); + $installedas_dest_file = $installedas_dest_dir . DIRECTORY_SEPARATOR . '.tmp' . basename($final_dest_file); + } + + $dest_dir = dirname($final_dest_file); + $dest_file = $dest_dir . DIRECTORY_SEPARATOR . '.tmp' . basename($final_dest_file); + if (preg_match('~/\.\.(/|\\z)|^\.\./~', str_replace('\\', '/', $dest_file))) { + return $this->raiseError("SECURITY ERROR: file $file (installed to $dest_file) contains parent directory reference ..", PEAR_INSTALLER_FAILED); + } + // }}} + + if (empty($this->_options['register-only']) && + (!file_exists($dest_dir) || !is_dir($dest_dir))) { + if (!$this->mkDirHier($dest_dir)) { + return $this->raiseError("failed to mkdir $dest_dir", + PEAR_INSTALLER_FAILED); + } + $this->log(3, "+ mkdir $dest_dir"); + } + + // pretty much nothing happens if we are only registering the install + if (empty($this->_options['register-only'])) { + if (empty($atts['replacements'])) { + if (!file_exists($orig_file)) { + return $this->raiseError("file $orig_file does not exist", + PEAR_INSTALLER_FAILED); + } + + if (!@copy($orig_file, $dest_file)) { + return $this->raiseError("failed to write $dest_file: $php_errormsg", + PEAR_INSTALLER_FAILED); + } + + $this->log(3, "+ cp $orig_file $dest_file"); + if (isset($atts['md5sum'])) { + $md5sum = md5_file($dest_file); + } + } else { + // {{{ file with replacements + if (!file_exists($orig_file)) { + return $this->raiseError("file does not exist", + PEAR_INSTALLER_FAILED); + } + + $contents = file_get_contents($orig_file); + if ($contents === false) { + $contents = ''; + } + + if (isset($atts['md5sum'])) { + $md5sum = md5($contents); + } + + $subst_from = $subst_to = array(); + foreach ($atts['replacements'] as $a) { + $to = ''; + if ($a['type'] == 'php-const') { + if (preg_match('/^[a-z0-9_]+\\z/i', $a['to'])) { + eval("\$to = $a[to];"); + } else { + if (!isset($options['soft'])) { + $this->log(0, "invalid php-const replacement: $a[to]"); + } + continue; + } + } elseif ($a['type'] == 'pear-config') { + if ($a['to'] == 'master_server') { + $chan = $this->_registry->getChannel($channel); + if (!PEAR::isError($chan)) { + $to = $chan->getServer(); + } else { + $to = $this->config->get($a['to'], null, $channel); + } + } else { + $to = $this->config->get($a['to'], null, $channel); + } + if (is_null($to)) { + if (!isset($options['soft'])) { + $this->log(0, "invalid pear-config replacement: $a[to]"); + } + continue; + } + } elseif ($a['type'] == 'package-info') { + if ($t = $this->pkginfo->packageInfo($a['to'])) { + $to = $t; + } else { + if (!isset($options['soft'])) { + $this->log(0, "invalid package-info replacement: $a[to]"); + } + continue; + } + } + if (!is_null($to)) { + $subst_from[] = $a['from']; + $subst_to[] = $to; + } + } + + $this->log(3, "doing ".sizeof($subst_from)." substitution(s) for $final_dest_file"); + if (sizeof($subst_from)) { + $contents = str_replace($subst_from, $subst_to, $contents); + } + + $wp = @fopen($dest_file, "wb"); + if (!is_resource($wp)) { + return $this->raiseError("failed to create $dest_file: $php_errormsg", + PEAR_INSTALLER_FAILED); + } + + if (@fwrite($wp, $contents) === false) { + return $this->raiseError("failed writing to $dest_file: $php_errormsg", + PEAR_INSTALLER_FAILED); + } + + fclose($wp); + // }}} + } + + // {{{ check the md5 + if (isset($md5sum)) { + if (strtolower($md5sum) === strtolower($atts['md5sum'])) { + $this->log(2, "md5sum ok: $final_dest_file"); + } else { + if (empty($options['force'])) { + // delete the file + if (file_exists($dest_file)) { + unlink($dest_file); + } + + if (!isset($options['ignore-errors'])) { + return $this->raiseError("bad md5sum for file $final_dest_file", + PEAR_INSTALLER_FAILED); + } + + if (!isset($options['soft'])) { + $this->log(0, "warning : bad md5sum for file $final_dest_file"); + } + } else { + if (!isset($options['soft'])) { + $this->log(0, "warning : bad md5sum for file $final_dest_file"); + } + } + } + } + // }}} + // {{{ set file permissions + if (!OS_WINDOWS) { + if ($atts['role'] == 'script') { + $mode = 0777 & ~(int)octdec($this->config->get('umask')); + $this->log(3, "+ chmod +x $dest_file"); + } else { + $mode = 0666 & ~(int)octdec($this->config->get('umask')); + } + + if ($atts['role'] != 'src') { + $this->addFileOperation("chmod", array($mode, $dest_file)); + if (!@chmod($dest_file, $mode)) { + if (!isset($options['soft'])) { + $this->log(0, "failed to change mode of $dest_file: $php_errormsg"); + } + } + } + } + // }}} + + if ($atts['role'] == 'src') { + rename($dest_file, $final_dest_file); + $this->log(2, "renamed source file $dest_file to $final_dest_file"); + } else { + $this->addFileOperation("rename", array($dest_file, $final_dest_file, + $atts['role'] == 'ext')); + } + } + + // Store the full path where the file was installed for easy unistall + if ($atts['role'] != 'script') { + $loc = $this->config->get($atts['role'] . '_dir'); + } else { + $loc = $this->config->get('bin_dir'); + } + + if ($atts['role'] != 'src') { + $this->addFileOperation("installed_as", array($file, $installed_as, + $loc, + dirname(substr($installedas_dest_file, strlen($loc))))); + } + + //$this->log(2, "installed: $dest_file"); + return PEAR_INSTALLER_OK; + } + + // }}} + // {{{ _installFile2() + + /** + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2 + * @param string filename + * @param array attributes from tag in package.xml + * @param string path to install the file in + * @param array options from command-line + * @access private + */ + function _installFile2(&$pkg, $file, &$real_atts, $tmp_path, $options) + { + $atts = $real_atts; + if (!isset($this->_registry)) { + $this->_registry = &$this->config->getRegistry(); + } + + $channel = $pkg->getChannel(); + // {{{ assemble the destination paths + if (!in_array($atts['attribs']['role'], + PEAR_Installer_Role::getValidRoles($pkg->getPackageType()))) { + return $this->raiseError('Invalid role `' . $atts['attribs']['role'] . + "' for file $file"); + } + + $role = &PEAR_Installer_Role::factory($pkg, $atts['attribs']['role'], $this->config); + $err = $role->setup($this, $pkg, $atts['attribs'], $file); + if (PEAR::isError($err)) { + return $err; + } + + if (!$role->isInstallable()) { + return; + } + + $info = $role->processInstallation($pkg, $atts['attribs'], $file, $tmp_path); + if (PEAR::isError($info)) { + return $info; + } + + list($save_destdir, $dest_dir, $dest_file, $orig_file) = $info; + if (preg_match('~/\.\.(/|\\z)|^\.\./~', str_replace('\\', '/', $dest_file))) { + return $this->raiseError("SECURITY ERROR: file $file (installed to $dest_file) contains parent directory reference ..", PEAR_INSTALLER_FAILED); + } + + $final_dest_file = $installed_as = $dest_file; + if (isset($this->_options['packagingroot'])) { + $final_dest_file = $this->_prependPath($final_dest_file, + $this->_options['packagingroot']); + } + + $dest_dir = dirname($final_dest_file); + $dest_file = $dest_dir . DIRECTORY_SEPARATOR . '.tmp' . basename($final_dest_file); + // }}} + + if (empty($this->_options['register-only'])) { + if (!file_exists($dest_dir) || !is_dir($dest_dir)) { + if (!$this->mkDirHier($dest_dir)) { + return $this->raiseError("failed to mkdir $dest_dir", + PEAR_INSTALLER_FAILED); + } + $this->log(3, "+ mkdir $dest_dir"); + } + } + + $attribs = $atts['attribs']; + unset($atts['attribs']); + // pretty much nothing happens if we are only registering the install + if (empty($this->_options['register-only'])) { + if (!count($atts)) { // no tasks + if (!file_exists($orig_file)) { + return $this->raiseError("file $orig_file does not exist", + PEAR_INSTALLER_FAILED); + } + + if (!@copy($orig_file, $dest_file)) { + return $this->raiseError("failed to write $dest_file: $php_errormsg", + PEAR_INSTALLER_FAILED); + } + + $this->log(3, "+ cp $orig_file $dest_file"); + if (isset($attribs['md5sum'])) { + $md5sum = md5_file($dest_file); + } + } else { // file with tasks + if (!file_exists($orig_file)) { + return $this->raiseError("file $orig_file does not exist", + PEAR_INSTALLER_FAILED); + } + + $contents = file_get_contents($orig_file); + if ($contents === false) { + $contents = ''; + } + + if (isset($attribs['md5sum'])) { + $md5sum = md5($contents); + } + + foreach ($atts as $tag => $raw) { + $tag = str_replace(array($pkg->getTasksNs() . ':', '-'), array('', '_'), $tag); + $task = "PEAR_Task_$tag"; + $task = &new $task($this->config, $this, PEAR_TASK_INSTALL); + if (!$task->isScript()) { // scripts are only handled after installation + $task->init($raw, $attribs, $pkg->getLastInstalledVersion()); + $res = $task->startSession($pkg, $contents, $final_dest_file); + if ($res === false) { + continue; // skip this file + } + + if (PEAR::isError($res)) { + return $res; + } + + $contents = $res; // save changes + } + + $wp = @fopen($dest_file, "wb"); + if (!is_resource($wp)) { + return $this->raiseError("failed to create $dest_file: $php_errormsg", + PEAR_INSTALLER_FAILED); + } + + if (fwrite($wp, $contents) === false) { + return $this->raiseError("failed writing to $dest_file: $php_errormsg", + PEAR_INSTALLER_FAILED); + } + + fclose($wp); + } + } + + // {{{ check the md5 + if (isset($md5sum)) { + // Make sure the original md5 sum matches with expected + if (strtolower($md5sum) === strtolower($attribs['md5sum'])) { + $this->log(2, "md5sum ok: $final_dest_file"); + + if (isset($contents)) { + // set md5 sum based on $content in case any tasks were run. + $real_atts['attribs']['md5sum'] = md5($contents); + } + } else { + if (empty($options['force'])) { + // delete the file + if (file_exists($dest_file)) { + unlink($dest_file); + } + + if (!isset($options['ignore-errors'])) { + return $this->raiseError("bad md5sum for file $final_dest_file", + PEAR_INSTALLER_FAILED); + } + + if (!isset($options['soft'])) { + $this->log(0, "warning : bad md5sum for file $final_dest_file"); + } + } else { + if (!isset($options['soft'])) { + $this->log(0, "warning : bad md5sum for file $final_dest_file"); + } + } + } + } else { + $real_atts['attribs']['md5sum'] = md5_file($dest_file); + } + + // }}} + // {{{ set file permissions + if (!OS_WINDOWS) { + if ($role->isExecutable()) { + $mode = 0777 & ~(int)octdec($this->config->get('umask')); + $this->log(3, "+ chmod +x $dest_file"); + } else { + $mode = 0666 & ~(int)octdec($this->config->get('umask')); + } + + if ($attribs['role'] != 'src') { + $this->addFileOperation("chmod", array($mode, $dest_file)); + if (!@chmod($dest_file, $mode)) { + if (!isset($options['soft'])) { + $this->log(0, "failed to change mode of $dest_file: $php_errormsg"); + } + } + } + } + // }}} + + if ($attribs['role'] == 'src') { + rename($dest_file, $final_dest_file); + $this->log(2, "renamed source file $dest_file to $final_dest_file"); + } else { + $this->addFileOperation("rename", array($dest_file, $final_dest_file, $role->isExtension())); + } + } + + // Store the full path where the file was installed for easy uninstall + if ($attribs['role'] != 'src') { + $loc = $this->config->get($role->getLocationConfig(), null, $channel); + $this->addFileOperation('installed_as', array($file, $installed_as, + $loc, + dirname(substr($installed_as, strlen($loc))))); + } + + //$this->log(2, "installed: $dest_file"); + return PEAR_INSTALLER_OK; + } + + // }}} + // {{{ addFileOperation() + + /** + * Add a file operation to the current file transaction. + * + * @see startFileTransaction() + * @param string $type This can be one of: + * - rename: rename a file ($data has 3 values) + * - backup: backup an existing file ($data has 1 value) + * - removebackup: clean up backups created during install ($data has 1 value) + * - chmod: change permissions on a file ($data has 2 values) + * - delete: delete a file ($data has 1 value) + * - rmdir: delete a directory if empty ($data has 1 value) + * - installed_as: mark a file as installed ($data has 4 values). + * @param array $data For all file operations, this array must contain the + * full path to the file or directory that is being operated on. For + * the rename command, the first parameter must be the file to rename, + * the second its new name, the third whether this is a PHP extension. + * + * The installed_as operation contains 4 elements in this order: + * 1. Filename as listed in the filelist element from package.xml + * 2. Full path to the installed file + * 3. Full path from the php_dir configuration variable used in this + * installation + * 4. Relative path from the php_dir that this file is installed in + */ + function addFileOperation($type, $data) + { + if (!is_array($data)) { + return $this->raiseError('Internal Error: $data in addFileOperation' + . ' must be an array, was ' . gettype($data)); + } + + if ($type == 'chmod') { + $octmode = decoct($data[0]); + $this->log(3, "adding to transaction: $type $octmode $data[1]"); + } else { + $this->log(3, "adding to transaction: $type " . implode(" ", $data)); + } + $this->file_operations[] = array($type, $data); + } + + // }}} + // {{{ startFileTransaction() + + function startFileTransaction($rollback_in_case = false) + { + if (count($this->file_operations) && $rollback_in_case) { + $this->rollbackFileTransaction(); + } + $this->file_operations = array(); + } + + // }}} + // {{{ commitFileTransaction() + + function commitFileTransaction() + { + $n = count($this->file_operations); + $this->log(2, "about to commit $n file operations"); + // {{{ first, check permissions and such manually + $errors = array(); + foreach ($this->file_operations as $tr) { + list($type, $data) = $tr; + switch ($type) { + case 'rename': + if (!file_exists($data[0])) { + $errors[] = "cannot rename file $data[0], doesn't exist"; + } + + // check that dest dir. is writable + if (!is_writable(dirname($data[1]))) { + $errors[] = "permission denied ($type): $data[1]"; + } + break; + case 'chmod': + // check that file is writable + if (!is_writable($data[1])) { + $errors[] = "permission denied ($type): $data[1] " . decoct($data[0]); + } + break; + case 'delete': + if (!file_exists($data[0])) { + $this->log(2, "warning: file $data[0] doesn't exist, can't be deleted"); + } + // check that directory is writable + if (file_exists($data[0])) { + if (!is_writable(dirname($data[0]))) { + $errors[] = "permission denied ($type): $data[0]"; + } else { + // make sure the file to be deleted can be opened for writing + $fp = false; + if (!is_dir($data[0]) && + (!is_writable($data[0]) || !($fp = @fopen($data[0], 'a')))) { + $errors[] = "permission denied ($type): $data[0]"; + } elseif ($fp) { + fclose($fp); + } + } + } + break; + } + + } + // }}} + $m = count($errors); + if ($m > 0) { + foreach ($errors as $error) { + if (!isset($this->_options['soft'])) { + $this->log(1, $error); + } + } + + if (!isset($this->_options['ignore-errors'])) { + return false; + } + } + + $this->_dirtree = array(); + // {{{ really commit the transaction + foreach ($this->file_operations as $i => $tr) { + if (!$tr) { + // support removal of non-existing backups + continue; + } + + list($type, $data) = $tr; + switch ($type) { + case 'backup': + if (!file_exists($data[0])) { + $this->file_operations[$i] = false; + break; + } + + if (!@copy($data[0], $data[0] . '.bak')) { + $this->log(1, 'Could not copy ' . $data[0] . ' to ' . $data[0] . + '.bak ' . $php_errormsg); + return false; + } + $this->log(3, "+ backup $data[0] to $data[0].bak"); + break; + case 'removebackup': + if (file_exists($data[0] . '.bak') && is_writable($data[0] . '.bak')) { + unlink($data[0] . '.bak'); + $this->log(3, "+ rm backup of $data[0] ($data[0].bak)"); + } + break; + case 'rename': + $test = file_exists($data[1]) ? @unlink($data[1]) : null; + if (!$test && file_exists($data[1])) { + if ($data[2]) { + $extra = ', this extension must be installed manually. Rename to "' . + basename($data[1]) . '"'; + } else { + $extra = ''; + } + + if (!isset($this->_options['soft'])) { + $this->log(1, 'Could not delete ' . $data[1] . ', cannot rename ' . + $data[0] . $extra); + } + + if (!isset($this->_options['ignore-errors'])) { + return false; + } + } + + // permissions issues with rename - copy() is far superior + $perms = @fileperms($data[0]); + if (!@copy($data[0], $data[1])) { + $this->log(1, 'Could not rename ' . $data[0] . ' to ' . $data[1] . + ' ' . $php_errormsg); + return false; + } + + // copy over permissions, otherwise they are lost + @chmod($data[1], $perms); + @unlink($data[0]); + $this->log(3, "+ mv $data[0] $data[1]"); + break; + case 'chmod': + if (!@chmod($data[1], $data[0])) { + $this->log(1, 'Could not chmod ' . $data[1] . ' to ' . + decoct($data[0]) . ' ' . $php_errormsg); + return false; + } + + $octmode = decoct($data[0]); + $this->log(3, "+ chmod $octmode $data[1]"); + break; + case 'delete': + if (file_exists($data[0])) { + if (!@unlink($data[0])) { + $this->log(1, 'Could not delete ' . $data[0] . ' ' . + $php_errormsg); + return false; + } + $this->log(3, "+ rm $data[0]"); + } + break; + case 'rmdir': + if (file_exists($data[0])) { + do { + $testme = opendir($data[0]); + while (false !== ($entry = readdir($testme))) { + if ($entry == '.' || $entry == '..') { + continue; + } + closedir($testme); + break 2; // this directory is not empty and can't be + // deleted + } + + closedir($testme); + if (!@rmdir($data[0])) { + $this->log(1, 'Could not rmdir ' . $data[0] . ' ' . + $php_errormsg); + return false; + } + $this->log(3, "+ rmdir $data[0]"); + } while (false); + } + break; + case 'installed_as': + $this->pkginfo->setInstalledAs($data[0], $data[1]); + if (!isset($this->_dirtree[dirname($data[1])])) { + $this->_dirtree[dirname($data[1])] = true; + $this->pkginfo->setDirtree(dirname($data[1])); + + while(!empty($data[3]) && dirname($data[3]) != $data[3] && + $data[3] != '/' && $data[3] != '\\') { + $this->pkginfo->setDirtree($pp = + $this->_prependPath($data[3], $data[2])); + $this->_dirtree[$pp] = true; + $data[3] = dirname($data[3]); + } + } + break; + } + } + // }}} + $this->log(2, "successfully committed $n file operations"); + $this->file_operations = array(); + return true; + } + + // }}} + // {{{ rollbackFileTransaction() + + function rollbackFileTransaction() + { + $n = count($this->file_operations); + $this->log(2, "rolling back $n file operations"); + foreach ($this->file_operations as $tr) { + list($type, $data) = $tr; + switch ($type) { + case 'backup': + if (file_exists($data[0] . '.bak')) { + if (file_exists($data[0] && is_writable($data[0]))) { + unlink($data[0]); + } + @copy($data[0] . '.bak', $data[0]); + $this->log(3, "+ restore $data[0] from $data[0].bak"); + } + break; + case 'removebackup': + if (file_exists($data[0] . '.bak') && is_writable($data[0] . '.bak')) { + unlink($data[0] . '.bak'); + $this->log(3, "+ rm backup of $data[0] ($data[0].bak)"); + } + break; + case 'rename': + @unlink($data[0]); + $this->log(3, "+ rm $data[0]"); + break; + case 'mkdir': + @rmdir($data[0]); + $this->log(3, "+ rmdir $data[0]"); + break; + case 'chmod': + break; + case 'delete': + break; + case 'installed_as': + $this->pkginfo->setInstalledAs($data[0], false); + break; + } + } + $this->pkginfo->resetDirtree(); + $this->file_operations = array(); + } + + // }}} + // {{{ mkDirHier($dir) + + function mkDirHier($dir) + { + $this->addFileOperation('mkdir', array($dir)); + return parent::mkDirHier($dir); + } + + // }}} + // {{{ download() + + /** + * Download any files and their dependencies, if necessary + * + * @param array a mixed list of package names, local files, or package.xml + * @param PEAR_Config + * @param array options from the command line + * @param array this is the array that will be populated with packages to + * install. Format of each entry: + * + * + * array('pkg' => 'package_name', 'file' => '/path/to/local/file', + * 'info' => array() // parsed package.xml + * ); + * + * @param array this will be populated with any error messages + * @param false private recursion variable + * @param false private recursion variable + * @param false private recursion variable + * @deprecated in favor of PEAR_Downloader + */ + function download($packages, $options, &$config, &$installpackages, + &$errors, $installed = false, $willinstall = false, $state = false) + { + // trickiness: initialize here + parent::PEAR_Downloader($this->ui, $options, $config); + $ret = parent::download($packages); + $errors = $this->getErrorMsgs(); + $installpackages = $this->getDownloadedPackages(); + trigger_error("PEAR Warning: PEAR_Installer::download() is deprecated " . + "in favor of PEAR_Downloader class", E_USER_WARNING); + return $ret; + } + + // }}} + // {{{ _parsePackageXml() + + function _parsePackageXml(&$descfile, &$tmpdir) + { + if (substr($descfile, -4) == '.xml') { + $tmpdir = false; + } else { + // {{{ Decompress pack in tmp dir ------------------------------------- + + // To allow relative package file names + $descfile = realpath($descfile); + + if (PEAR::isError($tmpdir = System::mktemp('-d'))) { + return $tmpdir; + } + $this->log(3, '+ tmp dir created at ' . $tmpdir); + // }}} + } + + // Parse xml file ----------------------------------------------- + $pkg = new PEAR_PackageFile($this->config, $this->debug, $tmpdir); + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $p = &$pkg->fromAnyFile($descfile, PEAR_VALIDATE_INSTALLING); + PEAR::staticPopErrorHandling(); + if (PEAR::isError($p)) { + if (is_array($p->getUserInfo())) { + foreach ($p->getUserInfo() as $err) { + $loglevel = $err['level'] == 'error' ? 0 : 1; + if (!isset($this->_options['soft'])) { + $this->log($loglevel, ucfirst($err['level']) . ': ' . $err['message']); + } + } + } + return $this->raiseError('Installation failed: invalid package file'); + } + + $descfile = $p->getPackageFile(); + return $p; + } + + // }}} + /** + * Set the list of PEAR_Downloader_Package objects to allow more sane + * dependency validation + * @param array + */ + function setDownloadedPackages(&$pkgs) + { + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $err = $this->analyzeDependencies($pkgs); + PEAR::popErrorHandling(); + if (PEAR::isError($err)) { + return $err; + } + $this->_downloadedPackages = &$pkgs; + } + + /** + * Set the list of PEAR_Downloader_Package objects to allow more sane + * dependency validation + * @param array + */ + function setUninstallPackages(&$pkgs) + { + $this->_downloadedPackages = &$pkgs; + } + + function getInstallPackages() + { + return $this->_downloadedPackages; + } + + // {{{ install() + + /** + * Installs the files within the package file specified. + * + * @param string|PEAR_Downloader_Package $pkgfile path to the package file, + * or a pre-initialized packagefile object + * @param array $options + * recognized options: + * - installroot : optional prefix directory for installation + * - force : force installation + * - register-only : update registry but don't install files + * - upgrade : upgrade existing install + * - soft : fail silently + * - nodeps : ignore dependency conflicts/missing dependencies + * - alldeps : install all dependencies + * - onlyreqdeps : install only required dependencies + * + * @return array|PEAR_Error package info if successful + */ + function install($pkgfile, $options = array()) + { + $this->_options = $options; + $this->_registry = &$this->config->getRegistry(); + if (is_object($pkgfile)) { + $dlpkg = &$pkgfile; + $pkg = $pkgfile->getPackageFile(); + $pkgfile = $pkg->getArchiveFile(); + $descfile = $pkg->getPackageFile(); + $tmpdir = dirname($descfile); + } else { + $descfile = $pkgfile; + $tmpdir = ''; + $pkg = $this->_parsePackageXml($descfile, $tmpdir); + if (PEAR::isError($pkg)) { + return $pkg; + } + } + + if (realpath($descfile) != realpath($pkgfile)) { + $tar = new Archive_Tar($pkgfile); + if (!$tar->extract($tmpdir)) { + return $this->raiseError("unable to unpack $pkgfile"); + } + } + + $pkgname = $pkg->getName(); + $channel = $pkg->getChannel(); + if (isset($this->_options['packagingroot'])) { + $regdir = $this->_prependPath( + $this->config->get('php_dir', null, 'pear.php.net'), + $this->_options['packagingroot']); + + $packrootphp_dir = $this->_prependPath( + $this->config->get('php_dir', null, $channel), + $this->_options['packagingroot']); + } + + if (isset($options['installroot'])) { + $this->config->setInstallRoot($options['installroot']); + $this->_registry = &$this->config->getRegistry(); + $installregistry = &$this->_registry; + $this->installroot = ''; // all done automagically now + $php_dir = $this->config->get('php_dir', null, $channel); + } else { + $this->config->setInstallRoot(false); + $this->_registry = &$this->config->getRegistry(); + if (isset($this->_options['packagingroot'])) { + $installregistry = &new PEAR_Registry($regdir); + if (!$installregistry->channelExists($channel, true)) { + // we need to fake a channel-discover of this channel + $chanobj = $this->_registry->getChannel($channel, true); + $installregistry->addChannel($chanobj); + } + $php_dir = $packrootphp_dir; + } else { + $installregistry = &$this->_registry; + $php_dir = $this->config->get('php_dir', null, $channel); + } + $this->installroot = ''; + } + + // {{{ checks to do when not in "force" mode + if (empty($options['force']) && + (file_exists($this->config->get('php_dir')) && + is_dir($this->config->get('php_dir')))) { + $testp = $channel == 'pear.php.net' ? $pkgname : array($channel, $pkgname); + $instfilelist = $pkg->getInstallationFileList(true); + if (PEAR::isError($instfilelist)) { + return $instfilelist; + } + + // ensure we have the most accurate registry + $installregistry->flushFileMap(); + $test = $installregistry->checkFileMap($instfilelist, $testp, '1.1'); + if (PEAR::isError($test)) { + return $test; + } + + if (sizeof($test)) { + $pkgs = $this->getInstallPackages(); + $found = false; + foreach ($pkgs as $param) { + if ($pkg->isSubpackageOf($param)) { + $found = true; + break; + } + } + + if ($found) { + // subpackages can conflict with earlier versions of parent packages + $parentreg = $installregistry->packageInfo($param->getPackage(), null, $param->getChannel()); + $tmp = $test; + foreach ($tmp as $file => $info) { + if (is_array($info)) { + if (strtolower($info[1]) == strtolower($param->getPackage()) && + strtolower($info[0]) == strtolower($param->getChannel()) + ) { + if (isset($parentreg['filelist'][$file])) { + unset($parentreg['filelist'][$file]); + } else{ + $pos = strpos($file, '/'); + $basedir = substr($file, 0, $pos); + $file2 = substr($file, $pos + 1); + if (isset($parentreg['filelist'][$file2]['baseinstalldir']) + && $parentreg['filelist'][$file2]['baseinstalldir'] === $basedir + ) { + unset($parentreg['filelist'][$file2]); + } + } + + unset($test[$file]); + } + } else { + if (strtolower($param->getChannel()) != 'pear.php.net') { + continue; + } + + if (strtolower($info) == strtolower($param->getPackage())) { + if (isset($parentreg['filelist'][$file])) { + unset($parentreg['filelist'][$file]); + } else{ + $pos = strpos($file, '/'); + $basedir = substr($file, 0, $pos); + $file2 = substr($file, $pos + 1); + if (isset($parentreg['filelist'][$file2]['baseinstalldir']) + && $parentreg['filelist'][$file2]['baseinstalldir'] === $basedir + ) { + unset($parentreg['filelist'][$file2]); + } + } + + unset($test[$file]); + } + } + } + + $pfk = &new PEAR_PackageFile($this->config); + $parentpkg = &$pfk->fromArray($parentreg); + $installregistry->updatePackage2($parentpkg); + } + + if ($param->getChannel() == 'pecl.php.net' && isset($options['upgrade'])) { + $tmp = $test; + foreach ($tmp as $file => $info) { + if (is_string($info)) { + // pear.php.net packages are always stored as strings + if (strtolower($info) == strtolower($param->getPackage())) { + // upgrading existing package + unset($test[$file]); + } + } + } + } + + if (count($test)) { + $msg = "$channel/$pkgname: conflicting files found:\n"; + $longest = max(array_map("strlen", array_keys($test))); + $fmt = "%${longest}s (%s)\n"; + foreach ($test as $file => $info) { + if (!is_array($info)) { + $info = array('pear.php.net', $info); + } + $info = $info[0] . '/' . $info[1]; + $msg .= sprintf($fmt, $file, $info); + } + + if (!isset($options['ignore-errors'])) { + return $this->raiseError($msg); + } + + if (!isset($options['soft'])) { + $this->log(0, "WARNING: $msg"); + } + } + } + } + // }}} + + $this->startFileTransaction(); + + if (empty($options['upgrade']) && empty($options['soft'])) { + // checks to do only when installing new packages + if ($channel == 'pecl.php.net') { + $test = $installregistry->packageExists($pkgname, $channel); + if (!$test) { + $test = $installregistry->packageExists($pkgname, 'pear.php.net'); + } + } else { + $test = $installregistry->packageExists($pkgname, $channel); + } + + if (empty($options['force']) && $test) { + return $this->raiseError("$channel/$pkgname is already installed"); + } + } else { + $usechannel = $channel; + if ($channel == 'pecl.php.net') { + $test = $installregistry->packageExists($pkgname, $channel); + if (!$test) { + $test = $installregistry->packageExists($pkgname, 'pear.php.net'); + $usechannel = 'pear.php.net'; + } + } else { + $test = $installregistry->packageExists($pkgname, $channel); + } + + if ($test) { + $v1 = $installregistry->packageInfo($pkgname, 'version', $usechannel); + $v2 = $pkg->getVersion(); + $cmp = version_compare("$v1", "$v2", 'gt'); + if (empty($options['force']) && !version_compare("$v2", "$v1", 'gt')) { + return $this->raiseError("upgrade to a newer version ($v2 is not newer than $v1)"); + } + + if (empty($options['register-only'])) { + // when upgrading, remove old release's files first: + if (PEAR::isError($err = $this->_deletePackageFiles($pkgname, $usechannel, + true))) { + if (!isset($options['ignore-errors'])) { + return $this->raiseError($err); + } + + if (!isset($options['soft'])) { + $this->log(0, 'WARNING: ' . $err->getMessage()); + } + } else { + $backedup = $err; + } + } + } + } + + // {{{ Copy files to dest dir --------------------------------------- + + // info from the package it self we want to access from _installFile + $this->pkginfo = &$pkg; + // used to determine whether we should build any C code + $this->source_files = 0; + + $savechannel = $this->config->get('default_channel'); + if (empty($options['register-only']) && !is_dir($php_dir)) { + if (PEAR::isError(System::mkdir(array('-p'), $php_dir))) { + return $this->raiseError("no installation destination directory '$php_dir'\n"); + } + } + + $tmp_path = dirname($descfile); + if (substr($pkgfile, -4) != '.xml') { + $tmp_path .= DIRECTORY_SEPARATOR . $pkgname . '-' . $pkg->getVersion(); + } + + $this->configSet('default_channel', $channel); + // {{{ install files + + $ver = $pkg->getPackagexmlVersion(); + if (version_compare($ver, '2.0', '>=')) { + $filelist = $pkg->getInstallationFilelist(); + } else { + $filelist = $pkg->getFileList(); + } + + if (PEAR::isError($filelist)) { + return $filelist; + } + + $p = &$installregistry->getPackage($pkgname, $channel); + $dirtree = (empty($options['register-only']) && $p) ? $p->getDirTree() : false; + + $pkg->resetFilelist(); + $pkg->setLastInstalledVersion($installregistry->packageInfo($pkg->getPackage(), + 'version', $pkg->getChannel())); + foreach ($filelist as $file => $atts) { + $this->expectError(PEAR_INSTALLER_FAILED); + if ($pkg->getPackagexmlVersion() == '1.0') { + $res = $this->_installFile($file, $atts, $tmp_path, $options); + } else { + $res = $this->_installFile2($pkg, $file, $atts, $tmp_path, $options); + } + $this->popExpect(); + + if (PEAR::isError($res)) { + if (empty($options['ignore-errors'])) { + $this->rollbackFileTransaction(); + if ($res->getMessage() == "file does not exist") { + $this->raiseError("file $file in package.xml does not exist"); + } + + return $this->raiseError($res); + } + + if (!isset($options['soft'])) { + $this->log(0, "Warning: " . $res->getMessage()); + } + } + + $real = isset($atts['attribs']) ? $atts['attribs'] : $atts; + if ($res == PEAR_INSTALLER_OK && $real['role'] != 'src') { + // Register files that were installed + $pkg->installedFile($file, $atts); + } + } + // }}} + + // {{{ compile and install source files + if ($this->source_files > 0 && empty($options['nobuild'])) { + if (PEAR::isError($err = + $this->_compileSourceFiles($savechannel, $pkg))) { + return $err; + } + } + // }}} + + if (isset($backedup)) { + $this->_removeBackups($backedup); + } + + if (!$this->commitFileTransaction()) { + $this->rollbackFileTransaction(); + $this->configSet('default_channel', $savechannel); + return $this->raiseError("commit failed", PEAR_INSTALLER_FAILED); + } + // }}} + + $ret = false; + $installphase = 'install'; + $oldversion = false; + // {{{ Register that the package is installed ----------------------- + if (empty($options['upgrade'])) { + // if 'force' is used, replace the info in registry + $usechannel = $channel; + if ($channel == 'pecl.php.net') { + $test = $installregistry->packageExists($pkgname, $channel); + if (!$test) { + $test = $installregistry->packageExists($pkgname, 'pear.php.net'); + $usechannel = 'pear.php.net'; + } + } else { + $test = $installregistry->packageExists($pkgname, $channel); + } + + if (!empty($options['force']) && $test) { + $oldversion = $installregistry->packageInfo($pkgname, 'version', $usechannel); + $installregistry->deletePackage($pkgname, $usechannel); + } + $ret = $installregistry->addPackage2($pkg); + } else { + if ($dirtree) { + $this->startFileTransaction(); + // attempt to delete empty directories + uksort($dirtree, array($this, '_sortDirs')); + foreach($dirtree as $dir => $notused) { + $this->addFileOperation('rmdir', array($dir)); + } + $this->commitFileTransaction(); + } + + $usechannel = $channel; + if ($channel == 'pecl.php.net') { + $test = $installregistry->packageExists($pkgname, $channel); + if (!$test) { + $test = $installregistry->packageExists($pkgname, 'pear.php.net'); + $usechannel = 'pear.php.net'; + } + } else { + $test = $installregistry->packageExists($pkgname, $channel); + } + + // new: upgrade installs a package if it isn't installed + if (!$test) { + $ret = $installregistry->addPackage2($pkg); + } else { + if ($usechannel != $channel) { + $installregistry->deletePackage($pkgname, $usechannel); + $ret = $installregistry->addPackage2($pkg); + } else { + $ret = $installregistry->updatePackage2($pkg); + } + $installphase = 'upgrade'; + } + } + + if (!$ret) { + $this->configSet('default_channel', $savechannel); + return $this->raiseError("Adding package $channel/$pkgname to registry failed"); + } + // }}} + + $this->configSet('default_channel', $savechannel); + if (class_exists('PEAR_Task_Common')) { // this is auto-included if any tasks exist + if (PEAR_Task_Common::hasPostinstallTasks()) { + PEAR_Task_Common::runPostinstallTasks($installphase); + } + } + + return $pkg->toArray(true); + } + + // }}} + + // {{{ _compileSourceFiles() + /** + * @param string + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2 + */ + function _compileSourceFiles($savechannel, &$filelist) + { + require_once 'PEAR/Builder.php'; + $this->log(1, "$this->source_files source files, building"); + $bob = &new PEAR_Builder($this->ui); + $bob->debug = $this->debug; + $built = $bob->build($filelist, array(&$this, '_buildCallback')); + if (PEAR::isError($built)) { + $this->rollbackFileTransaction(); + $this->configSet('default_channel', $savechannel); + return $built; + } + + $this->log(1, "\nBuild process completed successfully"); + foreach ($built as $ext) { + $bn = basename($ext['file']); + list($_ext_name, $_ext_suff) = explode('.', $bn); + if ($_ext_suff == '.so' || $_ext_suff == '.dll') { + if (extension_loaded($_ext_name)) { + $this->raiseError("Extension '$_ext_name' already loaded. " . + 'Please unload it in your php.ini file ' . + 'prior to install or upgrade'); + } + $role = 'ext'; + } else { + $role = 'src'; + } + + $dest = $ext['dest']; + $packagingroot = ''; + if (isset($this->_options['packagingroot'])) { + $packagingroot = $this->_options['packagingroot']; + } + + $copyto = $this->_prependPath($dest, $packagingroot); + $extra = $copyto != $dest ? " as '$copyto'" : ''; + $this->log(1, "Installing '$dest'$extra"); + + $copydir = dirname($copyto); + // pretty much nothing happens if we are only registering the install + if (empty($this->_options['register-only'])) { + if (!file_exists($copydir) || !is_dir($copydir)) { + if (!$this->mkDirHier($copydir)) { + return $this->raiseError("failed to mkdir $copydir", + PEAR_INSTALLER_FAILED); + } + + $this->log(3, "+ mkdir $copydir"); + } + + if (!@copy($ext['file'], $copyto)) { + return $this->raiseError("failed to write $copyto ($php_errormsg)", PEAR_INSTALLER_FAILED); + } + + $this->log(3, "+ cp $ext[file] $copyto"); + $this->addFileOperation('rename', array($ext['file'], $copyto)); + if (!OS_WINDOWS) { + $mode = 0666 & ~(int)octdec($this->config->get('umask')); + $this->addFileOperation('chmod', array($mode, $copyto)); + if (!@chmod($copyto, $mode)) { + $this->log(0, "failed to change mode of $copyto ($php_errormsg)"); + } + } + } + + + $data = array( + 'role' => $role, + 'name' => $bn, + 'installed_as' => $dest, + 'php_api' => $ext['php_api'], + 'zend_mod_api' => $ext['zend_mod_api'], + 'zend_ext_api' => $ext['zend_ext_api'], + ); + + if ($filelist->getPackageXmlVersion() == '1.0') { + $filelist->installedFile($bn, $data); + } else { + $filelist->installedFile($bn, array('attribs' => $data)); + } + } + } + + // }}} + function &getUninstallPackages() + { + return $this->_downloadedPackages; + } + // {{{ uninstall() + + /** + * Uninstall a package + * + * This method removes all files installed by the application, and then + * removes any empty directories. + * @param string package name + * @param array Command-line options. Possibilities include: + * + * - installroot: base installation dir, if not the default + * - register-only : update registry but don't remove files + * - nodeps: do not process dependencies of other packages to ensure + * uninstallation does not break things + */ + function uninstall($package, $options = array()) + { + $installRoot = isset($options['installroot']) ? $options['installroot'] : ''; + $this->config->setInstallRoot($installRoot); + + $this->installroot = ''; + $this->_registry = &$this->config->getRegistry(); + if (is_object($package)) { + $channel = $package->getChannel(); + $pkg = $package; + $package = $pkg->getPackage(); + } else { + $pkg = false; + $info = $this->_registry->parsePackageName($package, + $this->config->get('default_channel')); + $channel = $info['channel']; + $package = $info['package']; + } + + $savechannel = $this->config->get('default_channel'); + $this->configSet('default_channel', $channel); + if (!is_object($pkg)) { + $pkg = $this->_registry->getPackage($package, $channel); + } + + if (!$pkg) { + $this->configSet('default_channel', $savechannel); + return $this->raiseError($this->_registry->parsedPackageNameToString( + array( + 'channel' => $channel, + 'package' => $package + ), true) . ' not installed'); + } + + if ($pkg->getInstalledBinary()) { + // this is just an alias for a binary package + return $this->_registry->deletePackage($package, $channel); + } + + $filelist = $pkg->getFilelist(); + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + if (!class_exists('PEAR_Dependency2')) { + require_once 'PEAR/Dependency2.php'; + } + + $depchecker = &new PEAR_Dependency2($this->config, $options, + array('channel' => $channel, 'package' => $package), + PEAR_VALIDATE_UNINSTALLING); + $e = $depchecker->validatePackageUninstall($this); + PEAR::staticPopErrorHandling(); + if (PEAR::isError($e)) { + if (!isset($options['ignore-errors'])) { + return $this->raiseError($e); + } + + if (!isset($options['soft'])) { + $this->log(0, 'WARNING: ' . $e->getMessage()); + } + } elseif (is_array($e)) { + if (!isset($options['soft'])) { + $this->log(0, $e[0]); + } + } + + $this->pkginfo = &$pkg; + // pretty much nothing happens if we are only registering the uninstall + if (empty($options['register-only'])) { + // {{{ Delete the files + $this->startFileTransaction(); + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + if (PEAR::isError($err = $this->_deletePackageFiles($package, $channel))) { + PEAR::popErrorHandling(); + $this->rollbackFileTransaction(); + $this->configSet('default_channel', $savechannel); + if (!isset($options['ignore-errors'])) { + return $this->raiseError($err); + } + + if (!isset($options['soft'])) { + $this->log(0, 'WARNING: ' . $err->getMessage()); + } + } else { + PEAR::popErrorHandling(); + } + + if (!$this->commitFileTransaction()) { + $this->rollbackFileTransaction(); + if (!isset($options['ignore-errors'])) { + return $this->raiseError("uninstall failed"); + } + + if (!isset($options['soft'])) { + $this->log(0, 'WARNING: uninstall failed'); + } + } else { + $this->startFileTransaction(); + $dirtree = $pkg->getDirTree(); + if ($dirtree === false) { + $this->configSet('default_channel', $savechannel); + return $this->_registry->deletePackage($package, $channel); + } + + // attempt to delete empty directories + uksort($dirtree, array($this, '_sortDirs')); + foreach($dirtree as $dir => $notused) { + $this->addFileOperation('rmdir', array($dir)); + } + + if (!$this->commitFileTransaction()) { + $this->rollbackFileTransaction(); + if (!isset($options['ignore-errors'])) { + return $this->raiseError("uninstall failed"); + } + + if (!isset($options['soft'])) { + $this->log(0, 'WARNING: uninstall failed'); + } + } + } + // }}} + } + + $this->configSet('default_channel', $savechannel); + // Register that the package is no longer installed + return $this->_registry->deletePackage($package, $channel); + } + + /** + * Sort a list of arrays of array(downloaded packagefilename) by dependency. + * + * It also removes duplicate dependencies + * @param array an array of PEAR_PackageFile_v[1/2] objects + * @return array|PEAR_Error array of array(packagefilename, package.xml contents) + */ + function sortPackagesForUninstall(&$packages) + { + $this->_dependencyDB = &PEAR_DependencyDB::singleton($this->config); + if (PEAR::isError($this->_dependencyDB)) { + return $this->_dependencyDB; + } + usort($packages, array(&$this, '_sortUninstall')); + } + + function _sortUninstall($a, $b) + { + if (!$a->getDeps() && !$b->getDeps()) { + return 0; // neither package has dependencies, order is insignificant + } + if ($a->getDeps() && !$b->getDeps()) { + return -1; // $a must be installed after $b because $a has dependencies + } + if (!$a->getDeps() && $b->getDeps()) { + return 1; // $b must be installed after $a because $b has dependencies + } + // both packages have dependencies + if ($this->_dependencyDB->dependsOn($a, $b)) { + return -1; + } + if ($this->_dependencyDB->dependsOn($b, $a)) { + return 1; + } + return 0; + } + + // }}} + // {{{ _sortDirs() + function _sortDirs($a, $b) + { + if (strnatcmp($a, $b) == -1) return 1; + if (strnatcmp($a, $b) == 1) return -1; + return 0; + } + + // }}} + + // {{{ _buildCallback() + + function _buildCallback($what, $data) + { + if (($what == 'cmdoutput' && $this->debug > 1) || + ($what == 'output' && $this->debug > 0)) { + $this->ui->outputData(rtrim($data), 'build'); + } + } + + // }}} +} \ No newline at end of file diff --git a/library/pear/PEAR/Installer/Role.php b/library/pear/PEAR/Installer/Role.php new file mode 100644 index 000000000..1d4e7dc1c --- /dev/null +++ b/library/pear/PEAR/Installer/Role.php @@ -0,0 +1,276 @@ + + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Role.php 278552 2009-04-10 19:42:49Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ + +/** + * base class for installer roles + */ +require_once 'PEAR/Installer/Role/Common.php'; +require_once 'PEAR/XMLParser.php'; +/** + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.1 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_Installer_Role +{ + /** + * Set up any additional configuration variables that file roles require + * + * Never call this directly, it is called by the PEAR_Config constructor + * @param PEAR_Config + * @access private + * @static + */ + function initializeConfig(&$config) + { + if (!isset($GLOBALS['_PEAR_INSTALLER_ROLES'])) { + PEAR_Installer_Role::registerRoles(); + } + + foreach ($GLOBALS['_PEAR_INSTALLER_ROLES'] as $class => $info) { + if (!$info['config_vars']) { + continue; + } + + $config->_addConfigVars($class, $info['config_vars']); + } + } + + /** + * @param PEAR_PackageFile_v2 + * @param string role name + * @param PEAR_Config + * @return PEAR_Installer_Role_Common + * @static + */ + function &factory($pkg, $role, &$config) + { + if (!isset($GLOBALS['_PEAR_INSTALLER_ROLES'])) { + PEAR_Installer_Role::registerRoles(); + } + + if (!in_array($role, PEAR_Installer_Role::getValidRoles($pkg->getPackageType()))) { + $a = false; + return $a; + } + + $a = 'PEAR_Installer_Role_' . ucfirst($role); + if (!class_exists($a)) { + require_once str_replace('_', '/', $a) . '.php'; + } + + $b = new $a($config); + return $b; + } + + /** + * Get a list of file roles that are valid for the particular release type. + * + * For instance, src files serve no purpose in regular php releases. + * @param string + * @param bool clear cache + * @return array + * @static + */ + function getValidRoles($release, $clear = false) + { + if (!isset($GLOBALS['_PEAR_INSTALLER_ROLES'])) { + PEAR_Installer_Role::registerRoles(); + } + + static $ret = array(); + if ($clear) { + $ret = array(); + } + + if (isset($ret[$release])) { + return $ret[$release]; + } + + $ret[$release] = array(); + foreach ($GLOBALS['_PEAR_INSTALLER_ROLES'] as $role => $okreleases) { + if (in_array($release, $okreleases['releasetypes'])) { + $ret[$release][] = strtolower(str_replace('PEAR_Installer_Role_', '', $role)); + } + } + + return $ret[$release]; + } + + /** + * Get a list of roles that require their files to be installed + * + * Most roles must be installed, but src and package roles, for instance + * are pseudo-roles. src files are compiled into a new extension. Package + * roles are actually fully bundled releases of a package + * @param bool clear cache + * @return array + * @static + */ + function getInstallableRoles($clear = false) + { + if (!isset($GLOBALS['_PEAR_INSTALLER_ROLES'])) { + PEAR_Installer_Role::registerRoles(); + } + + static $ret; + if ($clear) { + unset($ret); + } + + if (isset($ret)) { + return $ret; + } + + $ret = array(); + foreach ($GLOBALS['_PEAR_INSTALLER_ROLES'] as $role => $okreleases) { + if ($okreleases['installable']) { + $ret[] = strtolower(str_replace('PEAR_Installer_Role_', '', $role)); + } + } + + return $ret; + } + + /** + * Return an array of roles that are affected by the baseinstalldir attribute + * + * Most roles ignore this attribute, and instead install directly into: + * PackageName/filepath + * so a tests file tests/file.phpt is installed into PackageName/tests/filepath.php + * @param bool clear cache + * @return array + * @static + */ + function getBaseinstallRoles($clear = false) + { + if (!isset($GLOBALS['_PEAR_INSTALLER_ROLES'])) { + PEAR_Installer_Role::registerRoles(); + } + + static $ret; + if ($clear) { + unset($ret); + } + + if (isset($ret)) { + return $ret; + } + + $ret = array(); + foreach ($GLOBALS['_PEAR_INSTALLER_ROLES'] as $role => $okreleases) { + if ($okreleases['honorsbaseinstall']) { + $ret[] = strtolower(str_replace('PEAR_Installer_Role_', '', $role)); + } + } + + return $ret; + } + + /** + * Return an array of file roles that should be analyzed for PHP content at package time, + * like the "php" role. + * @param bool clear cache + * @return array + * @static + */ + function getPhpRoles($clear = false) + { + if (!isset($GLOBALS['_PEAR_INSTALLER_ROLES'])) { + PEAR_Installer_Role::registerRoles(); + } + + static $ret; + if ($clear) { + unset($ret); + } + + if (isset($ret)) { + return $ret; + } + + $ret = array(); + foreach ($GLOBALS['_PEAR_INSTALLER_ROLES'] as $role => $okreleases) { + if ($okreleases['phpfile']) { + $ret[] = strtolower(str_replace('PEAR_Installer_Role_', '', $role)); + } + } + + return $ret; + } + + /** + * Scan through the Command directory looking for classes + * and see what commands they implement. + * @param string which directory to look for classes, defaults to + * the Installer/Roles subdirectory of + * the directory from where this file (__FILE__) is + * included. + * + * @return bool TRUE on success, a PEAR error on failure + * @access public + * @static + */ + function registerRoles($dir = null) + { + $GLOBALS['_PEAR_INSTALLER_ROLES'] = array(); + $parser = new PEAR_XMLParser; + if ($dir === null) { + $dir = dirname(__FILE__) . '/Role'; + } + + if (!file_exists($dir) || !is_dir($dir)) { + return PEAR::raiseError("registerRoles: opendir($dir) failed: does not exist/is not directory"); + } + + $dp = @opendir($dir); + if (empty($dp)) { + return PEAR::raiseError("registerRoles: opendir($dir) failed: $php_errmsg"); + } + + while ($entry = readdir($dp)) { + if ($entry{0} == '.' || substr($entry, -4) != '.xml') { + continue; + } + + $class = "PEAR_Installer_Role_".substr($entry, 0, -4); + // List of roles + if (!isset($GLOBALS['_PEAR_INSTALLER_ROLES'][$class])) { + $file = "$dir/$entry"; + $parser->parse(file_get_contents($file)); + $data = $parser->getData(); + if (!is_array($data['releasetypes'])) { + $data['releasetypes'] = array($data['releasetypes']); + } + + $GLOBALS['_PEAR_INSTALLER_ROLES'][$class] = $data; + } + } + + closedir($dp); + ksort($GLOBALS['_PEAR_INSTALLER_ROLES']); + PEAR_Installer_Role::getBaseinstallRoles(true); + PEAR_Installer_Role::getInstallableRoles(true); + PEAR_Installer_Role::getPhpRoles(true); + PEAR_Installer_Role::getValidRoles('****', true); + return true; + } +} \ No newline at end of file diff --git a/library/pear/PEAR/Installer/Role/Cfg.php b/library/pear/PEAR/Installer/Role/Cfg.php new file mode 100644 index 000000000..d484cd107 --- /dev/null +++ b/library/pear/PEAR/Installer/Role/Cfg.php @@ -0,0 +1,106 @@ + + * @copyright 2007-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Cfg.php 276383 2009-02-24 23:39:37Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.7.0 + */ + +/** + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 2007-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.1 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.7.0 + */ +class PEAR_Installer_Role_Cfg extends PEAR_Installer_Role_Common +{ + /** + * @var PEAR_Installer + */ + var $installer; + + /** + * the md5 of the original file + * + * @var unknown_type + */ + var $md5 = null; + + /** + * Do any unusual setup here + * @param PEAR_Installer + * @param PEAR_PackageFile_v2 + * @param array file attributes + * @param string file name + */ + function setup(&$installer, $pkg, $atts, $file) + { + $this->installer = &$installer; + $reg = &$this->installer->config->getRegistry(); + $package = $reg->getPackage($pkg->getPackage(), $pkg->getChannel()); + if ($package) { + $filelist = $package->getFilelist(); + if (isset($filelist[$file]) && isset($filelist[$file]['md5sum'])) { + $this->md5 = $filelist[$file]['md5sum']; + } + } + } + + function processInstallation($pkg, $atts, $file, $tmp_path, $layer = null) + { + $test = parent::processInstallation($pkg, $atts, $file, $tmp_path, $layer); + if (@file_exists($test[2]) && @file_exists($test[3])) { + $md5 = md5_file($test[2]); + // configuration has already been installed, check for mods + if ($md5 !== $this->md5 && $md5 !== md5_file($test[3])) { + // configuration has been modified, so save our version as + // configfile-version + $old = $test[2]; + $test[2] .= '.new-' . $pkg->getVersion(); + // backup original and re-install it + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $tmpcfg = $this->config->get('temp_dir'); + $newloc = System::mkdir(array('-p', $tmpcfg)); + if (!$newloc) { + // try temp_dir + $newloc = System::mktemp(array('-d')); + if (!$newloc || PEAR::isError($newloc)) { + PEAR::popErrorHandling(); + return PEAR::raiseError('Could not save existing configuration file '. + $old . ', unable to install. Please set temp_dir ' . + 'configuration variable to a writeable location and try again'); + } + } else { + $newloc = $tmpcfg; + } + + $temp_file = $newloc . DIRECTORY_SEPARATOR . uniqid('savefile'); + if (!@copy($old, $temp_file)) { + PEAR::popErrorHandling(); + return PEAR::raiseError('Could not save existing configuration file '. + $old . ', unable to install. Please set temp_dir ' . + 'configuration variable to a writeable location and try again'); + } + + PEAR::popErrorHandling(); + $this->installer->log(0, "WARNING: configuration file $old is being installed as $test[2], you should manually merge in changes to the existing configuration file"); + $this->installer->addFileOperation('rename', array($temp_file, $old, false)); + $this->installer->addFileOperation('delete', array($temp_file)); + } + } + + return $test; + } +} \ No newline at end of file diff --git a/library/pear/PEAR/Installer/Role/Cfg.xml b/library/pear/PEAR/Installer/Role/Cfg.xml new file mode 100644 index 000000000..7a415dc46 --- /dev/null +++ b/library/pear/PEAR/Installer/Role/Cfg.xml @@ -0,0 +1,15 @@ + + php + extsrc + extbin + zendextsrc + zendextbin + 1 + cfg_dir + + 1 + + + + + \ No newline at end of file diff --git a/library/pear/PEAR/Installer/Role/Common.php b/library/pear/PEAR/Installer/Role/Common.php new file mode 100644 index 000000000..57668ea0d --- /dev/null +++ b/library/pear/PEAR/Installer/Role/Common.php @@ -0,0 +1,174 @@ + + * @copyright 1997-2006 The PHP Group + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Common.php 276383 2009-02-24 23:39:37Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ +/** + * Base class for all installation roles. + * + * This class allows extensibility of file roles. Packages with complex + * customization can now provide custom file roles along with the possibility of + * adding configuration values to match. + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2006 The PHP Group + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.1 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_Installer_Role_Common +{ + /** + * @var PEAR_Config + * @access protected + */ + var $config; + + /** + * @param PEAR_Config + */ + function PEAR_Installer_Role_Common(&$config) + { + $this->config = $config; + } + + /** + * Retrieve configuration information about a file role from its XML info + * + * @param string $role Role Classname, as in "PEAR_Installer_Role_Data" + * @return array + */ + function getInfo($role) + { + if (empty($GLOBALS['_PEAR_INSTALLER_ROLES'][$role])) { + return PEAR::raiseError('Unknown Role class: "' . $role . '"'); + } + return $GLOBALS['_PEAR_INSTALLER_ROLES'][$role]; + } + + /** + * This is called for each file to set up the directories and files + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2 + * @param array attributes from the tag + * @param string file name + * @return array an array consisting of: + * + * 1 the original, pre-baseinstalldir installation directory + * 2 the final installation directory + * 3 the full path to the final location of the file + * 4 the location of the pre-installation file + */ + function processInstallation($pkg, $atts, $file, $tmp_path, $layer = null) + { + $roleInfo = PEAR_Installer_Role_Common::getInfo('PEAR_Installer_Role_' . + ucfirst(str_replace('pear_installer_role_', '', strtolower(get_class($this))))); + if (PEAR::isError($roleInfo)) { + return $roleInfo; + } + if (!$roleInfo['locationconfig']) { + return false; + } + if ($roleInfo['honorsbaseinstall']) { + $dest_dir = $save_destdir = $this->config->get($roleInfo['locationconfig'], $layer, + $pkg->getChannel()); + if (!empty($atts['baseinstalldir'])) { + $dest_dir .= DIRECTORY_SEPARATOR . $atts['baseinstalldir']; + } + } elseif ($roleInfo['unusualbaseinstall']) { + $dest_dir = $save_destdir = $this->config->get($roleInfo['locationconfig'], + $layer, $pkg->getChannel()) . DIRECTORY_SEPARATOR . $pkg->getPackage(); + if (!empty($atts['baseinstalldir'])) { + $dest_dir .= DIRECTORY_SEPARATOR . $atts['baseinstalldir']; + } + } else { + $dest_dir = $save_destdir = $this->config->get($roleInfo['locationconfig'], + $layer, $pkg->getChannel()) . DIRECTORY_SEPARATOR . $pkg->getPackage(); + } + if (dirname($file) != '.' && empty($atts['install-as'])) { + $dest_dir .= DIRECTORY_SEPARATOR . dirname($file); + } + if (empty($atts['install-as'])) { + $dest_file = $dest_dir . DIRECTORY_SEPARATOR . basename($file); + } else { + $dest_file = $dest_dir . DIRECTORY_SEPARATOR . $atts['install-as']; + } + $orig_file = $tmp_path . DIRECTORY_SEPARATOR . $file; + + // Clean up the DIRECTORY_SEPARATOR mess + $ds2 = DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR; + + list($dest_dir, $dest_file, $orig_file) = preg_replace(array('!\\\\+!', '!/!', "!$ds2+!"), + array(DIRECTORY_SEPARATOR, DIRECTORY_SEPARATOR, + DIRECTORY_SEPARATOR), + array($dest_dir, $dest_file, $orig_file)); + return array($save_destdir, $dest_dir, $dest_file, $orig_file); + } + + /** + * Get the name of the configuration variable that specifies the location of this file + * @return string|false + */ + function getLocationConfig() + { + $roleInfo = PEAR_Installer_Role_Common::getInfo('PEAR_Installer_Role_' . + ucfirst(str_replace('pear_installer_role_', '', strtolower(get_class($this))))); + if (PEAR::isError($roleInfo)) { + return $roleInfo; + } + return $roleInfo['locationconfig']; + } + + /** + * Do any unusual setup here + * @param PEAR_Installer + * @param PEAR_PackageFile_v2 + * @param array file attributes + * @param string file name + */ + function setup(&$installer, $pkg, $atts, $file) + { + } + + function isExecutable() + { + $roleInfo = PEAR_Installer_Role_Common::getInfo('PEAR_Installer_Role_' . + ucfirst(str_replace('pear_installer_role_', '', strtolower(get_class($this))))); + if (PEAR::isError($roleInfo)) { + return $roleInfo; + } + return $roleInfo['executable']; + } + + function isInstallable() + { + $roleInfo = PEAR_Installer_Role_Common::getInfo('PEAR_Installer_Role_' . + ucfirst(str_replace('pear_installer_role_', '', strtolower(get_class($this))))); + if (PEAR::isError($roleInfo)) { + return $roleInfo; + } + return $roleInfo['installable']; + } + + function isExtension() + { + $roleInfo = PEAR_Installer_Role_Common::getInfo('PEAR_Installer_Role_' . + ucfirst(str_replace('pear_installer_role_', '', strtolower(get_class($this))))); + if (PEAR::isError($roleInfo)) { + return $roleInfo; + } + return $roleInfo['phpextension']; + } +} +?> \ No newline at end of file diff --git a/library/pear/PEAR/Installer/Role/Data.php b/library/pear/PEAR/Installer/Role/Data.php new file mode 100644 index 000000000..b4f2bb834 --- /dev/null +++ b/library/pear/PEAR/Installer/Role/Data.php @@ -0,0 +1,28 @@ + + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Data.php 276383 2009-02-24 23:39:37Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ + +/** + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.1 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_Installer_Role_Data extends PEAR_Installer_Role_Common {} +?> \ No newline at end of file diff --git a/library/pear/PEAR/Installer/Role/Data.xml b/library/pear/PEAR/Installer/Role/Data.xml new file mode 100644 index 000000000..eae63720d --- /dev/null +++ b/library/pear/PEAR/Installer/Role/Data.xml @@ -0,0 +1,15 @@ + + php + extsrc + extbin + zendextsrc + zendextbin + 1 + data_dir + + + + + + + \ No newline at end of file diff --git a/library/pear/PEAR/Installer/Role/Doc.php b/library/pear/PEAR/Installer/Role/Doc.php new file mode 100644 index 000000000..279638440 --- /dev/null +++ b/library/pear/PEAR/Installer/Role/Doc.php @@ -0,0 +1,28 @@ + + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Doc.php 276383 2009-02-24 23:39:37Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ + +/** + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.1 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_Installer_Role_Doc extends PEAR_Installer_Role_Common {} +?> \ No newline at end of file diff --git a/library/pear/PEAR/Installer/Role/Doc.xml b/library/pear/PEAR/Installer/Role/Doc.xml new file mode 100644 index 000000000..173afba01 --- /dev/null +++ b/library/pear/PEAR/Installer/Role/Doc.xml @@ -0,0 +1,15 @@ + + php + extsrc + extbin + zendextsrc + zendextbin + 1 + doc_dir + + + + + + + \ No newline at end of file diff --git a/library/pear/PEAR/Installer/Role/Ext.php b/library/pear/PEAR/Installer/Role/Ext.php new file mode 100644 index 000000000..ab4e3a476 --- /dev/null +++ b/library/pear/PEAR/Installer/Role/Ext.php @@ -0,0 +1,28 @@ + + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Ext.php 276383 2009-02-24 23:39:37Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ + +/** + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.1 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_Installer_Role_Ext extends PEAR_Installer_Role_Common {} +?> \ No newline at end of file diff --git a/library/pear/PEAR/Installer/Role/Ext.xml b/library/pear/PEAR/Installer/Role/Ext.xml new file mode 100644 index 000000000..e2940fe1f --- /dev/null +++ b/library/pear/PEAR/Installer/Role/Ext.xml @@ -0,0 +1,12 @@ + + extbin + zendextbin + 1 + ext_dir + 1 + + + + 1 + + \ No newline at end of file diff --git a/library/pear/PEAR/Installer/Role/Php.php b/library/pear/PEAR/Installer/Role/Php.php new file mode 100644 index 000000000..afd2bbecc --- /dev/null +++ b/library/pear/PEAR/Installer/Role/Php.php @@ -0,0 +1,28 @@ + + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Php.php 276383 2009-02-24 23:39:37Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ + +/** + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.1 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_Installer_Role_Php extends PEAR_Installer_Role_Common {} +?> \ No newline at end of file diff --git a/library/pear/PEAR/Installer/Role/Php.xml b/library/pear/PEAR/Installer/Role/Php.xml new file mode 100644 index 000000000..6b9a0e67a --- /dev/null +++ b/library/pear/PEAR/Installer/Role/Php.xml @@ -0,0 +1,15 @@ + + php + extsrc + extbin + zendextsrc + zendextbin + 1 + php_dir + 1 + + 1 + + + + \ No newline at end of file diff --git a/library/pear/PEAR/Installer/Role/Script.php b/library/pear/PEAR/Installer/Role/Script.php new file mode 100644 index 000000000..1a988b543 --- /dev/null +++ b/library/pear/PEAR/Installer/Role/Script.php @@ -0,0 +1,28 @@ + + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Script.php 276383 2009-02-24 23:39:37Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ + +/** + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.1 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_Installer_Role_Script extends PEAR_Installer_Role_Common {} +?> \ No newline at end of file diff --git a/library/pear/PEAR/Installer/Role/Script.xml b/library/pear/PEAR/Installer/Role/Script.xml new file mode 100644 index 000000000..e732cf2af --- /dev/null +++ b/library/pear/PEAR/Installer/Role/Script.xml @@ -0,0 +1,15 @@ + + php + extsrc + extbin + zendextsrc + zendextbin + 1 + bin_dir + 1 + + + 1 + + + \ No newline at end of file diff --git a/library/pear/PEAR/Installer/Role/Src.php b/library/pear/PEAR/Installer/Role/Src.php new file mode 100644 index 000000000..7cea536f6 --- /dev/null +++ b/library/pear/PEAR/Installer/Role/Src.php @@ -0,0 +1,34 @@ + + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Src.php 276383 2009-02-24 23:39:37Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ + +/** + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.1 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_Installer_Role_Src extends PEAR_Installer_Role_Common +{ + function setup(&$installer, $pkg, $atts, $file) + { + $installer->source_files++; + } +} +?> \ No newline at end of file diff --git a/library/pear/PEAR/Installer/Role/Src.xml b/library/pear/PEAR/Installer/Role/Src.xml new file mode 100644 index 000000000..103483402 --- /dev/null +++ b/library/pear/PEAR/Installer/Role/Src.xml @@ -0,0 +1,12 @@ + + extsrc + zendextsrc + 1 + temp_dir + + + + + + + \ No newline at end of file diff --git a/library/pear/PEAR/Installer/Role/Test.php b/library/pear/PEAR/Installer/Role/Test.php new file mode 100644 index 000000000..f1c47fa50 --- /dev/null +++ b/library/pear/PEAR/Installer/Role/Test.php @@ -0,0 +1,28 @@ + + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Test.php 276383 2009-02-24 23:39:37Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ + +/** + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.1 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_Installer_Role_Test extends PEAR_Installer_Role_Common {} +?> \ No newline at end of file diff --git a/library/pear/PEAR/Installer/Role/Test.xml b/library/pear/PEAR/Installer/Role/Test.xml new file mode 100644 index 000000000..51d5b894e --- /dev/null +++ b/library/pear/PEAR/Installer/Role/Test.xml @@ -0,0 +1,15 @@ + + php + extsrc + extbin + zendextsrc + zendextbin + 1 + test_dir + + + + + + + \ No newline at end of file diff --git a/library/pear/PEAR/Installer/Role/Www.php b/library/pear/PEAR/Installer/Role/Www.php new file mode 100644 index 000000000..d74351807 --- /dev/null +++ b/library/pear/PEAR/Installer/Role/Www.php @@ -0,0 +1,28 @@ + + * @copyright 2007-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Www.php 276383 2009-02-24 23:39:37Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.7.0 + */ + +/** + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 2007-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.1 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.7.0 + */ +class PEAR_Installer_Role_Www extends PEAR_Installer_Role_Common {} +?> \ No newline at end of file diff --git a/library/pear/PEAR/Installer/Role/Www.xml b/library/pear/PEAR/Installer/Role/Www.xml new file mode 100644 index 000000000..7598be388 --- /dev/null +++ b/library/pear/PEAR/Installer/Role/Www.xml @@ -0,0 +1,15 @@ + + php + extsrc + extbin + zendextsrc + zendextbin + 1 + www_dir + 1 + + + + + + \ No newline at end of file diff --git a/library/pear/PEAR/PackageFile.php b/library/pear/PEAR/PackageFile.php new file mode 100644 index 000000000..5ce38245d --- /dev/null +++ b/library/pear/PEAR/PackageFile.php @@ -0,0 +1,501 @@ + + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: PackageFile.php 286670 2009-08-02 14:16:06Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ + +/** + * needed for PEAR_VALIDATE_* constants + */ +require_once 'PEAR/Validate.php'; +/** + * Error code if the package.xml tag does not contain a valid version + */ +define('PEAR_PACKAGEFILE_ERROR_NO_PACKAGEVERSION', 1); +/** + * Error code if the package.xml tag version is not supported (version 1.0 and 1.1 are the only supported versions, + * currently + */ +define('PEAR_PACKAGEFILE_ERROR_INVALID_PACKAGEVERSION', 2); +/** + * Abstraction for the package.xml package description file + * + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.1 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_PackageFile +{ + /** + * @var PEAR_Config + */ + var $_config; + var $_debug; + /** + * Temp directory for uncompressing tgz files. + * @var string|false + */ + var $_tmpdir; + var $_logger = false; + /** + * @var boolean + */ + var $_rawReturn = false; + + /** + * + * @param PEAR_Config $config + * @param ? $debug + * @param string @tmpdir Optional temporary directory for uncompressing + * files + */ + function PEAR_PackageFile(&$config, $debug = false, $tmpdir = false) + { + $this->_config = $config; + $this->_debug = $debug; + $this->_tmpdir = $tmpdir; + } + + /** + * Turn off validation - return a parsed package.xml without checking it + * + * This is used by the package-validate command + */ + function rawReturn() + { + $this->_rawReturn = true; + } + + function setLogger(&$l) + { + $this->_logger = &$l; + } + + /** + * Create a PEAR_PackageFile_Parser_v* of a given version. + * @param int $version + * @return PEAR_PackageFile_Parser_v1|PEAR_PackageFile_Parser_v1 + */ + function &parserFactory($version) + { + if (!in_array($version{0}, array('1', '2'))) { + $a = false; + return $a; + } + + include_once 'PEAR/PackageFile/Parser/v' . $version{0} . '.php'; + $version = $version{0}; + $class = "PEAR_PackageFile_Parser_v$version"; + $a = new $class; + return $a; + } + + /** + * For simpler unit-testing + * @return string + */ + function getClassPrefix() + { + return 'PEAR_PackageFile_v'; + } + + /** + * Create a PEAR_PackageFile_v* of a given version. + * @param int $version + * @return PEAR_PackageFile_v1|PEAR_PackageFile_v1 + */ + function &factory($version) + { + if (!in_array($version{0}, array('1', '2'))) { + $a = false; + return $a; + } + + include_once 'PEAR/PackageFile/v' . $version{0} . '.php'; + $version = $version{0}; + $class = $this->getClassPrefix() . $version; + $a = new $class; + return $a; + } + + /** + * Create a PEAR_PackageFile_v* from its toArray() method + * + * WARNING: no validation is performed, the array is assumed to be valid, + * always parse from xml if you want validation. + * @param array $arr + * @return PEAR_PackageFileManager_v1|PEAR_PackageFileManager_v2 + * @uses factory() to construct the returned object. + */ + function &fromArray($arr) + { + if (isset($arr['xsdversion'])) { + $obj = &$this->factory($arr['xsdversion']); + if ($this->_logger) { + $obj->setLogger($this->_logger); + } + + $obj->setConfig($this->_config); + $obj->fromArray($arr); + return $obj; + } + + if (isset($arr['package']['attribs']['version'])) { + $obj = &$this->factory($arr['package']['attribs']['version']); + } else { + $obj = &$this->factory('1.0'); + } + + if ($this->_logger) { + $obj->setLogger($this->_logger); + } + + $obj->setConfig($this->_config); + $obj->fromArray($arr); + return $obj; + } + + /** + * Create a PEAR_PackageFile_v* from an XML string. + * @access public + * @param string $data contents of package.xml file + * @param int $state package state (one of PEAR_VALIDATE_* constants) + * @param string $file full path to the package.xml file (and the files + * it references) + * @param string $archive optional name of the archive that the XML was + * extracted from, if any + * @return PEAR_PackageFile_v1|PEAR_PackageFile_v2 + * @uses parserFactory() to construct a parser to load the package. + */ + function &fromXmlString($data, $state, $file, $archive = false) + { + if (preg_match('/]+version="([0-9]+\.[0-9]+)"/', $data, $packageversion)) { + if (!in_array($packageversion[1], array('1.0', '2.0', '2.1'))) { + return PEAR::raiseError('package.xml version "' . $packageversion[1] . + '" is not supported, only 1.0, 2.0, and 2.1 are supported.'); + } + + $object = &$this->parserFactory($packageversion[1]); + if ($this->_logger) { + $object->setLogger($this->_logger); + } + + $object->setConfig($this->_config); + $pf = $object->parse($data, $file, $archive); + if (PEAR::isError($pf)) { + return $pf; + } + + if ($this->_rawReturn) { + return $pf; + } + + if (!$pf->validate($state)) {; + if ($this->_config->get('verbose') > 0 + && $this->_logger && $pf->getValidationWarnings(false) + ) { + foreach ($pf->getValidationWarnings(false) as $warning) { + $this->_logger->log(0, 'ERROR: ' . $warning['message']); + } + } + + $a = PEAR::raiseError('Parsing of package.xml from file "' . $file . '" failed', + 2, null, null, $pf->getValidationWarnings()); + return $a; + } + + if ($this->_logger && $pf->getValidationWarnings(false)) { + foreach ($pf->getValidationWarnings() as $warning) { + $this->_logger->log(0, 'WARNING: ' . $warning['message']); + } + } + + if (method_exists($pf, 'flattenFilelist')) { + $pf->flattenFilelist(); // for v2 + } + + return $pf; + } elseif (preg_match('/]+version="([^"]+)"/', $data, $packageversion)) { + $a = PEAR::raiseError('package.xml file "' . $file . + '" has unsupported package.xml version "' . $packageversion[1] . '"'); + return $a; + } else { + if (!class_exists('PEAR_ErrorStack')) { + require_once 'PEAR/ErrorStack.php'; + } + + PEAR_ErrorStack::staticPush('PEAR_PackageFile', + PEAR_PACKAGEFILE_ERROR_NO_PACKAGEVERSION, + 'warning', array('xml' => $data), 'package.xml "' . $file . + '" has no package.xml version'); + $object = &$this->parserFactory('1.0'); + $object->setConfig($this->_config); + $pf = $object->parse($data, $file, $archive); + if (PEAR::isError($pf)) { + return $pf; + } + + if ($this->_rawReturn) { + return $pf; + } + + if (!$pf->validate($state)) { + $a = PEAR::raiseError('Parsing of package.xml from file "' . $file . '" failed', + 2, null, null, $pf->getValidationWarnings()); + return $a; + } + + if ($this->_logger && $pf->getValidationWarnings(false)) { + foreach ($pf->getValidationWarnings() as $warning) { + $this->_logger->log(0, 'WARNING: ' . $warning['message']); + } + } + + if (method_exists($pf, 'flattenFilelist')) { + $pf->flattenFilelist(); // for v2 + } + + return $pf; + } + } + + /** + * Register a temporary file or directory. When the destructor is + * executed, all registered temporary files and directories are + * removed. + * + * @param string $file name of file or directory + * @return void + */ + function addTempFile($file) + { + $GLOBALS['_PEAR_Common_tempfiles'][] = $file; + } + + /** + * Create a PEAR_PackageFile_v* from a compresed Tar or Tgz file. + * @access public + * @param string contents of package.xml file + * @param int package state (one of PEAR_VALIDATE_* constants) + * @return PEAR_PackageFile_v1|PEAR_PackageFile_v2 + * @using Archive_Tar to extract the files + * @using fromPackageFile() to load the package after the package.xml + * file is extracted. + */ + function &fromTgzFile($file, $state) + { + if (!class_exists('Archive_Tar')) { + require_once 'Archive/Tar.php'; + } + + $tar = new Archive_Tar($file); + if ($this->_debug <= 1) { + $tar->pushErrorHandling(PEAR_ERROR_RETURN); + } + + $content = $tar->listContent(); + if ($this->_debug <= 1) { + $tar->popErrorHandling(); + } + + if (!is_array($content)) { + if (is_string($file) && strlen($file < 255) && + (!file_exists($file) || !@is_file($file))) { + $ret = PEAR::raiseError("could not open file \"$file\""); + return $ret; + } + + $file = realpath($file); + $ret = PEAR::raiseError("Could not get contents of package \"$file\"". + '. Invalid tgz file.'); + return $ret; + } + + if (!count($content) && !@is_file($file)) { + $ret = PEAR::raiseError("could not open file \"$file\""); + return $ret; + } + + $xml = null; + $origfile = $file; + foreach ($content as $file) { + $name = $file['filename']; + if ($name == 'package2.xml') { // allow a .tgz to distribute both versions + $xml = $name; + break; + } + + if ($name == 'package.xml') { + $xml = $name; + break; + } elseif (preg_match('/package.xml$/', $name, $match)) { + $xml = $name; + break; + } + } + + if ($this->_tmpdir) { + $tmpdir = $this->_tmpdir; + } else { + $tmpdir = System::mkTemp(array('-t', $this->_config->get('temp_dir'), '-d', 'pear')); + if ($tmpdir === false) { + $ret = PEAR::raiseError("there was a problem with getting the configured temp directory"); + return $ret; + } + + PEAR_PackageFile::addTempFile($tmpdir); + } + + $this->_extractErrors(); + PEAR::staticPushErrorHandling(PEAR_ERROR_CALLBACK, array($this, '_extractErrors')); + if (!$xml || !$tar->extractList(array($xml), $tmpdir)) { + $extra = implode("\n", $this->_extractErrors()); + if ($extra) { + $extra = ' ' . $extra; + } + + PEAR::staticPopErrorHandling(); + $ret = PEAR::raiseError('could not extract the package.xml file from "' . + $origfile . '"' . $extra); + return $ret; + } + + PEAR::staticPopErrorHandling(); + $ret = &PEAR_PackageFile::fromPackageFile("$tmpdir/$xml", $state, $origfile); + return $ret; + } + + /** + * helper for extracting Archive_Tar errors + * @var array + * @access private + */ + var $_extractErrors = array(); + + /** + * helper callback for extracting Archive_Tar errors + * + * @param PEAR_Error|null $err + * @return array + * @access private + */ + function _extractErrors($err = null) + { + static $errors = array(); + if ($err === null) { + $e = $errors; + $errors = array(); + return $e; + } + $errors[] = $err->getMessage(); + } + + /** + * Create a PEAR_PackageFile_v* from a package.xml file. + * + * @access public + * @param string $descfile name of package xml file + * @param int $state package state (one of PEAR_VALIDATE_* constants) + * @param string|false $archive name of the archive this package.xml came + * from, if any + * @return PEAR_PackageFile_v1|PEAR_PackageFile_v2 + * @uses PEAR_PackageFile::fromXmlString to create the oject after the + * XML is loaded from the package.xml file. + */ + function &fromPackageFile($descfile, $state, $archive = false) + { + $fp = false; + if (is_string($descfile) && strlen($descfile) < 255 && + ( + !file_exists($descfile) || !is_file($descfile) || !is_readable($descfile) + || (!$fp = @fopen($descfile, 'r')) + ) + ) { + $a = PEAR::raiseError("Unable to open $descfile"); + return $a; + } + + // read the whole thing so we only get one cdata callback + // for each block of cdata + fclose($fp); + $data = file_get_contents($descfile); + $ret = &PEAR_PackageFile::fromXmlString($data, $state, $descfile, $archive); + return $ret; + } + + + /** + * Create a PEAR_PackageFile_v* from a .tgz archive or package.xml file. + * + * This method is able to extract information about a package from a .tgz + * archive or from a XML package definition file. + * + * @access public + * @param string $info file name + * @param int $state package state (one of PEAR_VALIDATE_* constants) + * @return PEAR_PackageFile_v1|PEAR_PackageFile_v2 + * @uses fromPackageFile() if the file appears to be XML + * @uses fromTgzFile() to load all non-XML files + */ + function &fromAnyFile($info, $state) + { + if (is_dir($info)) { + $dir_name = realpath($info); + if (file_exists($dir_name . '/package.xml')) { + $info = PEAR_PackageFile::fromPackageFile($dir_name . '/package.xml', $state); + } elseif (file_exists($dir_name . '/package2.xml')) { + $info = PEAR_PackageFile::fromPackageFile($dir_name . '/package2.xml', $state); + } else { + $info = PEAR::raiseError("No package definition found in '$info' directory"); + } + + return $info; + } + + $fp = false; + if (is_string($info) && strlen($info) < 255 && + (file_exists($info) || ($fp = @fopen($info, 'r'))) + ) { + + if ($fp) { + fclose($fp); + } + + $tmp = substr($info, -4); + if ($tmp == '.xml') { + $info = &PEAR_PackageFile::fromPackageFile($info, $state); + } elseif ($tmp == '.tar' || $tmp == '.tgz') { + $info = &PEAR_PackageFile::fromTgzFile($info, $state); + } else { + $fp = fopen($info, 'r'); + $test = fread($fp, 5); + fclose($fp); + if ($test == ' + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: v1.php 286494 2009-07-29 06:57:11Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ +/** + * needed for PEAR_VALIDATE_* constants + */ +require_once 'PEAR/Validate.php'; +require_once 'System.php'; +require_once 'PEAR/PackageFile/v2.php'; +/** + * This class converts a PEAR_PackageFile_v1 object into any output format. + * + * Supported output formats include array, XML string, and a PEAR_PackageFile_v2 + * object, for converting package.xml 1.0 into package.xml 2.0 with no sweat. + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.1 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_PackageFile_Generator_v1 +{ + /** + * @var PEAR_PackageFile_v1 + */ + var $_packagefile; + function PEAR_PackageFile_Generator_v1(&$packagefile) + { + $this->_packagefile = &$packagefile; + } + + function getPackagerVersion() + { + return '1.9.1'; + } + + /** + * @param PEAR_Packager + * @param bool if true, a .tgz is written, otherwise a .tar is written + * @param string|null directory in which to save the .tgz + * @return string|PEAR_Error location of package or error object + */ + function toTgz(&$packager, $compress = true, $where = null) + { + require_once 'Archive/Tar.php'; + if ($where === null) { + if (!($where = System::mktemp(array('-d')))) { + return PEAR::raiseError('PEAR_Packagefile_v1::toTgz: mktemp failed'); + } + } elseif (!@System::mkDir(array('-p', $where))) { + return PEAR::raiseError('PEAR_Packagefile_v1::toTgz: "' . $where . '" could' . + ' not be created'); + } + if (file_exists($where . DIRECTORY_SEPARATOR . 'package.xml') && + !is_file($where . DIRECTORY_SEPARATOR . 'package.xml')) { + return PEAR::raiseError('PEAR_Packagefile_v1::toTgz: unable to save package.xml as' . + ' "' . $where . DIRECTORY_SEPARATOR . 'package.xml"'); + } + if (!$this->_packagefile->validate(PEAR_VALIDATE_PACKAGING)) { + return PEAR::raiseError('PEAR_Packagefile_v1::toTgz: invalid package file'); + } + $pkginfo = $this->_packagefile->getArray(); + $ext = $compress ? '.tgz' : '.tar'; + $pkgver = $pkginfo['package'] . '-' . $pkginfo['version']; + $dest_package = getcwd() . DIRECTORY_SEPARATOR . $pkgver . $ext; + if (file_exists(getcwd() . DIRECTORY_SEPARATOR . $pkgver . $ext) && + !is_file(getcwd() . DIRECTORY_SEPARATOR . $pkgver . $ext)) { + return PEAR::raiseError('PEAR_Packagefile_v1::toTgz: cannot create tgz file "' . + getcwd() . DIRECTORY_SEPARATOR . $pkgver . $ext . '"'); + } + if ($pkgfile = $this->_packagefile->getPackageFile()) { + $pkgdir = dirname(realpath($pkgfile)); + $pkgfile = basename($pkgfile); + } else { + return PEAR::raiseError('PEAR_Packagefile_v1::toTgz: package file object must ' . + 'be created from a real file'); + } + // {{{ Create the package file list + $filelist = array(); + $i = 0; + + foreach ($this->_packagefile->getFilelist() as $fname => $atts) { + $file = $pkgdir . DIRECTORY_SEPARATOR . $fname; + if (!file_exists($file)) { + return PEAR::raiseError("File does not exist: $fname"); + } else { + $filelist[$i++] = $file; + if (!isset($atts['md5sum'])) { + $this->_packagefile->setFileAttribute($fname, 'md5sum', md5_file($file)); + } + $packager->log(2, "Adding file $fname"); + } + } + // }}} + $packagexml = $this->toPackageFile($where, PEAR_VALIDATE_PACKAGING, 'package.xml', true); + if ($packagexml) { + $tar =& new Archive_Tar($dest_package, $compress); + $tar->setErrorHandling(PEAR_ERROR_RETURN); // XXX Don't print errors + // ----- Creates with the package.xml file + $ok = $tar->createModify(array($packagexml), '', $where); + if (PEAR::isError($ok)) { + return $ok; + } elseif (!$ok) { + return PEAR::raiseError('PEAR_Packagefile_v1::toTgz: tarball creation failed'); + } + // ----- Add the content of the package + if (!$tar->addModify($filelist, $pkgver, $pkgdir)) { + return PEAR::raiseError('PEAR_Packagefile_v1::toTgz: tarball creation failed'); + } + return $dest_package; + } + } + + /** + * @param string|null directory to place the package.xml in, or null for a temporary dir + * @param int one of the PEAR_VALIDATE_* constants + * @param string name of the generated file + * @param bool if true, then no analysis will be performed on role="php" files + * @return string|PEAR_Error path to the created file on success + */ + function toPackageFile($where = null, $state = PEAR_VALIDATE_NORMAL, $name = 'package.xml', + $nofilechecking = false) + { + if (!$this->_packagefile->validate($state, $nofilechecking)) { + return PEAR::raiseError('PEAR_Packagefile_v1::toPackageFile: invalid package.xml', + null, null, null, $this->_packagefile->getValidationWarnings()); + } + if ($where === null) { + if (!($where = System::mktemp(array('-d')))) { + return PEAR::raiseError('PEAR_Packagefile_v1::toPackageFile: mktemp failed'); + } + } elseif (!@System::mkDir(array('-p', $where))) { + return PEAR::raiseError('PEAR_Packagefile_v1::toPackageFile: "' . $where . '" could' . + ' not be created'); + } + $newpkgfile = $where . DIRECTORY_SEPARATOR . $name; + $np = @fopen($newpkgfile, 'wb'); + if (!$np) { + return PEAR::raiseError('PEAR_Packagefile_v1::toPackageFile: unable to save ' . + "$name as $newpkgfile"); + } + fwrite($np, $this->toXml($state, true)); + fclose($np); + return $newpkgfile; + } + + /** + * fix both XML encoding to be UTF8, and replace standard XML entities < > " & ' + * + * @param string $string + * @return string + * @access private + */ + function _fixXmlEncoding($string) + { + if (version_compare(phpversion(), '5.0.0', 'lt')) { + $string = utf8_encode($string); + } + return strtr($string, array( + '&' => '&', + '>' => '>', + '<' => '<', + '"' => '"', + '\'' => ''' )); + } + + /** + * Return an XML document based on the package info (as returned + * by the PEAR_Common::infoFrom* methods). + * + * @return string XML data + */ + function toXml($state = PEAR_VALIDATE_NORMAL, $nofilevalidation = false) + { + $this->_packagefile->setDate(date('Y-m-d')); + if (!$this->_packagefile->validate($state, $nofilevalidation)) { + return false; + } + $pkginfo = $this->_packagefile->getArray(); + static $maint_map = array( + "handle" => "user", + "name" => "name", + "email" => "email", + "role" => "role", + ); + $ret = "\n"; + $ret .= "\n"; + $ret .= "\n" . +" $pkginfo[package]"; + if (isset($pkginfo['extends'])) { + $ret .= "\n$pkginfo[extends]"; + } + $ret .= + "\n ".$this->_fixXmlEncoding($pkginfo['summary'])."\n" . +" ".trim($this->_fixXmlEncoding($pkginfo['description']))."\n \n" . +" \n"; + foreach ($pkginfo['maintainers'] as $maint) { + $ret .= " \n"; + foreach ($maint_map as $idx => $elm) { + $ret .= " <$elm>"; + $ret .= $this->_fixXmlEncoding($maint[$idx]); + $ret .= "\n"; + } + $ret .= " \n"; + } + $ret .= " \n"; + $ret .= $this->_makeReleaseXml($pkginfo, false, $state); + if (isset($pkginfo['changelog']) && count($pkginfo['changelog']) > 0) { + $ret .= " \n"; + foreach ($pkginfo['changelog'] as $oldrelease) { + $ret .= $this->_makeReleaseXml($oldrelease, true); + } + $ret .= " \n"; + } + $ret .= "\n"; + return $ret; + } + + // }}} + // {{{ _makeReleaseXml() + + /** + * Generate part of an XML description with release information. + * + * @param array $pkginfo array with release information + * @param bool $changelog whether the result will be in a changelog element + * + * @return string XML data + * + * @access private + */ + function _makeReleaseXml($pkginfo, $changelog = false, $state = PEAR_VALIDATE_NORMAL) + { + // XXX QUOTE ENTITIES IN PCDATA, OR EMBED IN CDATA BLOCKS!! + $indent = $changelog ? " " : ""; + $ret = "$indent \n"; + if (!empty($pkginfo['version'])) { + $ret .= "$indent $pkginfo[version]\n"; + } + if (!empty($pkginfo['release_date'])) { + $ret .= "$indent $pkginfo[release_date]\n"; + } + if (!empty($pkginfo['release_license'])) { + $ret .= "$indent $pkginfo[release_license]\n"; + } + if (!empty($pkginfo['release_state'])) { + $ret .= "$indent $pkginfo[release_state]\n"; + } + if (!empty($pkginfo['release_notes'])) { + $ret .= "$indent ".trim($this->_fixXmlEncoding($pkginfo['release_notes'])) + ."\n$indent \n"; + } + if (!empty($pkginfo['release_warnings'])) { + $ret .= "$indent ".$this->_fixXmlEncoding($pkginfo['release_warnings'])."\n"; + } + if (isset($pkginfo['release_deps']) && sizeof($pkginfo['release_deps']) > 0) { + $ret .= "$indent \n"; + foreach ($pkginfo['release_deps'] as $dep) { + $ret .= "$indent _fixXmlEncoding($c['name']) . "\""; + if (isset($c['default'])) { + $ret .= " default=\"" . $this->_fixXmlEncoding($c['default']) . "\""; + } + $ret .= " prompt=\"" . $this->_fixXmlEncoding($c['prompt']) . "\""; + $ret .= "/>\n"; + } + $ret .= "$indent \n"; + } + if (isset($pkginfo['provides'])) { + foreach ($pkginfo['provides'] as $key => $what) { + $ret .= "$indent recursiveXmlFilelist($pkginfo['filelist']); + } else { + foreach ($pkginfo['filelist'] as $file => $fa) { + if (!isset($fa['role'])) { + $fa['role'] = ''; + } + $ret .= "$indent _fixXmlEncoding($fa['baseinstalldir']) . '"'; + } + if (isset($fa['md5sum'])) { + $ret .= " md5sum=\"$fa[md5sum]\""; + } + if (isset($fa['platform'])) { + $ret .= " platform=\"$fa[platform]\""; + } + if (!empty($fa['install-as'])) { + $ret .= ' install-as="' . + $this->_fixXmlEncoding($fa['install-as']) . '"'; + } + $ret .= ' name="' . $this->_fixXmlEncoding($file) . '"'; + if (empty($fa['replacements'])) { + $ret .= "/>\n"; + } else { + $ret .= ">\n"; + foreach ($fa['replacements'] as $r) { + $ret .= "$indent $v) { + $ret .= " $k=\"" . $this->_fixXmlEncoding($v) .'"'; + } + $ret .= "/>\n"; + } + $ret .= "$indent \n"; + } + } + } + $ret .= "$indent \n"; + } + $ret .= "$indent \n"; + return $ret; + } + + /** + * @param array + * @access protected + */ + function recursiveXmlFilelist($list) + { + $this->_dirs = array(); + foreach ($list as $file => $attributes) { + $this->_addDir($this->_dirs, explode('/', dirname($file)), $file, $attributes); + } + return $this->_formatDir($this->_dirs); + } + + /** + * @param array + * @param array + * @param string|null + * @param array|null + * @access private + */ + function _addDir(&$dirs, $dir, $file = null, $attributes = null) + { + if ($dir == array() || $dir == array('.')) { + $dirs['files'][basename($file)] = $attributes; + return; + } + $curdir = array_shift($dir); + if (!isset($dirs['dirs'][$curdir])) { + $dirs['dirs'][$curdir] = array(); + } + $this->_addDir($dirs['dirs'][$curdir], $dir, $file, $attributes); + } + + /** + * @param array + * @param string + * @param string + * @access private + */ + function _formatDir($dirs, $indent = '', $curdir = '') + { + $ret = ''; + if (!count($dirs)) { + return ''; + } + if (isset($dirs['dirs'])) { + uksort($dirs['dirs'], 'strnatcasecmp'); + foreach ($dirs['dirs'] as $dir => $contents) { + $usedir = "$curdir/$dir"; + $ret .= "$indent \n"; + $ret .= $this->_formatDir($contents, "$indent ", $usedir); + $ret .= "$indent \n"; + } + } + if (isset($dirs['files'])) { + uksort($dirs['files'], 'strnatcasecmp'); + foreach ($dirs['files'] as $file => $attribs) { + $ret .= $this->_formatFile($file, $attribs, $indent); + } + } + return $ret; + } + + /** + * @param string + * @param array + * @param string + * @access private + */ + function _formatFile($file, $attributes, $indent) + { + $ret = "$indent _fixXmlEncoding($attributes['baseinstalldir']) . '"'; + } + if (isset($attributes['md5sum'])) { + $ret .= " md5sum=\"$attributes[md5sum]\""; + } + if (isset($attributes['platform'])) { + $ret .= " platform=\"$attributes[platform]\""; + } + if (!empty($attributes['install-as'])) { + $ret .= ' install-as="' . + $this->_fixXmlEncoding($attributes['install-as']) . '"'; + } + $ret .= ' name="' . $this->_fixXmlEncoding($file) . '"'; + if (empty($attributes['replacements'])) { + $ret .= "/>\n"; + } else { + $ret .= ">\n"; + foreach ($attributes['replacements'] as $r) { + $ret .= "$indent $v) { + $ret .= " $k=\"" . $this->_fixXmlEncoding($v) .'"'; + } + $ret .= "/>\n"; + } + $ret .= "$indent \n"; + } + return $ret; + } + + // {{{ _unIndent() + + /** + * Unindent given string (?) + * + * @param string $str The string that has to be unindented. + * @return string + * @access private + */ + function _unIndent($str) + { + // remove leading newlines + $str = preg_replace('/^[\r\n]+/', '', $str); + // find whitespace at the beginning of the first line + $indent_len = strspn($str, " \t"); + $indent = substr($str, 0, $indent_len); + $data = ''; + // remove the same amount of whitespace from following lines + foreach (explode("\n", $str) as $line) { + if (substr($line, 0, $indent_len) == $indent) { + $data .= substr($line, $indent_len) . "\n"; + } + } + return $data; + } + + /** + * @return array + */ + function dependenciesToV2() + { + $arr = array(); + $this->_convertDependencies2_0($arr); + return $arr['dependencies']; + } + + /** + * Convert a package.xml version 1.0 into version 2.0 + * + * Note that this does a basic conversion, to allow more advanced + * features like bundles and multiple releases + * @param string the classname to instantiate and return. This must be + * PEAR_PackageFile_v2 or a descendant + * @param boolean if true, only valid, deterministic package.xml 1.0 as defined by the + * strictest parameters will be converted + * @return PEAR_PackageFile_v2|PEAR_Error + */ + function &toV2($class = 'PEAR_PackageFile_v2', $strict = false) + { + if ($strict) { + if (!$this->_packagefile->validate()) { + $a = PEAR::raiseError('invalid package.xml version 1.0 cannot be converted' . + ' to version 2.0', null, null, null, + $this->_packagefile->getValidationWarnings(true)); + return $a; + } + } + + $arr = array( + 'attribs' => array( + 'version' => '2.0', + 'xmlns' => 'http://pear.php.net/dtd/package-2.0', + 'xmlns:tasks' => 'http://pear.php.net/dtd/tasks-1.0', + 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', + 'xsi:schemaLocation' => "http://pear.php.net/dtd/tasks-1.0\n" . +"http://pear.php.net/dtd/tasks-1.0.xsd\n" . +"http://pear.php.net/dtd/package-2.0\n" . +'http://pear.php.net/dtd/package-2.0.xsd', + ), + 'name' => $this->_packagefile->getPackage(), + 'channel' => 'pear.php.net', + ); + $arr['summary'] = $this->_packagefile->getSummary(); + $arr['description'] = $this->_packagefile->getDescription(); + $maintainers = $this->_packagefile->getMaintainers(); + foreach ($maintainers as $maintainer) { + if ($maintainer['role'] != 'lead') { + continue; + } + $new = array( + 'name' => $maintainer['name'], + 'user' => $maintainer['handle'], + 'email' => $maintainer['email'], + 'active' => 'yes', + ); + $arr['lead'][] = $new; + } + + if (!isset($arr['lead'])) { // some people... you know? + $arr['lead'] = array( + 'name' => 'unknown', + 'user' => 'unknown', + 'email' => 'noleadmaintainer@example.com', + 'active' => 'no', + ); + } + + if (count($arr['lead']) == 1) { + $arr['lead'] = $arr['lead'][0]; + } + + foreach ($maintainers as $maintainer) { + if ($maintainer['role'] == 'lead') { + continue; + } + $new = array( + 'name' => $maintainer['name'], + 'user' => $maintainer['handle'], + 'email' => $maintainer['email'], + 'active' => 'yes', + ); + $arr[$maintainer['role']][] = $new; + } + + if (isset($arr['developer']) && count($arr['developer']) == 1) { + $arr['developer'] = $arr['developer'][0]; + } + + if (isset($arr['contributor']) && count($arr['contributor']) == 1) { + $arr['contributor'] = $arr['contributor'][0]; + } + + if (isset($arr['helper']) && count($arr['helper']) == 1) { + $arr['helper'] = $arr['helper'][0]; + } + + $arr['date'] = $this->_packagefile->getDate(); + $arr['version'] = + array( + 'release' => $this->_packagefile->getVersion(), + 'api' => $this->_packagefile->getVersion(), + ); + $arr['stability'] = + array( + 'release' => $this->_packagefile->getState(), + 'api' => $this->_packagefile->getState(), + ); + $licensemap = + array( + 'php' => 'http://www.php.net/license', + 'php license' => 'http://www.php.net/license', + 'lgpl' => 'http://www.gnu.org/copyleft/lesser.html', + 'bsd' => 'http://www.opensource.org/licenses/bsd-license.php', + 'bsd style' => 'http://www.opensource.org/licenses/bsd-license.php', + 'bsd-style' => 'http://www.opensource.org/licenses/bsd-license.php', + 'mit' => 'http://www.opensource.org/licenses/mit-license.php', + 'gpl' => 'http://www.gnu.org/copyleft/gpl.html', + 'apache' => 'http://www.opensource.org/licenses/apache2.0.php' + ); + + if (isset($licensemap[strtolower($this->_packagefile->getLicense())])) { + $arr['license'] = array( + 'attribs' => array('uri' => + $licensemap[strtolower($this->_packagefile->getLicense())]), + '_content' => $this->_packagefile->getLicense() + ); + } else { + // don't use bogus uri + $arr['license'] = $this->_packagefile->getLicense(); + } + + $arr['notes'] = $this->_packagefile->getNotes(); + $temp = array(); + $arr['contents'] = $this->_convertFilelist2_0($temp); + $this->_convertDependencies2_0($arr); + $release = ($this->_packagefile->getConfigureOptions() || $this->_isExtension) ? + 'extsrcrelease' : 'phprelease'; + if ($release == 'extsrcrelease') { + $arr['channel'] = 'pecl.php.net'; + $arr['providesextension'] = $arr['name']; // assumption + } + + $arr[$release] = array(); + if ($this->_packagefile->getConfigureOptions()) { + $arr[$release]['configureoption'] = $this->_packagefile->getConfigureOptions(); + foreach ($arr[$release]['configureoption'] as $i => $opt) { + $arr[$release]['configureoption'][$i] = array('attribs' => $opt); + } + if (count($arr[$release]['configureoption']) == 1) { + $arr[$release]['configureoption'] = $arr[$release]['configureoption'][0]; + } + } + + $this->_convertRelease2_0($arr[$release], $temp); + if ($release == 'extsrcrelease' && count($arr[$release]) > 1) { + // multiple extsrcrelease tags added in PEAR 1.4.1 + $arr['dependencies']['required']['pearinstaller']['min'] = '1.4.1'; + } + + if ($cl = $this->_packagefile->getChangelog()) { + foreach ($cl as $release) { + $rel = array(); + $rel['version'] = + array( + 'release' => $release['version'], + 'api' => $release['version'], + ); + if (!isset($release['release_state'])) { + $release['release_state'] = 'stable'; + } + + $rel['stability'] = + array( + 'release' => $release['release_state'], + 'api' => $release['release_state'], + ); + if (isset($release['release_date'])) { + $rel['date'] = $release['release_date']; + } else { + $rel['date'] = date('Y-m-d'); + } + + if (isset($release['release_license'])) { + if (isset($licensemap[strtolower($release['release_license'])])) { + $uri = $licensemap[strtolower($release['release_license'])]; + } else { + $uri = 'http://www.example.com'; + } + $rel['license'] = array( + 'attribs' => array('uri' => $uri), + '_content' => $release['release_license'] + ); + } else { + $rel['license'] = $arr['license']; + } + + if (!isset($release['release_notes'])) { + $release['release_notes'] = 'no release notes'; + } + + $rel['notes'] = $release['release_notes']; + $arr['changelog']['release'][] = $rel; + } + } + + $ret = new $class; + $ret->setConfig($this->_packagefile->_config); + if (isset($this->_packagefile->_logger) && is_object($this->_packagefile->_logger)) { + $ret->setLogger($this->_packagefile->_logger); + } + + $ret->fromArray($arr); + return $ret; + } + + /** + * @param array + * @param bool + * @access private + */ + function _convertDependencies2_0(&$release, $internal = false) + { + $peardep = array('pearinstaller' => + array('min' => '1.4.0b1')); // this is a lot safer + $required = $optional = array(); + $release['dependencies'] = array('required' => array()); + if ($this->_packagefile->hasDeps()) { + foreach ($this->_packagefile->getDeps() as $dep) { + if (!isset($dep['optional']) || $dep['optional'] == 'no') { + $required[] = $dep; + } else { + $optional[] = $dep; + } + } + foreach (array('required', 'optional') as $arr) { + $deps = array(); + foreach ($$arr as $dep) { + // organize deps by dependency type and name + if (!isset($deps[$dep['type']])) { + $deps[$dep['type']] = array(); + } + if (isset($dep['name'])) { + $deps[$dep['type']][$dep['name']][] = $dep; + } else { + $deps[$dep['type']][] = $dep; + } + } + do { + if (isset($deps['php'])) { + $php = array(); + if (count($deps['php']) > 1) { + $php = $this->_processPhpDeps($deps['php']); + } else { + if (!isset($deps['php'][0])) { + list($key, $blah) = each ($deps['php']); // stupid buggy versions + $deps['php'] = array($blah[0]); + } + $php = $this->_processDep($deps['php'][0]); + if (!$php) { + break; // poor mans throw + } + } + $release['dependencies'][$arr]['php'] = $php; + } + } while (false); + do { + if (isset($deps['pkg'])) { + $pkg = array(); + $pkg = $this->_processMultipleDepsName($deps['pkg']); + if (!$pkg) { + break; // poor mans throw + } + $release['dependencies'][$arr]['package'] = $pkg; + } + } while (false); + do { + if (isset($deps['ext'])) { + $pkg = array(); + $pkg = $this->_processMultipleDepsName($deps['ext']); + $release['dependencies'][$arr]['extension'] = $pkg; + } + } while (false); + // skip sapi - it's not supported so nobody will have used it + // skip os - it's not supported in 1.0 + } + } + if (isset($release['dependencies']['required'])) { + $release['dependencies']['required'] = + array_merge($peardep, $release['dependencies']['required']); + } else { + $release['dependencies']['required'] = $peardep; + } + if (!isset($release['dependencies']['required']['php'])) { + $release['dependencies']['required']['php'] = + array('min' => '4.0.0'); + } + $order = array(); + $bewm = $release['dependencies']['required']; + $order['php'] = $bewm['php']; + $order['pearinstaller'] = $bewm['pearinstaller']; + isset($bewm['package']) ? $order['package'] = $bewm['package'] :0; + isset($bewm['extension']) ? $order['extension'] = $bewm['extension'] :0; + $release['dependencies']['required'] = $order; + } + + /** + * @param array + * @access private + */ + function _convertFilelist2_0(&$package) + { + $ret = array('dir' => + array( + 'attribs' => array('name' => '/'), + 'file' => array() + ) + ); + $package['platform'] = + $package['install-as'] = array(); + $this->_isExtension = false; + foreach ($this->_packagefile->getFilelist() as $name => $file) { + $file['name'] = $name; + if (isset($file['role']) && $file['role'] == 'src') { + $this->_isExtension = true; + } + if (isset($file['replacements'])) { + $repl = $file['replacements']; + unset($file['replacements']); + } else { + unset($repl); + } + if (isset($file['install-as'])) { + $package['install-as'][$name] = $file['install-as']; + unset($file['install-as']); + } + if (isset($file['platform'])) { + $package['platform'][$name] = $file['platform']; + unset($file['platform']); + } + $file = array('attribs' => $file); + if (isset($repl)) { + foreach ($repl as $replace ) { + $file['tasks:replace'][] = array('attribs' => $replace); + } + if (count($repl) == 1) { + $file['tasks:replace'] = $file['tasks:replace'][0]; + } + } + $ret['dir']['file'][] = $file; + } + return $ret; + } + + /** + * Post-process special files with install-as/platform attributes and + * make the release tag. + * + * This complex method follows this work-flow to create the release tags: + * + *
    +     * - if any install-as/platform exist, create a generic release and fill it with
    +     *   o  tags for 
    +     *   o  tags for 
    +     *   o  tags for 
    +     *   o  tags for 
    +     * - create a release for each platform encountered and fill with
    +     *   o  tags for 
    +     *   o  tags for 
    +     *   o  tags for 
    +     *   o  tags for 
    +     *   o  tags for 
    +     *   o  tags for 
    +     *   o  tags for 
    +     * 
    + * + * It does this by accessing the $package parameter, which contains an array with + * indices: + * + * - platform: mapping of file => OS the file should be installed on + * - install-as: mapping of file => installed name + * - osmap: mapping of OS => list of files that should be installed + * on that OS + * - notosmap: mapping of OS => list of files that should not be + * installed on that OS + * + * @param array + * @param array + * @access private + */ + function _convertRelease2_0(&$release, $package) + { + //- if any install-as/platform exist, create a generic release and fill it with + if (count($package['platform']) || count($package['install-as'])) { + $generic = array(); + $genericIgnore = array(); + foreach ($package['install-as'] as $file => $as) { + //o tags for + if (!isset($package['platform'][$file])) { + $generic[] = $file; + continue; + } + //o tags for + if (isset($package['platform'][$file]) && + $package['platform'][$file]{0} == '!') { + $generic[] = $file; + continue; + } + //o tags for + if (isset($package['platform'][$file]) && + $package['platform'][$file]{0} != '!') { + $genericIgnore[] = $file; + continue; + } + } + foreach ($package['platform'] as $file => $platform) { + if (isset($package['install-as'][$file])) { + continue; + } + if ($platform{0} != '!') { + //o tags for + $genericIgnore[] = $file; + } + } + if (count($package['platform'])) { + $oses = $notplatform = $platform = array(); + foreach ($package['platform'] as $file => $os) { + // get a list of oses + if ($os{0} == '!') { + if (isset($oses[substr($os, 1)])) { + continue; + } + $oses[substr($os, 1)] = count($oses); + } else { + if (isset($oses[$os])) { + continue; + } + $oses[$os] = count($oses); + } + } + //- create a release for each platform encountered and fill with + foreach ($oses as $os => $releaseNum) { + $release[$releaseNum]['installconditions']['os']['name'] = $os; + $release[$releaseNum]['filelist'] = array('install' => array(), + 'ignore' => array()); + foreach ($package['install-as'] as $file => $as) { + //o tags for + if (!isset($package['platform'][$file])) { + $release[$releaseNum]['filelist']['install'][] = + array( + 'attribs' => array( + 'name' => $file, + 'as' => $as, + ), + ); + continue; + } + //o tags for + // + if (isset($package['platform'][$file]) && + $package['platform'][$file] == $os) { + $release[$releaseNum]['filelist']['install'][] = + array( + 'attribs' => array( + 'name' => $file, + 'as' => $as, + ), + ); + continue; + } + //o tags for + // + if (isset($package['platform'][$file]) && + $package['platform'][$file] != "!$os" && + $package['platform'][$file]{0} == '!') { + $release[$releaseNum]['filelist']['install'][] = + array( + 'attribs' => array( + 'name' => $file, + 'as' => $as, + ), + ); + continue; + } + //o tags for + // + if (isset($package['platform'][$file]) && + $package['platform'][$file] == "!$os") { + $release[$releaseNum]['filelist']['ignore'][] = + array( + 'attribs' => array( + 'name' => $file, + ), + ); + continue; + } + //o tags for + // + if (isset($package['platform'][$file]) && + $package['platform'][$file]{0} != '!' && + $package['platform'][$file] != $os) { + $release[$releaseNum]['filelist']['ignore'][] = + array( + 'attribs' => array( + 'name' => $file, + ), + ); + continue; + } + } + foreach ($package['platform'] as $file => $platform) { + if (isset($package['install-as'][$file])) { + continue; + } + //o tags for + if ($platform == "!$os") { + $release[$releaseNum]['filelist']['ignore'][] = + array( + 'attribs' => array( + 'name' => $file, + ), + ); + continue; + } + //o tags for + if ($platform{0} != '!' && $platform != $os) { + $release[$releaseNum]['filelist']['ignore'][] = + array( + 'attribs' => array( + 'name' => $file, + ), + ); + } + } + if (!count($release[$releaseNum]['filelist']['install'])) { + unset($release[$releaseNum]['filelist']['install']); + } + if (!count($release[$releaseNum]['filelist']['ignore'])) { + unset($release[$releaseNum]['filelist']['ignore']); + } + } + if (count($generic) || count($genericIgnore)) { + $release[count($oses)] = array(); + if (count($generic)) { + foreach ($generic as $file) { + if (isset($package['install-as'][$file])) { + $installas = $package['install-as'][$file]; + } else { + $installas = $file; + } + $release[count($oses)]['filelist']['install'][] = + array( + 'attribs' => array( + 'name' => $file, + 'as' => $installas, + ) + ); + } + } + if (count($genericIgnore)) { + foreach ($genericIgnore as $file) { + $release[count($oses)]['filelist']['ignore'][] = + array( + 'attribs' => array( + 'name' => $file, + ) + ); + } + } + } + // cleanup + foreach ($release as $i => $rel) { + if (isset($rel['filelist']['install']) && + count($rel['filelist']['install']) == 1) { + $release[$i]['filelist']['install'] = + $release[$i]['filelist']['install'][0]; + } + if (isset($rel['filelist']['ignore']) && + count($rel['filelist']['ignore']) == 1) { + $release[$i]['filelist']['ignore'] = + $release[$i]['filelist']['ignore'][0]; + } + } + if (count($release) == 1) { + $release = $release[0]; + } + } else { + // no platform atts, but some install-as atts + foreach ($package['install-as'] as $file => $value) { + $release['filelist']['install'][] = + array( + 'attribs' => array( + 'name' => $file, + 'as' => $value + ) + ); + } + if (count($release['filelist']['install']) == 1) { + $release['filelist']['install'] = $release['filelist']['install'][0]; + } + } + } + } + + /** + * @param array + * @return array + * @access private + */ + function _processDep($dep) + { + if ($dep['type'] == 'php') { + if ($dep['rel'] == 'has') { + // come on - everyone has php! + return false; + } + } + $php = array(); + if ($dep['type'] != 'php') { + $php['name'] = $dep['name']; + if ($dep['type'] == 'pkg') { + $php['channel'] = 'pear.php.net'; + } + } + switch ($dep['rel']) { + case 'gt' : + $php['min'] = $dep['version']; + $php['exclude'] = $dep['version']; + break; + case 'ge' : + if (!isset($dep['version'])) { + if ($dep['type'] == 'php') { + if (isset($dep['name'])) { + $dep['version'] = $dep['name']; + } + } + } + $php['min'] = $dep['version']; + break; + case 'lt' : + $php['max'] = $dep['version']; + $php['exclude'] = $dep['version']; + break; + case 'le' : + $php['max'] = $dep['version']; + break; + case 'eq' : + $php['min'] = $dep['version']; + $php['max'] = $dep['version']; + break; + case 'ne' : + $php['exclude'] = $dep['version']; + break; + case 'not' : + $php['conflicts'] = 'yes'; + break; + } + return $php; + } + + /** + * @param array + * @return array + */ + function _processPhpDeps($deps) + { + $test = array(); + foreach ($deps as $dep) { + $test[] = $this->_processDep($dep); + } + $min = array(); + $max = array(); + foreach ($test as $dep) { + if (!$dep) { + continue; + } + if (isset($dep['min'])) { + $min[$dep['min']] = count($min); + } + if (isset($dep['max'])) { + $max[$dep['max']] = count($max); + } + } + if (count($min) > 0) { + uksort($min, 'version_compare'); + } + if (count($max) > 0) { + uksort($max, 'version_compare'); + } + if (count($min)) { + // get the highest minimum + $min = array_pop($a = array_flip($min)); + } else { + $min = false; + } + if (count($max)) { + // get the lowest maximum + $max = array_shift($a = array_flip($max)); + } else { + $max = false; + } + if ($min) { + $php['min'] = $min; + } + if ($max) { + $php['max'] = $max; + } + $exclude = array(); + foreach ($test as $dep) { + if (!isset($dep['exclude'])) { + continue; + } + $exclude[] = $dep['exclude']; + } + if (count($exclude)) { + $php['exclude'] = $exclude; + } + return $php; + } + + /** + * process multiple dependencies that have a name, like package deps + * @param array + * @return array + * @access private + */ + function _processMultipleDepsName($deps) + { + $ret = $tests = array(); + foreach ($deps as $name => $dep) { + foreach ($dep as $d) { + $tests[$name][] = $this->_processDep($d); + } + } + + foreach ($tests as $name => $test) { + $max = $min = $php = array(); + $php['name'] = $name; + foreach ($test as $dep) { + if (!$dep) { + continue; + } + if (isset($dep['channel'])) { + $php['channel'] = 'pear.php.net'; + } + if (isset($dep['conflicts']) && $dep['conflicts'] == 'yes') { + $php['conflicts'] = 'yes'; + } + if (isset($dep['min'])) { + $min[$dep['min']] = count($min); + } + if (isset($dep['max'])) { + $max[$dep['max']] = count($max); + } + } + if (count($min) > 0) { + uksort($min, 'version_compare'); + } + if (count($max) > 0) { + uksort($max, 'version_compare'); + } + if (count($min)) { + // get the highest minimum + $min = array_pop($a = array_flip($min)); + } else { + $min = false; + } + if (count($max)) { + // get the lowest maximum + $max = array_shift($a = array_flip($max)); + } else { + $max = false; + } + if ($min) { + $php['min'] = $min; + } + if ($max) { + $php['max'] = $max; + } + $exclude = array(); + foreach ($test as $dep) { + if (!isset($dep['exclude'])) { + continue; + } + $exclude[] = $dep['exclude']; + } + if (count($exclude)) { + $php['exclude'] = $exclude; + } + $ret[] = $php; + } + return $ret; + } +} +?> \ No newline at end of file diff --git a/library/pear/PEAR/PackageFile/Generator/v2.php b/library/pear/PEAR/PackageFile/Generator/v2.php new file mode 100644 index 000000000..5df7d2838 --- /dev/null +++ b/library/pear/PEAR/PackageFile/Generator/v2.php @@ -0,0 +1,893 @@ + + * @author Stephan Schmidt (original XML_Serializer code) + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: v2.php 278907 2009-04-17 21:10:04Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ +/** + * file/dir manipulation routines + */ +require_once 'System.php'; +require_once 'XML/Util.php'; + +/** + * This class converts a PEAR_PackageFile_v2 object into any output format. + * + * Supported output formats include array, XML string (using S. Schmidt's + * XML_Serializer, slightly customized) + * @category pear + * @package PEAR + * @author Greg Beaver + * @author Stephan Schmidt (original XML_Serializer code) + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.1 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_PackageFile_Generator_v2 +{ + /** + * default options for the serialization + * @access private + * @var array $_defaultOptions + */ + var $_defaultOptions = array( + 'indent' => ' ', // string used for indentation + 'linebreak' => "\n", // string used for newlines + 'typeHints' => false, // automatically add type hin attributes + 'addDecl' => true, // add an XML declaration + 'defaultTagName' => 'XML_Serializer_Tag', // tag used for indexed arrays or invalid names + 'classAsTagName' => false, // use classname for objects in indexed arrays + 'keyAttribute' => '_originalKey', // attribute where original key is stored + 'typeAttribute' => '_type', // attribute for type (only if typeHints => true) + 'classAttribute' => '_class', // attribute for class of objects (only if typeHints => true) + 'scalarAsAttributes' => false, // scalar values (strings, ints,..) will be serialized as attribute + 'prependAttributes' => '', // prepend string for attributes + 'indentAttributes' => false, // indent the attributes, if set to '_auto', it will indent attributes so they all start at the same column + 'mode' => 'simplexml', // use 'simplexml' to use parent name as tagname if transforming an indexed array + 'addDoctype' => false, // add a doctype declaration + 'doctype' => null, // supply a string or an array with id and uri ({@see XML_Util::getDoctypeDeclaration()} + 'rootName' => 'package', // name of the root tag + 'rootAttributes' => array( + 'version' => '2.0', + 'xmlns' => 'http://pear.php.net/dtd/package-2.0', + 'xmlns:tasks' => 'http://pear.php.net/dtd/tasks-1.0', + 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', + 'xsi:schemaLocation' => 'http://pear.php.net/dtd/tasks-1.0 +http://pear.php.net/dtd/tasks-1.0.xsd +http://pear.php.net/dtd/package-2.0 +http://pear.php.net/dtd/package-2.0.xsd', + ), // attributes of the root tag + 'attributesArray' => 'attribs', // all values in this key will be treated as attributes + 'contentName' => '_content', // this value will be used directly as content, instead of creating a new tag, may only be used in conjuction with attributesArray + 'beautifyFilelist' => false, + 'encoding' => 'UTF-8', + ); + + /** + * options for the serialization + * @access private + * @var array $options + */ + var $options = array(); + + /** + * current tag depth + * @var integer $_tagDepth + */ + var $_tagDepth = 0; + + /** + * serilialized representation of the data + * @var string $_serializedData + */ + var $_serializedData = null; + /** + * @var PEAR_PackageFile_v2 + */ + var $_packagefile; + /** + * @param PEAR_PackageFile_v2 + */ + function PEAR_PackageFile_Generator_v2(&$packagefile) + { + $this->_packagefile = &$packagefile; + if (isset($this->_packagefile->encoding)) { + $this->_defaultOptions['encoding'] = $this->_packagefile->encoding; + } + } + + /** + * @return string + */ + function getPackagerVersion() + { + return '1.9.1'; + } + + /** + * @param PEAR_Packager + * @param bool generate a .tgz or a .tar + * @param string|null temporary directory to package in + */ + function toTgz(&$packager, $compress = true, $where = null) + { + $a = null; + return $this->toTgz2($packager, $a, $compress, $where); + } + + /** + * Package up both a package.xml and package2.xml for the same release + * @param PEAR_Packager + * @param PEAR_PackageFile_v1 + * @param bool generate a .tgz or a .tar + * @param string|null temporary directory to package in + */ + function toTgz2(&$packager, &$pf1, $compress = true, $where = null) + { + require_once 'Archive/Tar.php'; + if (!$this->_packagefile->isEquivalent($pf1)) { + return PEAR::raiseError('PEAR_Packagefile_v2::toTgz: "' . + basename($pf1->getPackageFile()) . + '" is not equivalent to "' . basename($this->_packagefile->getPackageFile()) + . '"'); + } + + if ($where === null) { + if (!($where = System::mktemp(array('-d')))) { + return PEAR::raiseError('PEAR_Packagefile_v2::toTgz: mktemp failed'); + } + } elseif (!@System::mkDir(array('-p', $where))) { + return PEAR::raiseError('PEAR_Packagefile_v2::toTgz: "' . $where . '" could' . + ' not be created'); + } + + $file = $where . DIRECTORY_SEPARATOR . 'package.xml'; + if (file_exists($file) && !is_file($file)) { + return PEAR::raiseError('PEAR_Packagefile_v2::toTgz: unable to save package.xml as' . + ' "' . $file .'"'); + } + + if (!$this->_packagefile->validate(PEAR_VALIDATE_PACKAGING)) { + return PEAR::raiseError('PEAR_Packagefile_v2::toTgz: invalid package.xml'); + } + + $ext = $compress ? '.tgz' : '.tar'; + $pkgver = $this->_packagefile->getPackage() . '-' . $this->_packagefile->getVersion(); + $dest_package = getcwd() . DIRECTORY_SEPARATOR . $pkgver . $ext; + if (file_exists($dest_package) && !is_file($dest_package)) { + return PEAR::raiseError('PEAR_Packagefile_v2::toTgz: cannot create tgz file "' . + $dest_package . '"'); + } + + $pkgfile = $this->_packagefile->getPackageFile(); + if (!$pkgfile) { + return PEAR::raiseError('PEAR_Packagefile_v2::toTgz: package file object must ' . + 'be created from a real file'); + } + + $pkgdir = dirname(realpath($pkgfile)); + $pkgfile = basename($pkgfile); + + // {{{ Create the package file list + $filelist = array(); + $i = 0; + $this->_packagefile->flattenFilelist(); + $contents = $this->_packagefile->getContents(); + if (isset($contents['bundledpackage'])) { // bundles of packages + $contents = $contents['bundledpackage']; + if (!isset($contents[0])) { + $contents = array($contents); + } + + $packageDir = $where; + foreach ($contents as $i => $package) { + $fname = $package; + $file = $pkgdir . DIRECTORY_SEPARATOR . $fname; + if (!file_exists($file)) { + return $packager->raiseError("File does not exist: $fname"); + } + + $tfile = $packageDir . DIRECTORY_SEPARATOR . $fname; + System::mkdir(array('-p', dirname($tfile))); + copy($file, $tfile); + $filelist[$i++] = $tfile; + $packager->log(2, "Adding package $fname"); + } + } else { // normal packages + $contents = $contents['dir']['file']; + if (!isset($contents[0])) { + $contents = array($contents); + } + + $packageDir = $where; + foreach ($contents as $i => $file) { + $fname = $file['attribs']['name']; + $atts = $file['attribs']; + $orig = $file; + $file = $pkgdir . DIRECTORY_SEPARATOR . $fname; + if (!file_exists($file)) { + return $packager->raiseError("File does not exist: $fname"); + } + + $origperms = fileperms($file); + $tfile = $packageDir . DIRECTORY_SEPARATOR . $fname; + unset($orig['attribs']); + if (count($orig)) { // file with tasks + // run any package-time tasks + $contents = file_get_contents($file); + foreach ($orig as $tag => $raw) { + $tag = str_replace( + array($this->_packagefile->getTasksNs() . ':', '-'), + array('', '_'), $tag); + $task = "PEAR_Task_$tag"; + $task = &new $task($this->_packagefile->_config, + $this->_packagefile->_logger, + PEAR_TASK_PACKAGE); + $task->init($raw, $atts, null); + $res = $task->startSession($this->_packagefile, $contents, $tfile); + if (!$res) { + continue; // skip this task + } + + if (PEAR::isError($res)) { + return $res; + } + + $contents = $res; // save changes + System::mkdir(array('-p', dirname($tfile))); + $wp = fopen($tfile, "wb"); + fwrite($wp, $contents); + fclose($wp); + } + } + + if (!file_exists($tfile)) { + System::mkdir(array('-p', dirname($tfile))); + copy($file, $tfile); + } + + chmod($tfile, $origperms); + $filelist[$i++] = $tfile; + $this->_packagefile->setFileAttribute($fname, 'md5sum', md5_file($tfile), $i - 1); + $packager->log(2, "Adding file $fname"); + } + } + // }}} + + $name = $pf1 !== null ? 'package2.xml' : 'package.xml'; + $packagexml = $this->toPackageFile($where, PEAR_VALIDATE_PACKAGING, $name); + if ($packagexml) { + $tar =& new Archive_Tar($dest_package, $compress); + $tar->setErrorHandling(PEAR_ERROR_RETURN); // XXX Don't print errors + // ----- Creates with the package.xml file + $ok = $tar->createModify(array($packagexml), '', $where); + if (PEAR::isError($ok)) { + return $packager->raiseError($ok); + } elseif (!$ok) { + return $packager->raiseError('PEAR_Packagefile_v2::toTgz(): adding ' . $name . + ' failed'); + } + + // ----- Add the content of the package + if (!$tar->addModify($filelist, $pkgver, $where)) { + return $packager->raiseError( + 'PEAR_Packagefile_v2::toTgz(): tarball creation failed'); + } + + // add the package.xml version 1.0 + if ($pf1 !== null) { + $pfgen = &$pf1->getDefaultGenerator(); + $packagexml1 = $pfgen->toPackageFile($where, PEAR_VALIDATE_PACKAGING, 'package.xml', true); + if (!$tar->addModify(array($packagexml1), '', $where)) { + return $packager->raiseError( + 'PEAR_Packagefile_v2::toTgz(): adding package.xml failed'); + } + } + + return $dest_package; + } + } + + function toPackageFile($where = null, $state = PEAR_VALIDATE_NORMAL, $name = 'package.xml') + { + if (!$this->_packagefile->validate($state)) { + return PEAR::raiseError('PEAR_Packagefile_v2::toPackageFile: invalid package.xml', + null, null, null, $this->_packagefile->getValidationWarnings()); + } + + if ($where === null) { + if (!($where = System::mktemp(array('-d')))) { + return PEAR::raiseError('PEAR_Packagefile_v2::toPackageFile: mktemp failed'); + } + } elseif (!@System::mkDir(array('-p', $where))) { + return PEAR::raiseError('PEAR_Packagefile_v2::toPackageFile: "' . $where . '" could' . + ' not be created'); + } + + $newpkgfile = $where . DIRECTORY_SEPARATOR . $name; + $np = @fopen($newpkgfile, 'wb'); + if (!$np) { + return PEAR::raiseError('PEAR_Packagefile_v2::toPackageFile: unable to save ' . + "$name as $newpkgfile"); + } + fwrite($np, $this->toXml($state)); + fclose($np); + return $newpkgfile; + } + + function &toV2() + { + return $this->_packagefile; + } + + /** + * Return an XML document based on the package info (as returned + * by the PEAR_Common::infoFrom* methods). + * + * @return string XML data + */ + function toXml($state = PEAR_VALIDATE_NORMAL, $options = array()) + { + $this->_packagefile->setDate(date('Y-m-d')); + $this->_packagefile->setTime(date('H:i:s')); + if (!$this->_packagefile->validate($state)) { + return false; + } + + if (is_array($options)) { + $this->options = array_merge($this->_defaultOptions, $options); + } else { + $this->options = $this->_defaultOptions; + } + + $arr = $this->_packagefile->getArray(); + if (isset($arr['filelist'])) { + unset($arr['filelist']); + } + + if (isset($arr['_lastversion'])) { + unset($arr['_lastversion']); + } + + // Fix the notes a little bit + if (isset($arr['notes'])) { + // This trims out the indenting, needs fixing + $arr['notes'] = "\n" . trim($arr['notes']) . "\n"; + } + + if (isset($arr['changelog']) && !empty($arr['changelog'])) { + // Fix for inconsistency how the array is filled depending on the changelog release amount + if (!isset($arr['changelog']['release'][0])) { + $release = $arr['changelog']['release']; + unset($arr['changelog']['release']); + + $arr['changelog']['release'] = array(); + $arr['changelog']['release'][0] = $release; + } + + foreach (array_keys($arr['changelog']['release']) as $key) { + $c =& $arr['changelog']['release'][$key]; + if (isset($c['notes'])) { + // This trims out the indenting, needs fixing + $c['notes'] = "\n" . trim($c['notes']) . "\n"; + } + } + } + + if ($state ^ PEAR_VALIDATE_PACKAGING && !isset($arr['bundle'])) { + $use = $this->_recursiveXmlFilelist($arr['contents']['dir']['file']); + unset($arr['contents']['dir']['file']); + if (isset($use['dir'])) { + $arr['contents']['dir']['dir'] = $use['dir']; + } + if (isset($use['file'])) { + $arr['contents']['dir']['file'] = $use['file']; + } + $this->options['beautifyFilelist'] = true; + } + + $arr['attribs']['packagerversion'] = '1.9.1'; + if ($this->serialize($arr, $options)) { + return $this->_serializedData . "\n"; + } + + return false; + } + + + function _recursiveXmlFilelist($list) + { + $dirs = array(); + if (isset($list['attribs'])) { + $file = $list['attribs']['name']; + unset($list['attribs']['name']); + $attributes = $list['attribs']; + $this->_addDir($dirs, explode('/', dirname($file)), $file, $attributes); + } else { + foreach ($list as $a) { + $file = $a['attribs']['name']; + $attributes = $a['attribs']; + unset($a['attribs']); + $this->_addDir($dirs, explode('/', dirname($file)), $file, $attributes, $a); + } + } + $this->_formatDir($dirs); + $this->_deFormat($dirs); + return $dirs; + } + + function _addDir(&$dirs, $dir, $file = null, $attributes = null, $tasks = null) + { + if (!$tasks) { + $tasks = array(); + } + if ($dir == array() || $dir == array('.')) { + $dirs['file'][basename($file)] = $tasks; + $attributes['name'] = basename($file); + $dirs['file'][basename($file)]['attribs'] = $attributes; + return; + } + $curdir = array_shift($dir); + if (!isset($dirs['dir'][$curdir])) { + $dirs['dir'][$curdir] = array(); + } + $this->_addDir($dirs['dir'][$curdir], $dir, $file, $attributes, $tasks); + } + + function _formatDir(&$dirs) + { + if (!count($dirs)) { + return array(); + } + $newdirs = array(); + if (isset($dirs['dir'])) { + $newdirs['dir'] = $dirs['dir']; + } + if (isset($dirs['file'])) { + $newdirs['file'] = $dirs['file']; + } + $dirs = $newdirs; + if (isset($dirs['dir'])) { + uksort($dirs['dir'], 'strnatcasecmp'); + foreach ($dirs['dir'] as $dir => $contents) { + $this->_formatDir($dirs['dir'][$dir]); + } + } + if (isset($dirs['file'])) { + uksort($dirs['file'], 'strnatcasecmp'); + }; + } + + function _deFormat(&$dirs) + { + if (!count($dirs)) { + return array(); + } + $newdirs = array(); + if (isset($dirs['dir'])) { + foreach ($dirs['dir'] as $dir => $contents) { + $newdir = array(); + $newdir['attribs']['name'] = $dir; + $this->_deFormat($contents); + foreach ($contents as $tag => $val) { + $newdir[$tag] = $val; + } + $newdirs['dir'][] = $newdir; + } + if (count($newdirs['dir']) == 1) { + $newdirs['dir'] = $newdirs['dir'][0]; + } + } + if (isset($dirs['file'])) { + foreach ($dirs['file'] as $name => $file) { + $newdirs['file'][] = $file; + } + if (count($newdirs['file']) == 1) { + $newdirs['file'] = $newdirs['file'][0]; + } + } + $dirs = $newdirs; + } + + /** + * reset all options to default options + * + * @access public + * @see setOption(), XML_Unserializer() + */ + function resetOptions() + { + $this->options = $this->_defaultOptions; + } + + /** + * set an option + * + * You can use this method if you do not want to set all options in the constructor + * + * @access public + * @see resetOption(), XML_Serializer() + */ + function setOption($name, $value) + { + $this->options[$name] = $value; + } + + /** + * sets several options at once + * + * You can use this method if you do not want to set all options in the constructor + * + * @access public + * @see resetOption(), XML_Unserializer(), setOption() + */ + function setOptions($options) + { + $this->options = array_merge($this->options, $options); + } + + /** + * serialize data + * + * @access public + * @param mixed $data data to serialize + * @return boolean true on success, pear error on failure + */ + function serialize($data, $options = null) + { + // if options have been specified, use them instead + // of the previously defined ones + if (is_array($options)) { + $optionsBak = $this->options; + if (isset($options['overrideOptions']) && $options['overrideOptions'] == true) { + $this->options = array_merge($this->_defaultOptions, $options); + } else { + $this->options = array_merge($this->options, $options); + } + } else { + $optionsBak = null; + } + + // start depth is zero + $this->_tagDepth = 0; + $this->_serializedData = ''; + // serialize an array + if (is_array($data)) { + $tagName = isset($this->options['rootName']) ? $this->options['rootName'] : 'array'; + $this->_serializedData .= $this->_serializeArray($data, $tagName, $this->options['rootAttributes']); + } + + // add doctype declaration + if ($this->options['addDoctype'] === true) { + $this->_serializedData = XML_Util::getDoctypeDeclaration($tagName, $this->options['doctype']) + . $this->options['linebreak'] + . $this->_serializedData; + } + + // build xml declaration + if ($this->options['addDecl']) { + $atts = array(); + $encoding = isset($this->options['encoding']) ? $this->options['encoding'] : null; + $this->_serializedData = XML_Util::getXMLDeclaration('1.0', $encoding) + . $this->options['linebreak'] + . $this->_serializedData; + } + + + if ($optionsBak !== null) { + $this->options = $optionsBak; + } + + return true; + } + + /** + * get the result of the serialization + * + * @access public + * @return string serialized XML + */ + function getSerializedData() + { + if ($this->_serializedData === null) { + return $this->raiseError('No serialized data available. Use XML_Serializer::serialize() first.', XML_SERIALIZER_ERROR_NO_SERIALIZATION); + } + return $this->_serializedData; + } + + /** + * serialize any value + * + * This method checks for the type of the value and calls the appropriate method + * + * @access private + * @param mixed $value + * @param string $tagName + * @param array $attributes + * @return string + */ + function _serializeValue($value, $tagName = null, $attributes = array()) + { + if (is_array($value)) { + $xml = $this->_serializeArray($value, $tagName, $attributes); + } elseif (is_object($value)) { + $xml = $this->_serializeObject($value, $tagName); + } else { + $tag = array( + 'qname' => $tagName, + 'attributes' => $attributes, + 'content' => $value + ); + $xml = $this->_createXMLTag($tag); + } + return $xml; + } + + /** + * serialize an array + * + * @access private + * @param array $array array to serialize + * @param string $tagName name of the root tag + * @param array $attributes attributes for the root tag + * @return string $string serialized data + * @uses XML_Util::isValidName() to check, whether key has to be substituted + */ + function _serializeArray(&$array, $tagName = null, $attributes = array()) + { + $_content = null; + + /** + * check for special attributes + */ + if ($this->options['attributesArray'] !== null) { + if (isset($array[$this->options['attributesArray']])) { + $attributes = $array[$this->options['attributesArray']]; + unset($array[$this->options['attributesArray']]); + } + /** + * check for special content + */ + if ($this->options['contentName'] !== null) { + if (isset($array[$this->options['contentName']])) { + $_content = $array[$this->options['contentName']]; + unset($array[$this->options['contentName']]); + } + } + } + + /* + * if mode is set to simpleXML, check whether + * the array is associative or indexed + */ + if (is_array($array) && $this->options['mode'] == 'simplexml') { + $indexed = true; + if (!count($array)) { + $indexed = false; + } + foreach ($array as $key => $val) { + if (!is_int($key)) { + $indexed = false; + break; + } + } + + if ($indexed && $this->options['mode'] == 'simplexml') { + $string = ''; + foreach ($array as $key => $val) { + if ($this->options['beautifyFilelist'] && $tagName == 'dir') { + if (!isset($this->_curdir)) { + $this->_curdir = ''; + } + $savedir = $this->_curdir; + if (isset($val['attribs'])) { + if ($val['attribs']['name'] == '/') { + $this->_curdir = '/'; + } else { + if ($this->_curdir == '/') { + $this->_curdir = ''; + } + $this->_curdir .= '/' . $val['attribs']['name']; + } + } + } + $string .= $this->_serializeValue( $val, $tagName, $attributes); + if ($this->options['beautifyFilelist'] && $tagName == 'dir') { + $string .= ' '; + if (empty($savedir)) { + unset($this->_curdir); + } else { + $this->_curdir = $savedir; + } + } + + $string .= $this->options['linebreak']; + // do indentation + if ($this->options['indent'] !== null && $this->_tagDepth > 0) { + $string .= str_repeat($this->options['indent'], $this->_tagDepth); + } + } + return rtrim($string); + } + } + + if ($this->options['scalarAsAttributes'] === true) { + foreach ($array as $key => $value) { + if (is_scalar($value) && (XML_Util::isValidName($key) === true)) { + unset($array[$key]); + $attributes[$this->options['prependAttributes'].$key] = $value; + } + } + } + + // check for empty array => create empty tag + if (empty($array)) { + $tag = array( + 'qname' => $tagName, + 'content' => $_content, + 'attributes' => $attributes + ); + + } else { + $this->_tagDepth++; + $tmp = $this->options['linebreak']; + foreach ($array as $key => $value) { + // do indentation + if ($this->options['indent'] !== null && $this->_tagDepth > 0) { + $tmp .= str_repeat($this->options['indent'], $this->_tagDepth); + } + + // copy key + $origKey = $key; + // key cannot be used as tagname => use default tag + $valid = XML_Util::isValidName($key); + if (PEAR::isError($valid)) { + if ($this->options['classAsTagName'] && is_object($value)) { + $key = get_class($value); + } else { + $key = $this->options['defaultTagName']; + } + } + $atts = array(); + if ($this->options['typeHints'] === true) { + $atts[$this->options['typeAttribute']] = gettype($value); + if ($key !== $origKey) { + $atts[$this->options['keyAttribute']] = (string)$origKey; + } + + } + if ($this->options['beautifyFilelist'] && $key == 'dir') { + if (!isset($this->_curdir)) { + $this->_curdir = ''; + } + $savedir = $this->_curdir; + if (isset($value['attribs'])) { + if ($value['attribs']['name'] == '/') { + $this->_curdir = '/'; + } else { + $this->_curdir .= '/' . $value['attribs']['name']; + } + } + } + + if (is_string($value) && $value && ($value{strlen($value) - 1} == "\n")) { + $value .= str_repeat($this->options['indent'], $this->_tagDepth); + } + $tmp .= $this->_createXMLTag(array( + 'qname' => $key, + 'attributes' => $atts, + 'content' => $value ) + ); + if ($this->options['beautifyFilelist'] && $key == 'dir') { + if (isset($value['attribs'])) { + $tmp .= ' '; + if (empty($savedir)) { + unset($this->_curdir); + } else { + $this->_curdir = $savedir; + } + } + } + $tmp .= $this->options['linebreak']; + } + + $this->_tagDepth--; + if ($this->options['indent']!==null && $this->_tagDepth>0) { + $tmp .= str_repeat($this->options['indent'], $this->_tagDepth); + } + + if (trim($tmp) === '') { + $tmp = null; + } + + $tag = array( + 'qname' => $tagName, + 'content' => $tmp, + 'attributes' => $attributes + ); + } + if ($this->options['typeHints'] === true) { + if (!isset($tag['attributes'][$this->options['typeAttribute']])) { + $tag['attributes'][$this->options['typeAttribute']] = 'array'; + } + } + + $string = $this->_createXMLTag($tag, false); + return $string; + } + + /** + * create a tag from an array + * this method awaits an array in the following format + * array( + * 'qname' => $tagName, + * 'attributes' => array(), + * 'content' => $content, // optional + * 'namespace' => $namespace // optional + * 'namespaceUri' => $namespaceUri // optional + * ) + * + * @access private + * @param array $tag tag definition + * @param boolean $replaceEntities whether to replace XML entities in content or not + * @return string $string XML tag + */ + function _createXMLTag($tag, $replaceEntities = true) + { + if ($this->options['indentAttributes'] !== false) { + $multiline = true; + $indent = str_repeat($this->options['indent'], $this->_tagDepth); + + if ($this->options['indentAttributes'] == '_auto') { + $indent .= str_repeat(' ', (strlen($tag['qname'])+2)); + + } else { + $indent .= $this->options['indentAttributes']; + } + } else { + $indent = $multiline = false; + } + + if (is_array($tag['content'])) { + if (empty($tag['content'])) { + $tag['content'] = ''; + } + } elseif(is_scalar($tag['content']) && (string)$tag['content'] == '') { + $tag['content'] = ''; + } + + if (is_scalar($tag['content']) || is_null($tag['content'])) { + if ($this->options['encoding'] == 'UTF-8' && + version_compare(phpversion(), '5.0.0', 'lt') + ) { + $tag['content'] = utf8_encode($tag['content']); + } + + if ($replaceEntities === true) { + $replaceEntities = XML_UTIL_ENTITIES_XML; + } + + $tag = XML_Util::createTagFromArray($tag, $replaceEntities, $multiline, $indent, $this->options['linebreak']); + } elseif (is_array($tag['content'])) { + $tag = $this->_serializeArray($tag['content'], $tag['qname'], $tag['attributes']); + } elseif (is_object($tag['content'])) { + $tag = $this->_serializeObject($tag['content'], $tag['qname'], $tag['attributes']); + } elseif (is_resource($tag['content'])) { + settype($tag['content'], 'string'); + $tag = XML_Util::createTagFromArray($tag, $replaceEntities); + } + return $tag; + } +} \ No newline at end of file diff --git a/library/pear/PEAR/PackageFile/Parser/v1.php b/library/pear/PEAR/PackageFile/Parser/v1.php new file mode 100644 index 000000000..294d2a764 --- /dev/null +++ b/library/pear/PEAR/PackageFile/Parser/v1.php @@ -0,0 +1,459 @@ + + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: v1.php 276385 2009-02-24 23:46:03Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ +/** + * package.xml abstraction class + */ +require_once 'PEAR/PackageFile/v1.php'; +/** + * Parser for package.xml version 1.0 + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: @PEAR-VER@ + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_PackageFile_Parser_v1 +{ + var $_registry; + var $_config; + var $_logger; + /** + * BC hack to allow PEAR_Common::infoFromString() to sort of + * work with the version 2.0 format - there's no filelist though + * @param PEAR_PackageFile_v2 + */ + function fromV2($packagefile) + { + $info = $packagefile->getArray(true); + $ret = new PEAR_PackageFile_v1; + $ret->fromArray($info['old']); + } + + function setConfig(&$c) + { + $this->_config = &$c; + $this->_registry = &$c->getRegistry(); + } + + function setLogger(&$l) + { + $this->_logger = &$l; + } + + /** + * @param string contents of package.xml file, version 1.0 + * @return bool success of parsing + */ + function &parse($data, $file, $archive = false) + { + if (!extension_loaded('xml')) { + return PEAR::raiseError('Cannot create xml parser for parsing package.xml, no xml extension'); + } + $xp = xml_parser_create(); + if (!$xp) { + $a = &PEAR::raiseError('Cannot create xml parser for parsing package.xml'); + return $a; + } + xml_set_object($xp, $this); + xml_set_element_handler($xp, '_element_start_1_0', '_element_end_1_0'); + xml_set_character_data_handler($xp, '_pkginfo_cdata_1_0'); + xml_parser_set_option($xp, XML_OPTION_CASE_FOLDING, false); + + $this->element_stack = array(); + $this->_packageInfo = array('provides' => array()); + $this->current_element = false; + unset($this->dir_install); + $this->_packageInfo['filelist'] = array(); + $this->filelist =& $this->_packageInfo['filelist']; + $this->dir_names = array(); + $this->in_changelog = false; + $this->d_i = 0; + $this->cdata = ''; + $this->_isValid = true; + + if (!xml_parse($xp, $data, 1)) { + $code = xml_get_error_code($xp); + $line = xml_get_current_line_number($xp); + xml_parser_free($xp); + $a = &PEAR::raiseError(sprintf("XML error: %s at line %d", + $str = xml_error_string($code), $line), 2); + return $a; + } + + xml_parser_free($xp); + + $pf = new PEAR_PackageFile_v1; + $pf->setConfig($this->_config); + if (isset($this->_logger)) { + $pf->setLogger($this->_logger); + } + $pf->setPackagefile($file, $archive); + $pf->fromArray($this->_packageInfo); + return $pf; + } + // {{{ _unIndent() + + /** + * Unindent given string + * + * @param string $str The string that has to be unindented. + * @return string + * @access private + */ + function _unIndent($str) + { + // remove leading newlines + $str = preg_replace('/^[\r\n]+/', '', $str); + // find whitespace at the beginning of the first line + $indent_len = strspn($str, " \t"); + $indent = substr($str, 0, $indent_len); + $data = ''; + // remove the same amount of whitespace from following lines + foreach (explode("\n", $str) as $line) { + if (substr($line, 0, $indent_len) == $indent) { + $data .= substr($line, $indent_len) . "\n"; + } elseif (trim(substr($line, 0, $indent_len))) { + $data .= ltrim($line); + } + } + return $data; + } + + // Support for package DTD v1.0: + // {{{ _element_start_1_0() + + /** + * XML parser callback for ending elements. Used for version 1.0 + * packages. + * + * @param resource $xp XML parser resource + * @param string $name name of ending element + * + * @return void + * + * @access private + */ + function _element_start_1_0($xp, $name, $attribs) + { + array_push($this->element_stack, $name); + $this->current_element = $name; + $spos = sizeof($this->element_stack) - 2; + $this->prev_element = ($spos >= 0) ? $this->element_stack[$spos] : ''; + $this->current_attributes = $attribs; + $this->cdata = ''; + switch ($name) { + case 'dir': + if ($this->in_changelog) { + break; + } + if (array_key_exists('name', $attribs) && $attribs['name'] != '/') { + $attribs['name'] = preg_replace(array('!\\\\+!', '!/+!'), array('/', '/'), + $attribs['name']); + if (strrpos($attribs['name'], '/') === strlen($attribs['name']) - 1) { + $attribs['name'] = substr($attribs['name'], 0, + strlen($attribs['name']) - 1); + } + if (strpos($attribs['name'], '/') === 0) { + $attribs['name'] = substr($attribs['name'], 1); + } + $this->dir_names[] = $attribs['name']; + } + if (isset($attribs['baseinstalldir'])) { + $this->dir_install = $attribs['baseinstalldir']; + } + if (isset($attribs['role'])) { + $this->dir_role = $attribs['role']; + } + break; + case 'file': + if ($this->in_changelog) { + break; + } + if (isset($attribs['name'])) { + $path = ''; + if (count($this->dir_names)) { + foreach ($this->dir_names as $dir) { + $path .= $dir . '/'; + } + } + $path .= preg_replace(array('!\\\\+!', '!/+!'), array('/', '/'), + $attribs['name']); + unset($attribs['name']); + $this->current_path = $path; + $this->filelist[$path] = $attribs; + // Set the baseinstalldir only if the file don't have this attrib + if (!isset($this->filelist[$path]['baseinstalldir']) && + isset($this->dir_install)) + { + $this->filelist[$path]['baseinstalldir'] = $this->dir_install; + } + // Set the Role + if (!isset($this->filelist[$path]['role']) && isset($this->dir_role)) { + $this->filelist[$path]['role'] = $this->dir_role; + } + } + break; + case 'replace': + if (!$this->in_changelog) { + $this->filelist[$this->current_path]['replacements'][] = $attribs; + } + break; + case 'maintainers': + $this->_packageInfo['maintainers'] = array(); + $this->m_i = 0; // maintainers array index + break; + case 'maintainer': + // compatibility check + if (!isset($this->_packageInfo['maintainers'])) { + $this->_packageInfo['maintainers'] = array(); + $this->m_i = 0; + } + $this->_packageInfo['maintainers'][$this->m_i] = array(); + $this->current_maintainer =& $this->_packageInfo['maintainers'][$this->m_i]; + break; + case 'changelog': + $this->_packageInfo['changelog'] = array(); + $this->c_i = 0; // changelog array index + $this->in_changelog = true; + break; + case 'release': + if ($this->in_changelog) { + $this->_packageInfo['changelog'][$this->c_i] = array(); + $this->current_release = &$this->_packageInfo['changelog'][$this->c_i]; + } else { + $this->current_release = &$this->_packageInfo; + } + break; + case 'deps': + if (!$this->in_changelog) { + $this->_packageInfo['release_deps'] = array(); + } + break; + case 'dep': + // dependencies array index + if (!$this->in_changelog) { + $this->d_i++; + isset($attribs['type']) ? ($attribs['type'] = strtolower($attribs['type'])) : false; + $this->_packageInfo['release_deps'][$this->d_i] = $attribs; + } + break; + case 'configureoptions': + if (!$this->in_changelog) { + $this->_packageInfo['configure_options'] = array(); + } + break; + case 'configureoption': + if (!$this->in_changelog) { + $this->_packageInfo['configure_options'][] = $attribs; + } + break; + case 'provides': + if (empty($attribs['type']) || empty($attribs['name'])) { + break; + } + $attribs['explicit'] = true; + $this->_packageInfo['provides']["$attribs[type];$attribs[name]"] = $attribs; + break; + case 'package' : + if (isset($attribs['version'])) { + $this->_packageInfo['xsdversion'] = trim($attribs['version']); + } else { + $this->_packageInfo['xsdversion'] = '1.0'; + } + if (isset($attribs['packagerversion'])) { + $this->_packageInfo['packagerversion'] = $attribs['packagerversion']; + } + break; + } + } + + // }}} + // {{{ _element_end_1_0() + + /** + * XML parser callback for ending elements. Used for version 1.0 + * packages. + * + * @param resource $xp XML parser resource + * @param string $name name of ending element + * + * @return void + * + * @access private + */ + function _element_end_1_0($xp, $name) + { + $data = trim($this->cdata); + switch ($name) { + case 'name': + switch ($this->prev_element) { + case 'package': + $this->_packageInfo['package'] = $data; + break; + case 'maintainer': + $this->current_maintainer['name'] = $data; + break; + } + break; + case 'extends' : + $this->_packageInfo['extends'] = $data; + break; + case 'summary': + $this->_packageInfo['summary'] = $data; + break; + case 'description': + $data = $this->_unIndent($this->cdata); + $this->_packageInfo['description'] = $data; + break; + case 'user': + $this->current_maintainer['handle'] = $data; + break; + case 'email': + $this->current_maintainer['email'] = $data; + break; + case 'role': + $this->current_maintainer['role'] = $data; + break; + case 'version': + if ($this->in_changelog) { + $this->current_release['version'] = $data; + } else { + $this->_packageInfo['version'] = $data; + } + break; + case 'date': + if ($this->in_changelog) { + $this->current_release['release_date'] = $data; + } else { + $this->_packageInfo['release_date'] = $data; + } + break; + case 'notes': + // try to "de-indent" release notes in case someone + // has been over-indenting their xml ;-) + // Trim only on the right side + $data = rtrim($this->_unIndent($this->cdata)); + if ($this->in_changelog) { + $this->current_release['release_notes'] = $data; + } else { + $this->_packageInfo['release_notes'] = $data; + } + break; + case 'warnings': + if ($this->in_changelog) { + $this->current_release['release_warnings'] = $data; + } else { + $this->_packageInfo['release_warnings'] = $data; + } + break; + case 'state': + if ($this->in_changelog) { + $this->current_release['release_state'] = $data; + } else { + $this->_packageInfo['release_state'] = $data; + } + break; + case 'license': + if ($this->in_changelog) { + $this->current_release['release_license'] = $data; + } else { + $this->_packageInfo['release_license'] = $data; + } + break; + case 'dep': + if ($data && !$this->in_changelog) { + $this->_packageInfo['release_deps'][$this->d_i]['name'] = $data; + } + break; + case 'dir': + if ($this->in_changelog) { + break; + } + array_pop($this->dir_names); + break; + case 'file': + if ($this->in_changelog) { + break; + } + if ($data) { + $path = ''; + if (count($this->dir_names)) { + foreach ($this->dir_names as $dir) { + $path .= $dir . '/'; + } + } + $path .= $data; + $this->filelist[$path] = $this->current_attributes; + // Set the baseinstalldir only if the file don't have this attrib + if (!isset($this->filelist[$path]['baseinstalldir']) && + isset($this->dir_install)) + { + $this->filelist[$path]['baseinstalldir'] = $this->dir_install; + } + // Set the Role + if (!isset($this->filelist[$path]['role']) && isset($this->dir_role)) { + $this->filelist[$path]['role'] = $this->dir_role; + } + } + break; + case 'maintainer': + if (empty($this->_packageInfo['maintainers'][$this->m_i]['role'])) { + $this->_packageInfo['maintainers'][$this->m_i]['role'] = 'lead'; + } + $this->m_i++; + break; + case 'release': + if ($this->in_changelog) { + $this->c_i++; + } + break; + case 'changelog': + $this->in_changelog = false; + break; + } + array_pop($this->element_stack); + $spos = sizeof($this->element_stack) - 1; + $this->current_element = ($spos > 0) ? $this->element_stack[$spos] : ''; + $this->cdata = ''; + } + + // }}} + // {{{ _pkginfo_cdata_1_0() + + /** + * XML parser callback for character data. Used for version 1.0 + * packages. + * + * @param resource $xp XML parser resource + * @param string $name character data + * + * @return void + * + * @access private + */ + function _pkginfo_cdata_1_0($xp, $data) + { + if (isset($this->cdata)) { + $this->cdata .= $data; + } + } + + // }}} +} +?> \ No newline at end of file diff --git a/library/pear/PEAR/PackageFile/Parser/v2.php b/library/pear/PEAR/PackageFile/Parser/v2.php new file mode 100644 index 000000000..989923402 --- /dev/null +++ b/library/pear/PEAR/PackageFile/Parser/v2.php @@ -0,0 +1,113 @@ + + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: v2.php 276385 2009-02-24 23:46:03Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ +/** + * base xml parser class + */ +require_once 'PEAR/XMLParser.php'; +require_once 'PEAR/PackageFile/v2.php'; +/** + * Parser for package.xml version 2.0 + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: @PEAR-VER@ + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_PackageFile_Parser_v2 extends PEAR_XMLParser +{ + var $_config; + var $_logger; + var $_registry; + + function setConfig(&$c) + { + $this->_config = &$c; + $this->_registry = &$c->getRegistry(); + } + + function setLogger(&$l) + { + $this->_logger = &$l; + } + /** + * Unindent given string + * + * @param string $str The string that has to be unindented. + * @return string + * @access private + */ + function _unIndent($str) + { + // remove leading newlines + $str = preg_replace('/^[\r\n]+/', '', $str); + // find whitespace at the beginning of the first line + $indent_len = strspn($str, " \t"); + $indent = substr($str, 0, $indent_len); + $data = ''; + // remove the same amount of whitespace from following lines + foreach (explode("\n", $str) as $line) { + if (substr($line, 0, $indent_len) == $indent) { + $data .= substr($line, $indent_len) . "\n"; + } else { + $data .= $line . "\n"; + } + } + return $data; + } + + /** + * post-process data + * + * @param string $data + * @param string $element element name + */ + function postProcess($data, $element) + { + if ($element == 'notes') { + return trim($this->_unIndent($data)); + } + return trim($data); + } + + /** + * @param string + * @param string file name of the package.xml + * @param string|false name of the archive this package.xml came from, if any + * @param string class name to instantiate and return. This must be PEAR_PackageFile_v2 or + * a subclass + * @return PEAR_PackageFile_v2 + */ + function &parse($data, $file, $archive = false, $class = 'PEAR_PackageFile_v2') + { + if (PEAR::isError($err = parent::parse($data, $file))) { + return $err; + } + + $ret = new $class; + $ret->encoding = $this->encoding; + $ret->setConfig($this->_config); + if (isset($this->_logger)) { + $ret->setLogger($this->_logger); + } + + $ret->fromArray($this->_unserializedData); + $ret->setPackagefile($file, $archive); + return $ret; + } +} \ No newline at end of file diff --git a/library/pear/PEAR/PackageFile/v1.php b/library/pear/PEAR/PackageFile/v1.php new file mode 100644 index 000000000..6f0837060 --- /dev/null +++ b/library/pear/PEAR/PackageFile/v1.php @@ -0,0 +1,1612 @@ + + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: v1.php 276383 2009-02-24 23:39:37Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ +/** + * For error handling + */ +require_once 'PEAR/ErrorStack.php'; + +/** + * Error code if parsing is attempted with no xml extension + */ +define('PEAR_PACKAGEFILE_ERROR_NO_XML_EXT', 3); + +/** + * Error code if creating the xml parser resource fails + */ +define('PEAR_PACKAGEFILE_ERROR_CANT_MAKE_PARSER', 4); + +/** + * Error code used for all sax xml parsing errors + */ +define('PEAR_PACKAGEFILE_ERROR_PARSER_ERROR', 5); + +/** + * Error code used when there is no name + */ +define('PEAR_PACKAGEFILE_ERROR_NO_NAME', 6); + +/** + * Error code when a package name is not valid + */ +define('PEAR_PACKAGEFILE_ERROR_INVALID_NAME', 7); + +/** + * Error code used when no summary is parsed + */ +define('PEAR_PACKAGEFILE_ERROR_NO_SUMMARY', 8); + +/** + * Error code for summaries that are more than 1 line + */ +define('PEAR_PACKAGEFILE_ERROR_MULTILINE_SUMMARY', 9); + +/** + * Error code used when no description is present + */ +define('PEAR_PACKAGEFILE_ERROR_NO_DESCRIPTION', 10); + +/** + * Error code used when no license is present + */ +define('PEAR_PACKAGEFILE_ERROR_NO_LICENSE', 11); + +/** + * Error code used when a version number is not present + */ +define('PEAR_PACKAGEFILE_ERROR_NO_VERSION', 12); + +/** + * Error code used when a version number is invalid + */ +define('PEAR_PACKAGEFILE_ERROR_INVALID_VERSION', 13); + +/** + * Error code when release state is missing + */ +define('PEAR_PACKAGEFILE_ERROR_NO_STATE', 14); + +/** + * Error code when release state is invalid + */ +define('PEAR_PACKAGEFILE_ERROR_INVALID_STATE', 15); + +/** + * Error code when release state is missing + */ +define('PEAR_PACKAGEFILE_ERROR_NO_DATE', 16); + +/** + * Error code when release state is invalid + */ +define('PEAR_PACKAGEFILE_ERROR_INVALID_DATE', 17); + +/** + * Error code when no release notes are found + */ +define('PEAR_PACKAGEFILE_ERROR_NO_NOTES', 18); + +/** + * Error code when no maintainers are found + */ +define('PEAR_PACKAGEFILE_ERROR_NO_MAINTAINERS', 19); + +/** + * Error code when a maintainer has no handle + */ +define('PEAR_PACKAGEFILE_ERROR_NO_MAINTHANDLE', 20); + +/** + * Error code when a maintainer has no handle + */ +define('PEAR_PACKAGEFILE_ERROR_NO_MAINTROLE', 21); + +/** + * Error code when a maintainer has no name + */ +define('PEAR_PACKAGEFILE_ERROR_NO_MAINTNAME', 22); + +/** + * Error code when a maintainer has no email + */ +define('PEAR_PACKAGEFILE_ERROR_NO_MAINTEMAIL', 23); + +/** + * Error code when a maintainer has no handle + */ +define('PEAR_PACKAGEFILE_ERROR_INVALID_MAINTROLE', 24); + +/** + * Error code when a dependency is not a PHP dependency, but has no name + */ +define('PEAR_PACKAGEFILE_ERROR_NO_DEPNAME', 25); + +/** + * Error code when a dependency has no type (pkg, php, etc.) + */ +define('PEAR_PACKAGEFILE_ERROR_NO_DEPTYPE', 26); + +/** + * Error code when a dependency has no relation (lt, ge, has, etc.) + */ +define('PEAR_PACKAGEFILE_ERROR_NO_DEPREL', 27); + +/** + * Error code when a dependency is not a 'has' relation, but has no version + */ +define('PEAR_PACKAGEFILE_ERROR_NO_DEPVERSION', 28); + +/** + * Error code when a dependency has an invalid relation + */ +define('PEAR_PACKAGEFILE_ERROR_INVALID_DEPREL', 29); + +/** + * Error code when a dependency has an invalid type + */ +define('PEAR_PACKAGEFILE_ERROR_INVALID_DEPTYPE', 30); + +/** + * Error code when a dependency has an invalid optional option + */ +define('PEAR_PACKAGEFILE_ERROR_INVALID_DEPOPTIONAL', 31); + +/** + * Error code when a dependency is a pkg dependency, and has an invalid package name + */ +define('PEAR_PACKAGEFILE_ERROR_INVALID_DEPNAME', 32); + +/** + * Error code when a dependency has a channel="foo" attribute, and foo is not a registered channel + */ +define('PEAR_PACKAGEFILE_ERROR_UNKNOWN_DEPCHANNEL', 33); + +/** + * Error code when rel="has" and version attribute is present. + */ +define('PEAR_PACKAGEFILE_ERROR_DEPVERSION_IGNORED', 34); + +/** + * Error code when type="php" and dependency name is present + */ +define('PEAR_PACKAGEFILE_ERROR_DEPNAME_IGNORED', 35); + +/** + * Error code when a configure option has no name + */ +define('PEAR_PACKAGEFILE_ERROR_NO_CONFNAME', 36); + +/** + * Error code when a configure option has no name + */ +define('PEAR_PACKAGEFILE_ERROR_NO_CONFPROMPT', 37); + +/** + * Error code when a file in the filelist has an invalid role + */ +define('PEAR_PACKAGEFILE_ERROR_INVALID_FILEROLE', 38); + +/** + * Error code when a file in the filelist has no role + */ +define('PEAR_PACKAGEFILE_ERROR_NO_FILEROLE', 39); + +/** + * Error code when analyzing a php source file that has parse errors + */ +define('PEAR_PACKAGEFILE_ERROR_INVALID_PHPFILE', 40); + +/** + * Error code when analyzing a php source file reveals a source element + * without a package name prefix + */ +define('PEAR_PACKAGEFILE_ERROR_NO_PNAME_PREFIX', 41); + +/** + * Error code when an unknown channel is specified + */ +define('PEAR_PACKAGEFILE_ERROR_UNKNOWN_CHANNEL', 42); + +/** + * Error code when no files are found in the filelist + */ +define('PEAR_PACKAGEFILE_ERROR_NO_FILES', 43); + +/** + * Error code when a file is not valid php according to _analyzeSourceCode() + */ +define('PEAR_PACKAGEFILE_ERROR_INVALID_FILE', 44); + +/** + * Error code when the channel validator returns an error or warning + */ +define('PEAR_PACKAGEFILE_ERROR_CHANNELVAL', 45); + +/** + * Error code when a php5 package is packaged in php4 (analysis doesn't work) + */ +define('PEAR_PACKAGEFILE_ERROR_PHP5', 46); + +/** + * Error code when a file is listed in package.xml but does not exist + */ +define('PEAR_PACKAGEFILE_ERROR_FILE_NOTFOUND', 47); + +/** + * Error code when a + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.1 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_PackageFile_v1 +{ + /** + * @access private + * @var PEAR_ErrorStack + * @access private + */ + var $_stack; + + /** + * A registry object, used to access the package name validation regex for non-standard channels + * @var PEAR_Registry + * @access private + */ + var $_registry; + + /** + * An object that contains a log method that matches PEAR_Common::log's signature + * @var object + * @access private + */ + var $_logger; + + /** + * Parsed package information + * @var array + * @access private + */ + var $_packageInfo; + + /** + * path to package.xml + * @var string + * @access private + */ + var $_packageFile; + + /** + * path to package .tgz or false if this is a local/extracted package.xml + * @var string + * @access private + */ + var $_archiveFile; + + /** + * @var int + * @access private + */ + var $_isValid = 0; + + /** + * Determines whether this packagefile was initialized only with partial package info + * + * If this package file was constructed via parsing REST, it will only contain + * + * - package name + * - channel name + * - dependencies + * @var boolean + * @access private + */ + var $_incomplete = true; + + /** + * @param bool determines whether to return a PEAR_Error object, or use the PEAR_ErrorStack + * @param string Name of Error Stack class to use. + */ + function PEAR_PackageFile_v1() + { + $this->_stack = &new PEAR_ErrorStack('PEAR_PackageFile_v1'); + $this->_stack->setErrorMessageTemplate($this->_getErrorMessage()); + $this->_isValid = 0; + } + + function installBinary($installer) + { + return false; + } + + function isExtension($name) + { + return false; + } + + function setConfig(&$config) + { + $this->_config = &$config; + $this->_registry = &$config->getRegistry(); + } + + function setRequestedGroup() + { + // placeholder + } + + /** + * For saving in the registry. + * + * Set the last version that was installed + * @param string + */ + function setLastInstalledVersion($version) + { + $this->_packageInfo['_lastversion'] = $version; + } + + /** + * @return string|false + */ + function getLastInstalledVersion() + { + if (isset($this->_packageInfo['_lastversion'])) { + return $this->_packageInfo['_lastversion']; + } + return false; + } + + function getInstalledBinary() + { + return false; + } + + function listPostinstallScripts() + { + return false; + } + + function initPostinstallScripts() + { + return false; + } + + function setLogger(&$logger) + { + if ($logger && (!is_object($logger) || !method_exists($logger, 'log'))) { + return PEAR::raiseError('Logger must be compatible with PEAR_Common::log'); + } + $this->_logger = &$logger; + } + + function setPackagefile($file, $archive = false) + { + $this->_packageFile = $file; + $this->_archiveFile = $archive ? $archive : $file; + } + + function getPackageFile() + { + return isset($this->_packageFile) ? $this->_packageFile : false; + } + + function getPackageType() + { + return 'php'; + } + + function getArchiveFile() + { + return $this->_archiveFile; + } + + function packageInfo($field) + { + if (!is_string($field) || empty($field) || + !isset($this->_packageInfo[$field])) { + return false; + } + return $this->_packageInfo[$field]; + } + + function setDirtree($path) + { + if (!isset($this->_packageInfo['dirtree'])) { + $this->_packageInfo['dirtree'] = array(); + } + $this->_packageInfo['dirtree'][$path] = true; + } + + function getDirtree() + { + if (isset($this->_packageInfo['dirtree']) && count($this->_packageInfo['dirtree'])) { + return $this->_packageInfo['dirtree']; + } + return false; + } + + function resetDirtree() + { + unset($this->_packageInfo['dirtree']); + } + + function fromArray($pinfo) + { + $this->_incomplete = false; + $this->_packageInfo = $pinfo; + } + + function isIncomplete() + { + return $this->_incomplete; + } + + function getChannel() + { + return 'pear.php.net'; + } + + function getUri() + { + return false; + } + + function getTime() + { + return false; + } + + function getExtends() + { + if (isset($this->_packageInfo['extends'])) { + return $this->_packageInfo['extends']; + } + return false; + } + + /** + * @return array + */ + function toArray() + { + if (!$this->validate(PEAR_VALIDATE_NORMAL)) { + return false; + } + return $this->getArray(); + } + + function getArray() + { + return $this->_packageInfo; + } + + function getName() + { + return $this->getPackage(); + } + + function getPackage() + { + if (isset($this->_packageInfo['package'])) { + return $this->_packageInfo['package']; + } + return false; + } + + /** + * WARNING - don't use this unless you know what you are doing + */ + function setRawPackage($package) + { + $this->_packageInfo['package'] = $package; + } + + function setPackage($package) + { + $this->_packageInfo['package'] = $package; + $this->_isValid = false; + } + + function getVersion() + { + if (isset($this->_packageInfo['version'])) { + return $this->_packageInfo['version']; + } + return false; + } + + function setVersion($version) + { + $this->_packageInfo['version'] = $version; + $this->_isValid = false; + } + + function clearMaintainers() + { + unset($this->_packageInfo['maintainers']); + } + + function getMaintainers() + { + if (isset($this->_packageInfo['maintainers'])) { + return $this->_packageInfo['maintainers']; + } + return false; + } + + /** + * Adds a new maintainer - no checking of duplicates is performed, use + * updatemaintainer for that purpose. + */ + function addMaintainer($role, $handle, $name, $email) + { + $this->_packageInfo['maintainers'][] = + array('handle' => $handle, 'role' => $role, 'email' => $email, 'name' => $name); + $this->_isValid = false; + } + + function updateMaintainer($role, $handle, $name, $email) + { + $found = false; + if (!isset($this->_packageInfo['maintainers']) || + !is_array($this->_packageInfo['maintainers'])) { + return $this->addMaintainer($role, $handle, $name, $email); + } + foreach ($this->_packageInfo['maintainers'] as $i => $maintainer) { + if ($maintainer['handle'] == $handle) { + $found = $i; + break; + } + } + if ($found !== false) { + unset($this->_packageInfo['maintainers'][$found]); + $this->_packageInfo['maintainers'] = + array_values($this->_packageInfo['maintainers']); + } + $this->addMaintainer($role, $handle, $name, $email); + } + + function deleteMaintainer($handle) + { + $found = false; + foreach ($this->_packageInfo['maintainers'] as $i => $maintainer) { + if ($maintainer['handle'] == $handle) { + $found = $i; + break; + } + } + if ($found !== false) { + unset($this->_packageInfo['maintainers'][$found]); + $this->_packageInfo['maintainers'] = + array_values($this->_packageInfo['maintainers']); + return true; + } + return false; + } + + function getState() + { + if (isset($this->_packageInfo['release_state'])) { + return $this->_packageInfo['release_state']; + } + return false; + } + + function setRawState($state) + { + $this->_packageInfo['release_state'] = $state; + } + + function setState($state) + { + $this->_packageInfo['release_state'] = $state; + $this->_isValid = false; + } + + function getDate() + { + if (isset($this->_packageInfo['release_date'])) { + return $this->_packageInfo['release_date']; + } + return false; + } + + function setDate($date) + { + $this->_packageInfo['release_date'] = $date; + $this->_isValid = false; + } + + function getLicense() + { + if (isset($this->_packageInfo['release_license'])) { + return $this->_packageInfo['release_license']; + } + return false; + } + + function setLicense($date) + { + $this->_packageInfo['release_license'] = $date; + $this->_isValid = false; + } + + function getSummary() + { + if (isset($this->_packageInfo['summary'])) { + return $this->_packageInfo['summary']; + } + return false; + } + + function setSummary($summary) + { + $this->_packageInfo['summary'] = $summary; + $this->_isValid = false; + } + + function getDescription() + { + if (isset($this->_packageInfo['description'])) { + return $this->_packageInfo['description']; + } + return false; + } + + function setDescription($desc) + { + $this->_packageInfo['description'] = $desc; + $this->_isValid = false; + } + + function getNotes() + { + if (isset($this->_packageInfo['release_notes'])) { + return $this->_packageInfo['release_notes']; + } + return false; + } + + function setNotes($notes) + { + $this->_packageInfo['release_notes'] = $notes; + $this->_isValid = false; + } + + function getDeps() + { + if (isset($this->_packageInfo['release_deps'])) { + return $this->_packageInfo['release_deps']; + } + return false; + } + + /** + * Reset dependencies prior to adding new ones + */ + function clearDeps() + { + unset($this->_packageInfo['release_deps']); + } + + function addPhpDep($version, $rel) + { + $this->_isValid = false; + $this->_packageInfo['release_deps'][] = + array('type' => 'php', + 'rel' => $rel, + 'version' => $version); + } + + function addPackageDep($name, $version, $rel, $optional = 'no') + { + $this->_isValid = false; + $dep = + array('type' => 'pkg', + 'name' => $name, + 'rel' => $rel, + 'optional' => $optional); + if ($rel != 'has' && $rel != 'not') { + $dep['version'] = $version; + } + $this->_packageInfo['release_deps'][] = $dep; + } + + function addExtensionDep($name, $version, $rel, $optional = 'no') + { + $this->_isValid = false; + $this->_packageInfo['release_deps'][] = + array('type' => 'ext', + 'name' => $name, + 'rel' => $rel, + 'version' => $version, + 'optional' => $optional); + } + + /** + * WARNING - do not use this function directly unless you know what you're doing + */ + function setDeps($deps) + { + $this->_packageInfo['release_deps'] = $deps; + } + + function hasDeps() + { + return isset($this->_packageInfo['release_deps']) && + count($this->_packageInfo['release_deps']); + } + + function getDependencyGroup($group) + { + return false; + } + + function isCompatible($pf) + { + return false; + } + + function isSubpackageOf($p) + { + return $p->isSubpackage($this); + } + + function isSubpackage($p) + { + return false; + } + + function dependsOn($package, $channel) + { + if (strtolower($channel) != 'pear.php.net') { + return false; + } + if (!($deps = $this->getDeps())) { + return false; + } + foreach ($deps as $dep) { + if ($dep['type'] != 'pkg') { + continue; + } + if (strtolower($dep['name']) == strtolower($package)) { + return true; + } + } + return false; + } + + function getConfigureOptions() + { + if (isset($this->_packageInfo['configure_options'])) { + return $this->_packageInfo['configure_options']; + } + return false; + } + + function hasConfigureOptions() + { + return isset($this->_packageInfo['configure_options']) && + count($this->_packageInfo['configure_options']); + } + + function addConfigureOption($name, $prompt, $default = false) + { + $o = array('name' => $name, 'prompt' => $prompt); + if ($default !== false) { + $o['default'] = $default; + } + if (!isset($this->_packageInfo['configure_options'])) { + $this->_packageInfo['configure_options'] = array(); + } + $this->_packageInfo['configure_options'][] = $o; + } + + function clearConfigureOptions() + { + unset($this->_packageInfo['configure_options']); + } + + function getProvides() + { + if (isset($this->_packageInfo['provides'])) { + return $this->_packageInfo['provides']; + } + return false; + } + + function getProvidesExtension() + { + return false; + } + + function addFile($dir, $file, $attrs) + { + $dir = preg_replace(array('!\\\\+!', '!/+!'), array('/', '/'), $dir); + if ($dir == '/' || $dir == '') { + $dir = ''; + } else { + $dir .= '/'; + } + $file = $dir . $file; + $file = preg_replace('![\\/]+!', '/', $file); + $this->_packageInfo['filelist'][$file] = $attrs; + } + + function getInstallationFilelist() + { + return $this->getFilelist(); + } + + function getFilelist() + { + if (isset($this->_packageInfo['filelist'])) { + return $this->_packageInfo['filelist']; + } + return false; + } + + function setFileAttribute($file, $attr, $value) + { + $this->_packageInfo['filelist'][$file][$attr] = $value; + } + + function resetFilelist() + { + $this->_packageInfo['filelist'] = array(); + } + + function setInstalledAs($file, $path) + { + if ($path) { + return $this->_packageInfo['filelist'][$file]['installed_as'] = $path; + } + unset($this->_packageInfo['filelist'][$file]['installed_as']); + } + + function installedFile($file, $atts) + { + if (isset($this->_packageInfo['filelist'][$file])) { + $this->_packageInfo['filelist'][$file] = + array_merge($this->_packageInfo['filelist'][$file], $atts); + } else { + $this->_packageInfo['filelist'][$file] = $atts; + } + } + + function getChangelog() + { + if (isset($this->_packageInfo['changelog'])) { + return $this->_packageInfo['changelog']; + } + return false; + } + + function getPackagexmlVersion() + { + return '1.0'; + } + + /** + * Wrapper to {@link PEAR_ErrorStack::getErrors()} + * @param boolean determines whether to purge the error stack after retrieving + * @return array + */ + function getValidationWarnings($purge = true) + { + return $this->_stack->getErrors($purge); + } + + // }}} + /** + * Validation error. Also marks the object contents as invalid + * @param error code + * @param array error information + * @access private + */ + function _validateError($code, $params = array()) + { + $this->_stack->push($code, 'error', $params, false, false, debug_backtrace()); + $this->_isValid = false; + } + + /** + * Validation warning. Does not mark the object contents invalid. + * @param error code + * @param array error information + * @access private + */ + function _validateWarning($code, $params = array()) + { + $this->_stack->push($code, 'warning', $params, false, false, debug_backtrace()); + } + + /** + * @param integer error code + * @access protected + */ + function _getErrorMessage() + { + return array( + PEAR_PACKAGEFILE_ERROR_NO_NAME => + 'Missing Package Name', + PEAR_PACKAGEFILE_ERROR_NO_SUMMARY => + 'No summary found', + PEAR_PACKAGEFILE_ERROR_MULTILINE_SUMMARY => + 'Summary should be on one line', + PEAR_PACKAGEFILE_ERROR_NO_DESCRIPTION => + 'Missing description', + PEAR_PACKAGEFILE_ERROR_NO_LICENSE => + 'Missing license', + PEAR_PACKAGEFILE_ERROR_NO_VERSION => + 'No release version found', + PEAR_PACKAGEFILE_ERROR_NO_STATE => + 'No release state found', + PEAR_PACKAGEFILE_ERROR_NO_DATE => + 'No release date found', + PEAR_PACKAGEFILE_ERROR_NO_NOTES => + 'No release notes found', + PEAR_PACKAGEFILE_ERROR_NO_LEAD => + 'Package must have at least one lead maintainer', + PEAR_PACKAGEFILE_ERROR_NO_MAINTAINERS => + 'No maintainers found, at least one must be defined', + PEAR_PACKAGEFILE_ERROR_NO_MAINTHANDLE => + 'Maintainer %index% has no handle (user ID at channel server)', + PEAR_PACKAGEFILE_ERROR_NO_MAINTROLE => + 'Maintainer %index% has no role', + PEAR_PACKAGEFILE_ERROR_NO_MAINTNAME => + 'Maintainer %index% has no name', + PEAR_PACKAGEFILE_ERROR_NO_MAINTEMAIL => + 'Maintainer %index% has no email', + PEAR_PACKAGEFILE_ERROR_NO_DEPNAME => + 'Dependency %index% is not a php dependency, and has no name', + PEAR_PACKAGEFILE_ERROR_NO_DEPREL => + 'Dependency %index% has no relation (rel)', + PEAR_PACKAGEFILE_ERROR_NO_DEPTYPE => + 'Dependency %index% has no type', + PEAR_PACKAGEFILE_ERROR_DEPNAME_IGNORED => + 'PHP Dependency %index% has a name attribute of "%name%" which will be' . + ' ignored!', + PEAR_PACKAGEFILE_ERROR_NO_DEPVERSION => + 'Dependency %index% is not a rel="has" or rel="not" dependency, ' . + 'and has no version', + PEAR_PACKAGEFILE_ERROR_NO_DEPPHPVERSION => + 'Dependency %index% is a type="php" dependency, ' . + 'and has no version', + PEAR_PACKAGEFILE_ERROR_DEPVERSION_IGNORED => + 'Dependency %index% is a rel="%rel%" dependency, versioning is ignored', + PEAR_PACKAGEFILE_ERROR_INVALID_DEPOPTIONAL => + 'Dependency %index% has invalid optional value "%opt%", should be yes or no', + PEAR_PACKAGEFILE_PHP_NO_NOT => + 'Dependency %index%: php dependencies cannot use "not" rel, use "ne"' . + ' to exclude specific versions', + PEAR_PACKAGEFILE_ERROR_NO_CONFNAME => + 'Configure Option %index% has no name', + PEAR_PACKAGEFILE_ERROR_NO_CONFPROMPT => + 'Configure Option %index% has no prompt', + PEAR_PACKAGEFILE_ERROR_NO_FILES => + 'No files in section of package.xml', + PEAR_PACKAGEFILE_ERROR_NO_FILEROLE => + 'File "%file%" has no role, expecting one of "%roles%"', + PEAR_PACKAGEFILE_ERROR_INVALID_FILEROLE => + 'File "%file%" has invalid role "%role%", expecting one of "%roles%"', + PEAR_PACKAGEFILE_ERROR_INVALID_FILENAME => + 'File "%file%" cannot start with ".", cannot package or install', + PEAR_PACKAGEFILE_ERROR_INVALID_PHPFILE => + 'Parser error: invalid PHP found in file "%file%"', + PEAR_PACKAGEFILE_ERROR_NO_PNAME_PREFIX => + 'in %file%: %type% "%name%" not prefixed with package name "%package%"', + PEAR_PACKAGEFILE_ERROR_INVALID_FILE => + 'Parser error: invalid PHP file "%file%"', + PEAR_PACKAGEFILE_ERROR_CHANNELVAL => + 'Channel validator error: field "%field%" - %reason%', + PEAR_PACKAGEFILE_ERROR_PHP5 => + 'Error, PHP5 token encountered in %file%, analysis should be in PHP5', + PEAR_PACKAGEFILE_ERROR_FILE_NOTFOUND => + 'File "%file%" in package.xml does not exist', + PEAR_PACKAGEFILE_ERROR_NON_ISO_CHARS => + 'Package.xml contains non-ISO-8859-1 characters, and may not validate', + ); + } + + /** + * Validate XML package definition file. + * + * @access public + * @return boolean + */ + function validate($state = PEAR_VALIDATE_NORMAL, $nofilechecking = false) + { + if (($this->_isValid & $state) == $state) { + return true; + } + $this->_isValid = true; + $info = $this->_packageInfo; + if (empty($info['package'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_NAME); + $this->_packageName = $pn = 'unknown'; + } else { + $this->_packageName = $pn = $info['package']; + } + + if (empty($info['summary'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_SUMMARY); + } elseif (strpos(trim($info['summary']), "\n") !== false) { + $this->_validateWarning(PEAR_PACKAGEFILE_ERROR_MULTILINE_SUMMARY, + array('summary' => $info['summary'])); + } + if (empty($info['description'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_DESCRIPTION); + } + if (empty($info['release_license'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_LICENSE); + } + if (empty($info['version'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_VERSION); + } + if (empty($info['release_state'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_STATE); + } + if (empty($info['release_date'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_DATE); + } + if (empty($info['release_notes'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_NOTES); + } + if (empty($info['maintainers'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_MAINTAINERS); + } else { + $haslead = false; + $i = 1; + foreach ($info['maintainers'] as $m) { + if (empty($m['handle'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_MAINTHANDLE, + array('index' => $i)); + } + if (empty($m['role'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_MAINTROLE, + array('index' => $i, 'roles' => PEAR_Common::getUserRoles())); + } elseif ($m['role'] == 'lead') { + $haslead = true; + } + if (empty($m['name'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_MAINTNAME, + array('index' => $i)); + } + if (empty($m['email'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_MAINTEMAIL, + array('index' => $i)); + } + $i++; + } + if (!$haslead) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_LEAD); + } + } + if (!empty($info['release_deps'])) { + $i = 1; + foreach ($info['release_deps'] as $d) { + if (!isset($d['type']) || empty($d['type'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_DEPTYPE, + array('index' => $i, 'types' => PEAR_Common::getDependencyTypes())); + continue; + } + if (!isset($d['rel']) || empty($d['rel'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_DEPREL, + array('index' => $i, 'rels' => PEAR_Common::getDependencyRelations())); + continue; + } + if (!empty($d['optional'])) { + if (!in_array($d['optional'], array('yes', 'no'))) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_INVALID_DEPOPTIONAL, + array('index' => $i, 'opt' => $d['optional'])); + } + } + if ($d['rel'] != 'has' && $d['rel'] != 'not' && empty($d['version'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_DEPVERSION, + array('index' => $i)); + } elseif (($d['rel'] == 'has' || $d['rel'] == 'not') && !empty($d['version'])) { + $this->_validateWarning(PEAR_PACKAGEFILE_ERROR_DEPVERSION_IGNORED, + array('index' => $i, 'rel' => $d['rel'])); + } + if ($d['type'] == 'php' && !empty($d['name'])) { + $this->_validateWarning(PEAR_PACKAGEFILE_ERROR_DEPNAME_IGNORED, + array('index' => $i, 'name' => $d['name'])); + } elseif ($d['type'] != 'php' && empty($d['name'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_DEPNAME, + array('index' => $i)); + } + if ($d['type'] == 'php' && empty($d['version'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_DEPPHPVERSION, + array('index' => $i)); + } + if (($d['rel'] == 'not') && ($d['type'] == 'php')) { + $this->_validateError(PEAR_PACKAGEFILE_PHP_NO_NOT, + array('index' => $i)); + } + $i++; + } + } + if (!empty($info['configure_options'])) { + $i = 1; + foreach ($info['configure_options'] as $c) { + if (empty($c['name'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_CONFNAME, + array('index' => $i)); + } + if (empty($c['prompt'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_CONFPROMPT, + array('index' => $i)); + } + $i++; + } + } + if (empty($info['filelist'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_FILES); + $errors[] = 'no files'; + } else { + foreach ($info['filelist'] as $file => $fa) { + if (empty($fa['role'])) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_FILEROLE, + array('file' => $file, 'roles' => PEAR_Common::getFileRoles())); + continue; + } elseif (!in_array($fa['role'], PEAR_Common::getFileRoles())) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_INVALID_FILEROLE, + array('file' => $file, 'role' => $fa['role'], 'roles' => PEAR_Common::getFileRoles())); + } + if (preg_match('~/\.\.?(/|\\z)|^\.\.?/~', str_replace('\\', '/', $file))) { + // file contains .. parent directory or . cur directory references + $this->_validateError(PEAR_PACKAGEFILE_ERROR_INVALID_FILENAME, + array('file' => $file)); + } + if (isset($fa['install-as']) && + preg_match('~/\.\.?(/|\\z)|^\.\.?/~', + str_replace('\\', '/', $fa['install-as']))) { + // install-as contains .. parent directory or . cur directory references + $this->_validateError(PEAR_PACKAGEFILE_ERROR_INVALID_FILENAME, + array('file' => $file . ' [installed as ' . $fa['install-as'] . ']')); + } + if (isset($fa['baseinstalldir']) && + preg_match('~/\.\.?(/|\\z)|^\.\.?/~', + str_replace('\\', '/', $fa['baseinstalldir']))) { + // install-as contains .. parent directory or . cur directory references + $this->_validateError(PEAR_PACKAGEFILE_ERROR_INVALID_FILENAME, + array('file' => $file . ' [baseinstalldir ' . $fa['baseinstalldir'] . ']')); + } + } + } + if (isset($this->_registry) && $this->_isValid) { + $chan = $this->_registry->getChannel('pear.php.net'); + if (PEAR::isError($chan)) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_CHANNELVAL, $chan->getMessage()); + return $this->_isValid = 0; + } + $validator = $chan->getValidationObject(); + $validator->setPackageFile($this); + $validator->validate($state); + $failures = $validator->getFailures(); + foreach ($failures['errors'] as $error) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_CHANNELVAL, $error); + } + foreach ($failures['warnings'] as $warning) { + $this->_validateWarning(PEAR_PACKAGEFILE_ERROR_CHANNELVAL, $warning); + } + } + if ($this->_isValid && $state == PEAR_VALIDATE_PACKAGING && !$nofilechecking) { + if ($this->_analyzePhpFiles()) { + $this->_isValid = true; + } + } + if ($this->_isValid) { + return $this->_isValid = $state; + } + return $this->_isValid = 0; + } + + function _analyzePhpFiles() + { + if (!$this->_isValid) { + return false; + } + if (!isset($this->_packageFile)) { + return false; + } + $dir_prefix = dirname($this->_packageFile); + $common = new PEAR_Common; + $log = isset($this->_logger) ? array(&$this->_logger, 'log') : + array($common, 'log'); + $info = $this->getFilelist(); + foreach ($info as $file => $fa) { + if (!file_exists($dir_prefix . DIRECTORY_SEPARATOR . $file)) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_FILE_NOTFOUND, + array('file' => realpath($dir_prefix) . DIRECTORY_SEPARATOR . $file)); + continue; + } + if ($fa['role'] == 'php' && $dir_prefix) { + call_user_func_array($log, array(1, "Analyzing $file")); + $srcinfo = $this->_analyzeSourceCode($dir_prefix . DIRECTORY_SEPARATOR . $file); + if ($srcinfo) { + $this->_buildProvidesArray($srcinfo); + } + } + } + $this->_packageName = $pn = $this->getPackage(); + $pnl = strlen($pn); + if (isset($this->_packageInfo['provides'])) { + foreach ((array) $this->_packageInfo['provides'] as $key => $what) { + if (isset($what['explicit'])) { + // skip conformance checks if the provides entry is + // specified in the package.xml file + continue; + } + extract($what); + if ($type == 'class') { + if (!strncasecmp($name, $pn, $pnl)) { + continue; + } + $this->_validateWarning(PEAR_PACKAGEFILE_ERROR_NO_PNAME_PREFIX, + array('file' => $file, 'type' => $type, 'name' => $name, 'package' => $pn)); + } elseif ($type == 'function') { + if (strstr($name, '::') || !strncasecmp($name, $pn, $pnl)) { + continue; + } + $this->_validateWarning(PEAR_PACKAGEFILE_ERROR_NO_PNAME_PREFIX, + array('file' => $file, 'type' => $type, 'name' => $name, 'package' => $pn)); + } + } + } + return $this->_isValid; + } + + /** + * Get the default xml generator object + * + * @return PEAR_PackageFile_Generator_v1 + */ + function &getDefaultGenerator() + { + if (!class_exists('PEAR_PackageFile_Generator_v1')) { + require_once 'PEAR/PackageFile/Generator/v1.php'; + } + $a = &new PEAR_PackageFile_Generator_v1($this); + return $a; + } + + /** + * Get the contents of a file listed within the package.xml + * @param string + * @return string + */ + function getFileContents($file) + { + if ($this->_archiveFile == $this->_packageFile) { // unpacked + $dir = dirname($this->_packageFile); + $file = $dir . DIRECTORY_SEPARATOR . $file; + $file = str_replace(array('/', '\\'), + array(DIRECTORY_SEPARATOR, DIRECTORY_SEPARATOR), $file); + if (file_exists($file) && is_readable($file)) { + return implode('', file($file)); + } + } else { // tgz + if (!class_exists('Archive_Tar')) { + require_once 'Archive/Tar.php'; + } + $tar = &new Archive_Tar($this->_archiveFile); + $tar->pushErrorHandling(PEAR_ERROR_RETURN); + if ($file != 'package.xml' && $file != 'package2.xml') { + $file = $this->getPackage() . '-' . $this->getVersion() . '/' . $file; + } + $file = $tar->extractInString($file); + $tar->popErrorHandling(); + if (PEAR::isError($file)) { + return PEAR::raiseError("Cannot locate file '$file' in archive"); + } + return $file; + } + } + + // {{{ analyzeSourceCode() + /** + * Analyze the source code of the given PHP file + * + * @param string Filename of the PHP file + * @return mixed + * @access private + */ + function _analyzeSourceCode($file) + { + if (!function_exists("token_get_all")) { + return false; + } + if (!defined('T_DOC_COMMENT')) { + define('T_DOC_COMMENT', T_COMMENT); + } + if (!defined('T_INTERFACE')) { + define('T_INTERFACE', -1); + } + if (!defined('T_IMPLEMENTS')) { + define('T_IMPLEMENTS', -1); + } + if (!$fp = @fopen($file, "r")) { + return false; + } + fclose($fp); + $contents = file_get_contents($file); + $tokens = token_get_all($contents); +/* + for ($i = 0; $i < sizeof($tokens); $i++) { + @list($token, $data) = $tokens[$i]; + if (is_string($token)) { + var_dump($token); + } else { + print token_name($token) . ' '; + var_dump(rtrim($data)); + } + } +*/ + $look_for = 0; + $paren_level = 0; + $bracket_level = 0; + $brace_level = 0; + $lastphpdoc = ''; + $current_class = ''; + $current_interface = ''; + $current_class_level = -1; + $current_function = ''; + $current_function_level = -1; + $declared_classes = array(); + $declared_interfaces = array(); + $declared_functions = array(); + $declared_methods = array(); + $used_classes = array(); + $used_functions = array(); + $extends = array(); + $implements = array(); + $nodeps = array(); + $inquote = false; + $interface = false; + for ($i = 0; $i < sizeof($tokens); $i++) { + if (is_array($tokens[$i])) { + list($token, $data) = $tokens[$i]; + } else { + $token = $tokens[$i]; + $data = ''; + } + if ($inquote) { + if ($token != '"' && $token != T_END_HEREDOC) { + continue; + } else { + $inquote = false; + continue; + } + } + switch ($token) { + case T_WHITESPACE : + continue; + case ';': + if ($interface) { + $current_function = ''; + $current_function_level = -1; + } + break; + case '"': + case T_START_HEREDOC: + $inquote = true; + break; + case T_CURLY_OPEN: + case T_DOLLAR_OPEN_CURLY_BRACES: + case '{': $brace_level++; continue 2; + case '}': + $brace_level--; + if ($current_class_level == $brace_level) { + $current_class = ''; + $current_class_level = -1; + } + if ($current_function_level == $brace_level) { + $current_function = ''; + $current_function_level = -1; + } + continue 2; + case '[': $bracket_level++; continue 2; + case ']': $bracket_level--; continue 2; + case '(': $paren_level++; continue 2; + case ')': $paren_level--; continue 2; + case T_INTERFACE: + $interface = true; + case T_CLASS: + if (($current_class_level != -1) || ($current_function_level != -1)) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_INVALID_PHPFILE, + array('file' => $file)); + return false; + } + case T_FUNCTION: + case T_NEW: + case T_EXTENDS: + case T_IMPLEMENTS: + $look_for = $token; + continue 2; + case T_STRING: + if (version_compare(zend_version(), '2.0', '<')) { + if (in_array(strtolower($data), + array('public', 'private', 'protected', 'abstract', + 'interface', 'implements', 'throw') + )) { + $this->_validateWarning(PEAR_PACKAGEFILE_ERROR_PHP5, + array($file)); + } + } + if ($look_for == T_CLASS) { + $current_class = $data; + $current_class_level = $brace_level; + $declared_classes[] = $current_class; + } elseif ($look_for == T_INTERFACE) { + $current_interface = $data; + $current_class_level = $brace_level; + $declared_interfaces[] = $current_interface; + } elseif ($look_for == T_IMPLEMENTS) { + $implements[$current_class] = $data; + } elseif ($look_for == T_EXTENDS) { + $extends[$current_class] = $data; + } elseif ($look_for == T_FUNCTION) { + if ($current_class) { + $current_function = "$current_class::$data"; + $declared_methods[$current_class][] = $data; + } elseif ($current_interface) { + $current_function = "$current_interface::$data"; + $declared_methods[$current_interface][] = $data; + } else { + $current_function = $data; + $declared_functions[] = $current_function; + } + $current_function_level = $brace_level; + $m = array(); + } elseif ($look_for == T_NEW) { + $used_classes[$data] = true; + } + $look_for = 0; + continue 2; + case T_VARIABLE: + $look_for = 0; + continue 2; + case T_DOC_COMMENT: + case T_COMMENT: + if (preg_match('!^/\*\*\s!', $data)) { + $lastphpdoc = $data; + if (preg_match_all('/@nodep\s+(\S+)/', $lastphpdoc, $m)) { + $nodeps = array_merge($nodeps, $m[1]); + } + } + continue 2; + case T_DOUBLE_COLON: + if (!($tokens[$i - 1][0] == T_WHITESPACE || $tokens[$i - 1][0] == T_STRING)) { + $this->_validateError(PEAR_PACKAGEFILE_ERROR_INVALID_PHPFILE, + array('file' => $file)); + return false; + } + $class = $tokens[$i - 1][1]; + if (strtolower($class) != 'parent') { + $used_classes[$class] = true; + } + continue 2; + } + } + return array( + "source_file" => $file, + "declared_classes" => $declared_classes, + "declared_interfaces" => $declared_interfaces, + "declared_methods" => $declared_methods, + "declared_functions" => $declared_functions, + "used_classes" => array_diff(array_keys($used_classes), $nodeps), + "inheritance" => $extends, + "implements" => $implements, + ); + } + + /** + * Build a "provides" array from data returned by + * analyzeSourceCode(). The format of the built array is like + * this: + * + * array( + * 'class;MyClass' => 'array('type' => 'class', 'name' => 'MyClass'), + * ... + * ) + * + * + * @param array $srcinfo array with information about a source file + * as returned by the analyzeSourceCode() method. + * + * @return void + * + * @access private + * + */ + function _buildProvidesArray($srcinfo) + { + if (!$this->_isValid) { + return false; + } + $file = basename($srcinfo['source_file']); + $pn = $this->getPackage(); + $pnl = strlen($pn); + foreach ($srcinfo['declared_classes'] as $class) { + $key = "class;$class"; + if (isset($this->_packageInfo['provides'][$key])) { + continue; + } + $this->_packageInfo['provides'][$key] = + array('file'=> $file, 'type' => 'class', 'name' => $class); + if (isset($srcinfo['inheritance'][$class])) { + $this->_packageInfo['provides'][$key]['extends'] = + $srcinfo['inheritance'][$class]; + } + } + foreach ($srcinfo['declared_methods'] as $class => $methods) { + foreach ($methods as $method) { + $function = "$class::$method"; + $key = "function;$function"; + if ($method{0} == '_' || !strcasecmp($method, $class) || + isset($this->_packageInfo['provides'][$key])) { + continue; + } + $this->_packageInfo['provides'][$key] = + array('file'=> $file, 'type' => 'function', 'name' => $function); + } + } + + foreach ($srcinfo['declared_functions'] as $function) { + $key = "function;$function"; + if ($function{0} == '_' || isset($this->_packageInfo['provides'][$key])) { + continue; + } + if (!strstr($function, '::') && strncasecmp($function, $pn, $pnl)) { + $warnings[] = "in1 " . $file . ": function \"$function\" not prefixed with package name \"$pn\""; + } + $this->_packageInfo['provides'][$key] = + array('file'=> $file, 'type' => 'function', 'name' => $function); + } + } + + // }}} +} +?> diff --git a/library/pear/PEAR/PackageFile/v2.php b/library/pear/PEAR/PackageFile/v2.php new file mode 100644 index 000000000..0092168de --- /dev/null +++ b/library/pear/PEAR/PackageFile/v2.php @@ -0,0 +1,2045 @@ + + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: v2.php 276383 2009-02-24 23:39:37Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ +/** + * For error handling + */ +require_once 'PEAR/ErrorStack.php'; +/** + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.1 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_PackageFile_v2 +{ + + /** + * Parsed package information + * @var array + * @access private + */ + var $_packageInfo = array(); + + /** + * path to package .tgz or false if this is a local/extracted package.xml + * @var string|false + * @access private + */ + var $_archiveFile; + + /** + * path to package .xml or false if this is an abstract parsed-from-string xml + * @var string|false + * @access private + */ + var $_packageFile; + + /** + * This is used by file analysis routines to log progress information + * @var PEAR_Common + * @access protected + */ + var $_logger; + + /** + * This is set to the highest validation level that has been validated + * + * If the package.xml is invalid or unknown, this is set to 0. If + * normal validation has occurred, this is set to PEAR_VALIDATE_NORMAL. If + * downloading/installation validation has occurred it is set to PEAR_VALIDATE_DOWNLOADING + * or INSTALLING, and so on up to PEAR_VALIDATE_PACKAGING. This allows validation + * "caching" to occur, which is particularly important for package validation, so + * that PHP files are not validated twice + * @var int + * @access private + */ + var $_isValid = 0; + + /** + * True if the filelist has been validated + * @param bool + */ + var $_filesValid = false; + + /** + * @var PEAR_Registry + * @access protected + */ + var $_registry; + + /** + * @var PEAR_Config + * @access protected + */ + var $_config; + + /** + * Optional Dependency group requested for installation + * @var string + * @access private + */ + var $_requestedGroup = false; + + /** + * @var PEAR_ErrorStack + * @access protected + */ + var $_stack; + + /** + * Namespace prefix used for tasks in this package.xml - use tasks: whenever possible + */ + var $_tasksNs; + + /** + * Determines whether this packagefile was initialized only with partial package info + * + * If this package file was constructed via parsing REST, it will only contain + * + * - package name + * - channel name + * - dependencies + * @var boolean + * @access private + */ + var $_incomplete = true; + + /** + * @var PEAR_PackageFile_v2_Validator + */ + var $_v2Validator; + + /** + * The constructor merely sets up the private error stack + */ + function PEAR_PackageFile_v2() + { + $this->_stack = new PEAR_ErrorStack('PEAR_PackageFile_v2', false, null); + $this->_isValid = false; + } + + /** + * To make unit-testing easier + * @param PEAR_Frontend_* + * @param array options + * @param PEAR_Config + * @return PEAR_Downloader + * @access protected + */ + function &getPEARDownloader(&$i, $o, &$c) + { + $z = &new PEAR_Downloader($i, $o, $c); + return $z; + } + + /** + * To make unit-testing easier + * @param PEAR_Config + * @param array options + * @param array package name as returned from {@link PEAR_Registry::parsePackageName()} + * @param int PEAR_VALIDATE_* constant + * @return PEAR_Dependency2 + * @access protected + */ + function &getPEARDependency2(&$c, $o, $p, $s = PEAR_VALIDATE_INSTALLING) + { + if (!class_exists('PEAR_Dependency2')) { + require_once 'PEAR/Dependency2.php'; + } + $z = &new PEAR_Dependency2($c, $o, $p, $s); + return $z; + } + + function getInstalledBinary() + { + return isset($this->_packageInfo['#binarypackage']) ? $this->_packageInfo['#binarypackage'] : + false; + } + + /** + * Installation of source package has failed, attempt to download and install the + * binary version of this package. + * @param PEAR_Installer + * @return array|false + */ + function installBinary(&$installer) + { + if (!OS_WINDOWS) { + $a = false; + return $a; + } + if ($this->getPackageType() == 'extsrc' || $this->getPackageType() == 'zendextsrc') { + $releasetype = $this->getPackageType() . 'release'; + if (!is_array($installer->getInstallPackages())) { + $a = false; + return $a; + } + foreach ($installer->getInstallPackages() as $p) { + if ($p->isExtension($this->_packageInfo['providesextension'])) { + if ($p->getPackageType() != 'extsrc' && $p->getPackageType() != 'zendextsrc') { + $a = false; + return $a; // the user probably downloaded it separately + } + } + } + if (isset($this->_packageInfo[$releasetype]['binarypackage'])) { + $installer->log(0, 'Attempting to download binary version of extension "' . + $this->_packageInfo['providesextension'] . '"'); + $params = $this->_packageInfo[$releasetype]['binarypackage']; + if (!is_array($params) || !isset($params[0])) { + $params = array($params); + } + if (isset($this->_packageInfo['channel'])) { + foreach ($params as $i => $param) { + $params[$i] = array('channel' => $this->_packageInfo['channel'], + 'package' => $param, 'version' => $this->getVersion()); + } + } + $dl = &$this->getPEARDownloader($installer->ui, $installer->getOptions(), + $installer->config); + $verbose = $dl->config->get('verbose'); + $dl->config->set('verbose', -1); + foreach ($params as $param) { + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $ret = $dl->download(array($param)); + PEAR::popErrorHandling(); + if (is_array($ret) && count($ret)) { + break; + } + } + $dl->config->set('verbose', $verbose); + if (is_array($ret)) { + if (count($ret) == 1) { + $pf = $ret[0]->getPackageFile(); + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $err = $installer->install($ret[0]); + PEAR::popErrorHandling(); + if (is_array($err)) { + $this->_packageInfo['#binarypackage'] = $ret[0]->getPackage(); + // "install" self, so all dependencies will work transparently + $this->_registry->addPackage2($this); + $installer->log(0, 'Download and install of binary extension "' . + $this->_registry->parsedPackageNameToString( + array('channel' => $pf->getChannel(), + 'package' => $pf->getPackage()), true) . '" successful'); + $a = array($ret[0], $err); + return $a; + } + $installer->log(0, 'Download and install of binary extension "' . + $this->_registry->parsedPackageNameToString( + array('channel' => $pf->getChannel(), + 'package' => $pf->getPackage()), true) . '" failed'); + } + } + } + } + $a = false; + return $a; + } + + /** + * @return string|false Extension name + */ + function getProvidesExtension() + { + if (in_array($this->getPackageType(), + array('extsrc', 'extbin', 'zendextsrc', 'zendextbin'))) { + if (isset($this->_packageInfo['providesextension'])) { + return $this->_packageInfo['providesextension']; + } + } + return false; + } + + /** + * @param string Extension name + * @return bool + */ + function isExtension($extension) + { + if (in_array($this->getPackageType(), + array('extsrc', 'extbin', 'zendextsrc', 'zendextbin'))) { + return $this->_packageInfo['providesextension'] == $extension; + } + return false; + } + + /** + * Tests whether every part of the package.xml 1.0 is represented in + * this package.xml 2.0 + * @param PEAR_PackageFile_v1 + * @return bool + */ + function isEquivalent($pf1) + { + if (!$pf1) { + return true; + } + if ($this->getPackageType() == 'bundle') { + return false; + } + $this->_stack->getErrors(true); + if (!$pf1->validate(PEAR_VALIDATE_NORMAL)) { + return false; + } + $pass = true; + if ($pf1->getPackage() != $this->getPackage()) { + $this->_differentPackage($pf1->getPackage()); + $pass = false; + } + if ($pf1->getVersion() != $this->getVersion()) { + $this->_differentVersion($pf1->getVersion()); + $pass = false; + } + if (trim($pf1->getSummary()) != $this->getSummary()) { + $this->_differentSummary($pf1->getSummary()); + $pass = false; + } + if (preg_replace('/\s+/', '', $pf1->getDescription()) != + preg_replace('/\s+/', '', $this->getDescription())) { + $this->_differentDescription($pf1->getDescription()); + $pass = false; + } + if ($pf1->getState() != $this->getState()) { + $this->_differentState($pf1->getState()); + $pass = false; + } + if (!strstr(preg_replace('/\s+/', '', $this->getNotes()), + preg_replace('/\s+/', '', $pf1->getNotes()))) { + $this->_differentNotes($pf1->getNotes()); + $pass = false; + } + $mymaintainers = $this->getMaintainers(); + $yourmaintainers = $pf1->getMaintainers(); + for ($i1 = 0; $i1 < count($yourmaintainers); $i1++) { + $reset = false; + for ($i2 = 0; $i2 < count($mymaintainers); $i2++) { + if ($mymaintainers[$i2]['handle'] == $yourmaintainers[$i1]['handle']) { + if ($mymaintainers[$i2]['role'] != $yourmaintainers[$i1]['role']) { + $this->_differentRole($mymaintainers[$i2]['handle'], + $yourmaintainers[$i1]['role'], $mymaintainers[$i2]['role']); + $pass = false; + } + if ($mymaintainers[$i2]['email'] != $yourmaintainers[$i1]['email']) { + $this->_differentEmail($mymaintainers[$i2]['handle'], + $yourmaintainers[$i1]['email'], $mymaintainers[$i2]['email']); + $pass = false; + } + if ($mymaintainers[$i2]['name'] != $yourmaintainers[$i1]['name']) { + $this->_differentName($mymaintainers[$i2]['handle'], + $yourmaintainers[$i1]['name'], $mymaintainers[$i2]['name']); + $pass = false; + } + unset($mymaintainers[$i2]); + $mymaintainers = array_values($mymaintainers); + unset($yourmaintainers[$i1]); + $yourmaintainers = array_values($yourmaintainers); + $reset = true; + break; + } + } + if ($reset) { + $i1 = -1; + } + } + $this->_unmatchedMaintainers($mymaintainers, $yourmaintainers); + $filelist = $this->getFilelist(); + foreach ($pf1->getFilelist() as $file => $atts) { + if (!isset($filelist[$file])) { + $this->_missingFile($file); + $pass = false; + } + } + return $pass; + } + + function _differentPackage($package) + { + $this->_stack->push(__FUNCTION__, 'error', array('package' => $package, + 'self' => $this->getPackage()), + 'package.xml 1.0 package "%package%" does not match "%self%"'); + } + + function _differentVersion($version) + { + $this->_stack->push(__FUNCTION__, 'error', array('version' => $version, + 'self' => $this->getVersion()), + 'package.xml 1.0 version "%version%" does not match "%self%"'); + } + + function _differentState($state) + { + $this->_stack->push(__FUNCTION__, 'error', array('state' => $state, + 'self' => $this->getState()), + 'package.xml 1.0 state "%state%" does not match "%self%"'); + } + + function _differentRole($handle, $role, $selfrole) + { + $this->_stack->push(__FUNCTION__, 'error', array('handle' => $handle, + 'role' => $role, 'self' => $selfrole), + 'package.xml 1.0 maintainer "%handle%" role "%role%" does not match "%self%"'); + } + + function _differentEmail($handle, $email, $selfemail) + { + $this->_stack->push(__FUNCTION__, 'error', array('handle' => $handle, + 'email' => $email, 'self' => $selfemail), + 'package.xml 1.0 maintainer "%handle%" email "%email%" does not match "%self%"'); + } + + function _differentName($handle, $name, $selfname) + { + $this->_stack->push(__FUNCTION__, 'error', array('handle' => $handle, + 'name' => $name, 'self' => $selfname), + 'package.xml 1.0 maintainer "%handle%" name "%name%" does not match "%self%"'); + } + + function _unmatchedMaintainers($my, $yours) + { + if ($my) { + array_walk($my, create_function('&$i, $k', '$i = $i["handle"];')); + $this->_stack->push(__FUNCTION__, 'error', array('handles' => $my), + 'package.xml 2.0 has unmatched extra maintainers "%handles%"'); + } + if ($yours) { + array_walk($yours, create_function('&$i, $k', '$i = $i["handle"];')); + $this->_stack->push(__FUNCTION__, 'error', array('handles' => $yours), + 'package.xml 1.0 has unmatched extra maintainers "%handles%"'); + } + } + + function _differentNotes($notes) + { + $truncnotes = strlen($notes) < 25 ? $notes : substr($notes, 0, 24) . '...'; + $truncmynotes = strlen($this->getNotes()) < 25 ? $this->getNotes() : + substr($this->getNotes(), 0, 24) . '...'; + $this->_stack->push(__FUNCTION__, 'error', array('notes' => $truncnotes, + 'self' => $truncmynotes), + 'package.xml 1.0 release notes "%notes%" do not match "%self%"'); + } + + function _differentSummary($summary) + { + $truncsummary = strlen($summary) < 25 ? $summary : substr($summary, 0, 24) . '...'; + $truncmysummary = strlen($this->getsummary()) < 25 ? $this->getSummary() : + substr($this->getsummary(), 0, 24) . '...'; + $this->_stack->push(__FUNCTION__, 'error', array('summary' => $truncsummary, + 'self' => $truncmysummary), + 'package.xml 1.0 summary "%summary%" does not match "%self%"'); + } + + function _differentDescription($description) + { + $truncdescription = trim(strlen($description) < 25 ? $description : substr($description, 0, 24) . '...'); + $truncmydescription = trim(strlen($this->getDescription()) < 25 ? $this->getDescription() : + substr($this->getdescription(), 0, 24) . '...'); + $this->_stack->push(__FUNCTION__, 'error', array('description' => $truncdescription, + 'self' => $truncmydescription), + 'package.xml 1.0 description "%description%" does not match "%self%"'); + } + + function _missingFile($file) + { + $this->_stack->push(__FUNCTION__, 'error', array('file' => $file), + 'package.xml 1.0 file "%file%" is not present in '); + } + + /** + * WARNING - do not use this function unless you know what you're doing + */ + function setRawState($state) + { + if (!isset($this->_packageInfo['stability'])) { + $this->_packageInfo['stability'] = array(); + } + $this->_packageInfo['stability']['release'] = $state; + } + + /** + * WARNING - do not use this function unless you know what you're doing + */ + function setRawCompatible($compatible) + { + $this->_packageInfo['compatible'] = $compatible; + } + + /** + * WARNING - do not use this function unless you know what you're doing + */ + function setRawPackage($package) + { + $this->_packageInfo['name'] = $package; + } + + /** + * WARNING - do not use this function unless you know what you're doing + */ + function setRawChannel($channel) + { + $this->_packageInfo['channel'] = $channel; + } + + function setRequestedGroup($group) + { + $this->_requestedGroup = $group; + } + + function getRequestedGroup() + { + if (isset($this->_requestedGroup)) { + return $this->_requestedGroup; + } + return false; + } + + /** + * For saving in the registry. + * + * Set the last version that was installed + * @param string + */ + function setLastInstalledVersion($version) + { + $this->_packageInfo['_lastversion'] = $version; + } + + /** + * @return string|false + */ + function getLastInstalledVersion() + { + if (isset($this->_packageInfo['_lastversion'])) { + return $this->_packageInfo['_lastversion']; + } + return false; + } + + /** + * Determines whether this package.xml has post-install scripts or not + * @return array|false + */ + function listPostinstallScripts() + { + $filelist = $this->getFilelist(); + $contents = $this->getContents(); + $contents = $contents['dir']['file']; + if (!is_array($contents) || !isset($contents[0])) { + $contents = array($contents); + } + $taskfiles = array(); + foreach ($contents as $file) { + $atts = $file['attribs']; + unset($file['attribs']); + if (count($file)) { + $taskfiles[$atts['name']] = $file; + } + } + $common = new PEAR_Common; + $common->debug = $this->_config->get('verbose'); + $this->_scripts = array(); + $ret = array(); + foreach ($taskfiles as $name => $tasks) { + if (!isset($filelist[$name])) { + // ignored files will not be in the filelist + continue; + } + $atts = $filelist[$name]; + foreach ($tasks as $tag => $raw) { + $task = $this->getTask($tag); + $task = &new $task($this->_config, $common, PEAR_TASK_INSTALL); + if ($task->isScript()) { + $ret[] = $filelist[$name]['installed_as']; + } + } + } + if (count($ret)) { + return $ret; + } + return false; + } + + /** + * Initialize post-install scripts for running + * + * This method can be used to detect post-install scripts, as the return value + * indicates whether any exist + * @return bool + */ + function initPostinstallScripts() + { + $filelist = $this->getFilelist(); + $contents = $this->getContents(); + $contents = $contents['dir']['file']; + if (!is_array($contents) || !isset($contents[0])) { + $contents = array($contents); + } + $taskfiles = array(); + foreach ($contents as $file) { + $atts = $file['attribs']; + unset($file['attribs']); + if (count($file)) { + $taskfiles[$atts['name']] = $file; + } + } + $common = new PEAR_Common; + $common->debug = $this->_config->get('verbose'); + $this->_scripts = array(); + foreach ($taskfiles as $name => $tasks) { + if (!isset($filelist[$name])) { + // file was not installed due to installconditions + continue; + } + $atts = $filelist[$name]; + foreach ($tasks as $tag => $raw) { + $taskname = $this->getTask($tag); + $task = &new $taskname($this->_config, $common, PEAR_TASK_INSTALL); + if (!$task->isScript()) { + continue; // scripts are only handled after installation + } + $lastversion = isset($this->_packageInfo['_lastversion']) ? + $this->_packageInfo['_lastversion'] : null; + $task->init($raw, $atts, $lastversion); + $res = $task->startSession($this, $atts['installed_as']); + if (!$res) { + continue; // skip this file + } + if (PEAR::isError($res)) { + return $res; + } + $assign = &$task; + $this->_scripts[] = &$assign; + } + } + if (count($this->_scripts)) { + return true; + } + return false; + } + + function runPostinstallScripts() + { + if ($this->initPostinstallScripts()) { + $ui = &PEAR_Frontend::singleton(); + if ($ui) { + $ui->runPostinstallScripts($this->_scripts, $this); + } + } + } + + + /** + * Convert a recursive set of
    and tags into a single tag with + * tags. + */ + function flattenFilelist() + { + if (isset($this->_packageInfo['bundle'])) { + return; + } + $filelist = array(); + if (isset($this->_packageInfo['contents']['dir']['dir'])) { + $this->_getFlattenedFilelist($filelist, $this->_packageInfo['contents']['dir']); + if (!isset($filelist[1])) { + $filelist = $filelist[0]; + } + $this->_packageInfo['contents']['dir']['file'] = $filelist; + unset($this->_packageInfo['contents']['dir']['dir']); + } else { + // else already flattened but check for baseinstalldir propagation + if (isset($this->_packageInfo['contents']['dir']['attribs']['baseinstalldir'])) { + if (isset($this->_packageInfo['contents']['dir']['file'][0])) { + foreach ($this->_packageInfo['contents']['dir']['file'] as $i => $file) { + if (isset($file['attribs']['baseinstalldir'])) { + continue; + } + $this->_packageInfo['contents']['dir']['file'][$i]['attribs']['baseinstalldir'] + = $this->_packageInfo['contents']['dir']['attribs']['baseinstalldir']; + } + } else { + if (!isset($this->_packageInfo['contents']['dir']['file']['attribs']['baseinstalldir'])) { + $this->_packageInfo['contents']['dir']['file']['attribs']['baseinstalldir'] + = $this->_packageInfo['contents']['dir']['attribs']['baseinstalldir']; + } + } + } + } + } + + /** + * @param array the final flattened file list + * @param array the current directory being processed + * @param string|false any recursively inherited baeinstalldir attribute + * @param string private recursion variable + * @return array + * @access protected + */ + function _getFlattenedFilelist(&$files, $dir, $baseinstall = false, $path = '') + { + if (isset($dir['attribs']) && isset($dir['attribs']['baseinstalldir'])) { + $baseinstall = $dir['attribs']['baseinstalldir']; + } + if (isset($dir['dir'])) { + if (!isset($dir['dir'][0])) { + $dir['dir'] = array($dir['dir']); + } + foreach ($dir['dir'] as $subdir) { + if (!isset($subdir['attribs']) || !isset($subdir['attribs']['name'])) { + $name = '*unknown*'; + } else { + $name = $subdir['attribs']['name']; + } + $newpath = empty($path) ? $name : + $path . '/' . $name; + $this->_getFlattenedFilelist($files, $subdir, + $baseinstall, $newpath); + } + } + if (isset($dir['file'])) { + if (!isset($dir['file'][0])) { + $dir['file'] = array($dir['file']); + } + foreach ($dir['file'] as $file) { + $attrs = $file['attribs']; + $name = $attrs['name']; + if ($baseinstall && !isset($attrs['baseinstalldir'])) { + $attrs['baseinstalldir'] = $baseinstall; + } + $attrs['name'] = empty($path) ? $name : $path . '/' . $name; + $attrs['name'] = preg_replace(array('!\\\\+!', '!/+!'), array('/', '/'), + $attrs['name']); + $file['attribs'] = $attrs; + $files[] = $file; + } + } + } + + function setConfig(&$config) + { + $this->_config = &$config; + $this->_registry = &$config->getRegistry(); + } + + function setLogger(&$logger) + { + if (!is_object($logger) || !method_exists($logger, 'log')) { + return PEAR::raiseError('Logger must be compatible with PEAR_Common::log'); + } + $this->_logger = &$logger; + } + + /** + * WARNING - do not use this function directly unless you know what you're doing + */ + function setDeps($deps) + { + $this->_packageInfo['dependencies'] = $deps; + } + + /** + * WARNING - do not use this function directly unless you know what you're doing + */ + function setCompatible($compat) + { + $this->_packageInfo['compatible'] = $compat; + } + + function setPackagefile($file, $archive = false) + { + $this->_packageFile = $file; + $this->_archiveFile = $archive ? $archive : $file; + } + + /** + * Wrapper to {@link PEAR_ErrorStack::getErrors()} + * @param boolean determines whether to purge the error stack after retrieving + * @return array + */ + function getValidationWarnings($purge = true) + { + return $this->_stack->getErrors($purge); + } + + function getPackageFile() + { + return $this->_packageFile; + } + + function getArchiveFile() + { + return $this->_archiveFile; + } + + + /** + * Directly set the array that defines this packagefile + * + * WARNING: no validation. This should only be performed by internal methods + * inside PEAR or by inputting an array saved from an existing PEAR_PackageFile_v2 + * @param array + */ + function fromArray($pinfo) + { + unset($pinfo['old']); + unset($pinfo['xsdversion']); + $this->_incomplete = false; + $this->_packageInfo = $pinfo; + } + + function isIncomplete() + { + return $this->_incomplete; + } + + /** + * @return array + */ + function toArray($forreg = false) + { + if (!$this->validate(PEAR_VALIDATE_NORMAL)) { + return false; + } + return $this->getArray($forreg); + } + + function getArray($forReg = false) + { + if ($forReg) { + $arr = $this->_packageInfo; + $arr['old'] = array(); + $arr['old']['version'] = $this->getVersion(); + $arr['old']['release_date'] = $this->getDate(); + $arr['old']['release_state'] = $this->getState(); + $arr['old']['release_license'] = $this->getLicense(); + $arr['old']['release_notes'] = $this->getNotes(); + $arr['old']['release_deps'] = $this->getDeps(); + $arr['old']['maintainers'] = $this->getMaintainers(); + $arr['xsdversion'] = '2.0'; + return $arr; + } else { + $info = $this->_packageInfo; + unset($info['dirtree']); + if (isset($info['_lastversion'])) { + unset($info['_lastversion']); + } + if (isset($info['#binarypackage'])) { + unset($info['#binarypackage']); + } + return $info; + } + } + + function packageInfo($field) + { + $arr = $this->getArray(true); + if ($field == 'state') { + return $arr['stability']['release']; + } + if ($field == 'api-version') { + return $arr['version']['api']; + } + if ($field == 'api-state') { + return $arr['stability']['api']; + } + if (isset($arr['old'][$field])) { + if (!is_string($arr['old'][$field])) { + return null; + } + return $arr['old'][$field]; + } + if (isset($arr[$field])) { + if (!is_string($arr[$field])) { + return null; + } + return $arr[$field]; + } + return null; + } + + function getName() + { + return $this->getPackage(); + } + + function getPackage() + { + if (isset($this->_packageInfo['name'])) { + return $this->_packageInfo['name']; + } + return false; + } + + function getChannel() + { + if (isset($this->_packageInfo['uri'])) { + return '__uri'; + } + if (isset($this->_packageInfo['channel'])) { + return strtolower($this->_packageInfo['channel']); + } + return false; + } + + function getUri() + { + if (isset($this->_packageInfo['uri'])) { + return $this->_packageInfo['uri']; + } + return false; + } + + function getExtends() + { + if (isset($this->_packageInfo['extends'])) { + return $this->_packageInfo['extends']; + } + return false; + } + + function getSummary() + { + if (isset($this->_packageInfo['summary'])) { + return $this->_packageInfo['summary']; + } + return false; + } + + function getDescription() + { + if (isset($this->_packageInfo['description'])) { + return $this->_packageInfo['description']; + } + return false; + } + + function getMaintainers($raw = false) + { + if (!isset($this->_packageInfo['lead'])) { + return false; + } + if ($raw) { + $ret = array('lead' => $this->_packageInfo['lead']); + (isset($this->_packageInfo['developer'])) ? + $ret['developer'] = $this->_packageInfo['developer'] :null; + (isset($this->_packageInfo['contributor'])) ? + $ret['contributor'] = $this->_packageInfo['contributor'] :null; + (isset($this->_packageInfo['helper'])) ? + $ret['helper'] = $this->_packageInfo['helper'] :null; + return $ret; + } else { + $ret = array(); + $leads = isset($this->_packageInfo['lead'][0]) ? $this->_packageInfo['lead'] : + array($this->_packageInfo['lead']); + foreach ($leads as $lead) { + $s = $lead; + $s['handle'] = $s['user']; + unset($s['user']); + $s['role'] = 'lead'; + $ret[] = $s; + } + if (isset($this->_packageInfo['developer'])) { + $leads = isset($this->_packageInfo['developer'][0]) ? + $this->_packageInfo['developer'] : + array($this->_packageInfo['developer']); + foreach ($leads as $maintainer) { + $s = $maintainer; + $s['handle'] = $s['user']; + unset($s['user']); + $s['role'] = 'developer'; + $ret[] = $s; + } + } + if (isset($this->_packageInfo['contributor'])) { + $leads = isset($this->_packageInfo['contributor'][0]) ? + $this->_packageInfo['contributor'] : + array($this->_packageInfo['contributor']); + foreach ($leads as $maintainer) { + $s = $maintainer; + $s['handle'] = $s['user']; + unset($s['user']); + $s['role'] = 'contributor'; + $ret[] = $s; + } + } + if (isset($this->_packageInfo['helper'])) { + $leads = isset($this->_packageInfo['helper'][0]) ? + $this->_packageInfo['helper'] : + array($this->_packageInfo['helper']); + foreach ($leads as $maintainer) { + $s = $maintainer; + $s['handle'] = $s['user']; + unset($s['user']); + $s['role'] = 'helper'; + $ret[] = $s; + } + } + return $ret; + } + return false; + } + + function getLeads() + { + if (isset($this->_packageInfo['lead'])) { + return $this->_packageInfo['lead']; + } + return false; + } + + function getDevelopers() + { + if (isset($this->_packageInfo['developer'])) { + return $this->_packageInfo['developer']; + } + return false; + } + + function getContributors() + { + if (isset($this->_packageInfo['contributor'])) { + return $this->_packageInfo['contributor']; + } + return false; + } + + function getHelpers() + { + if (isset($this->_packageInfo['helper'])) { + return $this->_packageInfo['helper']; + } + return false; + } + + function setDate($date) + { + if (!isset($this->_packageInfo['date'])) { + // ensure that the extends tag is set up in the right location + $this->_packageInfo = $this->_insertBefore($this->_packageInfo, + array('time', 'version', + 'stability', 'license', 'notes', 'contents', 'compatible', + 'dependencies', 'providesextension', 'srcpackage', 'srcuri', + 'phprelease', 'extsrcrelease', 'extbinrelease', 'zendextsrcrelease', + 'zendextbinrelease', 'bundle', 'changelog'), array(), 'date'); + } + $this->_packageInfo['date'] = $date; + $this->_isValid = 0; + } + + function setTime($time) + { + $this->_isValid = 0; + if (!isset($this->_packageInfo['time'])) { + // ensure that the time tag is set up in the right location + $this->_packageInfo = $this->_insertBefore($this->_packageInfo, + array('version', + 'stability', 'license', 'notes', 'contents', 'compatible', + 'dependencies', 'providesextension', 'srcpackage', 'srcuri', + 'phprelease', 'extsrcrelease', 'extbinrelease', 'zendextsrcrelease', + 'zendextbinrelease', 'bundle', 'changelog'), $time, 'time'); + } + $this->_packageInfo['time'] = $time; + } + + function getDate() + { + if (isset($this->_packageInfo['date'])) { + return $this->_packageInfo['date']; + } + return false; + } + + function getTime() + { + if (isset($this->_packageInfo['time'])) { + return $this->_packageInfo['time']; + } + return false; + } + + /** + * @param package|api version category to return + */ + function getVersion($key = 'release') + { + if (isset($this->_packageInfo['version'][$key])) { + return $this->_packageInfo['version'][$key]; + } + return false; + } + + function getStability() + { + if (isset($this->_packageInfo['stability'])) { + return $this->_packageInfo['stability']; + } + return false; + } + + function getState($key = 'release') + { + if (isset($this->_packageInfo['stability'][$key])) { + return $this->_packageInfo['stability'][$key]; + } + return false; + } + + function getLicense($raw = false) + { + if (isset($this->_packageInfo['license'])) { + if ($raw) { + return $this->_packageInfo['license']; + } + if (is_array($this->_packageInfo['license'])) { + return $this->_packageInfo['license']['_content']; + } else { + return $this->_packageInfo['license']; + } + } + return false; + } + + function getLicenseLocation() + { + if (!isset($this->_packageInfo['license']) || !is_array($this->_packageInfo['license'])) { + return false; + } + return $this->_packageInfo['license']['attribs']; + } + + function getNotes() + { + if (isset($this->_packageInfo['notes'])) { + return $this->_packageInfo['notes']; + } + return false; + } + + /** + * Return the tag contents, if any + * @return array|false + */ + function getUsesrole() + { + if (isset($this->_packageInfo['usesrole'])) { + return $this->_packageInfo['usesrole']; + } + return false; + } + + /** + * Return the tag contents, if any + * @return array|false + */ + function getUsestask() + { + if (isset($this->_packageInfo['usestask'])) { + return $this->_packageInfo['usestask']; + } + return false; + } + + /** + * This should only be used to retrieve filenames and install attributes + */ + function getFilelist($preserve = false) + { + if (isset($this->_packageInfo['filelist']) && !$preserve) { + return $this->_packageInfo['filelist']; + } + $this->flattenFilelist(); + if ($contents = $this->getContents()) { + $ret = array(); + if (!isset($contents['dir'])) { + return false; + } + if (!isset($contents['dir']['file'][0])) { + $contents['dir']['file'] = array($contents['dir']['file']); + } + foreach ($contents['dir']['file'] as $file) { + $name = $file['attribs']['name']; + if (!$preserve) { + $file = $file['attribs']; + } + $ret[$name] = $file; + } + if (!$preserve) { + $this->_packageInfo['filelist'] = $ret; + } + return $ret; + } + return false; + } + + /** + * Return configure options array, if any + * + * @return array|false + */ + function getConfigureOptions() + { + if ($this->getPackageType() != 'extsrc' && $this->getPackageType() != 'zendextsrc') { + return false; + } + + $releases = $this->getReleases(); + if (isset($releases[0])) { + $releases = $releases[0]; + } + + if (isset($releases['configureoption'])) { + if (!isset($releases['configureoption'][0])) { + $releases['configureoption'] = array($releases['configureoption']); + } + + for ($i = 0; $i < count($releases['configureoption']); $i++) { + $releases['configureoption'][$i] = $releases['configureoption'][$i]['attribs']; + } + + return $releases['configureoption']; + } + + return false; + } + + /** + * This is only used at install-time, after all serialization + * is over. + */ + function resetFilelist() + { + $this->_packageInfo['filelist'] = array(); + } + + /** + * Retrieve a list of files that should be installed on this computer + * @return array + */ + function getInstallationFilelist($forfilecheck = false) + { + $contents = $this->getFilelist(true); + if (isset($contents['dir']['attribs']['baseinstalldir'])) { + $base = $contents['dir']['attribs']['baseinstalldir']; + } + if (isset($this->_packageInfo['bundle'])) { + return PEAR::raiseError( + 'Exception: bundles should be handled in download code only'); + } + $release = $this->getReleases(); + if ($release) { + if (!isset($release[0])) { + if (!isset($release['installconditions']) && !isset($release['filelist'])) { + if ($forfilecheck) { + return $this->getFilelist(); + } + return $contents; + } + $release = array($release); + } + $depchecker = &$this->getPEARDependency2($this->_config, array(), + array('channel' => $this->getChannel(), 'package' => $this->getPackage()), + PEAR_VALIDATE_INSTALLING); + foreach ($release as $instance) { + if (isset($instance['installconditions'])) { + $installconditions = $instance['installconditions']; + if (is_array($installconditions)) { + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + foreach ($installconditions as $type => $conditions) { + if (!isset($conditions[0])) { + $conditions = array($conditions); + } + foreach ($conditions as $condition) { + $ret = $depchecker->{"validate{$type}Dependency"}($condition); + if (PEAR::isError($ret)) { + PEAR::popErrorHandling(); + continue 3; // skip this release + } + } + } + PEAR::popErrorHandling(); + } + } + // this is the release to use + if (isset($instance['filelist'])) { + // ignore files + if (isset($instance['filelist']['ignore'])) { + $ignore = isset($instance['filelist']['ignore'][0]) ? + $instance['filelist']['ignore'] : + array($instance['filelist']['ignore']); + foreach ($ignore as $ig) { + unset ($contents[$ig['attribs']['name']]); + } + } + // install files as this name + if (isset($instance['filelist']['install'])) { + $installas = isset($instance['filelist']['install'][0]) ? + $instance['filelist']['install'] : + array($instance['filelist']['install']); + foreach ($installas as $as) { + $contents[$as['attribs']['name']]['attribs']['install-as'] = + $as['attribs']['as']; + } + } + } + if ($forfilecheck) { + foreach ($contents as $file => $attrs) { + $contents[$file] = $attrs['attribs']; + } + } + return $contents; + } + } else { // simple release - no installconditions or install-as + if ($forfilecheck) { + return $this->getFilelist(); + } + return $contents; + } + // no releases matched + return PEAR::raiseError('No releases in package.xml matched the existing operating ' . + 'system, extensions installed, or architecture, cannot install'); + } + + /** + * This is only used at install-time, after all serialization + * is over. + * @param string file name + * @param string installed path + */ + function setInstalledAs($file, $path) + { + if ($path) { + return $this->_packageInfo['filelist'][$file]['installed_as'] = $path; + } + unset($this->_packageInfo['filelist'][$file]['installed_as']); + } + + function getInstalledLocation($file) + { + if (isset($this->_packageInfo['filelist'][$file]['installed_as'])) { + return $this->_packageInfo['filelist'][$file]['installed_as']; + } + return false; + } + + /** + * This is only used at install-time, after all serialization + * is over. + */ + function installedFile($file, $atts) + { + if (isset($this->_packageInfo['filelist'][$file])) { + $this->_packageInfo['filelist'][$file] = + array_merge($this->_packageInfo['filelist'][$file], $atts['attribs']); + } else { + $this->_packageInfo['filelist'][$file] = $atts['attribs']; + } + } + + /** + * Retrieve the contents tag + */ + function getContents() + { + if (isset($this->_packageInfo['contents'])) { + return $this->_packageInfo['contents']; + } + return false; + } + + /** + * @param string full path to file + * @param string attribute name + * @param string attribute value + * @param int risky but fast - use this to choose a file based on its position in the list + * of files. Index is zero-based like PHP arrays. + * @return bool success of operation + */ + function setFileAttribute($filename, $attr, $value, $index = false) + { + $this->_isValid = 0; + if (in_array($attr, array('role', 'name', 'baseinstalldir'))) { + $this->_filesValid = false; + } + if ($index !== false && + isset($this->_packageInfo['contents']['dir']['file'][$index]['attribs'])) { + $this->_packageInfo['contents']['dir']['file'][$index]['attribs'][$attr] = $value; + return true; + } + if (!isset($this->_packageInfo['contents']['dir']['file'])) { + return false; + } + $files = $this->_packageInfo['contents']['dir']['file']; + if (!isset($files[0])) { + $files = array($files); + $ind = false; + } else { + $ind = true; + } + foreach ($files as $i => $file) { + if (isset($file['attribs'])) { + if ($file['attribs']['name'] == $filename) { + if ($ind) { + $this->_packageInfo['contents']['dir']['file'][$i]['attribs'][$attr] = $value; + } else { + $this->_packageInfo['contents']['dir']['file']['attribs'][$attr] = $value; + } + return true; + } + } + } + return false; + } + + function setDirtree($path) + { + if (!isset($this->_packageInfo['dirtree'])) { + $this->_packageInfo['dirtree'] = array(); + } + $this->_packageInfo['dirtree'][$path] = true; + } + + function getDirtree() + { + if (isset($this->_packageInfo['dirtree']) && count($this->_packageInfo['dirtree'])) { + return $this->_packageInfo['dirtree']; + } + return false; + } + + function resetDirtree() + { + unset($this->_packageInfo['dirtree']); + } + + /** + * Determines whether this package claims it is compatible with the version of + * the package that has a recommended version dependency + * @param PEAR_PackageFile_v2|PEAR_PackageFile_v1|PEAR_Downloader_Package + * @return boolean + */ + function isCompatible($pf) + { + if (!isset($this->_packageInfo['compatible'])) { + return false; + } + if (!isset($this->_packageInfo['channel'])) { + return false; + } + $me = $pf->getVersion(); + $compatible = $this->_packageInfo['compatible']; + if (!isset($compatible[0])) { + $compatible = array($compatible); + } + $found = false; + foreach ($compatible as $info) { + if (strtolower($info['name']) == strtolower($pf->getPackage())) { + if (strtolower($info['channel']) == strtolower($pf->getChannel())) { + $found = true; + break; + } + } + } + if (!$found) { + return false; + } + if (isset($info['exclude'])) { + if (!isset($info['exclude'][0])) { + $info['exclude'] = array($info['exclude']); + } + foreach ($info['exclude'] as $exclude) { + if (version_compare($me, $exclude, '==')) { + return false; + } + } + } + if (version_compare($me, $info['min'], '>=') && version_compare($me, $info['max'], '<=')) { + return true; + } + return false; + } + + /** + * @return array|false + */ + function getCompatible() + { + if (isset($this->_packageInfo['compatible'])) { + return $this->_packageInfo['compatible']; + } + return false; + } + + function getDependencies() + { + if (isset($this->_packageInfo['dependencies'])) { + return $this->_packageInfo['dependencies']; + } + return false; + } + + function isSubpackageOf($p) + { + return $p->isSubpackage($this); + } + + /** + * Determines whether the passed in package is a subpackage of this package. + * + * No version checking is done, only name verification. + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2 + * @return bool + */ + function isSubpackage($p) + { + $sub = array(); + if (isset($this->_packageInfo['dependencies']['required']['subpackage'])) { + $sub = $this->_packageInfo['dependencies']['required']['subpackage']; + if (!isset($sub[0])) { + $sub = array($sub); + } + } + if (isset($this->_packageInfo['dependencies']['optional']['subpackage'])) { + $sub1 = $this->_packageInfo['dependencies']['optional']['subpackage']; + if (!isset($sub1[0])) { + $sub1 = array($sub1); + } + $sub = array_merge($sub, $sub1); + } + if (isset($this->_packageInfo['dependencies']['group'])) { + $group = $this->_packageInfo['dependencies']['group']; + if (!isset($group[0])) { + $group = array($group); + } + foreach ($group as $deps) { + if (isset($deps['subpackage'])) { + $sub2 = $deps['subpackage']; + if (!isset($sub2[0])) { + $sub2 = array($sub2); + } + $sub = array_merge($sub, $sub2); + } + } + } + foreach ($sub as $dep) { + if (strtolower($dep['name']) == strtolower($p->getPackage())) { + if (isset($dep['channel'])) { + if (strtolower($dep['channel']) == strtolower($p->getChannel())) { + return true; + } + } else { + if ($dep['uri'] == $p->getURI()) { + return true; + } + } + } + } + return false; + } + + function dependsOn($package, $channel) + { + if (!($deps = $this->getDependencies())) { + return false; + } + foreach (array('package', 'subpackage') as $type) { + foreach (array('required', 'optional') as $needed) { + if (isset($deps[$needed][$type])) { + if (!isset($deps[$needed][$type][0])) { + $deps[$needed][$type] = array($deps[$needed][$type]); + } + foreach ($deps[$needed][$type] as $dep) { + $depchannel = isset($dep['channel']) ? $dep['channel'] : '__uri'; + if (strtolower($dep['name']) == strtolower($package) && + $depchannel == $channel) { + return true; + } + } + } + } + if (isset($deps['group'])) { + if (!isset($deps['group'][0])) { + $dep['group'] = array($deps['group']); + } + foreach ($deps['group'] as $group) { + if (isset($group[$type])) { + if (!is_array($group[$type])) { + $group[$type] = array($group[$type]); + } + foreach ($group[$type] as $dep) { + $depchannel = isset($dep['channel']) ? $dep['channel'] : '__uri'; + if (strtolower($dep['name']) == strtolower($package) && + $depchannel == $channel) { + return true; + } + } + } + } + } + } + return false; + } + + /** + * Get the contents of a dependency group + * @param string + * @return array|false + */ + function getDependencyGroup($name) + { + $name = strtolower($name); + if (!isset($this->_packageInfo['dependencies']['group'])) { + return false; + } + $groups = $this->_packageInfo['dependencies']['group']; + if (!isset($groups[0])) { + $groups = array($groups); + } + foreach ($groups as $group) { + if (strtolower($group['attribs']['name']) == $name) { + return $group; + } + } + return false; + } + + /** + * Retrieve a partial package.xml 1.0 representation of dependencies + * + * a very limited representation of dependencies is returned by this method. + * The tag for excluding certain versions of a dependency is + * completely ignored. In addition, dependency groups are ignored, with the + * assumption that all dependencies in dependency groups are also listed in + * the optional group that work with all dependency groups + * @param boolean return package.xml 2.0 tag + * @return array|false + */ + function getDeps($raw = false, $nopearinstaller = false) + { + if (isset($this->_packageInfo['dependencies'])) { + if ($raw) { + return $this->_packageInfo['dependencies']; + } + $ret = array(); + $map = array( + 'php' => 'php', + 'package' => 'pkg', + 'subpackage' => 'pkg', + 'extension' => 'ext', + 'os' => 'os', + 'pearinstaller' => 'pkg', + ); + foreach (array('required', 'optional') as $type) { + $optional = ($type == 'optional') ? 'yes' : 'no'; + if (!isset($this->_packageInfo['dependencies'][$type]) + || empty($this->_packageInfo['dependencies'][$type])) { + continue; + } + foreach ($this->_packageInfo['dependencies'][$type] as $dtype => $deps) { + if ($dtype == 'pearinstaller' && $nopearinstaller) { + continue; + } + if (!isset($deps[0])) { + $deps = array($deps); + } + foreach ($deps as $dep) { + if (!isset($map[$dtype])) { + // no support for arch type + continue; + } + if ($dtype == 'pearinstaller') { + $dep['name'] = 'PEAR'; + $dep['channel'] = 'pear.php.net'; + } + $s = array('type' => $map[$dtype]); + if (isset($dep['channel'])) { + $s['channel'] = $dep['channel']; + } + if (isset($dep['uri'])) { + $s['uri'] = $dep['uri']; + } + if (isset($dep['name'])) { + $s['name'] = $dep['name']; + } + if (isset($dep['conflicts'])) { + $s['rel'] = 'not'; + } else { + if (!isset($dep['min']) && + !isset($dep['max'])) { + $s['rel'] = 'has'; + $s['optional'] = $optional; + } elseif (isset($dep['min']) && + isset($dep['max'])) { + $s['rel'] = 'ge'; + $s1 = $s; + $s1['rel'] = 'le'; + $s['version'] = $dep['min']; + $s1['version'] = $dep['max']; + if (isset($dep['channel'])) { + $s1['channel'] = $dep['channel']; + } + if ($dtype != 'php') { + $s['name'] = $dep['name']; + $s1['name'] = $dep['name']; + } + $s['optional'] = $optional; + $s1['optional'] = $optional; + $ret[] = $s1; + } elseif (isset($dep['min'])) { + if (isset($dep['exclude']) && + $dep['exclude'] == $dep['min']) { + $s['rel'] = 'gt'; + } else { + $s['rel'] = 'ge'; + } + $s['version'] = $dep['min']; + $s['optional'] = $optional; + if ($dtype != 'php') { + $s['name'] = $dep['name']; + } + } elseif (isset($dep['max'])) { + if (isset($dep['exclude']) && + $dep['exclude'] == $dep['max']) { + $s['rel'] = 'lt'; + } else { + $s['rel'] = 'le'; + } + $s['version'] = $dep['max']; + $s['optional'] = $optional; + if ($dtype != 'php') { + $s['name'] = $dep['name']; + } + } + } + $ret[] = $s; + } + } + } + if (count($ret)) { + return $ret; + } + } + return false; + } + + /** + * @return php|extsrc|extbin|zendextsrc|zendextbin|bundle|false + */ + function getPackageType() + { + if (isset($this->_packageInfo['phprelease'])) { + return 'php'; + } + if (isset($this->_packageInfo['extsrcrelease'])) { + return 'extsrc'; + } + if (isset($this->_packageInfo['extbinrelease'])) { + return 'extbin'; + } + if (isset($this->_packageInfo['zendextsrcrelease'])) { + return 'zendextsrc'; + } + if (isset($this->_packageInfo['zendextbinrelease'])) { + return 'zendextbin'; + } + if (isset($this->_packageInfo['bundle'])) { + return 'bundle'; + } + return false; + } + + /** + * @return array|false + */ + function getReleases() + { + $type = $this->getPackageType(); + if ($type != 'bundle') { + $type .= 'release'; + } + if ($this->getPackageType() && isset($this->_packageInfo[$type])) { + return $this->_packageInfo[$type]; + } + return false; + } + + /** + * @return array + */ + function getChangelog() + { + if (isset($this->_packageInfo['changelog'])) { + return $this->_packageInfo['changelog']; + } + return false; + } + + function hasDeps() + { + return isset($this->_packageInfo['dependencies']); + } + + function getPackagexmlVersion() + { + if (isset($this->_packageInfo['zendextsrcrelease'])) { + return '2.1'; + } + if (isset($this->_packageInfo['zendextbinrelease'])) { + return '2.1'; + } + return '2.0'; + } + + /** + * @return array|false + */ + function getSourcePackage() + { + if (isset($this->_packageInfo['extbinrelease']) || + isset($this->_packageInfo['zendextbinrelease'])) { + return array('channel' => $this->_packageInfo['srcchannel'], + 'package' => $this->_packageInfo['srcpackage']); + } + return false; + } + + function getBundledPackages() + { + if (isset($this->_packageInfo['bundle'])) { + return $this->_packageInfo['contents']['bundledpackage']; + } + return false; + } + + function getLastModified() + { + if (isset($this->_packageInfo['_lastmodified'])) { + return $this->_packageInfo['_lastmodified']; + } + return false; + } + + /** + * Get the contents of a file listed within the package.xml + * @param string + * @return string + */ + function getFileContents($file) + { + if ($this->_archiveFile == $this->_packageFile) { // unpacked + $dir = dirname($this->_packageFile); + $file = $dir . DIRECTORY_SEPARATOR . $file; + $file = str_replace(array('/', '\\'), + array(DIRECTORY_SEPARATOR, DIRECTORY_SEPARATOR), $file); + if (file_exists($file) && is_readable($file)) { + return implode('', file($file)); + } + } else { // tgz + $tar = &new Archive_Tar($this->_archiveFile); + $tar->pushErrorHandling(PEAR_ERROR_RETURN); + if ($file != 'package.xml' && $file != 'package2.xml') { + $file = $this->getPackage() . '-' . $this->getVersion() . '/' . $file; + } + $file = $tar->extractInString($file); + $tar->popErrorHandling(); + if (PEAR::isError($file)) { + return PEAR::raiseError("Cannot locate file '$file' in archive"); + } + return $file; + } + } + + function &getRW() + { + if (!class_exists('PEAR_PackageFile_v2_rw')) { + require_once 'PEAR/PackageFile/v2/rw.php'; + } + $a = new PEAR_PackageFile_v2_rw; + foreach (get_object_vars($this) as $name => $unused) { + if (!isset($this->$name)) { + continue; + } + if ($name == '_config' || $name == '_logger'|| $name == '_registry' || + $name == '_stack') { + $a->$name = &$this->$name; + } else { + $a->$name = $this->$name; + } + } + return $a; + } + + function &getDefaultGenerator() + { + if (!class_exists('PEAR_PackageFile_Generator_v2')) { + require_once 'PEAR/PackageFile/Generator/v2.php'; + } + $a = &new PEAR_PackageFile_Generator_v2($this); + return $a; + } + + function analyzeSourceCode($file, $string = false) + { + if (!isset($this->_v2Validator) || + !is_a($this->_v2Validator, 'PEAR_PackageFile_v2_Validator')) { + if (!class_exists('PEAR_PackageFile_v2_Validator')) { + require_once 'PEAR/PackageFile/v2/Validator.php'; + } + $this->_v2Validator = new PEAR_PackageFile_v2_Validator; + } + return $this->_v2Validator->analyzeSourceCode($file, $string); + } + + function validate($state = PEAR_VALIDATE_NORMAL) + { + if (!isset($this->_packageInfo) || !is_array($this->_packageInfo)) { + return false; + } + if (!isset($this->_v2Validator) || + !is_a($this->_v2Validator, 'PEAR_PackageFile_v2_Validator')) { + if (!class_exists('PEAR_PackageFile_v2_Validator')) { + require_once 'PEAR/PackageFile/v2/Validator.php'; + } + $this->_v2Validator = new PEAR_PackageFile_v2_Validator; + } + if (isset($this->_packageInfo['xsdversion'])) { + unset($this->_packageInfo['xsdversion']); + } + return $this->_v2Validator->validate($this, $state); + } + + function getTasksNs() + { + if (!isset($this->_tasksNs)) { + if (isset($this->_packageInfo['attribs'])) { + foreach ($this->_packageInfo['attribs'] as $name => $value) { + if ($value == 'http://pear.php.net/dtd/tasks-1.0') { + $this->_tasksNs = str_replace('xmlns:', '', $name); + break; + } + } + } + } + return $this->_tasksNs; + } + + /** + * Determine whether a task name is a valid task. Custom tasks may be defined + * using subdirectories by putting a "-" in the name, as in + * + * Note that this method will auto-load the task class file and test for the existence + * of the name with "-" replaced by "_" as in PEAR/Task/mycustom/task.php makes class + * PEAR_Task_mycustom_task + * @param string + * @return boolean + */ + function getTask($task) + { + $this->getTasksNs(); + // transform all '-' to '/' and 'tasks:' to '' so tasks:replace becomes replace + $task = str_replace(array($this->_tasksNs . ':', '-'), array('', ' '), $task); + $taskfile = str_replace(' ', '/', ucwords($task)); + $task = str_replace(array(' ', '/'), '_', ucwords($task)); + if (class_exists("PEAR_Task_$task")) { + return "PEAR_Task_$task"; + } + $fp = @fopen("PEAR/Task/$taskfile.php", 'r', true); + if ($fp) { + fclose($fp); + require_once "PEAR/Task/$taskfile.php"; + return "PEAR_Task_$task"; + } + return false; + } + + /** + * Key-friendly array_splice + * @param tagname to splice a value in before + * @param mixed the value to splice in + * @param string the new tag name + */ + function _ksplice($array, $key, $value, $newkey) + { + $offset = array_search($key, array_keys($array)); + $after = array_slice($array, $offset); + $before = array_slice($array, 0, $offset); + $before[$newkey] = $value; + return array_merge($before, $after); + } + + /** + * @param array a list of possible keys, in the order they may occur + * @param mixed contents of the new package.xml tag + * @param string tag name + * @access private + */ + function _insertBefore($array, $keys, $contents, $newkey) + { + foreach ($keys as $key) { + if (isset($array[$key])) { + return $array = $this->_ksplice($array, $key, $contents, $newkey); + } + } + $array[$newkey] = $contents; + return $array; + } + + /** + * @param subsection of {@link $_packageInfo} + * @param array|string tag contents + * @param array format: + *
    +     * array(
    +     *   tagname => array(list of tag names that follow this one),
    +     *   childtagname => array(list of child tag names that follow this one),
    +     * )
    +     * 
    + * + * This allows construction of nested tags + * @access private + */ + function _mergeTag($manip, $contents, $order) + { + if (count($order)) { + foreach ($order as $tag => $curorder) { + if (!isset($manip[$tag])) { + // ensure that the tag is set up + $manip = $this->_insertBefore($manip, $curorder, array(), $tag); + } + if (count($order) > 1) { + $manip[$tag] = $this->_mergeTag($manip[$tag], $contents, array_slice($order, 1)); + return $manip; + } + } + } else { + return $manip; + } + if (is_array($manip[$tag]) && !empty($manip[$tag]) && isset($manip[$tag][0])) { + $manip[$tag][] = $contents; + } else { + if (!count($manip[$tag])) { + $manip[$tag] = $contents; + } else { + $manip[$tag] = array($manip[$tag]); + $manip[$tag][] = $contents; + } + } + return $manip; + } +} +?> diff --git a/library/pear/PEAR/PackageFile/v2/Validator.php b/library/pear/PEAR/PackageFile/v2/Validator.php new file mode 100644 index 000000000..cd9c3e2eb --- /dev/null +++ b/library/pear/PEAR/PackageFile/v2/Validator.php @@ -0,0 +1,2154 @@ + + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Validator.php 277885 2009-03-27 19:29:31Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a8 + */ +/** + * Private validation class used by PEAR_PackageFile_v2 - do not use directly, its + * sole purpose is to split up the PEAR/PackageFile/v2.php file to make it smaller + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.1 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a8 + * @access private + */ +class PEAR_PackageFile_v2_Validator +{ + /** + * @var array + */ + var $_packageInfo; + /** + * @var PEAR_PackageFile_v2 + */ + var $_pf; + /** + * @var PEAR_ErrorStack + */ + var $_stack; + /** + * @var int + */ + var $_isValid = 0; + /** + * @var int + */ + var $_filesValid = 0; + /** + * @var int + */ + var $_curState = 0; + /** + * @param PEAR_PackageFile_v2 + * @param int + */ + function validate(&$pf, $state = PEAR_VALIDATE_NORMAL) + { + $this->_pf = &$pf; + $this->_curState = $state; + $this->_packageInfo = $this->_pf->getArray(); + $this->_isValid = $this->_pf->_isValid; + $this->_filesValid = $this->_pf->_filesValid; + $this->_stack = &$pf->_stack; + $this->_stack->getErrors(true); + if (($this->_isValid & $state) == $state) { + return true; + } + if (!isset($this->_packageInfo) || !is_array($this->_packageInfo)) { + return false; + } + if (!isset($this->_packageInfo['attribs']['version']) || + ($this->_packageInfo['attribs']['version'] != '2.0' && + $this->_packageInfo['attribs']['version'] != '2.1') + ) { + $this->_noPackageVersion(); + } + $structure = + array( + 'name', + 'channel|uri', + '*extends', // can't be multiple, but this works fine + 'summary', + 'description', + '+lead', // these all need content checks + '*developer', + '*contributor', + '*helper', + 'date', + '*time', + 'version', + 'stability', + 'license->?uri->?filesource', + 'notes', + 'contents', //special validation needed + '*compatible', + 'dependencies', //special validation needed + '*usesrole', + '*usestask', // reserve these for 1.4.0a1 to implement + // this will allow a package.xml to gracefully say it + // needs a certain package installed in order to implement a role or task + '*providesextension', + '*srcpackage|*srcuri', + '+phprelease|+extsrcrelease|+extbinrelease|' . + '+zendextsrcrelease|+zendextbinrelease|bundle', //special validation needed + '*changelog', + ); + $test = $this->_packageInfo; + if (isset($test['dependencies']) && + isset($test['dependencies']['required']) && + isset($test['dependencies']['required']['pearinstaller']) && + isset($test['dependencies']['required']['pearinstaller']['min']) && + version_compare('1.9.1', + $test['dependencies']['required']['pearinstaller']['min'], '<') + ) { + $this->_pearVersionTooLow($test['dependencies']['required']['pearinstaller']['min']); + return false; + } + // ignore post-installation array fields + if (array_key_exists('filelist', $test)) { + unset($test['filelist']); + } + if (array_key_exists('_lastmodified', $test)) { + unset($test['_lastmodified']); + } + if (array_key_exists('#binarypackage', $test)) { + unset($test['#binarypackage']); + } + if (array_key_exists('old', $test)) { + unset($test['old']); + } + if (array_key_exists('_lastversion', $test)) { + unset($test['_lastversion']); + } + if (!$this->_stupidSchemaValidate($structure, $test, '')) { + return false; + } + if (empty($this->_packageInfo['name'])) { + $this->_tagCannotBeEmpty('name'); + } + $test = isset($this->_packageInfo['uri']) ? 'uri' :'channel'; + if (empty($this->_packageInfo[$test])) { + $this->_tagCannotBeEmpty($test); + } + if (is_array($this->_packageInfo['license']) && + (!isset($this->_packageInfo['license']['_content']) || + empty($this->_packageInfo['license']['_content']))) { + $this->_tagCannotBeEmpty('license'); + } elseif (empty($this->_packageInfo['license'])) { + $this->_tagCannotBeEmpty('license'); + } + if (empty($this->_packageInfo['summary'])) { + $this->_tagCannotBeEmpty('summary'); + } + if (empty($this->_packageInfo['description'])) { + $this->_tagCannotBeEmpty('description'); + } + if (empty($this->_packageInfo['date'])) { + $this->_tagCannotBeEmpty('date'); + } + if (empty($this->_packageInfo['notes'])) { + $this->_tagCannotBeEmpty('notes'); + } + if (isset($this->_packageInfo['time']) && empty($this->_packageInfo['time'])) { + $this->_tagCannotBeEmpty('time'); + } + if (isset($this->_packageInfo['dependencies'])) { + $this->_validateDependencies(); + } + if (isset($this->_packageInfo['compatible'])) { + $this->_validateCompatible(); + } + if (!isset($this->_packageInfo['bundle'])) { + if (empty($this->_packageInfo['contents'])) { + $this->_tagCannotBeEmpty('contents'); + } + if (!isset($this->_packageInfo['contents']['dir'])) { + $this->_filelistMustContainDir('contents'); + return false; + } + if (isset($this->_packageInfo['contents']['file'])) { + $this->_filelistCannotContainFile('contents'); + return false; + } + } + $this->_validateMaintainers(); + $this->_validateStabilityVersion(); + $fail = false; + if (array_key_exists('usesrole', $this->_packageInfo)) { + $roles = $this->_packageInfo['usesrole']; + if (!is_array($roles) || !isset($roles[0])) { + $roles = array($roles); + } + foreach ($roles as $role) { + if (!isset($role['role'])) { + $this->_usesroletaskMustHaveRoleTask('usesrole', 'role'); + $fail = true; + } else { + if (!isset($role['channel'])) { + if (!isset($role['uri'])) { + $this->_usesroletaskMustHaveChannelOrUri($role['role'], 'usesrole'); + $fail = true; + } + } elseif (!isset($role['package'])) { + $this->_usesroletaskMustHavePackage($role['role'], 'usesrole'); + $fail = true; + } + } + } + } + if (array_key_exists('usestask', $this->_packageInfo)) { + $roles = $this->_packageInfo['usestask']; + if (!is_array($roles) || !isset($roles[0])) { + $roles = array($roles); + } + foreach ($roles as $role) { + if (!isset($role['task'])) { + $this->_usesroletaskMustHaveRoleTask('usestask', 'task'); + $fail = true; + } else { + if (!isset($role['channel'])) { + if (!isset($role['uri'])) { + $this->_usesroletaskMustHaveChannelOrUri($role['task'], 'usestask'); + $fail = true; + } + } elseif (!isset($role['package'])) { + $this->_usesroletaskMustHavePackage($role['task'], 'usestask'); + $fail = true; + } + } + } + } + + if ($fail) { + return false; + } + + $list = $this->_packageInfo['contents']; + if (isset($list['dir']) && is_array($list['dir']) && isset($list['dir'][0])) { + $this->_multipleToplevelDirNotAllowed(); + return $this->_isValid = 0; + } + + $this->_validateFilelist(); + $this->_validateRelease(); + if (!$this->_stack->hasErrors()) { + $chan = $this->_pf->_registry->getChannel($this->_pf->getChannel(), true); + if (PEAR::isError($chan)) { + $this->_unknownChannel($this->_pf->getChannel()); + } else { + $valpack = $chan->getValidationPackage(); + // for channel validator packages, always use the default PEAR validator. + // otherwise, they can't be installed or packaged + $validator = $chan->getValidationObject($this->_pf->getPackage()); + if (!$validator) { + $this->_stack->push(__FUNCTION__, 'error', + array_merge( + array('channel' => $chan->getName(), + 'package' => $this->_pf->getPackage()), + $valpack + ), + 'package "%channel%/%package%" cannot be properly validated without ' . + 'validation package "%channel%/%name%-%version%"'); + return $this->_isValid = 0; + } + $validator->setPackageFile($this->_pf); + $validator->validate($state); + $failures = $validator->getFailures(); + foreach ($failures['errors'] as $error) { + $this->_stack->push(__FUNCTION__, 'error', $error, + 'Channel validator error: field "%field%" - %reason%'); + } + foreach ($failures['warnings'] as $warning) { + $this->_stack->push(__FUNCTION__, 'warning', $warning, + 'Channel validator warning: field "%field%" - %reason%'); + } + } + } + + $this->_pf->_isValid = $this->_isValid = !$this->_stack->hasErrors('error'); + if ($this->_isValid && $state == PEAR_VALIDATE_PACKAGING && !$this->_filesValid) { + if ($this->_pf->getPackageType() == 'bundle') { + if ($this->_analyzeBundledPackages()) { + $this->_filesValid = $this->_pf->_filesValid = true; + } else { + $this->_pf->_isValid = $this->_isValid = 0; + } + } else { + if (!$this->_analyzePhpFiles()) { + $this->_pf->_isValid = $this->_isValid = 0; + } else { + $this->_filesValid = $this->_pf->_filesValid = true; + } + } + } + + if ($this->_isValid) { + return $this->_pf->_isValid = $this->_isValid = $state; + } + + return $this->_pf->_isValid = $this->_isValid = 0; + } + + function _stupidSchemaValidate($structure, $xml, $root) + { + if (!is_array($xml)) { + $xml = array(); + } + $keys = array_keys($xml); + reset($keys); + $key = current($keys); + while ($key == 'attribs' || $key == '_contents') { + $key = next($keys); + } + $unfoundtags = $optionaltags = array(); + $ret = true; + $mismatch = false; + foreach ($structure as $struc) { + if ($key) { + $tag = $xml[$key]; + } + $test = $this->_processStructure($struc); + if (isset($test['choices'])) { + $loose = true; + foreach ($test['choices'] as $choice) { + if ($key == $choice['tag']) { + $key = next($keys); + while ($key == 'attribs' || $key == '_contents') { + $key = next($keys); + } + $unfoundtags = $optionaltags = array(); + $mismatch = false; + if ($key && $key != $choice['tag'] && isset($choice['multiple'])) { + $unfoundtags[] = $choice['tag']; + $optionaltags[] = $choice['tag']; + if ($key) { + $mismatch = true; + } + } + $ret &= $this->_processAttribs($choice, $tag, $root); + continue 2; + } else { + $unfoundtags[] = $choice['tag']; + $mismatch = true; + } + if (!isset($choice['multiple']) || $choice['multiple'] != '*') { + $loose = false; + } else { + $optionaltags[] = $choice['tag']; + } + } + if (!$loose) { + $this->_invalidTagOrder($unfoundtags, $key, $root); + return false; + } + } else { + if ($key != $test['tag']) { + if (isset($test['multiple']) && $test['multiple'] != '*') { + $unfoundtags[] = $test['tag']; + $this->_invalidTagOrder($unfoundtags, $key, $root); + return false; + } else { + if ($key) { + $mismatch = true; + } + $unfoundtags[] = $test['tag']; + $optionaltags[] = $test['tag']; + } + if (!isset($test['multiple'])) { + $this->_invalidTagOrder($unfoundtags, $key, $root); + return false; + } + continue; + } else { + $unfoundtags = $optionaltags = array(); + $mismatch = false; + } + $key = next($keys); + while ($key == 'attribs' || $key == '_contents') { + $key = next($keys); + } + if ($key && $key != $test['tag'] && isset($test['multiple'])) { + $unfoundtags[] = $test['tag']; + $optionaltags[] = $test['tag']; + $mismatch = true; + } + $ret &= $this->_processAttribs($test, $tag, $root); + continue; + } + } + if (!$mismatch && count($optionaltags)) { + // don't error out on any optional tags + $unfoundtags = array_diff($unfoundtags, $optionaltags); + } + if (count($unfoundtags)) { + $this->_invalidTagOrder($unfoundtags, $key, $root); + } elseif ($key) { + // unknown tags + $this->_invalidTagOrder('*no tags allowed here*', $key, $root); + while ($key = next($keys)) { + $this->_invalidTagOrder('*no tags allowed here*', $key, $root); + } + } + return $ret; + } + + function _processAttribs($choice, $tag, $context) + { + if (isset($choice['attribs'])) { + if (!is_array($tag)) { + $tag = array($tag); + } + $tags = $tag; + if (!isset($tags[0])) { + $tags = array($tags); + } + $ret = true; + foreach ($tags as $i => $tag) { + if (!is_array($tag) || !isset($tag['attribs'])) { + foreach ($choice['attribs'] as $attrib) { + if ($attrib{0} != '?') { + $ret &= $this->_tagHasNoAttribs($choice['tag'], + $context); + continue 2; + } + } + } + foreach ($choice['attribs'] as $attrib) { + if ($attrib{0} != '?') { + if (!isset($tag['attribs'][$attrib])) { + $ret &= $this->_tagMissingAttribute($choice['tag'], + $attrib, $context); + } + } + } + } + return $ret; + } + return true; + } + + function _processStructure($key) + { + $ret = array(); + if (count($pieces = explode('|', $key)) > 1) { + $ret['choices'] = array(); + foreach ($pieces as $piece) { + $ret['choices'][] = $this->_processStructure($piece); + } + return $ret; + } + $multi = $key{0}; + if ($multi == '+' || $multi == '*') { + $ret['multiple'] = $key{0}; + $key = substr($key, 1); + } + if (count($attrs = explode('->', $key)) > 1) { + $ret['tag'] = array_shift($attrs); + $ret['attribs'] = $attrs; + } else { + $ret['tag'] = $key; + } + return $ret; + } + + function _validateStabilityVersion() + { + $structure = array('release', 'api'); + $a = $this->_stupidSchemaValidate($structure, $this->_packageInfo['version'], ''); + $a &= $this->_stupidSchemaValidate($structure, $this->_packageInfo['stability'], ''); + if ($a) { + if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/', + $this->_packageInfo['version']['release'])) { + $this->_invalidVersion('release', $this->_packageInfo['version']['release']); + } + if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/', + $this->_packageInfo['version']['api'])) { + $this->_invalidVersion('api', $this->_packageInfo['version']['api']); + } + if (!in_array($this->_packageInfo['stability']['release'], + array('snapshot', 'devel', 'alpha', 'beta', 'stable'))) { + $this->_invalidState('release', $this->_packageInfo['stability']['release']); + } + if (!in_array($this->_packageInfo['stability']['api'], + array('devel', 'alpha', 'beta', 'stable'))) { + $this->_invalidState('api', $this->_packageInfo['stability']['api']); + } + } + } + + function _validateMaintainers() + { + $structure = + array( + 'name', + 'user', + 'email', + 'active', + ); + foreach (array('lead', 'developer', 'contributor', 'helper') as $type) { + if (!isset($this->_packageInfo[$type])) { + continue; + } + if (isset($this->_packageInfo[$type][0])) { + foreach ($this->_packageInfo[$type] as $lead) { + $this->_stupidSchemaValidate($structure, $lead, '<' . $type . '>'); + } + } else { + $this->_stupidSchemaValidate($structure, $this->_packageInfo[$type], + '<' . $type . '>'); + } + } + } + + function _validatePhpDep($dep, $installcondition = false) + { + $structure = array( + 'min', + '*max', + '*exclude', + ); + $type = $installcondition ? '' : ''; + $this->_stupidSchemaValidate($structure, $dep, $type); + if (isset($dep['min'])) { + if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?(?:-[a-zA-Z0-9]+)?\\z/', + $dep['min'])) { + $this->_invalidVersion($type . '', $dep['min']); + } + } + if (isset($dep['max'])) { + if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?(?:-[a-zA-Z0-9]+)?\\z/', + $dep['max'])) { + $this->_invalidVersion($type . '', $dep['max']); + } + } + if (isset($dep['exclude'])) { + if (!is_array($dep['exclude'])) { + $dep['exclude'] = array($dep['exclude']); + } + foreach ($dep['exclude'] as $exclude) { + if (!preg_match( + '/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?(?:-[a-zA-Z0-9]+)?\\z/', + $exclude)) { + $this->_invalidVersion($type . '', $exclude); + } + } + } + } + + function _validatePearinstallerDep($dep) + { + $structure = array( + 'min', + '*max', + '*recommended', + '*exclude', + ); + $this->_stupidSchemaValidate($structure, $dep, ''); + if (isset($dep['min'])) { + if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/', + $dep['min'])) { + $this->_invalidVersion('', + $dep['min']); + } + } + if (isset($dep['max'])) { + if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/', + $dep['max'])) { + $this->_invalidVersion('', + $dep['max']); + } + } + if (isset($dep['recommended'])) { + if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/', + $dep['recommended'])) { + $this->_invalidVersion('', + $dep['recommended']); + } + } + if (isset($dep['exclude'])) { + if (!is_array($dep['exclude'])) { + $dep['exclude'] = array($dep['exclude']); + } + foreach ($dep['exclude'] as $exclude) { + if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/', + $exclude)) { + $this->_invalidVersion('', + $exclude); + } + } + } + } + + function _validatePackageDep($dep, $group, $type = '') + { + if (isset($dep['uri'])) { + if (isset($dep['conflicts'])) { + $structure = array( + 'name', + 'uri', + 'conflicts', + '*providesextension', + ); + } else { + $structure = array( + 'name', + 'uri', + '*providesextension', + ); + } + } else { + if (isset($dep['conflicts'])) { + $structure = array( + 'name', + 'channel', + '*min', + '*max', + '*exclude', + 'conflicts', + '*providesextension', + ); + } else { + $structure = array( + 'name', + 'channel', + '*min', + '*max', + '*recommended', + '*exclude', + '*nodefault', + '*providesextension', + ); + } + } + if (isset($dep['name'])) { + $type .= '' . $dep['name'] . ''; + } + $this->_stupidSchemaValidate($structure, $dep, '' . $group . $type); + if (isset($dep['uri']) && (isset($dep['min']) || isset($dep['max']) || + isset($dep['recommended']) || isset($dep['exclude']))) { + $this->_uriDepsCannotHaveVersioning('' . $group . $type); + } + if (isset($dep['channel']) && strtolower($dep['channel']) == '__uri') { + $this->_DepchannelCannotBeUri('' . $group . $type); + } + if (isset($dep['min'])) { + if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/', + $dep['min'])) { + $this->_invalidVersion('' . $group . $type . '', $dep['min']); + } + } + if (isset($dep['max'])) { + if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/', + $dep['max'])) { + $this->_invalidVersion('' . $group . $type . '', $dep['max']); + } + } + if (isset($dep['recommended'])) { + if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/', + $dep['recommended'])) { + $this->_invalidVersion('' . $group . $type . '', + $dep['recommended']); + } + } + if (isset($dep['exclude'])) { + if (!is_array($dep['exclude'])) { + $dep['exclude'] = array($dep['exclude']); + } + foreach ($dep['exclude'] as $exclude) { + if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/', + $exclude)) { + $this->_invalidVersion('' . $group . $type . '', + $exclude); + } + } + } + } + + function _validateSubpackageDep($dep, $group) + { + $this->_validatePackageDep($dep, $group, ''); + if (isset($dep['providesextension'])) { + $this->_subpackageCannotProvideExtension(isset($dep['name']) ? $dep['name'] : ''); + } + if (isset($dep['conflicts'])) { + $this->_subpackagesCannotConflict(isset($dep['name']) ? $dep['name'] : ''); + } + } + + function _validateExtensionDep($dep, $group = false, $installcondition = false) + { + if (isset($dep['conflicts'])) { + $structure = array( + 'name', + '*min', + '*max', + '*exclude', + 'conflicts', + ); + } else { + $structure = array( + 'name', + '*min', + '*max', + '*recommended', + '*exclude', + ); + } + if ($installcondition) { + $type = ''; + } else { + $type = '' . $group . ''; + } + if (isset($dep['name'])) { + $type .= '' . $dep['name'] . ''; + } + $this->_stupidSchemaValidate($structure, $dep, $type); + if (isset($dep['min'])) { + if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/', + $dep['min'])) { + $this->_invalidVersion(substr($type, 1) . '_invalidVersion(substr($type, 1) . '_invalidVersion(substr($type, 1) . '_invalidVersion(substr($type, 1) . '' : ''; + if ($this->_stupidSchemaValidate($structure, $dep, $type)) { + if ($dep['name'] == '*') { + if (array_key_exists('conflicts', $dep)) { + $this->_cannotConflictWithAllOs($type); + } + } + } + } + + function _validateArchDep($dep, $installcondition = false) + { + $structure = array( + 'pattern', + '*conflicts', + ); + $type = $installcondition ? '' : ''; + $this->_stupidSchemaValidate($structure, $dep, $type); + } + + function _validateInstallConditions($cond, $release) + { + $structure = array( + '*php', + '*extension', + '*os', + '*arch', + ); + if (!$this->_stupidSchemaValidate($structure, + $cond, $release)) { + return false; + } + foreach (array('php', 'extension', 'os', 'arch') as $type) { + if (isset($cond[$type])) { + $iter = $cond[$type]; + if (!is_array($iter) || !isset($iter[0])) { + $iter = array($iter); + } + foreach ($iter as $package) { + if ($type == 'extension') { + $this->{"_validate{$type}Dep"}($package, false, true); + } else { + $this->{"_validate{$type}Dep"}($package, true); + } + } + } + } + } + + function _validateDependencies() + { + $structure = array( + 'required', + '*optional', + '*group->name->hint' + ); + if (!$this->_stupidSchemaValidate($structure, + $this->_packageInfo['dependencies'], '')) { + return false; + } + foreach (array('required', 'optional') as $simpledep) { + if (isset($this->_packageInfo['dependencies'][$simpledep])) { + if ($simpledep == 'optional') { + $structure = array( + '*package', + '*subpackage', + '*extension', + ); + } else { + $structure = array( + 'php', + 'pearinstaller', + '*package', + '*subpackage', + '*extension', + '*os', + '*arch', + ); + } + if ($this->_stupidSchemaValidate($structure, + $this->_packageInfo['dependencies'][$simpledep], + "<$simpledep>")) { + foreach (array('package', 'subpackage', 'extension') as $type) { + if (isset($this->_packageInfo['dependencies'][$simpledep][$type])) { + $iter = $this->_packageInfo['dependencies'][$simpledep][$type]; + if (!isset($iter[0])) { + $iter = array($iter); + } + foreach ($iter as $package) { + if ($type != 'extension') { + if (isset($package['uri'])) { + if (isset($package['channel'])) { + $this->_UrlOrChannel($type, + $package['name']); + } + } else { + if (!isset($package['channel'])) { + $this->_NoChannel($type, $package['name']); + } + } + } + $this->{"_validate{$type}Dep"}($package, "<$simpledep>"); + } + } + } + if ($simpledep == 'optional') { + continue; + } + foreach (array('php', 'pearinstaller', 'os', 'arch') as $type) { + if (isset($this->_packageInfo['dependencies'][$simpledep][$type])) { + $iter = $this->_packageInfo['dependencies'][$simpledep][$type]; + if (!isset($iter[0])) { + $iter = array($iter); + } + foreach ($iter as $package) { + $this->{"_validate{$type}Dep"}($package); + } + } + } + } + } + } + if (isset($this->_packageInfo['dependencies']['group'])) { + $groups = $this->_packageInfo['dependencies']['group']; + if (!isset($groups[0])) { + $groups = array($groups); + } + $structure = array( + '*package', + '*subpackage', + '*extension', + ); + foreach ($groups as $group) { + if ($this->_stupidSchemaValidate($structure, $group, '')) { + if (!PEAR_Validate::validGroupName($group['attribs']['name'])) { + $this->_invalidDepGroupName($group['attribs']['name']); + } + foreach (array('package', 'subpackage', 'extension') as $type) { + if (isset($group[$type])) { + $iter = $group[$type]; + if (!isset($iter[0])) { + $iter = array($iter); + } + foreach ($iter as $package) { + if ($type != 'extension') { + if (isset($package['uri'])) { + if (isset($package['channel'])) { + $this->_UrlOrChannelGroup($type, + $package['name'], + $group['name']); + } + } else { + if (!isset($package['channel'])) { + $this->_NoChannelGroup($type, + $package['name'], + $group['name']); + } + } + } + $this->{"_validate{$type}Dep"}($package, ''); + } + } + } + } + } + } + } + + function _validateCompatible() + { + $compat = $this->_packageInfo['compatible']; + if (!isset($compat[0])) { + $compat = array($compat); + } + $required = array('name', 'channel', 'min', 'max', '*exclude'); + foreach ($compat as $package) { + $type = ''; + if (is_array($package) && array_key_exists('name', $package)) { + $type .= '' . $package['name'] . ''; + } + $this->_stupidSchemaValidate($required, $package, $type); + if (is_array($package) && array_key_exists('min', $package)) { + if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/', + $package['min'])) { + $this->_invalidVersion(substr($type, 1) . '_invalidVersion(substr($type, 1) . '_invalidVersion(substr($type, 1) . '_NoBundledPackages(); + } + if (!is_array($list['bundledpackage']) || !isset($list['bundledpackage'][0])) { + return $this->_AtLeast2BundledPackages(); + } + foreach ($list['bundledpackage'] as $package) { + if (!is_string($package)) { + $this->_bundledPackagesMustBeFilename(); + } + } + } + + function _validateFilelist($list = false, $allowignore = false, $dirs = '') + { + $iscontents = false; + if (!$list) { + $iscontents = true; + $list = $this->_packageInfo['contents']; + if (isset($this->_packageInfo['bundle'])) { + return $this->_validateBundle($list); + } + } + if ($allowignore) { + $struc = array( + '*install->name->as', + '*ignore->name' + ); + } else { + $struc = array( + '*dir->name->?baseinstalldir', + '*file->name->role->?baseinstalldir->?md5sum' + ); + if (isset($list['dir']) && isset($list['file'])) { + // stave off validation errors without requiring a set order. + $_old = $list; + if (isset($list['attribs'])) { + $list = array('attribs' => $_old['attribs']); + } + $list['dir'] = $_old['dir']; + $list['file'] = $_old['file']; + } + } + if (!isset($list['attribs']) || !isset($list['attribs']['name'])) { + $unknown = $allowignore ? '' : '
    '; + $dirname = $iscontents ? '' : $unknown; + } else { + $dirname = ''; + if (preg_match('~/\.\.?(/|\\z)|^\.\.?/~', + str_replace('\\', '/', $list['attribs']['name']))) { + // file contains .. parent directory or . cur directory + $this->_invalidDirName($list['attribs']['name']); + } + } + $res = $this->_stupidSchemaValidate($struc, $list, $dirname); + if ($allowignore && $res) { + $ignored_or_installed = array(); + $this->_pf->getFilelist(); + $fcontents = $this->_pf->getContents(); + $filelist = array(); + if (!isset($fcontents['dir']['file'][0])) { + $fcontents['dir']['file'] = array($fcontents['dir']['file']); + } + foreach ($fcontents['dir']['file'] as $file) { + $filelist[$file['attribs']['name']] = true; + } + if (isset($list['install'])) { + if (!isset($list['install'][0])) { + $list['install'] = array($list['install']); + } + foreach ($list['install'] as $file) { + if (!isset($filelist[$file['attribs']['name']])) { + $this->_notInContents($file['attribs']['name'], 'install'); + continue; + } + if (array_key_exists($file['attribs']['name'], $ignored_or_installed)) { + $this->_multipleInstallAs($file['attribs']['name']); + } + if (!isset($ignored_or_installed[$file['attribs']['name']])) { + $ignored_or_installed[$file['attribs']['name']] = array(); + } + $ignored_or_installed[$file['attribs']['name']][] = 1; + if (preg_match('~/\.\.?(/|\\z)|^\.\.?/~', + str_replace('\\', '/', $file['attribs']['as']))) { + // file contains .. parent directory or . cur directory references + $this->_invalidFileInstallAs($file['attribs']['name'], + $file['attribs']['as']); + } + } + } + if (isset($list['ignore'])) { + if (!isset($list['ignore'][0])) { + $list['ignore'] = array($list['ignore']); + } + foreach ($list['ignore'] as $file) { + if (!isset($filelist[$file['attribs']['name']])) { + $this->_notInContents($file['attribs']['name'], 'ignore'); + continue; + } + if (array_key_exists($file['attribs']['name'], $ignored_or_installed)) { + $this->_ignoreAndInstallAs($file['attribs']['name']); + } + } + } + } + if (!$allowignore && isset($list['file'])) { + if (is_string($list['file'])) { + $this->_oldStyleFileNotAllowed(); + return false; + } + if (!isset($list['file'][0])) { + // single file + $list['file'] = array($list['file']); + } + foreach ($list['file'] as $i => $file) + { + if (isset($file['attribs']) && isset($file['attribs']['name'])) { + if ($file['attribs']['name']{0} == '.' && + $file['attribs']['name']{1} == '/') { + // name is something like "./doc/whatever.txt" + $this->_invalidFileName($file['attribs']['name'], $dirname); + } + if (preg_match('~/\.\.?(/|\\z)|^\.\.?/~', + str_replace('\\', '/', $file['attribs']['name']))) { + // file contains .. parent directory or . cur directory + $this->_invalidFileName($file['attribs']['name'], $dirname); + } + } + if (isset($file['attribs']) && isset($file['attribs']['role'])) { + if (!$this->_validateRole($file['attribs']['role'])) { + if (isset($this->_packageInfo['usesrole'])) { + $roles = $this->_packageInfo['usesrole']; + if (!isset($roles[0])) { + $roles = array($roles); + } + foreach ($roles as $role) { + if ($role['role'] = $file['attribs']['role']) { + $msg = 'This package contains role "%role%" and requires ' . + 'package "%package%" to be used'; + if (isset($role['uri'])) { + $params = array('role' => $role['role'], + 'package' => $role['uri']); + } else { + $params = array('role' => $role['role'], + 'package' => $this->_pf->_registry-> + parsedPackageNameToString(array('package' => + $role['package'], 'channel' => $role['channel']), + true)); + } + $this->_stack->push('_mustInstallRole', 'error', $params, $msg); + } + } + } + $this->_invalidFileRole($file['attribs']['name'], + $dirname, $file['attribs']['role']); + } + } + if (!isset($file['attribs'])) { + continue; + } + $save = $file['attribs']; + if ($dirs) { + $save['name'] = $dirs . '/' . $save['name']; + } + unset($file['attribs']); + if (count($file) && $this->_curState != PEAR_VALIDATE_DOWNLOADING) { // has tasks + foreach ($file as $task => $value) { + if ($tagClass = $this->_pf->getTask($task)) { + if (!is_array($value) || !isset($value[0])) { + $value = array($value); + } + foreach ($value as $v) { + $ret = call_user_func(array($tagClass, 'validateXml'), + $this->_pf, $v, $this->_pf->_config, $save); + if (is_array($ret)) { + $this->_invalidTask($task, $ret, isset($save['name']) ? + $save['name'] : ''); + } + } + } else { + if (isset($this->_packageInfo['usestask'])) { + $roles = $this->_packageInfo['usestask']; + if (!isset($roles[0])) { + $roles = array($roles); + } + foreach ($roles as $role) { + if ($role['task'] = $task) { + $msg = 'This package contains task "%task%" and requires ' . + 'package "%package%" to be used'; + if (isset($role['uri'])) { + $params = array('task' => $role['task'], + 'package' => $role['uri']); + } else { + $params = array('task' => $role['task'], + 'package' => $this->_pf->_registry-> + parsedPackageNameToString(array('package' => + $role['package'], 'channel' => $role['channel']), + true)); + } + $this->_stack->push('_mustInstallTask', 'error', + $params, $msg); + } + } + } + $this->_unknownTask($task, $save['name']); + } + } + } + } + } + if (isset($list['ignore'])) { + if (!$allowignore) { + $this->_ignoreNotAllowed('ignore'); + } + } + if (isset($list['install'])) { + if (!$allowignore) { + $this->_ignoreNotAllowed('install'); + } + } + if (isset($list['file'])) { + if ($allowignore) { + $this->_fileNotAllowed('file'); + } + } + if (isset($list['dir'])) { + if ($allowignore) { + $this->_fileNotAllowed('dir'); + } else { + if (!isset($list['dir'][0])) { + $list['dir'] = array($list['dir']); + } + foreach ($list['dir'] as $dir) { + if (isset($dir['attribs']) && isset($dir['attribs']['name'])) { + if ($dir['attribs']['name'] == '/' || + !isset($this->_packageInfo['contents']['dir']['dir'])) { + // always use nothing if the filelist has already been flattened + $newdirs = ''; + } elseif ($dirs == '') { + $newdirs = $dir['attribs']['name']; + } else { + $newdirs = $dirs . '/' . $dir['attribs']['name']; + } + } else { + $newdirs = $dirs; + } + $this->_validateFilelist($dir, $allowignore, $newdirs); + } + } + } + } + + function _validateRelease() + { + if (isset($this->_packageInfo['phprelease'])) { + $release = 'phprelease'; + if (isset($this->_packageInfo['providesextension'])) { + $this->_cannotProvideExtension($release); + } + if (isset($this->_packageInfo['srcpackage']) || isset($this->_packageInfo['srcuri'])) { + $this->_cannotHaveSrcpackage($release); + } + $releases = $this->_packageInfo['phprelease']; + if (!is_array($releases)) { + return true; + } + if (!isset($releases[0])) { + $releases = array($releases); + } + foreach ($releases as $rel) { + $this->_stupidSchemaValidate(array( + '*installconditions', + '*filelist', + ), $rel, ''); + } + } + foreach (array('', 'zend') as $prefix) { + $releasetype = $prefix . 'extsrcrelease'; + if (isset($this->_packageInfo[$releasetype])) { + $release = $releasetype; + if (!isset($this->_packageInfo['providesextension'])) { + $this->_mustProvideExtension($release); + } + if (isset($this->_packageInfo['srcpackage']) || isset($this->_packageInfo['srcuri'])) { + $this->_cannotHaveSrcpackage($release); + } + $releases = $this->_packageInfo[$releasetype]; + if (!is_array($releases)) { + return true; + } + if (!isset($releases[0])) { + $releases = array($releases); + } + foreach ($releases as $rel) { + $this->_stupidSchemaValidate(array( + '*installconditions', + '*configureoption->name->prompt->?default', + '*binarypackage', + '*filelist', + ), $rel, '<' . $releasetype . '>'); + if (isset($rel['binarypackage'])) { + if (!is_array($rel['binarypackage']) || !isset($rel['binarypackage'][0])) { + $rel['binarypackage'] = array($rel['binarypackage']); + } + foreach ($rel['binarypackage'] as $bin) { + if (!is_string($bin)) { + $this->_binaryPackageMustBePackagename(); + } + } + } + } + } + $releasetype = 'extbinrelease'; + if (isset($this->_packageInfo[$releasetype])) { + $release = $releasetype; + if (!isset($this->_packageInfo['providesextension'])) { + $this->_mustProvideExtension($release); + } + if (isset($this->_packageInfo['channel']) && + !isset($this->_packageInfo['srcpackage'])) { + $this->_mustSrcPackage($release); + } + if (isset($this->_packageInfo['uri']) && !isset($this->_packageInfo['srcuri'])) { + $this->_mustSrcuri($release); + } + $releases = $this->_packageInfo[$releasetype]; + if (!is_array($releases)) { + return true; + } + if (!isset($releases[0])) { + $releases = array($releases); + } + foreach ($releases as $rel) { + $this->_stupidSchemaValidate(array( + '*installconditions', + '*filelist', + ), $rel, '<' . $releasetype . '>'); + } + } + } + if (isset($this->_packageInfo['bundle'])) { + $release = 'bundle'; + if (isset($this->_packageInfo['providesextension'])) { + $this->_cannotProvideExtension($release); + } + if (isset($this->_packageInfo['srcpackage']) || isset($this->_packageInfo['srcuri'])) { + $this->_cannotHaveSrcpackage($release); + } + $releases = $this->_packageInfo['bundle']; + if (!is_array($releases) || !isset($releases[0])) { + $releases = array($releases); + } + foreach ($releases as $rel) { + $this->_stupidSchemaValidate(array( + '*installconditions', + '*filelist', + ), $rel, ''); + } + } + foreach ($releases as $rel) { + if (is_array($rel) && array_key_exists('installconditions', $rel)) { + $this->_validateInstallConditions($rel['installconditions'], + "<$release>"); + } + if (is_array($rel) && array_key_exists('filelist', $rel)) { + if ($rel['filelist']) { + + $this->_validateFilelist($rel['filelist'], true); + } + } + } + } + + /** + * This is here to allow role extension through plugins + * @param string + */ + function _validateRole($role) + { + return in_array($role, PEAR_Installer_Role::getValidRoles($this->_pf->getPackageType())); + } + + function _pearVersionTooLow($version) + { + $this->_stack->push(__FUNCTION__, 'error', + array('version' => $version), + 'This package.xml requires PEAR version %version% to parse properly, we are ' . + 'version 1.9.1'); + } + + function _invalidTagOrder($oktags, $actual, $root) + { + $this->_stack->push(__FUNCTION__, 'error', + array('oktags' => $oktags, 'actual' => $actual, 'root' => $root), + 'Invalid tag order in %root%, found <%actual%> expected one of "%oktags%"'); + } + + function _ignoreNotAllowed($type) + { + $this->_stack->push(__FUNCTION__, 'error', array('type' => $type), + '<%type%> is not allowed inside global , only inside ' . + '//, use and only'); + } + + function _fileNotAllowed($type) + { + $this->_stack->push(__FUNCTION__, 'error', array('type' => $type), + '<%type%> is not allowed inside release , only inside ' . + ', use and only'); + } + + function _oldStyleFileNotAllowed() + { + $this->_stack->push(__FUNCTION__, 'error', array(), + 'Old-style name is not allowed. Use' . + ''); + } + + function _tagMissingAttribute($tag, $attr, $context) + { + $this->_stack->push(__FUNCTION__, 'error', array('tag' => $tag, + 'attribute' => $attr, 'context' => $context), + 'tag <%tag%> in context "%context%" has no attribute "%attribute%"'); + } + + function _tagHasNoAttribs($tag, $context) + { + $this->_stack->push(__FUNCTION__, 'error', array('tag' => $tag, + 'context' => $context), + 'tag <%tag%> has no attributes in context "%context%"'); + } + + function _invalidInternalStructure() + { + $this->_stack->push(__FUNCTION__, 'exception', array(), + 'internal array was not generated by compatible parser, or extreme parser error, cannot continue'); + } + + function _invalidFileRole($file, $dir, $role) + { + $this->_stack->push(__FUNCTION__, 'error', array( + 'file' => $file, 'dir' => $dir, 'role' => $role, + 'roles' => PEAR_Installer_Role::getValidRoles($this->_pf->getPackageType())), + 'File "%file%" in directory "%dir%" has invalid role "%role%", should be one of %roles%'); + } + + function _invalidFileName($file, $dir) + { + $this->_stack->push(__FUNCTION__, 'error', array( + 'file' => $file), + 'File "%file%" in directory "%dir%" cannot begin with "./" or contain ".."'); + } + + function _invalidFileInstallAs($file, $as) + { + $this->_stack->push(__FUNCTION__, 'error', array( + 'file' => $file, 'as' => $as), + 'File "%file%" cannot contain "./" or contain ".."'); + } + + function _invalidDirName($dir) + { + $this->_stack->push(__FUNCTION__, 'error', array( + 'dir' => $file), + 'Directory "%dir%" cannot begin with "./" or contain ".."'); + } + + function _filelistCannotContainFile($filelist) + { + $this->_stack->push(__FUNCTION__, 'error', array('tag' => $filelist), + '<%tag%> can only contain , contains . Use ' . + ' as the first dir element'); + } + + function _filelistMustContainDir($filelist) + { + $this->_stack->push(__FUNCTION__, 'error', array('tag' => $filelist), + '<%tag%> must contain . Use as the ' . + 'first dir element'); + } + + function _tagCannotBeEmpty($tag) + { + $this->_stack->push(__FUNCTION__, 'error', array('tag' => $tag), + '<%tag%> cannot be empty (<%tag%/>)'); + } + + function _UrlOrChannel($type, $name) + { + $this->_stack->push(__FUNCTION__, 'error', array('type' => $type, + 'name' => $name), + 'Required dependency <%type%> "%name%" can have either url OR ' . + 'channel attributes, and not both'); + } + + function _NoChannel($type, $name) + { + $this->_stack->push(__FUNCTION__, 'error', array('type' => $type, + 'name' => $name), + 'Required dependency <%type%> "%name%" must have either url OR ' . + 'channel attributes'); + } + + function _UrlOrChannelGroup($type, $name, $group) + { + $this->_stack->push(__FUNCTION__, 'error', array('type' => $type, + 'name' => $name, 'group' => $group), + 'Group "%group%" dependency <%type%> "%name%" can have either url OR ' . + 'channel attributes, and not both'); + } + + function _NoChannelGroup($type, $name, $group) + { + $this->_stack->push(__FUNCTION__, 'error', array('type' => $type, + 'name' => $name, 'group' => $group), + 'Group "%group%" dependency <%type%> "%name%" must have either url OR ' . + 'channel attributes'); + } + + function _unknownChannel($channel) + { + $this->_stack->push(__FUNCTION__, 'error', array('channel' => $channel), + 'Unknown channel "%channel%"'); + } + + function _noPackageVersion() + { + $this->_stack->push(__FUNCTION__, 'error', array(), + 'package.xml tag has no version attribute, or version is not 2.0'); + } + + function _NoBundledPackages() + { + $this->_stack->push(__FUNCTION__, 'error', array(), + 'No tag was found in , required for bundle packages'); + } + + function _AtLeast2BundledPackages() + { + $this->_stack->push(__FUNCTION__, 'error', array(), + 'At least 2 packages must be bundled in a bundle package'); + } + + function _ChannelOrUri($name) + { + $this->_stack->push(__FUNCTION__, 'error', array('name' => $name), + 'Bundled package "%name%" can have either a uri or a channel, not both'); + } + + function _noChildTag($child, $tag) + { + $this->_stack->push(__FUNCTION__, 'error', array('child' => $child, 'tag' => $tag), + 'Tag <%tag%> is missing child tag <%child%>'); + } + + function _invalidVersion($type, $value) + { + $this->_stack->push(__FUNCTION__, 'error', array('type' => $type, 'value' => $value), + 'Version type <%type%> is not a valid version (%value%)'); + } + + function _invalidState($type, $value) + { + $states = array('stable', 'beta', 'alpha', 'devel'); + if ($type != 'api') { + $states[] = 'snapshot'; + } + if (strtolower($value) == 'rc') { + $this->_stack->push(__FUNCTION__, 'error', + array('version' => $this->_packageInfo['version']['release']), + 'RC is not a state, it is a version postfix, try %version%RC1, stability beta'); + } + $this->_stack->push(__FUNCTION__, 'error', array('type' => $type, 'value' => $value, + 'types' => $states), + 'Stability type <%type%> is not a valid stability (%value%), must be one of ' . + '%types%'); + } + + function _invalidTask($task, $ret, $file) + { + switch ($ret[0]) { + case PEAR_TASK_ERROR_MISSING_ATTRIB : + $info = array('attrib' => $ret[1], 'task' => $task, 'file' => $file); + $msg = 'task <%task%> is missing attribute "%attrib%" in file %file%'; + break; + case PEAR_TASK_ERROR_NOATTRIBS : + $info = array('task' => $task, 'file' => $file); + $msg = 'task <%task%> has no attributes in file %file%'; + break; + case PEAR_TASK_ERROR_WRONG_ATTRIB_VALUE : + $info = array('attrib' => $ret[1], 'values' => $ret[3], + 'was' => $ret[2], 'task' => $task, 'file' => $file); + $msg = 'task <%task%> attribute "%attrib%" has the wrong value "%was%" '. + 'in file %file%, expecting one of "%values%"'; + break; + case PEAR_TASK_ERROR_INVALID : + $info = array('reason' => $ret[1], 'task' => $task, 'file' => $file); + $msg = 'task <%task%> in file %file% is invalid because of "%reason%"'; + break; + } + $this->_stack->push(__FUNCTION__, 'error', $info, $msg); + } + + function _unknownTask($task, $file) + { + $this->_stack->push(__FUNCTION__, 'error', array('task' => $task, 'file' => $file), + 'Unknown task "%task%" passed in file '); + } + + function _subpackageCannotProvideExtension($name) + { + $this->_stack->push(__FUNCTION__, 'error', array('name' => $name), + 'Subpackage dependency "%name%" cannot use , ' . + 'only package dependencies can use this tag'); + } + + function _subpackagesCannotConflict($name) + { + $this->_stack->push(__FUNCTION__, 'error', array('name' => $name), + 'Subpackage dependency "%name%" cannot use , ' . + 'only package dependencies can use this tag'); + } + + function _cannotProvideExtension($release) + { + $this->_stack->push(__FUNCTION__, 'error', array('release' => $release), + '<%release%> packages cannot use , only extbinrelease, extsrcrelease, zendextsrcrelease, and zendextbinrelease can provide a PHP extension'); + } + + function _mustProvideExtension($release) + { + $this->_stack->push(__FUNCTION__, 'error', array('release' => $release), + '<%release%> packages must use to indicate which PHP extension is provided'); + } + + function _cannotHaveSrcpackage($release) + { + $this->_stack->push(__FUNCTION__, 'error', array('release' => $release), + '<%release%> packages cannot specify a source code package, only extension binaries may use the tag'); + } + + function _mustSrcPackage($release) + { + $this->_stack->push(__FUNCTION__, 'error', array('release' => $release), + '/ packages must specify a source code package with '); + } + + function _mustSrcuri($release) + { + $this->_stack->push(__FUNCTION__, 'error', array('release' => $release), + '/ packages must specify a source code package with '); + } + + function _uriDepsCannotHaveVersioning($type) + { + $this->_stack->push(__FUNCTION__, 'error', array('type' => $type), + '%type%: dependencies with a tag cannot have any versioning information'); + } + + function _conflictingDepsCannotHaveVersioning($type) + { + $this->_stack->push(__FUNCTION__, 'error', array('type' => $type), + '%type%: conflicting dependencies cannot have versioning info, use to ' . + 'exclude specific versions of a dependency'); + } + + function _DepchannelCannotBeUri($type) + { + $this->_stack->push(__FUNCTION__, 'error', array('type' => $type), + '%type%: channel cannot be __uri, this is a pseudo-channel reserved for uri ' . + 'dependencies only'); + } + + function _bundledPackagesMustBeFilename() + { + $this->_stack->push(__FUNCTION__, 'error', array(), + ' tags must contain only the filename of a package release ' . + 'in the bundle'); + } + + function _binaryPackageMustBePackagename() + { + $this->_stack->push(__FUNCTION__, 'error', array(), + ' tags must contain the name of a package that is ' . + 'a compiled version of this extsrc/zendextsrc package'); + } + + function _fileNotFound($file) + { + $this->_stack->push(__FUNCTION__, 'error', array('file' => $file), + 'File "%file%" in package.xml does not exist'); + } + + function _notInContents($file, $tag) + { + $this->_stack->push(__FUNCTION__, 'error', array('file' => $file, 'tag' => $tag), + '<%tag% name="%file%"> is invalid, file is not in '); + } + + function _cannotValidateNoPathSet() + { + $this->_stack->push(__FUNCTION__, 'error', array(), + 'Cannot validate files, no path to package file is set (use setPackageFile())'); + } + + function _usesroletaskMustHaveChannelOrUri($role, $tag) + { + $this->_stack->push(__FUNCTION__, 'error', array('role' => $role, 'tag' => $tag), + '<%tag%> for role "%role%" must contain either , or and '); + } + + function _usesroletaskMustHavePackage($role, $tag) + { + $this->_stack->push(__FUNCTION__, 'error', array('role' => $role, 'tag' => $tag), + '<%tag%> for role "%role%" must contain '); + } + + function _usesroletaskMustHaveRoleTask($tag, $type) + { + $this->_stack->push(__FUNCTION__, 'error', array('tag' => $tag, 'type' => $type), + '<%tag%> must contain <%type%> defining the %type% to be used'); + } + + function _cannotConflictWithAllOs($type) + { + $this->_stack->push(__FUNCTION__, 'error', array('tag' => $tag), + '%tag% cannot conflict with all OSes'); + } + + function _invalidDepGroupName($name) + { + $this->_stack->push(__FUNCTION__, 'error', array('name' => $name), + 'Invalid dependency group name "%name%"'); + } + + function _multipleToplevelDirNotAllowed() + { + $this->_stack->push(__FUNCTION__, 'error', array(), + 'Multiple top-level tags are not allowed. Enclose them ' . + 'in a '); + } + + function _multipleInstallAs($file) + { + $this->_stack->push(__FUNCTION__, 'error', array('file' => $file), + 'Only one tag is allowed for file "%file%"'); + } + + function _ignoreAndInstallAs($file) + { + $this->_stack->push(__FUNCTION__, 'error', array('file' => $file), + 'Cannot have both and tags for file "%file%"'); + } + + function _analyzeBundledPackages() + { + if (!$this->_isValid) { + return false; + } + if (!$this->_pf->getPackageType() == 'bundle') { + return false; + } + if (!isset($this->_pf->_packageFile)) { + return false; + } + $dir_prefix = dirname($this->_pf->_packageFile); + $common = new PEAR_Common; + $log = isset($this->_pf->_logger) ? array(&$this->_pf->_logger, 'log') : + array($common, 'log'); + $info = $this->_pf->getContents(); + $info = $info['bundledpackage']; + if (!is_array($info)) { + $info = array($info); + } + $pkg = &new PEAR_PackageFile($this->_pf->_config); + foreach ($info as $package) { + if (!file_exists($dir_prefix . DIRECTORY_SEPARATOR . $package)) { + $this->_fileNotFound($dir_prefix . DIRECTORY_SEPARATOR . $package); + $this->_isValid = 0; + continue; + } + call_user_func_array($log, array(1, "Analyzing bundled package $package")); + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $ret = $pkg->fromAnyFile($dir_prefix . DIRECTORY_SEPARATOR . $package, + PEAR_VALIDATE_NORMAL); + PEAR::popErrorHandling(); + if (PEAR::isError($ret)) { + call_user_func_array($log, array(0, "ERROR: package $package is not a valid " . + 'package')); + $inf = $ret->getUserInfo(); + if (is_array($inf)) { + foreach ($inf as $err) { + call_user_func_array($log, array(1, $err['message'])); + } + } + return false; + } + } + return true; + } + + function _analyzePhpFiles() + { + if (!$this->_isValid) { + return false; + } + if (!isset($this->_pf->_packageFile)) { + $this->_cannotValidateNoPathSet(); + return false; + } + $dir_prefix = dirname($this->_pf->_packageFile); + $common = new PEAR_Common; + $log = isset($this->_pf->_logger) ? array(&$this->_pf->_logger, 'log') : + array(&$common, 'log'); + $info = $this->_pf->getContents(); + if (!$info || !isset($info['dir']['file'])) { + $this->_tagCannotBeEmpty('contents>_fileNotFound($dir_prefix . DIRECTORY_SEPARATOR . $file); + $this->_isValid = 0; + continue; + } + if (in_array($fa['role'], PEAR_Installer_Role::getPhpRoles()) && $dir_prefix) { + call_user_func_array($log, array(1, "Analyzing $file")); + $srcinfo = $this->analyzeSourceCode($dir_prefix . DIRECTORY_SEPARATOR . $file); + if ($srcinfo) { + $provides = array_merge($provides, $this->_buildProvidesArray($srcinfo)); + } + } + } + $this->_packageName = $pn = $this->_pf->getPackage(); + $pnl = strlen($pn); + foreach ($provides as $key => $what) { + if (isset($what['explicit']) || !$what) { + // skip conformance checks if the provides entry is + // specified in the package.xml file + continue; + } + extract($what); + if ($type == 'class') { + if (!strncasecmp($name, $pn, $pnl)) { + continue; + } + $this->_stack->push(__FUNCTION__, 'warning', + array('file' => $file, 'type' => $type, 'name' => $name, 'package' => $pn), + 'in %file%: %type% "%name%" not prefixed with package name "%package%"'); + } elseif ($type == 'function') { + if (strstr($name, '::') || !strncasecmp($name, $pn, $pnl)) { + continue; + } + $this->_stack->push(__FUNCTION__, 'warning', + array('file' => $file, 'type' => $type, 'name' => $name, 'package' => $pn), + 'in %file%: %type% "%name%" not prefixed with package name "%package%"'); + } + } + return $this->_isValid; + } + + /** + * Analyze the source code of the given PHP file + * + * @param string Filename of the PHP file + * @param boolean whether to analyze $file as the file contents + * @return mixed + */ + function analyzeSourceCode($file, $string = false) + { + if (!function_exists("token_get_all")) { + $this->_stack->push(__FUNCTION__, 'error', array('file' => $file), + 'Parser error: token_get_all() function must exist to analyze source code, PHP may have been compiled with --disable-tokenizer'); + return false; + } + + if (!defined('T_DOC_COMMENT')) { + define('T_DOC_COMMENT', T_COMMENT); + } + + if (!defined('T_INTERFACE')) { + define('T_INTERFACE', -1); + } + + if (!defined('T_IMPLEMENTS')) { + define('T_IMPLEMENTS', -1); + } + + if ($string) { + $contents = $file; + } else { + if (!$fp = @fopen($file, "r")) { + return false; + } + fclose($fp); + $contents = file_get_contents($file); + } + + // Silence this function so we can catch PHP Warnings and show our own custom message + $tokens = @token_get_all($contents); + if (isset($php_errormsg)) { + if (isset($this->_stack)) { + $pn = $this->_pf->getPackage(); + $this->_stack->push(__FUNCTION__, 'warning', + array('file' => $file, 'package' => $pn), + 'in %file%: Could not process file for unkown reasons,' . + ' possibly a PHP parse error in %file% from %package%'); + } + } +/* + for ($i = 0; $i < sizeof($tokens); $i++) { + @list($token, $data) = $tokens[$i]; + if (is_string($token)) { + var_dump($token); + } else { + print token_name($token) . ' '; + var_dump(rtrim($data)); + } + } +*/ + $look_for = 0; + $paren_level = 0; + $bracket_level = 0; + $brace_level = 0; + $lastphpdoc = ''; + $current_class = ''; + $current_interface = ''; + $current_class_level = -1; + $current_function = ''; + $current_function_level = -1; + $declared_classes = array(); + $declared_interfaces = array(); + $declared_functions = array(); + $declared_methods = array(); + $used_classes = array(); + $used_functions = array(); + $extends = array(); + $implements = array(); + $nodeps = array(); + $inquote = false; + $interface = false; + for ($i = 0; $i < sizeof($tokens); $i++) { + if (is_array($tokens[$i])) { + list($token, $data) = $tokens[$i]; + } else { + $token = $tokens[$i]; + $data = ''; + } + + if ($inquote) { + if ($token != '"' && $token != T_END_HEREDOC) { + continue; + } else { + $inquote = false; + continue; + } + } + + switch ($token) { + case T_WHITESPACE : + continue; + case ';': + if ($interface) { + $current_function = ''; + $current_function_level = -1; + } + break; + case '"': + case T_START_HEREDOC: + $inquote = true; + break; + case T_CURLY_OPEN: + case T_DOLLAR_OPEN_CURLY_BRACES: + case '{': $brace_level++; continue 2; + case '}': + $brace_level--; + if ($current_class_level == $brace_level) { + $current_class = ''; + $current_class_level = -1; + } + if ($current_function_level == $brace_level) { + $current_function = ''; + $current_function_level = -1; + } + continue 2; + case '[': $bracket_level++; continue 2; + case ']': $bracket_level--; continue 2; + case '(': $paren_level++; continue 2; + case ')': $paren_level--; continue 2; + case T_INTERFACE: + $interface = true; + case T_CLASS: + if (($current_class_level != -1) || ($current_function_level != -1)) { + if (isset($this->_stack)) { + $this->_stack->push(__FUNCTION__, 'error', array('file' => $file), + 'Parser error: invalid PHP found in file "%file%"'); + } else { + PEAR::raiseError("Parser error: invalid PHP found in file \"$file\"", + PEAR_COMMON_ERROR_INVALIDPHP); + } + + return false; + } + case T_FUNCTION: + case T_NEW: + case T_EXTENDS: + case T_IMPLEMENTS: + $look_for = $token; + continue 2; + case T_STRING: + if (version_compare(zend_version(), '2.0', '<')) { + if (in_array(strtolower($data), + array('public', 'private', 'protected', 'abstract', + 'interface', 'implements', 'throw') + ) + ) { + if (isset($this->_stack)) { + $this->_stack->push(__FUNCTION__, 'warning', array( + 'file' => $file), + 'Error, PHP5 token encountered in %file%,' . + ' analysis should be in PHP5'); + } else { + PEAR::raiseError('Error: PHP5 token encountered in ' . $file . + 'packaging should be done in PHP 5'); + return false; + } + } + } + + if ($look_for == T_CLASS) { + $current_class = $data; + $current_class_level = $brace_level; + $declared_classes[] = $current_class; + } elseif ($look_for == T_INTERFACE) { + $current_interface = $data; + $current_class_level = $brace_level; + $declared_interfaces[] = $current_interface; + } elseif ($look_for == T_IMPLEMENTS) { + $implements[$current_class] = $data; + } elseif ($look_for == T_EXTENDS) { + $extends[$current_class] = $data; + } elseif ($look_for == T_FUNCTION) { + if ($current_class) { + $current_function = "$current_class::$data"; + $declared_methods[$current_class][] = $data; + } elseif ($current_interface) { + $current_function = "$current_interface::$data"; + $declared_methods[$current_interface][] = $data; + } else { + $current_function = $data; + $declared_functions[] = $current_function; + } + + $current_function_level = $brace_level; + $m = array(); + } elseif ($look_for == T_NEW) { + $used_classes[$data] = true; + } + + $look_for = 0; + continue 2; + case T_VARIABLE: + $look_for = 0; + continue 2; + case T_DOC_COMMENT: + case T_COMMENT: + if (preg_match('!^/\*\*\s!', $data)) { + $lastphpdoc = $data; + if (preg_match_all('/@nodep\s+(\S+)/', $lastphpdoc, $m)) { + $nodeps = array_merge($nodeps, $m[1]); + } + } + continue 2; + case T_DOUBLE_COLON: + if (!($tokens[$i - 1][0] == T_WHITESPACE || $tokens[$i - 1][0] == T_STRING)) { + if (isset($this->_stack)) { + $this->_stack->push(__FUNCTION__, 'warning', array('file' => $file), + 'Parser error: invalid PHP found in file "%file%"'); + } else { + PEAR::raiseError("Parser error: invalid PHP found in file \"$file\"", + PEAR_COMMON_ERROR_INVALIDPHP); + } + + return false; + } + + $class = $tokens[$i - 1][1]; + if (strtolower($class) != 'parent') { + $used_classes[$class] = true; + } + + continue 2; + } + } + + return array( + "source_file" => $file, + "declared_classes" => $declared_classes, + "declared_interfaces" => $declared_interfaces, + "declared_methods" => $declared_methods, + "declared_functions" => $declared_functions, + "used_classes" => array_diff(array_keys($used_classes), $nodeps), + "inheritance" => $extends, + "implements" => $implements, + ); + } + + /** + * Build a "provides" array from data returned by + * analyzeSourceCode(). The format of the built array is like + * this: + * + * array( + * 'class;MyClass' => 'array('type' => 'class', 'name' => 'MyClass'), + * ... + * ) + * + * + * @param array $srcinfo array with information about a source file + * as returned by the analyzeSourceCode() method. + * + * @return void + * + * @access private + * + */ + function _buildProvidesArray($srcinfo) + { + if (!$this->_isValid) { + return array(); + } + + $providesret = array(); + $file = basename($srcinfo['source_file']); + $pn = isset($this->_pf) ? $this->_pf->getPackage() : ''; + $pnl = strlen($pn); + foreach ($srcinfo['declared_classes'] as $class) { + $key = "class;$class"; + if (isset($providesret[$key])) { + continue; + } + + $providesret[$key] = + array('file'=> $file, 'type' => 'class', 'name' => $class); + if (isset($srcinfo['inheritance'][$class])) { + $providesret[$key]['extends'] = + $srcinfo['inheritance'][$class]; + } + } + + foreach ($srcinfo['declared_methods'] as $class => $methods) { + foreach ($methods as $method) { + $function = "$class::$method"; + $key = "function;$function"; + if ($method{0} == '_' || !strcasecmp($method, $class) || + isset($providesret[$key])) { + continue; + } + + $providesret[$key] = + array('file'=> $file, 'type' => 'function', 'name' => $function); + } + } + + foreach ($srcinfo['declared_functions'] as $function) { + $key = "function;$function"; + if ($function{0} == '_' || isset($providesret[$key])) { + continue; + } + + if (!strstr($function, '::') && strncasecmp($function, $pn, $pnl)) { + $warnings[] = "in1 " . $file . ": function \"$function\" not prefixed with package name \"$pn\""; + } + + $providesret[$key] = + array('file'=> $file, 'type' => 'function', 'name' => $function); + } + + return $providesret; + } +} \ No newline at end of file diff --git a/library/pear/PEAR/PackageFile/v2/rw.php b/library/pear/PEAR/PackageFile/v2/rw.php new file mode 100644 index 000000000..d0a6d1fe7 --- /dev/null +++ b/library/pear/PEAR/PackageFile/v2/rw.php @@ -0,0 +1,1604 @@ + + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: rw.php 276385 2009-02-24 23:46:03Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a8 + */ +/** + * For base class + */ +require_once 'PEAR/PackageFile/v2.php'; +/** + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.1 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a8 + */ +class PEAR_PackageFile_v2_rw extends PEAR_PackageFile_v2 +{ + /** + * @param string Extension name + * @return bool success of operation + */ + function setProvidesExtension($extension) + { + if (in_array($this->getPackageType(), + array('extsrc', 'extbin', 'zendextsrc', 'zendextbin'))) { + if (!isset($this->_packageInfo['providesextension'])) { + // ensure that the channel tag is set up in the right location + $this->_packageInfo = $this->_insertBefore($this->_packageInfo, + array('usesrole', 'usestask', 'srcpackage', 'srcuri', 'phprelease', + 'extsrcrelease', 'extbinrelease', 'zendextsrcrelease', 'zendextbinrelease', + 'bundle', 'changelog'), + $extension, 'providesextension'); + } + $this->_packageInfo['providesextension'] = $extension; + return true; + } + return false; + } + + function setPackage($package) + { + $this->_isValid = 0; + if (!isset($this->_packageInfo['attribs'])) { + $this->_packageInfo = array_merge(array('attribs' => array( + 'version' => '2.0', + 'xmlns' => 'http://pear.php.net/dtd/package-2.0', + 'xmlns:tasks' => 'http://pear.php.net/dtd/tasks-1.0', + 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', + 'xsi:schemaLocation' => 'http://pear.php.net/dtd/tasks-1.0 + http://pear.php.net/dtd/tasks-1.0.xsd + http://pear.php.net/dtd/package-2.0 + http://pear.php.net/dtd/package-2.0.xsd', + )), $this->_packageInfo); + } + if (!isset($this->_packageInfo['name'])) { + return $this->_packageInfo = array_merge(array('name' => $package), + $this->_packageInfo); + } + $this->_packageInfo['name'] = $package; + } + + /** + * set this as a package.xml version 2.1 + * @access private + */ + function _setPackageVersion2_1() + { + $info = array( + 'version' => '2.1', + 'xmlns' => 'http://pear.php.net/dtd/package-2.1', + 'xmlns:tasks' => 'http://pear.php.net/dtd/tasks-1.0', + 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', + 'xsi:schemaLocation' => 'http://pear.php.net/dtd/tasks-1.0 + http://pear.php.net/dtd/tasks-1.0.xsd + http://pear.php.net/dtd/package-2.1 + http://pear.php.net/dtd/package-2.1.xsd', + ); + if (!isset($this->_packageInfo['attribs'])) { + $this->_packageInfo = array_merge(array('attribs' => $info), $this->_packageInfo); + } else { + $this->_packageInfo['attribs'] = $info; + } + } + + function setUri($uri) + { + unset($this->_packageInfo['channel']); + $this->_isValid = 0; + if (!isset($this->_packageInfo['uri'])) { + // ensure that the uri tag is set up in the right location + $this->_packageInfo = $this->_insertBefore($this->_packageInfo, + array('extends', 'summary', 'description', 'lead', + 'developer', 'contributor', 'helper', 'date', 'time', 'version', + 'stability', 'license', 'notes', 'contents', 'compatible', + 'dependencies', 'providesextension', 'usesrole', 'usestask', 'srcpackage', 'srcuri', + 'phprelease', 'extsrcrelease', 'zendextsrcrelease', 'zendextbinrelease', + 'extbinrelease', 'bundle', 'changelog'), $uri, 'uri'); + } + $this->_packageInfo['uri'] = $uri; + } + + function setChannel($channel) + { + unset($this->_packageInfo['uri']); + $this->_isValid = 0; + if (!isset($this->_packageInfo['channel'])) { + // ensure that the channel tag is set up in the right location + $this->_packageInfo = $this->_insertBefore($this->_packageInfo, + array('extends', 'summary', 'description', 'lead', + 'developer', 'contributor', 'helper', 'date', 'time', 'version', + 'stability', 'license', 'notes', 'contents', 'compatible', + 'dependencies', 'providesextension', 'usesrole', 'usestask', 'srcpackage', 'srcuri', + 'phprelease', 'extsrcrelease', 'zendextsrcrelease', 'zendextbinrelease', + 'extbinrelease', 'bundle', 'changelog'), $channel, 'channel'); + } + $this->_packageInfo['channel'] = $channel; + } + + function setExtends($extends) + { + $this->_isValid = 0; + if (!isset($this->_packageInfo['extends'])) { + // ensure that the extends tag is set up in the right location + $this->_packageInfo = $this->_insertBefore($this->_packageInfo, + array('summary', 'description', 'lead', + 'developer', 'contributor', 'helper', 'date', 'time', 'version', + 'stability', 'license', 'notes', 'contents', 'compatible', + 'dependencies', 'providesextension', 'usesrole', 'usestask', 'srcpackage', 'srcuri', + 'phprelease', 'extsrcrelease', 'zendextsrcrelease', 'zendextbinrelease', + 'extbinrelease', 'bundle', 'changelog'), $extends, 'extends'); + } + $this->_packageInfo['extends'] = $extends; + } + + function setSummary($summary) + { + $this->_isValid = 0; + if (!isset($this->_packageInfo['summary'])) { + // ensure that the summary tag is set up in the right location + $this->_packageInfo = $this->_insertBefore($this->_packageInfo, + array('description', 'lead', + 'developer', 'contributor', 'helper', 'date', 'time', 'version', + 'stability', 'license', 'notes', 'contents', 'compatible', + 'dependencies', 'providesextension', 'usesrole', 'usestask', 'srcpackage', 'srcuri', + 'phprelease', 'extsrcrelease', 'zendextsrcrelease', 'zendextbinrelease', + 'extbinrelease', 'bundle', 'changelog'), $summary, 'summary'); + } + $this->_packageInfo['summary'] = $summary; + } + + function setDescription($desc) + { + $this->_isValid = 0; + if (!isset($this->_packageInfo['description'])) { + // ensure that the description tag is set up in the right location + $this->_packageInfo = $this->_insertBefore($this->_packageInfo, + array('lead', + 'developer', 'contributor', 'helper', 'date', 'time', 'version', + 'stability', 'license', 'notes', 'contents', 'compatible', + 'dependencies', 'providesextension', 'usesrole', 'usestask', 'srcpackage', 'srcuri', + 'phprelease', 'extsrcrelease', 'zendextsrcrelease', 'zendextbinrelease', + 'extbinrelease', 'bundle', 'changelog'), $desc, 'description'); + } + $this->_packageInfo['description'] = $desc; + } + + /** + * Adds a new maintainer - no checking of duplicates is performed, use + * updatemaintainer for that purpose. + */ + function addMaintainer($role, $handle, $name, $email, $active = 'yes') + { + if (!in_array($role, array('lead', 'developer', 'contributor', 'helper'))) { + return false; + } + if (isset($this->_packageInfo[$role])) { + if (!isset($this->_packageInfo[$role][0])) { + $this->_packageInfo[$role] = array($this->_packageInfo[$role]); + } + $this->_packageInfo[$role][] = + array( + 'name' => $name, + 'user' => $handle, + 'email' => $email, + 'active' => $active, + ); + } else { + $testarr = array('lead', + 'developer', 'contributor', 'helper', 'date', 'time', 'version', + 'stability', 'license', 'notes', 'contents', 'compatible', + 'dependencies', 'providesextension', 'usesrole', 'usestask', + 'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease', + 'extbinrelease', 'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog'); + foreach (array('lead', 'developer', 'contributor', 'helper') as $testrole) { + array_shift($testarr); + if ($role == $testrole) { + break; + } + } + if (!isset($this->_packageInfo[$role])) { + // ensure that the extends tag is set up in the right location + $this->_packageInfo = $this->_insertBefore($this->_packageInfo, $testarr, + array(), $role); + } + $this->_packageInfo[$role] = + array( + 'name' => $name, + 'user' => $handle, + 'email' => $email, + 'active' => $active, + ); + } + $this->_isValid = 0; + } + + function updateMaintainer($newrole, $handle, $name, $email, $active = 'yes') + { + $found = false; + foreach (array('lead', 'developer', 'contributor', 'helper') as $role) { + if (!isset($this->_packageInfo[$role])) { + continue; + } + $info = $this->_packageInfo[$role]; + if (!isset($info[0])) { + if ($info['user'] == $handle) { + $found = true; + break; + } + } + foreach ($info as $i => $maintainer) { + if ($maintainer['user'] == $handle) { + $found = $i; + break 2; + } + } + } + if ($found === false) { + return $this->addMaintainer($newrole, $handle, $name, $email, $active); + } + if ($found !== false) { + if ($found === true) { + unset($this->_packageInfo[$role]); + } else { + unset($this->_packageInfo[$role][$found]); + $this->_packageInfo[$role] = array_values($this->_packageInfo[$role]); + } + } + $this->addMaintainer($newrole, $handle, $name, $email, $active); + $this->_isValid = 0; + } + + function deleteMaintainer($handle) + { + $found = false; + foreach (array('lead', 'developer', 'contributor', 'helper') as $role) { + if (!isset($this->_packageInfo[$role])) { + continue; + } + if (!isset($this->_packageInfo[$role][0])) { + $this->_packageInfo[$role] = array($this->_packageInfo[$role]); + } + foreach ($this->_packageInfo[$role] as $i => $maintainer) { + if ($maintainer['user'] == $handle) { + $found = $i; + break; + } + } + if ($found !== false) { + unset($this->_packageInfo[$role][$found]); + if (!count($this->_packageInfo[$role]) && $role == 'lead') { + $this->_isValid = 0; + } + if (!count($this->_packageInfo[$role])) { + unset($this->_packageInfo[$role]); + return true; + } + $this->_packageInfo[$role] = + array_values($this->_packageInfo[$role]); + if (count($this->_packageInfo[$role]) == 1) { + $this->_packageInfo[$role] = $this->_packageInfo[$role][0]; + } + return true; + } + if (count($this->_packageInfo[$role]) == 1) { + $this->_packageInfo[$role] = $this->_packageInfo[$role][0]; + } + } + return false; + } + + function setReleaseVersion($version) + { + if (isset($this->_packageInfo['version']) && + isset($this->_packageInfo['version']['release'])) { + unset($this->_packageInfo['version']['release']); + } + $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $version, array( + 'version' => array('stability', 'license', 'notes', 'contents', 'compatible', + 'dependencies', 'providesextension', 'usesrole', 'usestask', 'srcpackage', 'srcuri', + 'phprelease', 'extsrcrelease', 'zendextsrcrelease', 'zendextbinrelease', + 'extbinrelease', 'bundle', 'changelog'), + 'release' => array('api'))); + $this->_isValid = 0; + } + + function setAPIVersion($version) + { + if (isset($this->_packageInfo['version']) && + isset($this->_packageInfo['version']['api'])) { + unset($this->_packageInfo['version']['api']); + } + $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $version, array( + 'version' => array('stability', 'license', 'notes', 'contents', 'compatible', + 'dependencies', 'providesextension', 'usesrole', 'usestask', 'srcpackage', 'srcuri', + 'phprelease', 'extsrcrelease', 'zendextsrcrelease', 'zendextbinrelease', + 'extbinrelease', 'bundle', 'changelog'), + 'api' => array())); + $this->_isValid = 0; + } + + /** + * snapshot|devel|alpha|beta|stable + */ + function setReleaseStability($state) + { + if (isset($this->_packageInfo['stability']) && + isset($this->_packageInfo['stability']['release'])) { + unset($this->_packageInfo['stability']['release']); + } + $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $state, array( + 'stability' => array('license', 'notes', 'contents', 'compatible', + 'dependencies', 'providesextension', 'usesrole', 'usestask', 'srcpackage', 'srcuri', + 'phprelease', 'extsrcrelease', 'zendextsrcrelease', 'zendextbinrelease', + 'extbinrelease', 'bundle', 'changelog'), + 'release' => array('api'))); + $this->_isValid = 0; + } + + /** + * @param devel|alpha|beta|stable + */ + function setAPIStability($state) + { + if (isset($this->_packageInfo['stability']) && + isset($this->_packageInfo['stability']['api'])) { + unset($this->_packageInfo['stability']['api']); + } + $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $state, array( + 'stability' => array('license', 'notes', 'contents', 'compatible', + 'dependencies', 'providesextension', 'usesrole', 'usestask', 'srcpackage', 'srcuri', + 'phprelease', 'extsrcrelease', 'zendextsrcrelease', 'zendextbinrelease', + 'extbinrelease', 'bundle', 'changelog'), + 'api' => array())); + $this->_isValid = 0; + } + + function setLicense($license, $uri = false, $filesource = false) + { + if (!isset($this->_packageInfo['license'])) { + // ensure that the license tag is set up in the right location + $this->_packageInfo = $this->_insertBefore($this->_packageInfo, + array('notes', 'contents', 'compatible', + 'dependencies', 'providesextension', 'usesrole', 'usestask', 'srcpackage', 'srcuri', + 'phprelease', 'extsrcrelease', 'zendextsrcrelease', 'zendextbinrelease', + 'extbinrelease', 'bundle', 'changelog'), 0, 'license'); + } + if ($uri || $filesource) { + $attribs = array(); + if ($uri) { + $attribs['uri'] = $uri; + } + $uri = true; // for test below + if ($filesource) { + $attribs['filesource'] = $filesource; + } + } + $license = $uri ? array('attribs' => $attribs, '_content' => $license) : $license; + $this->_packageInfo['license'] = $license; + $this->_isValid = 0; + } + + function setNotes($notes) + { + $this->_isValid = 0; + if (!isset($this->_packageInfo['notes'])) { + // ensure that the notes tag is set up in the right location + $this->_packageInfo = $this->_insertBefore($this->_packageInfo, + array('contents', 'compatible', + 'dependencies', 'providesextension', 'usesrole', 'usestask', 'srcpackage', 'srcuri', + 'phprelease', 'extsrcrelease', 'zendextsrcrelease', 'zendextbinrelease', + 'extbinrelease', 'bundle', 'changelog'), $notes, 'notes'); + } + $this->_packageInfo['notes'] = $notes; + } + + /** + * This is only used at install-time, after all serialization + * is over. + * @param string file name + * @param string installed path + */ + function setInstalledAs($file, $path) + { + if ($path) { + return $this->_packageInfo['filelist'][$file]['installed_as'] = $path; + } + unset($this->_packageInfo['filelist'][$file]['installed_as']); + } + + /** + * This is only used at install-time, after all serialization + * is over. + */ + function installedFile($file, $atts) + { + if (isset($this->_packageInfo['filelist'][$file])) { + $this->_packageInfo['filelist'][$file] = + array_merge($this->_packageInfo['filelist'][$file], $atts['attribs']); + } else { + $this->_packageInfo['filelist'][$file] = $atts['attribs']; + } + } + + /** + * Reset the listing of package contents + * @param string base installation dir for the whole package, if any + */ + function clearContents($baseinstall = false) + { + $this->_filesValid = false; + $this->_isValid = 0; + if (!isset($this->_packageInfo['contents'])) { + $this->_packageInfo = $this->_insertBefore($this->_packageInfo, + array('compatible', + 'dependencies', 'providesextension', 'usesrole', 'usestask', + 'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease', + 'extbinrelease', 'zendextsrcrelease', 'zendextbinrelease', + 'bundle', 'changelog'), array(), 'contents'); + } + if ($this->getPackageType() != 'bundle') { + $this->_packageInfo['contents'] = + array('dir' => array('attribs' => array('name' => '/'))); + if ($baseinstall) { + $this->_packageInfo['contents']['dir']['attribs']['baseinstalldir'] = $baseinstall; + } + } else { + $this->_packageInfo['contents'] = array('bundledpackage' => array()); + } + } + + /** + * @param string relative path of the bundled package. + */ + function addBundledPackage($path) + { + if ($this->getPackageType() != 'bundle') { + return false; + } + $this->_filesValid = false; + $this->_isValid = 0; + $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $path, array( + 'contents' => array('compatible', 'dependencies', 'providesextension', + 'usesrole', 'usestask', 'srcpackage', 'srcuri', 'phprelease', + 'extsrcrelease', 'extbinrelease', 'zendextsrcrelease', 'zendextbinrelease', + 'bundle', 'changelog'), + 'bundledpackage' => array())); + } + + /** + * @param string file name + * @param PEAR_Task_Common a read/write task + */ + function addTaskToFile($filename, $task) + { + if (!method_exists($task, 'getXml')) { + return false; + } + if (!method_exists($task, 'getName')) { + return false; + } + if (!method_exists($task, 'validate')) { + return false; + } + if (!$task->validate()) { + return false; + } + if (!isset($this->_packageInfo['contents']['dir']['file'])) { + return false; + } + $this->getTasksNs(); // discover the tasks namespace if not done already + $files = $this->_packageInfo['contents']['dir']['file']; + if (!isset($files[0])) { + $files = array($files); + $ind = false; + } else { + $ind = true; + } + foreach ($files as $i => $file) { + if (isset($file['attribs'])) { + if ($file['attribs']['name'] == $filename) { + if ($ind) { + $t = isset($this->_packageInfo['contents']['dir']['file'][$i] + ['attribs'][$this->_tasksNs . + ':' . $task->getName()]) ? + $this->_packageInfo['contents']['dir']['file'][$i] + ['attribs'][$this->_tasksNs . + ':' . $task->getName()] : false; + if ($t && !isset($t[0])) { + $this->_packageInfo['contents']['dir']['file'][$i] + [$this->_tasksNs . ':' . $task->getName()] = array($t); + } + $this->_packageInfo['contents']['dir']['file'][$i][$this->_tasksNs . + ':' . $task->getName()][] = $task->getXml(); + } else { + $t = isset($this->_packageInfo['contents']['dir']['file'] + ['attribs'][$this->_tasksNs . + ':' . $task->getName()]) ? $this->_packageInfo['contents']['dir']['file'] + ['attribs'][$this->_tasksNs . + ':' . $task->getName()] : false; + if ($t && !isset($t[0])) { + $this->_packageInfo['contents']['dir']['file'] + [$this->_tasksNs . ':' . $task->getName()] = array($t); + } + $this->_packageInfo['contents']['dir']['file'][$this->_tasksNs . + ':' . $task->getName()][] = $task->getXml(); + } + return true; + } + } + } + return false; + } + + /** + * @param string path to the file + * @param string filename + * @param array extra attributes + */ + function addFile($dir, $file, $attrs) + { + if ($this->getPackageType() == 'bundle') { + return false; + } + $this->_filesValid = false; + $this->_isValid = 0; + $dir = preg_replace(array('!\\\\+!', '!/+!'), array('/', '/'), $dir); + if ($dir == '/' || $dir == '') { + $dir = ''; + } else { + $dir .= '/'; + } + $attrs['name'] = $dir . $file; + if (!isset($this->_packageInfo['contents'])) { + // ensure that the contents tag is set up + $this->_packageInfo = $this->_insertBefore($this->_packageInfo, + array('compatible', 'dependencies', 'providesextension', 'usesrole', 'usestask', + 'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease', + 'extbinrelease', 'zendextsrcrelease', 'zendextbinrelease', + 'bundle', 'changelog'), array(), 'contents'); + } + if (isset($this->_packageInfo['contents']['dir']['file'])) { + if (!isset($this->_packageInfo['contents']['dir']['file'][0])) { + $this->_packageInfo['contents']['dir']['file'] = + array($this->_packageInfo['contents']['dir']['file']); + } + $this->_packageInfo['contents']['dir']['file'][]['attribs'] = $attrs; + } else { + $this->_packageInfo['contents']['dir']['file']['attribs'] = $attrs; + } + } + + /** + * @param string Dependent package name + * @param string Dependent package's channel name + * @param string minimum version of specified package that this release is guaranteed to be + * compatible with + * @param string maximum version of specified package that this release is guaranteed to be + * compatible with + * @param string versions of specified package that this release is not compatible with + */ + function addCompatiblePackage($name, $channel, $min, $max, $exclude = false) + { + $this->_isValid = 0; + $set = array( + 'name' => $name, + 'channel' => $channel, + 'min' => $min, + 'max' => $max, + ); + if ($exclude) { + $set['exclude'] = $exclude; + } + $this->_isValid = 0; + $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $set, array( + 'compatible' => array('dependencies', 'providesextension', 'usesrole', 'usestask', + 'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease', 'extbinrelease', + 'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog') + )); + } + + /** + * Removes the tag entirely + */ + function resetUsesrole() + { + if (isset($this->_packageInfo['usesrole'])) { + unset($this->_packageInfo['usesrole']); + } + } + + /** + * @param string + * @param string package name or uri + * @param string channel name if non-uri + */ + function addUsesrole($role, $packageOrUri, $channel = false) { + $set = array('role' => $role); + if ($channel) { + $set['package'] = $packageOrUri; + $set['channel'] = $channel; + } else { + $set['uri'] = $packageOrUri; + } + $this->_isValid = 0; + $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $set, array( + 'usesrole' => array('usestask', 'srcpackage', 'srcuri', + 'phprelease', 'extsrcrelease', 'extbinrelease', + 'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog') + )); + } + + /** + * Removes the tag entirely + */ + function resetUsestask() + { + if (isset($this->_packageInfo['usestask'])) { + unset($this->_packageInfo['usestask']); + } + } + + + /** + * @param string + * @param string package name or uri + * @param string channel name if non-uri + */ + function addUsestask($task, $packageOrUri, $channel = false) { + $set = array('task' => $task); + if ($channel) { + $set['package'] = $packageOrUri; + $set['channel'] = $channel; + } else { + $set['uri'] = $packageOrUri; + } + $this->_isValid = 0; + $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $set, array( + 'usestask' => array('srcpackage', 'srcuri', + 'phprelease', 'extsrcrelease', 'extbinrelease', + 'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog') + )); + } + + /** + * Remove all compatible tags + */ + function clearCompatible() + { + unset($this->_packageInfo['compatible']); + } + + /** + * Reset dependencies prior to adding new ones + */ + function clearDeps() + { + if (!isset($this->_packageInfo['dependencies'])) { + $this->_packageInfo = $this->_mergeTag($this->_packageInfo, array(), + array( + 'dependencies' => array('providesextension', 'usesrole', 'usestask', + 'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease', 'extbinrelease', + 'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog'))); + } + $this->_packageInfo['dependencies'] = array(); + } + + /** + * @param string minimum PHP version allowed + * @param string maximum PHP version allowed + * @param array $exclude incompatible PHP versions + */ + function setPhpDep($min, $max = false, $exclude = false) + { + $this->_isValid = 0; + $dep = + array( + 'min' => $min, + ); + if ($max) { + $dep['max'] = $max; + } + if ($exclude) { + if (count($exclude) == 1) { + $exclude = $exclude[0]; + } + $dep['exclude'] = $exclude; + } + if (isset($this->_packageInfo['dependencies']['required']['php'])) { + $this->_stack->push(__FUNCTION__, 'warning', array('dep' => + $this->_packageInfo['dependencies']['required']['php']), + 'warning: PHP dependency already exists, overwriting'); + unset($this->_packageInfo['dependencies']['required']['php']); + } + $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $dep, + array( + 'dependencies' => array('providesextension', 'usesrole', 'usestask', + 'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease', 'extbinrelease', + 'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog'), + 'required' => array('optional', 'group'), + 'php' => array('pearinstaller', 'package', 'subpackage', 'extension', 'os', 'arch') + )); + return true; + } + + /** + * @param string minimum allowed PEAR installer version + * @param string maximum allowed PEAR installer version + * @param string recommended PEAR installer version + * @param array incompatible version of the PEAR installer + */ + function setPearinstallerDep($min, $max = false, $recommended = false, $exclude = false) + { + $this->_isValid = 0; + $dep = + array( + 'min' => $min, + ); + if ($max) { + $dep['max'] = $max; + } + if ($recommended) { + $dep['recommended'] = $recommended; + } + if ($exclude) { + if (count($exclude) == 1) { + $exclude = $exclude[0]; + } + $dep['exclude'] = $exclude; + } + if (isset($this->_packageInfo['dependencies']['required']['pearinstaller'])) { + $this->_stack->push(__FUNCTION__, 'warning', array('dep' => + $this->_packageInfo['dependencies']['required']['pearinstaller']), + 'warning: PEAR Installer dependency already exists, overwriting'); + unset($this->_packageInfo['dependencies']['required']['pearinstaller']); + } + $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $dep, + array( + 'dependencies' => array('providesextension', 'usesrole', 'usestask', + 'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease', 'extbinrelease', + 'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog'), + 'required' => array('optional', 'group'), + 'pearinstaller' => array('package', 'subpackage', 'extension', 'os', 'arch') + )); + } + + /** + * Mark a package as conflicting with this package + * @param string package name + * @param string package channel + * @param string extension this package provides, if any + * @param string|false minimum version required + * @param string|false maximum version allowed + * @param array|false versions to exclude from installation + */ + function addConflictingPackageDepWithChannel($name, $channel, + $providesextension = false, $min = false, $max = false, $exclude = false) + { + $this->_isValid = 0; + $dep = $this->_constructDep($name, $channel, false, $min, $max, false, + $exclude, $providesextension, false, true); + $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $dep, + array( + 'dependencies' => array('providesextension', 'usesrole', 'usestask', + 'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease', 'extbinrelease', + 'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog'), + 'required' => array('optional', 'group'), + 'package' => array('subpackage', 'extension', 'os', 'arch') + )); + } + + /** + * Mark a package as conflicting with this package + * @param string package name + * @param string package channel + * @param string extension this package provides, if any + */ + function addConflictingPackageDepWithUri($name, $uri, $providesextension = false) + { + $this->_isValid = 0; + $dep = + array( + 'name' => $name, + 'uri' => $uri, + 'conflicts' => '', + ); + if ($providesextension) { + $dep['providesextension'] = $providesextension; + } + $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $dep, + array( + 'dependencies' => array('providesextension', 'usesrole', 'usestask', + 'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease', 'extbinrelease', + 'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog'), + 'required' => array('optional', 'group'), + 'package' => array('subpackage', 'extension', 'os', 'arch') + )); + } + + function addDependencyGroup($name, $hint) + { + $this->_isValid = 0; + $this->_packageInfo = $this->_mergeTag($this->_packageInfo, + array('attribs' => array('name' => $name, 'hint' => $hint)), + array( + 'dependencies' => array('providesextension', 'usesrole', 'usestask', + 'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease', 'extbinrelease', + 'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog'), + 'group' => array(), + )); + } + + /** + * @param string package name + * @param string|false channel name, false if this is a uri + * @param string|false uri name, false if this is a channel + * @param string|false minimum version required + * @param string|false maximum version allowed + * @param string|false recommended installation version + * @param array|false versions to exclude from installation + * @param string extension this package provides, if any + * @param bool if true, tells the installer to ignore the default optional dependency group + * when installing this package + * @param bool if true, tells the installer to negate this dependency (conflicts) + * @return array + * @access private + */ + function _constructDep($name, $channel, $uri, $min, $max, $recommended, $exclude, + $providesextension = false, $nodefault = false, + $conflicts = false) + { + $dep = + array( + 'name' => $name, + ); + if ($channel) { + $dep['channel'] = $channel; + } elseif ($uri) { + $dep['uri'] = $uri; + } + if ($min) { + $dep['min'] = $min; + } + if ($max) { + $dep['max'] = $max; + } + if ($recommended) { + $dep['recommended'] = $recommended; + } + if ($exclude) { + if (is_array($exclude) && count($exclude) == 1) { + $exclude = $exclude[0]; + } + $dep['exclude'] = $exclude; + } + if ($conflicts) { + $dep['conflicts'] = ''; + } + if ($nodefault) { + $dep['nodefault'] = ''; + } + if ($providesextension) { + $dep['providesextension'] = $providesextension; + } + return $dep; + } + + /** + * @param package|subpackage + * @param string group name + * @param string package name + * @param string package channel + * @param string minimum version + * @param string maximum version + * @param string recommended version + * @param array|false optional excluded versions + * @param string extension this package provides, if any + * @param bool if true, tells the installer to ignore the default optional dependency group + * when installing this package + * @return bool false if the dependency group has not been initialized with + * {@link addDependencyGroup()}, or a subpackage is added with + * a providesextension + */ + function addGroupPackageDepWithChannel($type, $groupname, $name, $channel, $min = false, + $max = false, $recommended = false, $exclude = false, + $providesextension = false, $nodefault = false) + { + if ($type == 'subpackage' && $providesextension) { + return false; // subpackages must be php packages + } + $dep = $this->_constructDep($name, $channel, false, $min, $max, $recommended, $exclude, + $providesextension, $nodefault); + return $this->_addGroupDependency($type, $dep, $groupname); + } + + /** + * @param package|subpackage + * @param string group name + * @param string package name + * @param string package uri + * @param string extension this package provides, if any + * @param bool if true, tells the installer to ignore the default optional dependency group + * when installing this package + * @return bool false if the dependency group has not been initialized with + * {@link addDependencyGroup()} + */ + function addGroupPackageDepWithURI($type, $groupname, $name, $uri, $providesextension = false, + $nodefault = false) + { + if ($type == 'subpackage' && $providesextension) { + return false; // subpackages must be php packages + } + $dep = $this->_constructDep($name, false, $uri, false, false, false, false, + $providesextension, $nodefault); + return $this->_addGroupDependency($type, $dep, $groupname); + } + + /** + * @param string group name (must be pre-existing) + * @param string extension name + * @param string minimum version allowed + * @param string maximum version allowed + * @param string recommended version + * @param array incompatible versions + */ + function addGroupExtensionDep($groupname, $name, $min = false, $max = false, + $recommended = false, $exclude = false) + { + $this->_isValid = 0; + $dep = $this->_constructDep($name, false, false, $min, $max, $recommended, $exclude); + return $this->_addGroupDependency('extension', $dep, $groupname); + } + + /** + * @param package|subpackage|extension + * @param array dependency contents + * @param string name of the dependency group to add this to + * @return boolean + * @access private + */ + function _addGroupDependency($type, $dep, $groupname) + { + $arr = array('subpackage', 'extension'); + if ($type != 'package') { + array_shift($arr); + } + if ($type == 'extension') { + array_shift($arr); + } + if (!isset($this->_packageInfo['dependencies']['group'])) { + return false; + } else { + if (!isset($this->_packageInfo['dependencies']['group'][0])) { + if ($this->_packageInfo['dependencies']['group']['attribs']['name'] == $groupname) { + $this->_packageInfo['dependencies']['group'] = $this->_mergeTag( + $this->_packageInfo['dependencies']['group'], $dep, + array( + $type => $arr + )); + $this->_isValid = 0; + return true; + } else { + return false; + } + } else { + foreach ($this->_packageInfo['dependencies']['group'] as $i => $group) { + if ($group['attribs']['name'] == $groupname) { + $this->_packageInfo['dependencies']['group'][$i] = $this->_mergeTag( + $this->_packageInfo['dependencies']['group'][$i], $dep, + array( + $type => $arr + )); + $this->_isValid = 0; + return true; + } + } + return false; + } + } + } + + /** + * @param optional|required + * @param string package name + * @param string package channel + * @param string minimum version + * @param string maximum version + * @param string recommended version + * @param string extension this package provides, if any + * @param bool if true, tells the installer to ignore the default optional dependency group + * when installing this package + * @param array|false optional excluded versions + */ + function addPackageDepWithChannel($type, $name, $channel, $min = false, $max = false, + $recommended = false, $exclude = false, + $providesextension = false, $nodefault = false) + { + if (!in_array($type, array('optional', 'required'), true)) { + $type = 'required'; + } + $this->_isValid = 0; + $arr = array('optional', 'group'); + if ($type != 'required') { + array_shift($arr); + } + $dep = $this->_constructDep($name, $channel, false, $min, $max, $recommended, $exclude, + $providesextension, $nodefault); + $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $dep, + array( + 'dependencies' => array('providesextension', 'usesrole', 'usestask', + 'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease', 'extbinrelease', + 'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog'), + $type => $arr, + 'package' => array('subpackage', 'extension', 'os', 'arch') + )); + } + + /** + * @param optional|required + * @param string name of the package + * @param string uri of the package + * @param string extension this package provides, if any + * @param bool if true, tells the installer to ignore the default optional dependency group + * when installing this package + */ + function addPackageDepWithUri($type, $name, $uri, $providesextension = false, + $nodefault = false) + { + $this->_isValid = 0; + $arr = array('optional', 'group'); + if ($type != 'required') { + array_shift($arr); + } + $dep = $this->_constructDep($name, false, $uri, false, false, false, false, + $providesextension, $nodefault); + $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $dep, + array( + 'dependencies' => array('providesextension', 'usesrole', 'usestask', + 'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease', 'extbinrelease', + 'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog'), + $type => $arr, + 'package' => array('subpackage', 'extension', 'os', 'arch') + )); + } + + /** + * @param optional|required optional, required + * @param string package name + * @param string package channel + * @param string minimum version + * @param string maximum version + * @param string recommended version + * @param array incompatible versions + * @param bool if true, tells the installer to ignore the default optional dependency group + * when installing this package + */ + function addSubpackageDepWithChannel($type, $name, $channel, $min = false, $max = false, + $recommended = false, $exclude = false, + $nodefault = false) + { + $this->_isValid = 0; + $arr = array('optional', 'group'); + if ($type != 'required') { + array_shift($arr); + } + $dep = $this->_constructDep($name, $channel, false, $min, $max, $recommended, $exclude, + $nodefault); + $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $dep, + array( + 'dependencies' => array('providesextension', 'usesrole', 'usestask', + 'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease', 'extbinrelease', + 'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog'), + $type => $arr, + 'subpackage' => array('extension', 'os', 'arch') + )); + } + + /** + * @param optional|required optional, required + * @param string package name + * @param string package uri for download + * @param bool if true, tells the installer to ignore the default optional dependency group + * when installing this package + */ + function addSubpackageDepWithUri($type, $name, $uri, $nodefault = false) + { + $this->_isValid = 0; + $arr = array('optional', 'group'); + if ($type != 'required') { + array_shift($arr); + } + $dep = $this->_constructDep($name, false, $uri, false, false, false, false, $nodefault); + $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $dep, + array( + 'dependencies' => array('providesextension', 'usesrole', 'usestask', + 'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease', 'extbinrelease', + 'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog'), + $type => $arr, + 'subpackage' => array('extension', 'os', 'arch') + )); + } + + /** + * @param optional|required optional, required + * @param string extension name + * @param string minimum version + * @param string maximum version + * @param string recommended version + * @param array incompatible versions + */ + function addExtensionDep($type, $name, $min = false, $max = false, $recommended = false, + $exclude = false) + { + $this->_isValid = 0; + $arr = array('optional', 'group'); + if ($type != 'required') { + array_shift($arr); + } + $dep = $this->_constructDep($name, false, false, $min, $max, $recommended, $exclude); + $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $dep, + array( + 'dependencies' => array('providesextension', 'usesrole', 'usestask', + 'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease', 'extbinrelease', + 'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog'), + $type => $arr, + 'extension' => array('os', 'arch') + )); + } + + /** + * @param string Operating system name + * @param boolean true if this package cannot be installed on this OS + */ + function addOsDep($name, $conflicts = false) + { + $this->_isValid = 0; + $dep = array('name' => $name); + if ($conflicts) { + $dep['conflicts'] = ''; + } + $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $dep, + array( + 'dependencies' => array('providesextension', 'usesrole', 'usestask', + 'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease', 'extbinrelease', + 'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog'), + 'required' => array('optional', 'group'), + 'os' => array('arch') + )); + } + + /** + * @param string Architecture matching pattern + * @param boolean true if this package cannot be installed on this architecture + */ + function addArchDep($pattern, $conflicts = false) + { + $this->_isValid = 0; + $dep = array('pattern' => $pattern); + if ($conflicts) { + $dep['conflicts'] = ''; + } + $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $dep, + array( + 'dependencies' => array('providesextension', 'usesrole', 'usestask', + 'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease', 'extbinrelease', + 'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog'), + 'required' => array('optional', 'group'), + 'arch' => array() + )); + } + + /** + * Set the kind of package, and erase all release tags + * + * - a php package is a PEAR-style package + * - an extbin package is a PECL-style extension binary + * - an extsrc package is a PECL-style source for a binary + * - an zendextbin package is a PECL-style zend extension binary + * - an zendextsrc package is a PECL-style source for a zend extension binary + * - a bundle package is a collection of other pre-packaged packages + * @param php|extbin|extsrc|zendextsrc|zendextbin|bundle + * @return bool success + */ + function setPackageType($type) + { + $this->_isValid = 0; + if (!in_array($type, array('php', 'extbin', 'extsrc', 'zendextsrc', + 'zendextbin', 'bundle'))) { + return false; + } + + if (in_array($type, array('zendextsrc', 'zendextbin'))) { + $this->_setPackageVersion2_1(); + } + + if ($type != 'bundle') { + $type .= 'release'; + } + + foreach (array('phprelease', 'extbinrelease', 'extsrcrelease', + 'zendextsrcrelease', 'zendextbinrelease', 'bundle') as $test) { + unset($this->_packageInfo[$test]); + } + + if (!isset($this->_packageInfo[$type])) { + // ensure that the release tag is set up + $this->_packageInfo = $this->_insertBefore($this->_packageInfo, array('changelog'), + array(), $type); + } + + $this->_packageInfo[$type] = array(); + return true; + } + + /** + * @return bool true if package type is set up + */ + function addRelease() + { + if ($type = $this->getPackageType()) { + if ($type != 'bundle') { + $type .= 'release'; + } + $this->_packageInfo = $this->_mergeTag($this->_packageInfo, array(), + array($type => array('changelog'))); + return true; + } + return false; + } + + /** + * Get the current release tag in order to add to it + * @param bool returns only releases that have installcondition if true + * @return array|null + */ + function &_getCurrentRelease($strict = true) + { + if ($p = $this->getPackageType()) { + if ($strict) { + if ($p == 'extsrc' || $p == 'zendextsrc') { + $a = null; + return $a; + } + } + if ($p != 'bundle') { + $p .= 'release'; + } + if (isset($this->_packageInfo[$p][0])) { + return $this->_packageInfo[$p][count($this->_packageInfo[$p]) - 1]; + } else { + return $this->_packageInfo[$p]; + } + } else { + $a = null; + return $a; + } + } + + /** + * Add a file to the current release that should be installed under a different name + * @param string path to file + * @param string name the file should be installed as + */ + function addInstallAs($path, $as) + { + $r = &$this->_getCurrentRelease(); + if ($r === null) { + return false; + } + $this->_isValid = 0; + $r = $this->_mergeTag($r, array('attribs' => array('name' => $path, 'as' => $as)), + array( + 'filelist' => array(), + 'install' => array('ignore') + )); + } + + /** + * Add a file to the current release that should be ignored + * @param string path to file + * @return bool success of operation + */ + function addIgnore($path) + { + $r = &$this->_getCurrentRelease(); + if ($r === null) { + return false; + } + $this->_isValid = 0; + $r = $this->_mergeTag($r, array('attribs' => array('name' => $path)), + array( + 'filelist' => array(), + 'ignore' => array() + )); + } + + /** + * Add an extension binary package for this extension source code release + * + * Note that the package must be from the same channel as the extension source package + * @param string + */ + function addBinarypackage($package) + { + if ($this->getPackageType() != 'extsrc' && $this->getPackageType() != 'zendextsrc') { + return false; + } + $r = &$this->_getCurrentRelease(false); + if ($r === null) { + return false; + } + $this->_isValid = 0; + $r = $this->_mergeTag($r, $package, + array( + 'binarypackage' => array('filelist'), + )); + } + + /** + * Add a configureoption to an extension source package + * @param string + * @param string + * @param string + */ + function addConfigureOption($name, $prompt, $default = null) + { + if ($this->getPackageType() != 'extsrc' && $this->getPackageType() != 'zendextsrc') { + return false; + } + + $r = &$this->_getCurrentRelease(false); + if ($r === null) { + return false; + } + + $opt = array('attribs' => array('name' => $name, 'prompt' => $prompt)); + if ($default !== null) { + $opt['attribs']['default'] = $default; + } + + $this->_isValid = 0; + $r = $this->_mergeTag($r, $opt, + array( + 'configureoption' => array('binarypackage', 'filelist'), + )); + } + + /** + * Set an installation condition based on php version for the current release set + * @param string minimum version + * @param string maximum version + * @param false|array incompatible versions of PHP + */ + function setPhpInstallCondition($min, $max, $exclude = false) + { + $r = &$this->_getCurrentRelease(); + if ($r === null) { + return false; + } + $this->_isValid = 0; + if (isset($r['installconditions']['php'])) { + unset($r['installconditions']['php']); + } + $dep = array('min' => $min, 'max' => $max); + if ($exclude) { + if (is_array($exclude) && count($exclude) == 1) { + $exclude = $exclude[0]; + } + $dep['exclude'] = $exclude; + } + if ($this->getPackageType() == 'extsrc' || $this->getPackageType() == 'zendextsrc') { + $r = $this->_mergeTag($r, $dep, + array( + 'installconditions' => array('configureoption', 'binarypackage', + 'filelist'), + 'php' => array('extension', 'os', 'arch') + )); + } else { + $r = $this->_mergeTag($r, $dep, + array( + 'installconditions' => array('filelist'), + 'php' => array('extension', 'os', 'arch') + )); + } + } + + /** + * @param optional|required optional, required + * @param string extension name + * @param string minimum version + * @param string maximum version + * @param string recommended version + * @param array incompatible versions + */ + function addExtensionInstallCondition($name, $min = false, $max = false, $recommended = false, + $exclude = false) + { + $r = &$this->_getCurrentRelease(); + if ($r === null) { + return false; + } + $this->_isValid = 0; + $dep = $this->_constructDep($name, false, false, $min, $max, $recommended, $exclude); + if ($this->getPackageType() == 'extsrc' || $this->getPackageType() == 'zendextsrc') { + $r = $this->_mergeTag($r, $dep, + array( + 'installconditions' => array('configureoption', 'binarypackage', + 'filelist'), + 'extension' => array('os', 'arch') + )); + } else { + $r = $this->_mergeTag($r, $dep, + array( + 'installconditions' => array('filelist'), + 'extension' => array('os', 'arch') + )); + } + } + + /** + * Set an installation condition based on operating system for the current release set + * @param string OS name + * @param bool whether this OS is incompatible with the current release + */ + function setOsInstallCondition($name, $conflicts = false) + { + $r = &$this->_getCurrentRelease(); + if ($r === null) { + return false; + } + $this->_isValid = 0; + if (isset($r['installconditions']['os'])) { + unset($r['installconditions']['os']); + } + $dep = array('name' => $name); + if ($conflicts) { + $dep['conflicts'] = ''; + } + if ($this->getPackageType() == 'extsrc' || $this->getPackageType() == 'zendextsrc') { + $r = $this->_mergeTag($r, $dep, + array( + 'installconditions' => array('configureoption', 'binarypackage', + 'filelist'), + 'os' => array('arch') + )); + } else { + $r = $this->_mergeTag($r, $dep, + array( + 'installconditions' => array('filelist'), + 'os' => array('arch') + )); + } + } + + /** + * Set an installation condition based on architecture for the current release set + * @param string architecture pattern + * @param bool whether this arch is incompatible with the current release + */ + function setArchInstallCondition($pattern, $conflicts = false) + { + $r = &$this->_getCurrentRelease(); + if ($r === null) { + return false; + } + $this->_isValid = 0; + if (isset($r['installconditions']['arch'])) { + unset($r['installconditions']['arch']); + } + $dep = array('pattern' => $pattern); + if ($conflicts) { + $dep['conflicts'] = ''; + } + if ($this->getPackageType() == 'extsrc' || $this->getPackageType() == 'zendextsrc') { + $r = $this->_mergeTag($r, $dep, + array( + 'installconditions' => array('configureoption', 'binarypackage', + 'filelist'), + 'arch' => array() + )); + } else { + $r = $this->_mergeTag($r, $dep, + array( + 'installconditions' => array('filelist'), + 'arch' => array() + )); + } + } + + /** + * For extension binary releases, this is used to specify either the + * static URI to a source package, or the package name and channel of the extsrc/zendextsrc + * package it is based on. + * @param string Package name, or full URI to source package (extsrc/zendextsrc type) + */ + function setSourcePackage($packageOrUri) + { + $this->_isValid = 0; + if (isset($this->_packageInfo['channel'])) { + $this->_packageInfo = $this->_insertBefore($this->_packageInfo, array('phprelease', + 'extsrcrelease', 'extbinrelease', 'zendextsrcrelease', 'zendextbinrelease', + 'bundle', 'changelog'), + $packageOrUri, 'srcpackage'); + } else { + $this->_packageInfo = $this->_insertBefore($this->_packageInfo, array('phprelease', + 'extsrcrelease', 'extbinrelease', 'zendextsrcrelease', 'zendextbinrelease', + 'bundle', 'changelog'), $packageOrUri, 'srcuri'); + } + } + + /** + * Generate a valid change log entry from the current package.xml + * @param string|false + */ + function generateChangeLogEntry($notes = false) + { + return array( + 'version' => + array( + 'release' => $this->getVersion('release'), + 'api' => $this->getVersion('api'), + ), + 'stability' => + $this->getStability(), + 'date' => $this->getDate(), + 'license' => $this->getLicense(true), + 'notes' => $notes ? $notes : $this->getNotes() + ); + } + + /** + * @param string release version to set change log notes for + * @param array output of {@link generateChangeLogEntry()} + */ + function setChangelogEntry($releaseversion, $contents) + { + if (!isset($this->_packageInfo['changelog'])) { + $this->_packageInfo['changelog']['release'] = $contents; + return; + } + if (!isset($this->_packageInfo['changelog']['release'][0])) { + if ($this->_packageInfo['changelog']['release']['version']['release'] == $releaseversion) { + $this->_packageInfo['changelog']['release'] = array( + $this->_packageInfo['changelog']['release']); + } else { + $this->_packageInfo['changelog']['release'] = array( + $this->_packageInfo['changelog']['release']); + return $this->_packageInfo['changelog']['release'][] = $contents; + } + } + foreach($this->_packageInfo['changelog']['release'] as $index => $changelog) { + if (isset($changelog['version']) && + strnatcasecmp($changelog['version']['release'], $releaseversion) == 0) { + $curlog = $index; + } + } + if (isset($curlog)) { + $this->_packageInfo['changelog']['release'][$curlog] = $contents; + } else { + $this->_packageInfo['changelog']['release'][] = $contents; + } + } + + /** + * Remove the changelog entirely + */ + function clearChangeLog() + { + unset($this->_packageInfo['changelog']); + } +} \ No newline at end of file diff --git a/library/pear/PEAR/Packager.php b/library/pear/PEAR/Packager.php new file mode 100644 index 000000000..dcc3da417 --- /dev/null +++ b/library/pear/PEAR/Packager.php @@ -0,0 +1,201 @@ + + * @author Tomas V. V. Cox + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Packager.php 286809 2009-08-04 15:10:26Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 0.1 + */ + +/** + * base class + */ +require_once 'PEAR/Common.php'; +require_once 'PEAR/PackageFile.php'; +require_once 'System.php'; + +/** + * Administration class used to make a PEAR release tarball. + * + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.1 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 0.1 + */ +class PEAR_Packager extends PEAR_Common +{ + /** + * @var PEAR_Registry + */ + var $_registry; + + function package($pkgfile = null, $compress = true, $pkg2 = null) + { + // {{{ validate supplied package.xml file + if (empty($pkgfile)) { + $pkgfile = 'package.xml'; + } + + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $pkg = &new PEAR_PackageFile($this->config, $this->debug); + $pf = &$pkg->fromPackageFile($pkgfile, PEAR_VALIDATE_NORMAL); + $main = &$pf; + PEAR::staticPopErrorHandling(); + if (PEAR::isError($pf)) { + if (is_array($pf->getUserInfo())) { + foreach ($pf->getUserInfo() as $error) { + $this->log(0, 'Error: ' . $error['message']); + } + } + + $this->log(0, $pf->getMessage()); + return $this->raiseError("Cannot package, errors in package file"); + } + + foreach ($pf->getValidationWarnings() as $warning) { + $this->log(1, 'Warning: ' . $warning['message']); + } + + // }}} + if ($pkg2) { + $this->log(0, 'Attempting to process the second package file'); + PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); + $pf2 = &$pkg->fromPackageFile($pkg2, PEAR_VALIDATE_NORMAL); + PEAR::staticPopErrorHandling(); + if (PEAR::isError($pf2)) { + if (is_array($pf2->getUserInfo())) { + foreach ($pf2->getUserInfo() as $error) { + $this->log(0, 'Error: ' . $error['message']); + } + } + $this->log(0, $pf2->getMessage()); + return $this->raiseError("Cannot package, errors in second package file"); + } + + foreach ($pf2->getValidationWarnings() as $warning) { + $this->log(1, 'Warning: ' . $warning['message']); + } + + if ($pf2->getPackagexmlVersion() == '2.0' || + $pf2->getPackagexmlVersion() == '2.1' + ) { + $main = &$pf2; + $other = &$pf; + } else { + $main = &$pf; + $other = &$pf2; + } + + if ($main->getPackagexmlVersion() != '2.0' && + $main->getPackagexmlVersion() != '2.1') { + return PEAR::raiseError('Error: cannot package two package.xml version 1.0, can ' . + 'only package together a package.xml 1.0 and package.xml 2.0'); + } + + if ($other->getPackagexmlVersion() != '1.0') { + return PEAR::raiseError('Error: cannot package two package.xml version 2.0, can ' . + 'only package together a package.xml 1.0 and package.xml 2.0'); + } + } + + $main->setLogger($this); + if (!$main->validate(PEAR_VALIDATE_PACKAGING)) { + foreach ($main->getValidationWarnings() as $warning) { + $this->log(0, 'Error: ' . $warning['message']); + } + return $this->raiseError("Cannot package, errors in package"); + } + + foreach ($main->getValidationWarnings() as $warning) { + $this->log(1, 'Warning: ' . $warning['message']); + } + + if ($pkg2) { + $other->setLogger($this); + $a = false; + if (!$other->validate(PEAR_VALIDATE_NORMAL) || $a = !$main->isEquivalent($other)) { + foreach ($other->getValidationWarnings() as $warning) { + $this->log(0, 'Error: ' . $warning['message']); + } + + foreach ($main->getValidationWarnings() as $warning) { + $this->log(0, 'Error: ' . $warning['message']); + } + + if ($a) { + return $this->raiseError('The two package.xml files are not equivalent!'); + } + + return $this->raiseError("Cannot package, errors in package"); + } + + foreach ($other->getValidationWarnings() as $warning) { + $this->log(1, 'Warning: ' . $warning['message']); + } + + $gen = &$main->getDefaultGenerator(); + $tgzfile = $gen->toTgz2($this, $other, $compress); + if (PEAR::isError($tgzfile)) { + return $tgzfile; + } + + $dest_package = basename($tgzfile); + $pkgdir = dirname($pkgfile); + + // TAR the Package ------------------------------------------- + $this->log(1, "Package $dest_package done"); + if (file_exists("$pkgdir/CVS/Root")) { + $cvsversion = preg_replace('/[^a-z0-9]/i', '_', $pf->getVersion()); + $cvstag = "RELEASE_$cvsversion"; + $this->log(1, 'Tag the released code with "pear cvstag ' . + $main->getPackageFile() . '"'); + $this->log(1, "(or set the CVS tag $cvstag by hand)"); + } elseif (file_exists("$pkgdir/.svn")) { + $svnversion = preg_replace('/[^a-z0-9]/i', '.', $pf->getVersion()); + $svntag = $pf->getName() . "-$svnversion"; + $this->log(1, 'Tag the released code with "pear svntag ' . + $main->getPackageFile() . '"'); + $this->log(1, "(or set the SVN tag $svntag by hand)"); + } + } else { // this branch is executed for single packagefile packaging + $gen = &$pf->getDefaultGenerator(); + $tgzfile = $gen->toTgz($this, $compress); + if (PEAR::isError($tgzfile)) { + $this->log(0, $tgzfile->getMessage()); + return $this->raiseError("Cannot package, errors in package"); + } + + $dest_package = basename($tgzfile); + $pkgdir = dirname($pkgfile); + + // TAR the Package ------------------------------------------- + $this->log(1, "Package $dest_package done"); + if (file_exists("$pkgdir/CVS/Root")) { + $cvsversion = preg_replace('/[^a-z0-9]/i', '_', $pf->getVersion()); + $cvstag = "RELEASE_$cvsversion"; + $this->log(1, "Tag the released code with `pear cvstag $pkgfile'"); + $this->log(1, "(or set the CVS tag $cvstag by hand)"); + } elseif (file_exists("$pkgdir/.svn")) { + $svnversion = preg_replace('/[^a-z0-9]/i', '.', $pf->getVersion()); + $svntag = $pf->getName() . "-$svnversion"; + $this->log(1, "Tag the released code with `pear svntag $pkgfile'"); + $this->log(1, "(or set the SVN tag $svntag by hand)"); + } + } + + return $dest_package; + } +} \ No newline at end of file diff --git a/library/pear/PEAR/REST.php b/library/pear/PEAR/REST.php new file mode 100644 index 000000000..516d1bf5b --- /dev/null +++ b/library/pear/PEAR/REST.php @@ -0,0 +1,448 @@ + + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: REST.php 296767 2010-03-25 00:58:33Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ + +/** + * For downloading xml files + */ +require_once 'PEAR.php'; +require_once 'PEAR/XMLParser.php'; + +/** + * Intelligently retrieve data, following hyperlinks if necessary, and re-directing + * as well + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.1 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_REST +{ + var $config; + var $_options; + + function PEAR_REST(&$config, $options = array()) + { + $this->config = &$config; + $this->_options = $options; + } + + /** + * Retrieve REST data, but always retrieve the local cache if it is available. + * + * This is useful for elements that should never change, such as information on a particular + * release + * @param string full URL to this resource + * @param array|false contents of the accept-encoding header + * @param boolean if true, xml will be returned as a string, otherwise, xml will be + * parsed using PEAR_XMLParser + * @return string|array + */ + function retrieveCacheFirst($url, $accept = false, $forcestring = false, $channel = false) + { + $cachefile = $this->config->get('cache_dir') . DIRECTORY_SEPARATOR . + md5($url) . 'rest.cachefile'; + + if (file_exists($cachefile)) { + return unserialize(implode('', file($cachefile))); + } + + return $this->retrieveData($url, $accept, $forcestring, $channel); + } + + /** + * Retrieve a remote REST resource + * @param string full URL to this resource + * @param array|false contents of the accept-encoding header + * @param boolean if true, xml will be returned as a string, otherwise, xml will be + * parsed using PEAR_XMLParser + * @return string|array + */ + function retrieveData($url, $accept = false, $forcestring = false, $channel = false) + { + $cacheId = $this->getCacheId($url); + if ($ret = $this->useLocalCache($url, $cacheId)) { + return $ret; + } + + $file = $trieddownload = false; + if (!isset($this->_options['offline'])) { + $trieddownload = true; + $file = $this->downloadHttp($url, $cacheId ? $cacheId['lastChange'] : false, $accept, $channel); + } + + if (PEAR::isError($file)) { + if ($file->getCode() !== -9276) { + return $file; + } + + $trieddownload = false; + $file = false; // use local copy if available on socket connect error + } + + if (!$file) { + $ret = $this->getCache($url); + if (!PEAR::isError($ret) && $trieddownload) { + // reset the age of the cache if the server says it was unmodified + $this->saveCache($url, $ret, null, true, $cacheId); + } + + return $ret; + } + + if (is_array($file)) { + $headers = $file[2]; + $lastmodified = $file[1]; + $content = $file[0]; + } else { + $headers = array(); + $lastmodified = false; + $content = $file; + } + + if ($forcestring) { + $this->saveCache($url, $content, $lastmodified, false, $cacheId); + return $content; + } + + if (isset($headers['content-type'])) { + switch ($headers['content-type']) { + case 'text/xml' : + case 'application/xml' : + case 'text/plain' : + if ($headers['content-type'] === 'text/plain') { + $check = substr($content, 0, 5); + if ($check !== 'parse($content); + PEAR::popErrorHandling(); + if (PEAR::isError($err)) { + return PEAR::raiseError('Invalid xml downloaded from "' . $url . '": ' . + $err->getMessage()); + } + $content = $parser->getData(); + case 'text/html' : + default : + // use it as a string + } + } else { + // assume XML + $parser = new PEAR_XMLParser; + $parser->parse($content); + $content = $parser->getData(); + } + + $this->saveCache($url, $content, $lastmodified, false, $cacheId); + return $content; + } + + function useLocalCache($url, $cacheid = null) + { + if ($cacheid === null) { + $cacheidfile = $this->config->get('cache_dir') . DIRECTORY_SEPARATOR . + md5($url) . 'rest.cacheid'; + if (!file_exists($cacheidfile)) { + return false; + } + + $cacheid = unserialize(implode('', file($cacheidfile))); + } + + $cachettl = $this->config->get('cache_ttl'); + // If cache is newer than $cachettl seconds, we use the cache! + if (time() - $cacheid['age'] < $cachettl) { + return $this->getCache($url); + } + + return false; + } + + function getCacheId($url) + { + $cacheidfile = $this->config->get('cache_dir') . DIRECTORY_SEPARATOR . + md5($url) . 'rest.cacheid'; + + if (!file_exists($cacheidfile)) { + return false; + } + + $ret = unserialize(implode('', file($cacheidfile))); + return $ret; + } + + function getCache($url) + { + $cachefile = $this->config->get('cache_dir') . DIRECTORY_SEPARATOR . + md5($url) . 'rest.cachefile'; + + if (!file_exists($cachefile)) { + return PEAR::raiseError('No cached content available for "' . $url . '"'); + } + + return unserialize(implode('', file($cachefile))); + } + + /** + * @param string full URL to REST resource + * @param string original contents of the REST resource + * @param array HTTP Last-Modified and ETag headers + * @param bool if true, then the cache id file should be regenerated to + * trigger a new time-to-live value + */ + function saveCache($url, $contents, $lastmodified, $nochange = false, $cacheid = null) + { + $cachedir = $this->config->get('cache_dir') . DIRECTORY_SEPARATOR . md5($url); + $cacheidfile = $cachedir . 'rest.cacheid'; + $cachefile = $cachedir . 'rest.cachefile'; + + if ($cacheid === null && $nochange) { + $cacheid = unserialize(implode('', file($cacheidfile))); + } + + $fp = @fopen($cacheidfile, 'wb'); + if (!$fp) { + $cache_dir = $this->config->get('cache_dir'); + if (is_dir($cache_dir)) { + return false; + } + + System::mkdir(array('-p', $cache_dir)); + $fp = @fopen($cacheidfile, 'wb'); + if (!$fp) { + return false; + } + } + + if ($nochange) { + fwrite($fp, serialize(array( + 'age' => time(), + 'lastChange' => $cacheid['lastChange'], + )) + ); + + fclose($fp); + return true; + } + + fwrite($fp, serialize(array( + 'age' => time(), + 'lastChange' => $lastmodified, + )) + ); + + fclose($fp); + $fp = @fopen($cachefile, 'wb'); + if (!$fp) { + if (file_exists($cacheidfile)) { + @unlink($cacheidfile); + } + + return false; + } + + fwrite($fp, serialize($contents)); + fclose($fp); + return true; + } + + /** + * Efficiently Download a file through HTTP. Returns downloaded file as a string in-memory + * This is best used for small files + * + * If an HTTP proxy has been configured (http_proxy PEAR_Config + * setting), the proxy will be used. + * + * @param string $url the URL to download + * @param string $save_dir directory to save file in + * @param false|string|array $lastmodified header values to check against for caching + * use false to return the header values from this download + * @param false|array $accept Accept headers to send + * @return string|array Returns the contents of the downloaded file or a PEAR + * error on failure. If the error is caused by + * socket-related errors, the error object will + * have the fsockopen error code available through + * getCode(). If caching is requested, then return the header + * values. + * + * @access public + */ + function downloadHttp($url, $lastmodified = null, $accept = false, $channel = false) + { + static $redirect = 0; + // always reset , so we are clean case of error + $wasredirect = $redirect; + $redirect = 0; + + $info = parse_url($url); + if (!isset($info['scheme']) || !in_array($info['scheme'], array('http', 'https'))) { + return PEAR::raiseError('Cannot download non-http URL "' . $url . '"'); + } + + if (!isset($info['host'])) { + return PEAR::raiseError('Cannot download from non-URL "' . $url . '"'); + } + + $host = isset($info['host']) ? $info['host'] : null; + $port = isset($info['port']) ? $info['port'] : null; + $path = isset($info['path']) ? $info['path'] : null; + $schema = (isset($info['scheme']) && $info['scheme'] == 'https') ? 'https' : 'http'; + + $proxy_host = $proxy_port = $proxy_user = $proxy_pass = ''; + if ($this->config->get('http_proxy')&& + $proxy = parse_url($this->config->get('http_proxy')) + ) { + $proxy_host = isset($proxy['host']) ? $proxy['host'] : null; + if ($schema === 'https') { + $proxy_host = 'ssl://' . $proxy_host; + } + + $proxy_port = isset($proxy['port']) ? $proxy['port'] : 8080; + $proxy_user = isset($proxy['user']) ? urldecode($proxy['user']) : null; + $proxy_pass = isset($proxy['pass']) ? urldecode($proxy['pass']) : null; + $proxy_schema = (isset($proxy['scheme']) && $proxy['scheme'] == 'https') ? 'https' : 'http'; + } + + if (empty($port)) { + $port = (isset($info['scheme']) && $info['scheme'] == 'https') ? 443 : 80; + } + + if (isset($proxy['host'])) { + $request = "GET $url HTTP/1.1\r\n"; + } else { + $request = "GET $path HTTP/1.1\r\n"; + } + + $request .= "Host: $host\r\n"; + $ifmodifiedsince = ''; + if (is_array($lastmodified)) { + if (isset($lastmodified['Last-Modified'])) { + $ifmodifiedsince = 'If-Modified-Since: ' . $lastmodified['Last-Modified'] . "\r\n"; + } + + if (isset($lastmodified['ETag'])) { + $ifmodifiedsince .= "If-None-Match: $lastmodified[ETag]\r\n"; + } + } else { + $ifmodifiedsince = ($lastmodified ? "If-Modified-Since: $lastmodified\r\n" : ''); + } + + $request .= $ifmodifiedsince . + "User-Agent: PEAR/1.9.1/PHP/" . PHP_VERSION . "\r\n"; + + $username = $this->config->get('username', null, $channel); + $password = $this->config->get('password', null, $channel); + + if ($username && $password) { + $tmp = base64_encode("$username:$password"); + $request .= "Authorization: Basic $tmp\r\n"; + } + + if ($proxy_host != '' && $proxy_user != '') { + $request .= 'Proxy-Authorization: Basic ' . + base64_encode($proxy_user . ':' . $proxy_pass) . "\r\n"; + } + + if ($accept) { + $request .= 'Accept: ' . implode(', ', $accept) . "\r\n"; + } + + $request .= "Accept-Encoding:\r\n"; + $request .= "Connection: close\r\n"; + $request .= "\r\n"; + + if ($proxy_host != '') { + $fp = @fsockopen($proxy_host, $proxy_port, $errno, $errstr, 15); + if (!$fp) { + return PEAR::raiseError("Connection to `$proxy_host:$proxy_port' failed: $errstr", -9276); + } + } else { + if ($schema === 'https') { + $host = 'ssl://' . $host; + } + + $fp = @fsockopen($host, $port, $errno, $errstr); + if (!$fp) { + return PEAR::raiseError("Connection to `$host:$port' failed: $errstr", $errno); + } + } + + fwrite($fp, $request); + + $headers = array(); + $reply = 0; + while ($line = trim(fgets($fp, 1024))) { + if (preg_match('/^([^:]+):\s+(.*)\s*\\z/', $line, $matches)) { + $headers[strtolower($matches[1])] = trim($matches[2]); + } elseif (preg_match('|^HTTP/1.[01] ([0-9]{3}) |', $line, $matches)) { + $reply = (int)$matches[1]; + if ($reply == 304 && ($lastmodified || ($lastmodified === false))) { + return false; + } + + if (!in_array($reply, array(200, 301, 302, 303, 305, 307))) { + return PEAR::raiseError("File $schema://$host:$port$path not valid (received: $line)"); + } + } + } + + if ($reply != 200) { + if (!isset($headers['location'])) { + return PEAR::raiseError("File $schema://$host:$port$path not valid (redirected but no location)"); + } + + if ($wasredirect > 4) { + return PEAR::raiseError("File $schema://$host:$port$path not valid (redirection looped more than 5 times)"); + } + + $redirect = $wasredirect + 1; + return $this->downloadHttp($headers['location'], $lastmodified, $accept, $channel); + } + + $length = isset($headers['content-length']) ? $headers['content-length'] : -1; + + $data = ''; + while ($chunk = @fread($fp, 8192)) { + $data .= $chunk; + } + fclose($fp); + + if ($lastmodified === false || $lastmodified) { + if (isset($headers['etag'])) { + $lastmodified = array('ETag' => $headers['etag']); + } + + if (isset($headers['last-modified'])) { + if (is_array($lastmodified)) { + $lastmodified['Last-Modified'] = $headers['last-modified']; + } else { + $lastmodified = $headers['last-modified']; + } + } + + return array($data, $lastmodified, $headers); + } + + return $data; + } +} \ No newline at end of file diff --git a/library/pear/PEAR/REST/10.php b/library/pear/PEAR/REST/10.php new file mode 100644 index 000000000..b1b39412d --- /dev/null +++ b/library/pear/PEAR/REST/10.php @@ -0,0 +1,867 @@ + + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: 10.php 287558 2009-08-21 22:21:28Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a12 + */ + +/** + * For downloading REST xml/txt files + */ +require_once 'PEAR/REST.php'; + +/** + * Implement REST 1.0 + * + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.1 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a12 + */ +class PEAR_REST_10 +{ + /** + * @var PEAR_REST + */ + var $_rest; + function PEAR_REST_10($config, $options = array()) + { + $this->_rest = &new PEAR_REST($config, $options); + } + + /** + * Retrieve information about a remote package to be downloaded from a REST server + * + * @param string $base The uri to prepend to all REST calls + * @param array $packageinfo an array of format: + *
    +     *  array(
    +     *   'package' => 'packagename',
    +     *   'channel' => 'channelname',
    +     *  ['state' => 'alpha' (or valid state),]
    +     *  -or-
    +     *  ['version' => '1.whatever']
    +     * 
    + * @param string $prefstate Current preferred_state config variable value + * @param bool $installed the installed version of this package to compare against + * @return array|false|PEAR_Error see {@link _returnDownloadURL()} + */ + function getDownloadURL($base, $packageinfo, $prefstate, $installed, $channel = false) + { + $states = $this->betterStates($prefstate, true); + if (!$states) { + return PEAR::raiseError('"' . $prefstate . '" is not a valid state'); + } + + $channel = $packageinfo['channel']; + $package = $packageinfo['package']; + $state = isset($packageinfo['state']) ? $packageinfo['state'] : null; + $version = isset($packageinfo['version']) ? $packageinfo['version'] : null; + $restFile = $base . 'r/' . strtolower($package) . '/allreleases.xml'; + + $info = $this->_rest->retrieveData($restFile, false, false, $channel); + if (PEAR::isError($info)) { + return PEAR::raiseError('No releases available for package "' . + $channel . '/' . $package . '"'); + } + + if (!isset($info['r'])) { + return false; + } + + $release = $found = false; + if (!is_array($info['r']) || !isset($info['r'][0])) { + $info['r'] = array($info['r']); + } + + foreach ($info['r'] as $release) { + if (!isset($this->_rest->_options['force']) && ($installed && + version_compare($release['v'], $installed, '<'))) { + continue; + } + + if (isset($state)) { + // try our preferred state first + if ($release['s'] == $state) { + $found = true; + break; + } + // see if there is something newer and more stable + // bug #7221 + if (in_array($release['s'], $this->betterStates($state), true)) { + $found = true; + break; + } + } elseif (isset($version)) { + if ($release['v'] == $version) { + $found = true; + break; + } + } else { + if (in_array($release['s'], $states)) { + $found = true; + break; + } + } + } + + return $this->_returnDownloadURL($base, $package, $release, $info, $found, false, $channel); + } + + function getDepDownloadURL($base, $xsdversion, $dependency, $deppackage, + $prefstate = 'stable', $installed = false, $channel = false) + { + $states = $this->betterStates($prefstate, true); + if (!$states) { + return PEAR::raiseError('"' . $prefstate . '" is not a valid state'); + } + + $channel = $dependency['channel']; + $package = $dependency['name']; + $state = isset($dependency['state']) ? $dependency['state'] : null; + $version = isset($dependency['version']) ? $dependency['version'] : null; + $restFile = $base . 'r/' . strtolower($package) . '/allreleases.xml'; + + $info = $this->_rest->retrieveData($restFile, false, false, $channel); + if (PEAR::isError($info)) { + return PEAR::raiseError('Package "' . $deppackage['channel'] . '/' . $deppackage['package'] + . '" dependency "' . $channel . '/' . $package . '" has no releases'); + } + + if (!is_array($info) || !isset($info['r'])) { + return false; + } + + $exclude = array(); + $min = $max = $recommended = false; + if ($xsdversion == '1.0') { + switch ($dependency['rel']) { + case 'ge' : + $min = $dependency['version']; + break; + case 'gt' : + $min = $dependency['version']; + $exclude = array($dependency['version']); + break; + case 'eq' : + $recommended = $dependency['version']; + break; + case 'lt' : + $max = $dependency['version']; + $exclude = array($dependency['version']); + break; + case 'le' : + $max = $dependency['version']; + break; + case 'ne' : + $exclude = array($dependency['version']); + break; + } + } else { + $min = isset($dependency['min']) ? $dependency['min'] : false; + $max = isset($dependency['max']) ? $dependency['max'] : false; + $recommended = isset($dependency['recommended']) ? + $dependency['recommended'] : false; + if (isset($dependency['exclude'])) { + if (!isset($dependency['exclude'][0])) { + $exclude = array($dependency['exclude']); + } + } + } + $release = $found = false; + if (!is_array($info['r']) || !isset($info['r'][0])) { + $info['r'] = array($info['r']); + } + foreach ($info['r'] as $release) { + if (!isset($this->_rest->_options['force']) && ($installed && + version_compare($release['v'], $installed, '<'))) { + continue; + } + if (in_array($release['v'], $exclude)) { // skip excluded versions + continue; + } + // allow newer releases to say "I'm OK with the dependent package" + if ($xsdversion == '2.0' && isset($release['co'])) { + if (!is_array($release['co']) || !isset($release['co'][0])) { + $release['co'] = array($release['co']); + } + foreach ($release['co'] as $entry) { + if (isset($entry['x']) && !is_array($entry['x'])) { + $entry['x'] = array($entry['x']); + } elseif (!isset($entry['x'])) { + $entry['x'] = array(); + } + if ($entry['c'] == $deppackage['channel'] && + strtolower($entry['p']) == strtolower($deppackage['package']) && + version_compare($deppackage['version'], $entry['min'], '>=') && + version_compare($deppackage['version'], $entry['max'], '<=') && + !in_array($release['v'], $entry['x'])) { + $recommended = $release['v']; + break; + } + } + } + if ($recommended) { + if ($release['v'] != $recommended) { // if we want a specific + // version, then skip all others + continue; + } else { + if (!in_array($release['s'], $states)) { + // the stability is too low, but we must return the + // recommended version if possible + return $this->_returnDownloadURL($base, $package, $release, $info, true, false, $channel); + } + } + } + if ($min && version_compare($release['v'], $min, 'lt')) { // skip too old versions + continue; + } + if ($max && version_compare($release['v'], $max, 'gt')) { // skip too new versions + continue; + } + if ($installed && version_compare($release['v'], $installed, '<')) { + continue; + } + if (in_array($release['s'], $states)) { // if in the preferred state... + $found = true; // ... then use it + break; + } + } + return $this->_returnDownloadURL($base, $package, $release, $info, $found, false, $channel); + } + + /** + * Take raw data and return the array needed for processing a download URL + * + * @param string $base REST base uri + * @param string $package Package name + * @param array $release an array of format array('v' => version, 's' => state) + * describing the release to download + * @param array $info list of all releases as defined by allreleases.xml + * @param bool|null $found determines whether the release was found or this is the next + * best alternative. If null, then versions were skipped because + * of PHP dependency + * @return array|PEAR_Error + * @access private + */ + function _returnDownloadURL($base, $package, $release, $info, $found, $phpversion = false, $channel = false) + { + if (!$found) { + $release = $info['r'][0]; + } + + $packageLower = strtolower($package); + $pinfo = $this->_rest->retrieveCacheFirst($base . 'p/' . $packageLower . '/' . + 'info.xml', false, false, $channel); + if (PEAR::isError($pinfo)) { + return PEAR::raiseError('Package "' . $package . + '" does not have REST info xml available'); + } + + $releaseinfo = $this->_rest->retrieveCacheFirst($base . 'r/' . $packageLower . '/' . + $release['v'] . '.xml', false, false, $channel); + if (PEAR::isError($releaseinfo)) { + return PEAR::raiseError('Package "' . $package . '" Version "' . $release['v'] . + '" does not have REST xml available'); + } + + $packagexml = $this->_rest->retrieveCacheFirst($base . 'r/' . $packageLower . '/' . + 'deps.' . $release['v'] . '.txt', false, true, $channel); + if (PEAR::isError($packagexml)) { + return PEAR::raiseError('Package "' . $package . '" Version "' . $release['v'] . + '" does not have REST dependency information available'); + } + + $packagexml = unserialize($packagexml); + if (!$packagexml) { + $packagexml = array(); + } + + $allinfo = $this->_rest->retrieveData($base . 'r/' . $packageLower . + '/allreleases.xml', false, false, $channel); + if (!is_array($allinfo['r']) || !isset($allinfo['r'][0])) { + $allinfo['r'] = array($allinfo['r']); + } + + $compatible = false; + foreach ($allinfo['r'] as $release) { + if ($release['v'] != $releaseinfo['v']) { + continue; + } + + if (!isset($release['co'])) { + break; + } + + $compatible = array(); + if (!is_array($release['co']) || !isset($release['co'][0])) { + $release['co'] = array($release['co']); + } + + foreach ($release['co'] as $entry) { + $comp = array(); + $comp['name'] = $entry['p']; + $comp['channel'] = $entry['c']; + $comp['min'] = $entry['min']; + $comp['max'] = $entry['max']; + if (isset($entry['x']) && !is_array($entry['x'])) { + $comp['exclude'] = $entry['x']; + } + + $compatible[] = $comp; + } + + if (count($compatible) == 1) { + $compatible = $compatible[0]; + } + + break; + } + + $deprecated = false; + if (isset($pinfo['dc']) && isset($pinfo['dp'])) { + if (is_array($pinfo['dp'])) { + $deprecated = array('channel' => (string) $pinfo['dc'], + 'package' => trim($pinfo['dp']['_content'])); + } else { + $deprecated = array('channel' => (string) $pinfo['dc'], + 'package' => trim($pinfo['dp'])); + } + } + + $return = array( + 'version' => $releaseinfo['v'], + 'info' => $packagexml, + 'package' => $releaseinfo['p']['_content'], + 'stability' => $releaseinfo['st'], + 'compatible' => $compatible, + 'deprecated' => $deprecated, + ); + + if ($found) { + $return['url'] = $releaseinfo['g']; + return $return; + } + + $return['php'] = $phpversion; + return $return; + } + + function listPackages($base, $channel = false) + { + $packagelist = $this->_rest->retrieveData($base . 'p/packages.xml', false, false, $channel); + if (PEAR::isError($packagelist)) { + return $packagelist; + } + + if (!is_array($packagelist) || !isset($packagelist['p'])) { + return array(); + } + + if (!is_array($packagelist['p'])) { + $packagelist['p'] = array($packagelist['p']); + } + + return $packagelist['p']; + } + + /** + * List all categories of a REST server + * + * @param string $base base URL of the server + * @return array of categorynames + */ + function listCategories($base, $channel = false) + { + $categories = array(); + + // c/categories.xml does not exist; + // check for every package its category manually + // This is SLOOOWWWW : /// + $packagelist = $this->_rest->retrieveData($base . 'p/packages.xml', false, false, $channel); + if (PEAR::isError($packagelist)) { + return $packagelist; + } + + if (!is_array($packagelist) || !isset($packagelist['p'])) { + $ret = array(); + return $ret; + } + + if (!is_array($packagelist['p'])) { + $packagelist['p'] = array($packagelist['p']); + } + + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + foreach ($packagelist['p'] as $package) { + $inf = $this->_rest->retrieveData($base . 'p/' . strtolower($package) . '/info.xml', false, false, $channel); + if (PEAR::isError($inf)) { + PEAR::popErrorHandling(); + return $inf; + } + $cat = $inf['ca']['_content']; + if (!isset($categories[$cat])) { + $categories[$cat] = $inf['ca']; + } + } + + return array_values($categories); + } + + /** + * List a category of a REST server + * + * @param string $base base URL of the server + * @param string $category name of the category + * @param boolean $info also download full package info + * @return array of packagenames + */ + function listCategory($base, $category, $info = false, $channel = false) + { + // gives '404 Not Found' error when category doesn't exist + $packagelist = $this->_rest->retrieveData($base.'c/'.urlencode($category).'/packages.xml', false, false, $channel); + if (PEAR::isError($packagelist)) { + return $packagelist; + } + + if (!is_array($packagelist) || !isset($packagelist['p'])) { + return array(); + } + + if (!is_array($packagelist['p']) || + !isset($packagelist['p'][0])) { // only 1 pkg + $packagelist = array($packagelist['p']); + } else { + $packagelist = $packagelist['p']; + } + + if ($info == true) { + // get individual package info + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + foreach ($packagelist as $i => $packageitem) { + $url = sprintf('%s'.'r/%s/latest.txt', + $base, + strtolower($packageitem['_content'])); + $version = $this->_rest->retrieveData($url, false, false, $channel); + if (PEAR::isError($version)) { + break; // skipit + } + $url = sprintf('%s'.'r/%s/%s.xml', + $base, + strtolower($packageitem['_content']), + $version); + $info = $this->_rest->retrieveData($url, false, false, $channel); + if (PEAR::isError($info)) { + break; // skipit + } + $packagelist[$i]['info'] = $info; + } + PEAR::popErrorHandling(); + } + + return $packagelist; + } + + + function listAll($base, $dostable, $basic = true, $searchpackage = false, $searchsummary = false, $channel = false) + { + $packagelist = $this->_rest->retrieveData($base . 'p/packages.xml', false, false, $channel); + if (PEAR::isError($packagelist)) { + return $packagelist; + } + if ($this->_rest->config->get('verbose') > 0) { + $ui = &PEAR_Frontend::singleton(); + $ui->log('Retrieving data...0%', true); + } + $ret = array(); + if (!is_array($packagelist) || !isset($packagelist['p'])) { + return $ret; + } + if (!is_array($packagelist['p'])) { + $packagelist['p'] = array($packagelist['p']); + } + + // only search-packagename = quicksearch ! + if ($searchpackage && (!$searchsummary || empty($searchpackage))) { + $newpackagelist = array(); + foreach ($packagelist['p'] as $package) { + if (!empty($searchpackage) && stristr($package, $searchpackage) !== false) { + $newpackagelist[] = $package; + } + } + $packagelist['p'] = $newpackagelist; + } + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $next = .1; + foreach ($packagelist['p'] as $progress => $package) { + if ($this->_rest->config->get('verbose') > 0) { + if ($progress / count($packagelist['p']) >= $next) { + if ($next == .5) { + $ui->log('50%', false); + } else { + $ui->log('.', false); + } + $next += .1; + } + } + + if ($basic) { // remote-list command + if ($dostable) { + $latest = $this->_rest->retrieveData($base . 'r/' . strtolower($package) . + '/stable.txt', false, false, $channel); + } else { + $latest = $this->_rest->retrieveData($base . 'r/' . strtolower($package) . + '/latest.txt', false, false, $channel); + } + if (PEAR::isError($latest)) { + $latest = false; + } + $info = array('stable' => $latest); + } else { // list-all command + $inf = $this->_rest->retrieveData($base . 'p/' . strtolower($package) . '/info.xml', false, false, $channel); + if (PEAR::isError($inf)) { + PEAR::popErrorHandling(); + return $inf; + } + if ($searchpackage) { + $found = (!empty($searchpackage) && stristr($package, $searchpackage) !== false); + if (!$found && !(isset($searchsummary) && !empty($searchsummary) + && (stristr($inf['s'], $searchsummary) !== false + || stristr($inf['d'], $searchsummary) !== false))) + { + continue; + }; + } + $releases = $this->_rest->retrieveData($base . 'r/' . strtolower($package) . + '/allreleases.xml', false, false, $channel); + if (PEAR::isError($releases)) { + continue; + } + if (!isset($releases['r'][0])) { + $releases['r'] = array($releases['r']); + } + unset($latest); + unset($unstable); + unset($stable); + unset($state); + foreach ($releases['r'] as $release) { + if (!isset($latest)) { + if ($dostable && $release['s'] == 'stable') { + $latest = $release['v']; + $state = 'stable'; + } + if (!$dostable) { + $latest = $release['v']; + $state = $release['s']; + } + } + if (!isset($stable) && $release['s'] == 'stable') { + $stable = $release['v']; + if (!isset($unstable)) { + $unstable = $stable; + } + } + if (!isset($unstable) && $release['s'] != 'stable') { + $latest = $unstable = $release['v']; + $state = $release['s']; + } + if (isset($latest) && !isset($state)) { + $state = $release['s']; + } + if (isset($latest) && isset($stable) && isset($unstable)) { + break; + } + } + $deps = array(); + if (!isset($unstable)) { + $unstable = false; + $state = 'stable'; + if (isset($stable)) { + $latest = $unstable = $stable; + } + } else { + $latest = $unstable; + } + if (!isset($latest)) { + $latest = false; + } + if ($latest) { + $d = $this->_rest->retrieveCacheFirst($base . 'r/' . strtolower($package) . '/deps.' . + $latest . '.txt', false, false, $channel); + if (!PEAR::isError($d)) { + $d = unserialize($d); + if ($d) { + if (isset($d['required'])) { + if (!class_exists('PEAR_PackageFile_v2')) { + require_once 'PEAR/PackageFile/v2.php'; + } + if (!isset($pf)) { + $pf = new PEAR_PackageFile_v2; + } + $pf->setDeps($d); + $tdeps = $pf->getDeps(); + } else { + $tdeps = $d; + } + foreach ($tdeps as $dep) { + if ($dep['type'] !== 'pkg') { + continue; + } + $deps[] = $dep; + } + } + } + } + if (!isset($stable)) { + $stable = '-n/a-'; + } + if (!$searchpackage) { + $info = array('stable' => $latest, 'summary' => $inf['s'], 'description' => + $inf['d'], 'deps' => $deps, 'category' => $inf['ca']['_content'], + 'unstable' => $unstable, 'state' => $state); + } else { + $info = array('stable' => $stable, 'summary' => $inf['s'], 'description' => + $inf['d'], 'deps' => $deps, 'category' => $inf['ca']['_content'], + 'unstable' => $unstable, 'state' => $state); + } + } + $ret[$package] = $info; + } + PEAR::popErrorHandling(); + return $ret; + } + + function listLatestUpgrades($base, $pref_state, $installed, $channel, &$reg) + { + $packagelist = $this->_rest->retrieveData($base . 'p/packages.xml', false, false, $channel); + if (PEAR::isError($packagelist)) { + return $packagelist; + } + + $ret = array(); + if (!is_array($packagelist) || !isset($packagelist['p'])) { + return $ret; + } + + if (!is_array($packagelist['p'])) { + $packagelist['p'] = array($packagelist['p']); + } + + foreach ($packagelist['p'] as $package) { + if (!isset($installed[strtolower($package)])) { + continue; + } + + $inst_version = $reg->packageInfo($package, 'version', $channel); + $inst_state = $reg->packageInfo($package, 'release_state', $channel); + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $info = $this->_rest->retrieveData($base . 'r/' . strtolower($package) . + '/allreleases.xml', false, false, $channel); + PEAR::popErrorHandling(); + if (PEAR::isError($info)) { + continue; // no remote releases + } + + if (!isset($info['r'])) { + continue; + } + + $release = $found = false; + if (!is_array($info['r']) || !isset($info['r'][0])) { + $info['r'] = array($info['r']); + } + + // $info['r'] is sorted by version number + usort($info['r'], array($this, '_sortReleasesByVersionNumber')); + foreach ($info['r'] as $release) { + if ($inst_version && version_compare($release['v'], $inst_version, '<=')) { + // not newer than the one installed + break; + } + + // new version > installed version + if (!$pref_state) { + // every state is a good state + $found = true; + break; + } else { + $new_state = $release['s']; + // if new state >= installed state: go + if (in_array($new_state, $this->betterStates($inst_state, true))) { + $found = true; + break; + } else { + // only allow to lower the state of package, + // if new state >= preferred state: go + if (in_array($new_state, $this->betterStates($pref_state, true))) { + $found = true; + break; + } + } + } + } + + if (!$found) { + continue; + } + + $relinfo = $this->_rest->retrieveCacheFirst($base . 'r/' . strtolower($package) . '/' . + $release['v'] . '.xml', false, false, $channel); + if (PEAR::isError($relinfo)) { + return $relinfo; + } + + $ret[$package] = array( + 'version' => $release['v'], + 'state' => $release['s'], + 'filesize' => $relinfo['f'], + ); + } + + return $ret; + } + + function packageInfo($base, $package, $channel = false) + { + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $pinfo = $this->_rest->retrieveData($base . 'p/' . strtolower($package) . '/info.xml', false, false, $channel); + if (PEAR::isError($pinfo)) { + PEAR::popErrorHandling(); + return PEAR::raiseError('Unknown package: "' . $package . '" in channel "' . $channel . '"' . "\n". 'Debug: ' . + $pinfo->getMessage()); + } + + $releases = array(); + $allreleases = $this->_rest->retrieveData($base . 'r/' . strtolower($package) . + '/allreleases.xml', false, false, $channel); + if (!PEAR::isError($allreleases)) { + if (!class_exists('PEAR_PackageFile_v2')) { + require_once 'PEAR/PackageFile/v2.php'; + } + + if (!is_array($allreleases['r']) || !isset($allreleases['r'][0])) { + $allreleases['r'] = array($allreleases['r']); + } + + $pf = new PEAR_PackageFile_v2; + foreach ($allreleases['r'] as $release) { + $ds = $this->_rest->retrieveCacheFirst($base . 'r/' . strtolower($package) . '/deps.' . + $release['v'] . '.txt', false, false, $channel); + if (PEAR::isError($ds)) { + continue; + } + + if (!isset($latest)) { + $latest = $release['v']; + } + + $pf->setDeps(unserialize($ds)); + $ds = $pf->getDeps(); + $info = $this->_rest->retrieveCacheFirst($base . 'r/' . strtolower($package) + . '/' . $release['v'] . '.xml', false, false, $channel); + + if (PEAR::isError($info)) { + continue; + } + + $releases[$release['v']] = array( + 'doneby' => $info['m'], + 'license' => $info['l'], + 'summary' => $info['s'], + 'description' => $info['d'], + 'releasedate' => $info['da'], + 'releasenotes' => $info['n'], + 'state' => $release['s'], + 'deps' => $ds ? $ds : array(), + ); + } + } else { + $latest = ''; + } + + PEAR::popErrorHandling(); + if (isset($pinfo['dc']) && isset($pinfo['dp'])) { + if (is_array($pinfo['dp'])) { + $deprecated = array('channel' => (string) $pinfo['dc'], + 'package' => trim($pinfo['dp']['_content'])); + } else { + $deprecated = array('channel' => (string) $pinfo['dc'], + 'package' => trim($pinfo['dp'])); + } + } else { + $deprecated = false; + } + + if (!isset($latest)) { + $latest = ''; + } + + return array( + 'name' => $pinfo['n'], + 'channel' => $pinfo['c'], + 'category' => $pinfo['ca']['_content'], + 'stable' => $latest, + 'license' => $pinfo['l'], + 'summary' => $pinfo['s'], + 'description' => $pinfo['d'], + 'releases' => $releases, + 'deprecated' => $deprecated, + ); + } + + /** + * Return an array containing all of the states that are more stable than + * or equal to the passed in state + * + * @param string Release state + * @param boolean Determines whether to include $state in the list + * @return false|array False if $state is not a valid release state + */ + function betterStates($state, $include = false) + { + static $states = array('snapshot', 'devel', 'alpha', 'beta', 'stable'); + $i = array_search($state, $states); + if ($i === false) { + return false; + } + + if ($include) { + $i--; + } + + return array_slice($states, $i + 1); + } + + /** + * Sort releases by version number + * + * @access private + */ + function _sortReleasesByVersionNumber($a, $b) + { + if (version_compare($a['v'], $b['v'], '=')) { + return 0; + } + + if (version_compare($a['v'], $b['v'], '>')) { + return -1; + } + + if (version_compare($a['v'], $b['v'], '<')) { + return 1; + } + } +} \ No newline at end of file diff --git a/library/pear/PEAR/REST/11.php b/library/pear/PEAR/REST/11.php new file mode 100644 index 000000000..19910e633 --- /dev/null +++ b/library/pear/PEAR/REST/11.php @@ -0,0 +1,341 @@ + + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: 11.php 286670 2009-08-02 14:16:06Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.3 + */ + +/** + * For downloading REST xml/txt files + */ +require_once 'PEAR/REST.php'; + +/** + * Implement REST 1.1 + * + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.1 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.3 + */ +class PEAR_REST_11 +{ + /** + * @var PEAR_REST + */ + var $_rest; + + function PEAR_REST_11($config, $options = array()) + { + $this->_rest = &new PEAR_REST($config, $options); + } + + function listAll($base, $dostable, $basic = true, $searchpackage = false, $searchsummary = false, $channel = false) + { + $categorylist = $this->_rest->retrieveData($base . 'c/categories.xml', false, false, $channel); + if (PEAR::isError($categorylist)) { + return $categorylist; + } + + $ret = array(); + if (!is_array($categorylist['c']) || !isset($categorylist['c'][0])) { + $categorylist['c'] = array($categorylist['c']); + } + + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + + foreach ($categorylist['c'] as $progress => $category) { + $category = $category['_content']; + $packagesinfo = $this->_rest->retrieveData($base . + 'c/' . urlencode($category) . '/packagesinfo.xml', false, false, $channel); + + if (PEAR::isError($packagesinfo)) { + continue; + } + + if (!is_array($packagesinfo) || !isset($packagesinfo['pi'])) { + continue; + } + + if (!is_array($packagesinfo['pi']) || !isset($packagesinfo['pi'][0])) { + $packagesinfo['pi'] = array($packagesinfo['pi']); + } + + foreach ($packagesinfo['pi'] as $packageinfo) { + if (empty($packageinfo)) { + continue; + } + + $info = $packageinfo['p']; + $package = $info['n']; + $releases = isset($packageinfo['a']) ? $packageinfo['a'] : false; + unset($latest); + unset($unstable); + unset($stable); + unset($state); + + if ($releases) { + if (!isset($releases['r'][0])) { + $releases['r'] = array($releases['r']); + } + + foreach ($releases['r'] as $release) { + if (!isset($latest)) { + if ($dostable && $release['s'] == 'stable') { + $latest = $release['v']; + $state = 'stable'; + } + if (!$dostable) { + $latest = $release['v']; + $state = $release['s']; + } + } + + if (!isset($stable) && $release['s'] == 'stable') { + $stable = $release['v']; + if (!isset($unstable)) { + $unstable = $stable; + } + } + + if (!isset($unstable) && $release['s'] != 'stable') { + $unstable = $release['v']; + $state = $release['s']; + } + + if (isset($latest) && !isset($state)) { + $state = $release['s']; + } + + if (isset($latest) && isset($stable) && isset($unstable)) { + break; + } + } + } + + if ($basic) { // remote-list command + if (!isset($latest)) { + $latest = false; + } + + if ($dostable) { + // $state is not set if there are no releases + if (isset($state) && $state == 'stable') { + $ret[$package] = array('stable' => $latest); + } else { + $ret[$package] = array('stable' => '-n/a-'); + } + } else { + $ret[$package] = array('stable' => $latest); + } + + continue; + } + + // list-all command + if (!isset($unstable)) { + $unstable = false; + $state = 'stable'; + if (isset($stable)) { + $latest = $unstable = $stable; + } + } else { + $latest = $unstable; + } + + if (!isset($latest)) { + $latest = false; + } + + $deps = array(); + if ($latest && isset($packageinfo['deps'])) { + if (!is_array($packageinfo['deps']) || + !isset($packageinfo['deps'][0]) + ) { + $packageinfo['deps'] = array($packageinfo['deps']); + } + + $d = false; + foreach ($packageinfo['deps'] as $dep) { + if ($dep['v'] == $latest) { + $d = unserialize($dep['d']); + } + } + + if ($d) { + if (isset($d['required'])) { + if (!class_exists('PEAR_PackageFile_v2')) { + require_once 'PEAR/PackageFile/v2.php'; + } + + if (!isset($pf)) { + $pf = new PEAR_PackageFile_v2; + } + + $pf->setDeps($d); + $tdeps = $pf->getDeps(); + } else { + $tdeps = $d; + } + + foreach ($tdeps as $dep) { + if ($dep['type'] !== 'pkg') { + continue; + } + + $deps[] = $dep; + } + } + } + + $info = array( + 'stable' => $latest, + 'summary' => $info['s'], + 'description' => $info['d'], + 'deps' => $deps, + 'category' => $info['ca']['_content'], + 'unstable' => $unstable, + 'state' => $state + ); + $ret[$package] = $info; + } + } + + PEAR::popErrorHandling(); + return $ret; + } + + /** + * List all categories of a REST server + * + * @param string $base base URL of the server + * @return array of categorynames + */ + function listCategories($base, $channel = false) + { + $categorylist = $this->_rest->retrieveData($base . 'c/categories.xml', false, false, $channel); + if (PEAR::isError($categorylist)) { + return $categorylist; + } + + if (!is_array($categorylist) || !isset($categorylist['c'])) { + return array(); + } + + if (isset($categorylist['c']['_content'])) { + // only 1 category + $categorylist['c'] = array($categorylist['c']); + } + + return $categorylist['c']; + } + + /** + * List packages in a category of a REST server + * + * @param string $base base URL of the server + * @param string $category name of the category + * @param boolean $info also download full package info + * @return array of packagenames + */ + function listCategory($base, $category, $info = false, $channel = false) + { + if ($info == false) { + $url = '%s'.'c/%s/packages.xml'; + } else { + $url = '%s'.'c/%s/packagesinfo.xml'; + } + $url = sprintf($url, + $base, + urlencode($category)); + + // gives '404 Not Found' error when category doesn't exist + $packagelist = $this->_rest->retrieveData($url, false, false, $channel); + if (PEAR::isError($packagelist)) { + return $packagelist; + } + if (!is_array($packagelist)) { + return array(); + } + + if ($info == false) { + if (!isset($packagelist['p'])) { + return array(); + } + if (!is_array($packagelist['p']) || + !isset($packagelist['p'][0])) { // only 1 pkg + $packagelist = array($packagelist['p']); + } else { + $packagelist = $packagelist['p']; + } + return $packagelist; + } + + // info == true + if (!isset($packagelist['pi'])) { + return array(); + } + + if (!is_array($packagelist['pi']) || + !isset($packagelist['pi'][0])) { // only 1 pkg + $packagelist_pre = array($packagelist['pi']); + } else { + $packagelist_pre = $packagelist['pi']; + } + + $packagelist = array(); + foreach ($packagelist_pre as $i => $item) { + // compatibility with r/.xml + if (isset($item['a']['r'][0])) { + // multiple releases + $item['p']['v'] = $item['a']['r'][0]['v']; + $item['p']['st'] = $item['a']['r'][0]['s']; + } elseif (isset($item['a'])) { + // first and only release + $item['p']['v'] = $item['a']['r']['v']; + $item['p']['st'] = $item['a']['r']['s']; + } + + $packagelist[$i] = array('attribs' => $item['p']['r'], + '_content' => $item['p']['n'], + 'info' => $item['p']); + } + + return $packagelist; + } + + /** + * Return an array containing all of the states that are more stable than + * or equal to the passed in state + * + * @param string Release state + * @param boolean Determines whether to include $state in the list + * @return false|array False if $state is not a valid release state + */ + function betterStates($state, $include = false) + { + static $states = array('snapshot', 'devel', 'alpha', 'beta', 'stable'); + $i = array_search($state, $states); + if ($i === false) { + return false; + } + if ($include) { + $i--; + } + return array_slice($states, $i + 1); + } +} +?> \ No newline at end of file diff --git a/library/pear/PEAR/REST/13.php b/library/pear/PEAR/REST/13.php new file mode 100644 index 000000000..b6e6a86b5 --- /dev/null +++ b/library/pear/PEAR/REST/13.php @@ -0,0 +1,299 @@ + + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: 13.php 287110 2009-08-11 18:51:15Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a12 + */ + +/** + * For downloading REST xml/txt files + */ +require_once 'PEAR/REST.php'; +require_once 'PEAR/REST/10.php'; + +/** + * Implement REST 1.3 + * + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.1 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a12 + */ +class PEAR_REST_13 extends PEAR_REST_10 +{ + /** + * Retrieve information about a remote package to be downloaded from a REST server + * + * This is smart enough to resolve the minimum PHP version dependency prior to download + * @param string $base The uri to prepend to all REST calls + * @param array $packageinfo an array of format: + *
    +     *  array(
    +     *   'package' => 'packagename',
    +     *   'channel' => 'channelname',
    +     *  ['state' => 'alpha' (or valid state),]
    +     *  -or-
    +     *  ['version' => '1.whatever']
    +     * 
    + * @param string $prefstate Current preferred_state config variable value + * @param bool $installed the installed version of this package to compare against + * @return array|false|PEAR_Error see {@link _returnDownloadURL()} + */ + function getDownloadURL($base, $packageinfo, $prefstate, $installed, $channel = false) + { + $states = $this->betterStates($prefstate, true); + if (!$states) { + return PEAR::raiseError('"' . $prefstate . '" is not a valid state'); + } + + $channel = $packageinfo['channel']; + $package = $packageinfo['package']; + $state = isset($packageinfo['state']) ? $packageinfo['state'] : null; + $version = isset($packageinfo['version']) ? $packageinfo['version'] : null; + $restFile = $base . 'r/' . strtolower($package) . '/allreleases2.xml'; + + $info = $this->_rest->retrieveData($restFile, false, false, $channel); + if (PEAR::isError($info)) { + return PEAR::raiseError('No releases available for package "' . + $channel . '/' . $package . '"'); + } + + if (!isset($info['r'])) { + return false; + } + + $release = $found = false; + if (!is_array($info['r']) || !isset($info['r'][0])) { + $info['r'] = array($info['r']); + } + + $skippedphp = false; + foreach ($info['r'] as $release) { + if (!isset($this->_rest->_options['force']) && ($installed && + version_compare($release['v'], $installed, '<'))) { + continue; + } + + if (isset($state)) { + // try our preferred state first + if ($release['s'] == $state) { + if (!isset($version) && version_compare($release['m'], phpversion(), '>')) { + // skip releases that require a PHP version newer than our PHP version + $skippedphp = $release; + continue; + } + $found = true; + break; + } + + // see if there is something newer and more stable + // bug #7221 + if (in_array($release['s'], $this->betterStates($state), true)) { + if (!isset($version) && version_compare($release['m'], phpversion(), '>')) { + // skip releases that require a PHP version newer than our PHP version + $skippedphp = $release; + continue; + } + $found = true; + break; + } + } elseif (isset($version)) { + if ($release['v'] == $version) { + if (!isset($this->_rest->_options['force']) && + !isset($version) && + version_compare($release['m'], phpversion(), '>')) { + // skip releases that require a PHP version newer than our PHP version + $skippedphp = $release; + continue; + } + $found = true; + break; + } + } else { + if (in_array($release['s'], $states)) { + if (version_compare($release['m'], phpversion(), '>')) { + // skip releases that require a PHP version newer than our PHP version + $skippedphp = $release; + continue; + } + $found = true; + break; + } + } + } + + if (!$found && $skippedphp) { + $found = null; + } + + return $this->_returnDownloadURL($base, $package, $release, $info, $found, $skippedphp, $channel); + } + + function getDepDownloadURL($base, $xsdversion, $dependency, $deppackage, + $prefstate = 'stable', $installed = false, $channel = false) + { + $states = $this->betterStates($prefstate, true); + if (!$states) { + return PEAR::raiseError('"' . $prefstate . '" is not a valid state'); + } + + $channel = $dependency['channel']; + $package = $dependency['name']; + $state = isset($dependency['state']) ? $dependency['state'] : null; + $version = isset($dependency['version']) ? $dependency['version'] : null; + $restFile = $base . 'r/' . strtolower($package) .'/allreleases2.xml'; + + $info = $this->_rest->retrieveData($restFile, false, false, $channel); + if (PEAR::isError($info)) { + return PEAR::raiseError('Package "' . $deppackage['channel'] . '/' . $deppackage['package'] + . '" dependency "' . $channel . '/' . $package . '" has no releases'); + } + + if (!is_array($info) || !isset($info['r'])) { + return false; + } + + $exclude = array(); + $min = $max = $recommended = false; + if ($xsdversion == '1.0') { + $pinfo['package'] = $dependency['name']; + $pinfo['channel'] = 'pear.php.net'; // this is always true - don't change this + switch ($dependency['rel']) { + case 'ge' : + $min = $dependency['version']; + break; + case 'gt' : + $min = $dependency['version']; + $exclude = array($dependency['version']); + break; + case 'eq' : + $recommended = $dependency['version']; + break; + case 'lt' : + $max = $dependency['version']; + $exclude = array($dependency['version']); + break; + case 'le' : + $max = $dependency['version']; + break; + case 'ne' : + $exclude = array($dependency['version']); + break; + } + } else { + $pinfo['package'] = $dependency['name']; + $min = isset($dependency['min']) ? $dependency['min'] : false; + $max = isset($dependency['max']) ? $dependency['max'] : false; + $recommended = isset($dependency['recommended']) ? + $dependency['recommended'] : false; + if (isset($dependency['exclude'])) { + if (!isset($dependency['exclude'][0])) { + $exclude = array($dependency['exclude']); + } + } + } + + $skippedphp = $found = $release = false; + if (!is_array($info['r']) || !isset($info['r'][0])) { + $info['r'] = array($info['r']); + } + + foreach ($info['r'] as $release) { + if (!isset($this->_rest->_options['force']) && ($installed && + version_compare($release['v'], $installed, '<'))) { + continue; + } + + if (in_array($release['v'], $exclude)) { // skip excluded versions + continue; + } + + // allow newer releases to say "I'm OK with the dependent package" + if ($xsdversion == '2.0' && isset($release['co'])) { + if (!is_array($release['co']) || !isset($release['co'][0])) { + $release['co'] = array($release['co']); + } + + foreach ($release['co'] as $entry) { + if (isset($entry['x']) && !is_array($entry['x'])) { + $entry['x'] = array($entry['x']); + } elseif (!isset($entry['x'])) { + $entry['x'] = array(); + } + + if ($entry['c'] == $deppackage['channel'] && + strtolower($entry['p']) == strtolower($deppackage['package']) && + version_compare($deppackage['version'], $entry['min'], '>=') && + version_compare($deppackage['version'], $entry['max'], '<=') && + !in_array($release['v'], $entry['x'])) { + if (version_compare($release['m'], phpversion(), '>')) { + // skip dependency releases that require a PHP version + // newer than our PHP version + $skippedphp = $release; + continue; + } + + $recommended = $release['v']; + break; + } + } + } + + if ($recommended) { + if ($release['v'] != $recommended) { // if we want a specific + // version, then skip all others + continue; + } + + if (!in_array($release['s'], $states)) { + // the stability is too low, but we must return the + // recommended version if possible + return $this->_returnDownloadURL($base, $package, $release, $info, true, false, $channel); + } + } + + if ($min && version_compare($release['v'], $min, 'lt')) { // skip too old versions + continue; + } + + if ($max && version_compare($release['v'], $max, 'gt')) { // skip too new versions + continue; + } + + if ($installed && version_compare($release['v'], $installed, '<')) { + continue; + } + + if (in_array($release['s'], $states)) { // if in the preferred state... + if (version_compare($release['m'], phpversion(), '>')) { + // skip dependency releases that require a PHP version + // newer than our PHP version + $skippedphp = $release; + continue; + } + + $found = true; // ... then use it + break; + } + } + + if (!$found && $skippedphp) { + $found = null; + } + + return $this->_returnDownloadURL($base, $package, $release, $info, $found, $skippedphp, $channel); + } +} \ No newline at end of file diff --git a/library/pear/PEAR/Registry.php b/library/pear/PEAR/Registry.php new file mode 100644 index 000000000..4dd0734a9 --- /dev/null +++ b/library/pear/PEAR/Registry.php @@ -0,0 +1,2395 @@ + + * @author Tomas V. V. Cox + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Registry.php 287555 2009-08-21 21:27:27Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 0.1 + */ + +/** + * for PEAR_Error + */ +require_once 'PEAR.php'; +require_once 'PEAR/DependencyDB.php'; + +define('PEAR_REGISTRY_ERROR_LOCK', -2); +define('PEAR_REGISTRY_ERROR_FORMAT', -3); +define('PEAR_REGISTRY_ERROR_FILE', -4); +define('PEAR_REGISTRY_ERROR_CONFLICT', -5); +define('PEAR_REGISTRY_ERROR_CHANNEL_FILE', -6); + +/** + * Administration class used to maintain the installed package database. + * @category pear + * @package PEAR + * @author Stig Bakken + * @author Tomas V. V. Cox + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.1 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_Registry extends PEAR +{ + /** + * File containing all channel information. + * @var string + */ + var $channels = ''; + + /** Directory where registry files are stored. + * @var string + */ + var $statedir = ''; + + /** File where the file map is stored + * @var string + */ + var $filemap = ''; + + /** Directory where registry files for channels are stored. + * @var string + */ + var $channelsdir = ''; + + /** Name of file used for locking the registry + * @var string + */ + var $lockfile = ''; + + /** File descriptor used during locking + * @var resource + */ + var $lock_fp = null; + + /** Mode used during locking + * @var int + */ + var $lock_mode = 0; // XXX UNUSED + + /** Cache of package information. Structure: + * array( + * 'package' => array('id' => ... ), + * ... ) + * @var array + */ + var $pkginfo_cache = array(); + + /** Cache of file map. Structure: + * array( '/path/to/file' => 'package', ... ) + * @var array + */ + var $filemap_cache = array(); + + /** + * @var false|PEAR_ChannelFile + */ + var $_pearChannel; + + /** + * @var false|PEAR_ChannelFile + */ + var $_peclChannel; + + /** + * @var false|PEAR_ChannelFile + */ + var $_docChannel; + + /** + * @var PEAR_DependencyDB + */ + var $_dependencyDB; + + /** + * @var PEAR_Config + */ + var $_config; + + /** + * PEAR_Registry constructor. + * + * @param string (optional) PEAR install directory (for .php files) + * @param PEAR_ChannelFile PEAR_ChannelFile object representing the PEAR channel, if + * default values are not desired. Only used the very first time a PEAR + * repository is initialized + * @param PEAR_ChannelFile PEAR_ChannelFile object representing the PECL channel, if + * default values are not desired. Only used the very first time a PEAR + * repository is initialized + * + * @access public + */ + function PEAR_Registry($pear_install_dir = PEAR_INSTALL_DIR, $pear_channel = false, + $pecl_channel = false) + { + parent::PEAR(); + $this->setInstallDir($pear_install_dir); + $this->_pearChannel = $pear_channel; + $this->_peclChannel = $pecl_channel; + $this->_config = false; + } + + function setInstallDir($pear_install_dir = PEAR_INSTALL_DIR) + { + $ds = DIRECTORY_SEPARATOR; + $this->install_dir = $pear_install_dir; + $this->channelsdir = $pear_install_dir.$ds.'.channels'; + $this->statedir = $pear_install_dir.$ds.'.registry'; + $this->filemap = $pear_install_dir.$ds.'.filemap'; + $this->lockfile = $pear_install_dir.$ds.'.lock'; + } + + function hasWriteAccess() + { + if (!file_exists($this->install_dir)) { + $dir = $this->install_dir; + while ($dir && $dir != '.') { + $olddir = $dir; + $dir = dirname($dir); + if ($dir != '.' && file_exists($dir)) { + if (is_writeable($dir)) { + return true; + } + + return false; + } + + if ($dir == $olddir) { // this can happen in safe mode + return @is_writable($dir); + } + } + + return false; + } + + return is_writeable($this->install_dir); + } + + function setConfig(&$config, $resetInstallDir = true) + { + $this->_config = &$config; + if ($resetInstallDir) { + $this->setInstallDir($config->get('php_dir')); + } + } + + function _initializeChannelDirs() + { + static $running = false; + if (!$running) { + $running = true; + $ds = DIRECTORY_SEPARATOR; + if (!is_dir($this->channelsdir) || + !file_exists($this->channelsdir . $ds . 'pear.php.net.reg') || + !file_exists($this->channelsdir . $ds . 'pecl.php.net.reg') || + !file_exists($this->channelsdir . $ds . 'doc.php.net.reg') || + !file_exists($this->channelsdir . $ds . '__uri.reg')) { + if (!file_exists($this->channelsdir . $ds . 'pear.php.net.reg')) { + $pear_channel = $this->_pearChannel; + if (!is_a($pear_channel, 'PEAR_ChannelFile') || !$pear_channel->validate()) { + if (!class_exists('PEAR_ChannelFile')) { + require_once 'PEAR/ChannelFile.php'; + } + + $pear_channel = new PEAR_ChannelFile; + $pear_channel->setAlias('pear'); + $pear_channel->setServer('pear.php.net'); + $pear_channel->setSummary('PHP Extension and Application Repository'); + $pear_channel->setDefaultPEARProtocols(); + $pear_channel->setBaseURL('REST1.0', 'http://pear.php.net/rest/'); + $pear_channel->setBaseURL('REST1.1', 'http://pear.php.net/rest/'); + $pear_channel->setBaseURL('REST1.3', 'http://pear.php.net/rest/'); + //$pear_channel->setBaseURL('REST1.4', 'http://pear.php.net/rest/'); + } else { + $pear_channel->setServer('pear.php.net'); + $pear_channel->setAlias('pear'); + } + + $pear_channel->validate(); + $this->_addChannel($pear_channel); + } + + if (!file_exists($this->channelsdir . $ds . 'pecl.php.net.reg')) { + $pecl_channel = $this->_peclChannel; + if (!is_a($pecl_channel, 'PEAR_ChannelFile') || !$pecl_channel->validate()) { + if (!class_exists('PEAR_ChannelFile')) { + require_once 'PEAR/ChannelFile.php'; + } + + $pecl_channel = new PEAR_ChannelFile; + $pecl_channel->setAlias('pecl'); + $pecl_channel->setServer('pecl.php.net'); + $pecl_channel->setSummary('PHP Extension Community Library'); + $pecl_channel->setDefaultPEARProtocols(); + $pecl_channel->setBaseURL('REST1.0', 'http://pecl.php.net/rest/'); + $pecl_channel->setBaseURL('REST1.1', 'http://pecl.php.net/rest/'); + $pecl_channel->setValidationPackage('PEAR_Validator_PECL', '1.0'); + } else { + $pecl_channel->setServer('pecl.php.net'); + $pecl_channel->setAlias('pecl'); + } + + $pecl_channel->validate(); + $this->_addChannel($pecl_channel); + } + + if (!file_exists($this->channelsdir . $ds . 'doc.php.net.reg')) { + $doc_channel = $this->_docChannel; + if (!is_a($doc_channel, 'PEAR_ChannelFile') || !$doc_channel->validate()) { + if (!class_exists('PEAR_ChannelFile')) { + require_once 'PEAR/ChannelFile.php'; + } + + $doc_channel = new PEAR_ChannelFile; + $doc_channel->setAlias('phpdocs'); + $doc_channel->setServer('doc.php.net'); + $doc_channel->setSummary('PHP Documentation Team'); + $doc_channel->setDefaultPEARProtocols(); + $doc_channel->setBaseURL('REST1.0', 'http://doc.php.net/rest/'); + $doc_channel->setBaseURL('REST1.1', 'http://doc.php.net/rest/'); + $doc_channel->setBaseURL('REST1.3', 'http://doc.php.net/rest/'); + } else { + $doc_channel->setServer('doc.php.net'); + $doc_channel->setAlias('doc'); + } + + $doc_channel->validate(); + $this->_addChannel($doc_channel); + } + + if (!file_exists($this->channelsdir . $ds . '__uri.reg')) { + if (!class_exists('PEAR_ChannelFile')) { + require_once 'PEAR/ChannelFile.php'; + } + + $private = new PEAR_ChannelFile; + $private->setName('__uri'); + $private->setDefaultPEARProtocols(); + $private->setBaseURL('REST1.0', '****'); + $private->setSummary('Pseudo-channel for static packages'); + $this->_addChannel($private); + } + $this->_rebuildFileMap(); + } + + $running = false; + } + } + + function _initializeDirs() + { + $ds = DIRECTORY_SEPARATOR; + // XXX Compatibility code should be removed in the future + // rename all registry files if any to lowercase + if (!OS_WINDOWS && file_exists($this->statedir) && is_dir($this->statedir) && + $handle = opendir($this->statedir)) { + $dest = $this->statedir . $ds; + while (false !== ($file = readdir($handle))) { + if (preg_match('/^.*[A-Z].*\.reg\\z/', $file)) { + rename($dest . $file, $dest . strtolower($file)); + } + } + closedir($handle); + } + + $this->_initializeChannelDirs(); + if (!file_exists($this->filemap)) { + $this->_rebuildFileMap(); + } + $this->_initializeDepDB(); + } + + function _initializeDepDB() + { + if (!isset($this->_dependencyDB)) { + static $initializing = false; + if (!$initializing) { + $initializing = true; + if (!$this->_config) { // never used? + $file = OS_WINDOWS ? 'pear.ini' : '.pearrc'; + $this->_config = &new PEAR_Config($this->statedir . DIRECTORY_SEPARATOR . + $file); + $this->_config->setRegistry($this); + $this->_config->set('php_dir', $this->install_dir); + } + + $this->_dependencyDB = &PEAR_DependencyDB::singleton($this->_config); + if (PEAR::isError($this->_dependencyDB)) { + // attempt to recover by removing the dep db + if (file_exists($this->_config->get('php_dir', null, 'pear.php.net') . + DIRECTORY_SEPARATOR . '.depdb')) { + @unlink($this->_config->get('php_dir', null, 'pear.php.net') . + DIRECTORY_SEPARATOR . '.depdb'); + } + + $this->_dependencyDB = &PEAR_DependencyDB::singleton($this->_config); + if (PEAR::isError($this->_dependencyDB)) { + echo $this->_dependencyDB->getMessage(); + echo 'Unrecoverable error'; + exit(1); + } + } + + $initializing = false; + } + } + } + + /** + * PEAR_Registry destructor. Makes sure no locks are forgotten. + * + * @access private + */ + function _PEAR_Registry() + { + parent::_PEAR(); + if (is_resource($this->lock_fp)) { + $this->_unlock(); + } + } + + /** + * Make sure the directory where we keep registry files exists. + * + * @return bool TRUE if directory exists, FALSE if it could not be + * created + * + * @access private + */ + function _assertStateDir($channel = false) + { + if ($channel && $this->_getChannelFromAlias($channel) != 'pear.php.net') { + return $this->_assertChannelStateDir($channel); + } + + static $init = false; + if (!file_exists($this->statedir)) { + if (!$this->hasWriteAccess()) { + return false; + } + + require_once 'System.php'; + if (!System::mkdir(array('-p', $this->statedir))) { + return $this->raiseError("could not create directory '{$this->statedir}'"); + } + $init = true; + } elseif (!is_dir($this->statedir)) { + return $this->raiseError('Cannot create directory ' . $this->statedir . ', ' . + 'it already exists and is not a directory'); + } + + $ds = DIRECTORY_SEPARATOR; + if (!file_exists($this->channelsdir)) { + if (!file_exists($this->channelsdir . $ds . 'pear.php.net.reg') || + !file_exists($this->channelsdir . $ds . 'pecl.php.net.reg') || + !file_exists($this->channelsdir . $ds . 'doc.php.net.reg') || + !file_exists($this->channelsdir . $ds . '__uri.reg')) { + $init = true; + } + } elseif (!is_dir($this->channelsdir)) { + return $this->raiseError('Cannot create directory ' . $this->channelsdir . ', ' . + 'it already exists and is not a directory'); + } + + if ($init) { + static $running = false; + if (!$running) { + $running = true; + $this->_initializeDirs(); + $running = false; + $init = false; + } + } else { + $this->_initializeDepDB(); + } + + return true; + } + + /** + * Make sure the directory where we keep registry files exists for a non-standard channel. + * + * @param string channel name + * @return bool TRUE if directory exists, FALSE if it could not be + * created + * + * @access private + */ + function _assertChannelStateDir($channel) + { + $ds = DIRECTORY_SEPARATOR; + if (!$channel || $this->_getChannelFromAlias($channel) == 'pear.php.net') { + if (!file_exists($this->channelsdir . $ds . 'pear.php.net.reg')) { + $this->_initializeChannelDirs(); + } + return $this->_assertStateDir($channel); + } + + $channelDir = $this->_channelDirectoryName($channel); + if (!is_dir($this->channelsdir) || + !file_exists($this->channelsdir . $ds . 'pear.php.net.reg')) { + $this->_initializeChannelDirs(); + } + + if (!file_exists($channelDir)) { + if (!$this->hasWriteAccess()) { + return false; + } + + require_once 'System.php'; + if (!System::mkdir(array('-p', $channelDir))) { + return $this->raiseError("could not create directory '" . $channelDir . + "'"); + } + } elseif (!is_dir($channelDir)) { + return $this->raiseError("could not create directory '" . $channelDir . + "', already exists and is not a directory"); + } + + return true; + } + + /** + * Make sure the directory where we keep registry files for channels exists + * + * @return bool TRUE if directory exists, FALSE if it could not be + * created + * + * @access private + */ + function _assertChannelDir() + { + if (!file_exists($this->channelsdir)) { + if (!$this->hasWriteAccess()) { + return false; + } + + require_once 'System.php'; + if (!System::mkdir(array('-p', $this->channelsdir))) { + return $this->raiseError("could not create directory '{$this->channelsdir}'"); + } + } elseif (!is_dir($this->channelsdir)) { + return $this->raiseError("could not create directory '{$this->channelsdir}" . + "', it already exists and is not a directory"); + } + + if (!file_exists($this->channelsdir . DIRECTORY_SEPARATOR . '.alias')) { + if (!$this->hasWriteAccess()) { + return false; + } + + require_once 'System.php'; + if (!System::mkdir(array('-p', $this->channelsdir . DIRECTORY_SEPARATOR . '.alias'))) { + return $this->raiseError("could not create directory '{$this->channelsdir}/.alias'"); + } + } elseif (!is_dir($this->channelsdir . DIRECTORY_SEPARATOR . '.alias')) { + return $this->raiseError("could not create directory '{$this->channelsdir}" . + "/.alias', it already exists and is not a directory"); + } + + return true; + } + + /** + * Get the name of the file where data for a given package is stored. + * + * @param string channel name, or false if this is a PEAR package + * @param string package name + * + * @return string registry file name + * + * @access public + */ + function _packageFileName($package, $channel = false) + { + if ($channel && $this->_getChannelFromAlias($channel) != 'pear.php.net') { + return $this->_channelDirectoryName($channel) . DIRECTORY_SEPARATOR . + strtolower($package) . '.reg'; + } + + return $this->statedir . DIRECTORY_SEPARATOR . strtolower($package) . '.reg'; + } + + /** + * Get the name of the file where data for a given channel is stored. + * @param string channel name + * @return string registry file name + */ + function _channelFileName($channel, $noaliases = false) + { + if (!$noaliases) { + if (file_exists($this->_getChannelAliasFileName($channel))) { + $channel = implode('', file($this->_getChannelAliasFileName($channel))); + } + } + return $this->channelsdir . DIRECTORY_SEPARATOR . str_replace('/', '_', + strtolower($channel)) . '.reg'; + } + + /** + * @param string + * @return string + */ + function _getChannelAliasFileName($alias) + { + return $this->channelsdir . DIRECTORY_SEPARATOR . '.alias' . + DIRECTORY_SEPARATOR . str_replace('/', '_', strtolower($alias)) . '.txt'; + } + + /** + * Get the name of a channel from its alias + */ + function _getChannelFromAlias($channel) + { + if (!$this->_channelExists($channel)) { + if ($channel == 'pear.php.net') { + return 'pear.php.net'; + } + + if ($channel == 'pecl.php.net') { + return 'pecl.php.net'; + } + + if ($channel == 'doc.php.net') { + return 'doc.php.net'; + } + + if ($channel == '__uri') { + return '__uri'; + } + + return false; + } + + $channel = strtolower($channel); + if (file_exists($this->_getChannelAliasFileName($channel))) { + // translate an alias to an actual channel + return implode('', file($this->_getChannelAliasFileName($channel))); + } + + return $channel; + } + + /** + * Get the alias of a channel from its alias or its name + */ + function _getAlias($channel) + { + if (!$this->_channelExists($channel)) { + if ($channel == 'pear.php.net') { + return 'pear'; + } + + if ($channel == 'pecl.php.net') { + return 'pecl'; + } + + if ($channel == 'doc.php.net') { + return 'phpdocs'; + } + + return false; + } + + $channel = $this->_getChannel($channel); + if (PEAR::isError($channel)) { + return $channel; + } + + return $channel->getAlias(); + } + + /** + * Get the name of the file where data for a given package is stored. + * + * @param string channel name, or false if this is a PEAR package + * @param string package name + * + * @return string registry file name + * + * @access public + */ + function _channelDirectoryName($channel) + { + if (!$channel || $this->_getChannelFromAlias($channel) == 'pear.php.net') { + return $this->statedir; + } + + $ch = $this->_getChannelFromAlias($channel); + if (!$ch) { + $ch = $channel; + } + + return $this->statedir . DIRECTORY_SEPARATOR . strtolower('.channel.' . + str_replace('/', '_', $ch)); + } + + function _openPackageFile($package, $mode, $channel = false) + { + if (!$this->_assertStateDir($channel)) { + return null; + } + + if (!in_array($mode, array('r', 'rb')) && !$this->hasWriteAccess()) { + return null; + } + + $file = $this->_packageFileName($package, $channel); + if (!file_exists($file) && $mode == 'r' || $mode == 'rb') { + return null; + } + + $fp = @fopen($file, $mode); + if (!$fp) { + return null; + } + + return $fp; + } + + function _closePackageFile($fp) + { + fclose($fp); + } + + function _openChannelFile($channel, $mode) + { + if (!$this->_assertChannelDir()) { + return null; + } + + if (!in_array($mode, array('r', 'rb')) && !$this->hasWriteAccess()) { + return null; + } + + $file = $this->_channelFileName($channel); + if (!file_exists($file) && $mode == 'r' || $mode == 'rb') { + return null; + } + + $fp = @fopen($file, $mode); + if (!$fp) { + return null; + } + + return $fp; + } + + function _closeChannelFile($fp) + { + fclose($fp); + } + + function _rebuildFileMap() + { + if (!class_exists('PEAR_Installer_Role')) { + require_once 'PEAR/Installer/Role.php'; + } + + $channels = $this->_listAllPackages(); + $files = array(); + foreach ($channels as $channel => $packages) { + foreach ($packages as $package) { + $version = $this->_packageInfo($package, 'version', $channel); + $filelist = $this->_packageInfo($package, 'filelist', $channel); + if (!is_array($filelist)) { + continue; + } + + foreach ($filelist as $name => $attrs) { + if (isset($attrs['attribs'])) { + $attrs = $attrs['attribs']; + } + + // it is possible for conflicting packages in different channels to + // conflict with data files/doc files + if ($name == 'dirtree') { + continue; + } + + if (isset($attrs['role']) && !in_array($attrs['role'], + PEAR_Installer_Role::getInstallableRoles())) { + // these are not installed + continue; + } + + if (isset($attrs['role']) && !in_array($attrs['role'], + PEAR_Installer_Role::getBaseinstallRoles())) { + $attrs['baseinstalldir'] = $package; + } + + if (isset($attrs['baseinstalldir'])) { + $file = $attrs['baseinstalldir'].DIRECTORY_SEPARATOR.$name; + } else { + $file = $name; + } + + $file = preg_replace(',^/+,', '', $file); + if ($channel != 'pear.php.net') { + if (!isset($files[$attrs['role']])) { + $files[$attrs['role']] = array(); + } + $files[$attrs['role']][$file] = array(strtolower($channel), + strtolower($package)); + } else { + if (!isset($files[$attrs['role']])) { + $files[$attrs['role']] = array(); + } + $files[$attrs['role']][$file] = strtolower($package); + } + } + } + } + + + $this->_assertStateDir(); + if (!$this->hasWriteAccess()) { + return false; + } + + $fp = @fopen($this->filemap, 'wb'); + if (!$fp) { + return false; + } + + $this->filemap_cache = $files; + fwrite($fp, serialize($files)); + fclose($fp); + return true; + } + + function _readFileMap() + { + if (!file_exists($this->filemap)) { + return array(); + } + + $fp = @fopen($this->filemap, 'r'); + if (!$fp) { + return $this->raiseError('PEAR_Registry: could not open filemap "' . $this->filemap . '"', PEAR_REGISTRY_ERROR_FILE, null, null, $php_errormsg); + } + + clearstatcache(); + $rt = get_magic_quotes_runtime(); + set_magic_quotes_runtime(0); + $fsize = filesize($this->filemap); + fclose($fp); + $data = file_get_contents($this->filemap); + set_magic_quotes_runtime($rt); + $tmp = unserialize($data); + if (!$tmp && $fsize > 7) { + return $this->raiseError('PEAR_Registry: invalid filemap data', PEAR_REGISTRY_ERROR_FORMAT, null, null, $data); + } + + $this->filemap_cache = $tmp; + return true; + } + + /** + * Lock the registry. + * + * @param integer lock mode, one of LOCK_EX, LOCK_SH or LOCK_UN. + * See flock manual for more information. + * + * @return bool TRUE on success, FALSE if locking failed, or a + * PEAR error if some other error occurs (such as the + * lock file not being writable). + * + * @access private + */ + function _lock($mode = LOCK_EX) + { + if (stristr(php_uname(), 'Windows 9')) { + return true; + } + + if ($mode != LOCK_UN && is_resource($this->lock_fp)) { + // XXX does not check type of lock (LOCK_SH/LOCK_EX) + return true; + } + + if (!$this->_assertStateDir()) { + if ($mode == LOCK_EX) { + return $this->raiseError('Registry directory is not writeable by the current user'); + } + + return true; + } + + $open_mode = 'w'; + // XXX People reported problems with LOCK_SH and 'w' + if ($mode === LOCK_SH || $mode === LOCK_UN) { + if (!file_exists($this->lockfile)) { + touch($this->lockfile); + } + $open_mode = 'r'; + } + + if (!is_resource($this->lock_fp)) { + $this->lock_fp = @fopen($this->lockfile, $open_mode); + } + + if (!is_resource($this->lock_fp)) { + $this->lock_fp = null; + return $this->raiseError("could not create lock file" . + (isset($php_errormsg) ? ": " . $php_errormsg : "")); + } + + if (!(int)flock($this->lock_fp, $mode)) { + switch ($mode) { + case LOCK_SH: $str = 'shared'; break; + case LOCK_EX: $str = 'exclusive'; break; + case LOCK_UN: $str = 'unlock'; break; + default: $str = 'unknown'; break; + } + + //is resource at this point, close it on error. + fclose($this->lock_fp); + $this->lock_fp = null; + return $this->raiseError("could not acquire $str lock ($this->lockfile)", + PEAR_REGISTRY_ERROR_LOCK); + } + + return true; + } + + function _unlock() + { + $ret = $this->_lock(LOCK_UN); + if (is_resource($this->lock_fp)) { + fclose($this->lock_fp); + } + + $this->lock_fp = null; + return $ret; + } + + function _packageExists($package, $channel = false) + { + return file_exists($this->_packageFileName($package, $channel)); + } + + /** + * Determine whether a channel exists in the registry + * + * @param string Channel name + * @param bool if true, then aliases will be ignored + * @return boolean + */ + function _channelExists($channel, $noaliases = false) + { + $a = file_exists($this->_channelFileName($channel, $noaliases)); + if (!$a && $channel == 'pear.php.net') { + return true; + } + + if (!$a && $channel == 'pecl.php.net') { + return true; + } + + if (!$a && $channel == 'doc.php.net') { + return true; + } + + return $a; + } + + /** + * Determine whether a mirror exists within the deafult channel in the registry + * + * @param string Channel name + * @param string Mirror name + * + * @return boolean + */ + function _mirrorExists($channel, $mirror) + { + $data = $this->_channelInfo($channel); + if (!isset($data['servers']['mirror'])) { + return false; + } + + foreach ($data['servers']['mirror'] as $m) { + if ($m['attribs']['host'] == $mirror) { + return true; + } + } + + return false; + } + + /** + * @param PEAR_ChannelFile Channel object + * @param donotuse + * @param string Last-Modified HTTP tag from remote request + * @return boolean|PEAR_Error True on creation, false if it already exists + */ + function _addChannel($channel, $update = false, $lastmodified = false) + { + if (!is_a($channel, 'PEAR_ChannelFile')) { + return false; + } + + if (!$channel->validate()) { + return false; + } + + if (file_exists($this->_channelFileName($channel->getName()))) { + if (!$update) { + return false; + } + + $checker = $this->_getChannel($channel->getName()); + if (PEAR::isError($checker)) { + return $checker; + } + + if ($channel->getAlias() != $checker->getAlias()) { + if (file_exists($this->_getChannelAliasFileName($checker->getAlias()))) { + @unlink($this->_getChannelAliasFileName($checker->getAlias())); + } + } + } else { + if ($update && !in_array($channel->getName(), array('pear.php.net', 'pecl.php.net', 'doc.php.net'))) { + return false; + } + } + + $ret = $this->_assertChannelDir(); + if (PEAR::isError($ret)) { + return $ret; + } + + $ret = $this->_assertChannelStateDir($channel->getName()); + if (PEAR::isError($ret)) { + return $ret; + } + + if ($channel->getAlias() != $channel->getName()) { + if (file_exists($this->_getChannelAliasFileName($channel->getAlias())) && + $this->_getChannelFromAlias($channel->getAlias()) != $channel->getName()) { + $channel->setAlias($channel->getName()); + } + + if (!$this->hasWriteAccess()) { + return false; + } + + $fp = @fopen($this->_getChannelAliasFileName($channel->getAlias()), 'w'); + if (!$fp) { + return false; + } + + fwrite($fp, $channel->getName()); + fclose($fp); + } + + if (!$this->hasWriteAccess()) { + return false; + } + + $fp = @fopen($this->_channelFileName($channel->getName()), 'wb'); + if (!$fp) { + return false; + } + + $info = $channel->toArray(); + if ($lastmodified) { + $info['_lastmodified'] = $lastmodified; + } else { + $info['_lastmodified'] = date('r'); + } + + fwrite($fp, serialize($info)); + fclose($fp); + return true; + } + + /** + * Deletion fails if there are any packages installed from the channel + * @param string|PEAR_ChannelFile channel name + * @return boolean|PEAR_Error True on deletion, false if it doesn't exist + */ + function _deleteChannel($channel) + { + if (!is_string($channel)) { + if (!is_a($channel, 'PEAR_ChannelFile')) { + return false; + } + + if (!$channel->validate()) { + return false; + } + $channel = $channel->getName(); + } + + if ($this->_getChannelFromAlias($channel) == '__uri') { + return false; + } + + if ($this->_getChannelFromAlias($channel) == 'pecl.php.net') { + return false; + } + + if ($this->_getChannelFromAlias($channel) == 'doc.php.net') { + return false; + } + + if (!$this->_channelExists($channel)) { + return false; + } + + if (!$channel || $this->_getChannelFromAlias($channel) == 'pear.php.net') { + return false; + } + + $channel = $this->_getChannelFromAlias($channel); + if ($channel == 'pear.php.net') { + return false; + } + + $test = $this->_listChannelPackages($channel); + if (count($test)) { + return false; + } + + $test = @rmdir($this->_channelDirectoryName($channel)); + if (!$test) { + return false; + } + + $file = $this->_getChannelAliasFileName($this->_getAlias($channel)); + if (file_exists($file)) { + $test = @unlink($file); + if (!$test) { + return false; + } + } + + $file = $this->_channelFileName($channel); + $ret = true; + if (file_exists($file)) { + $ret = @unlink($file); + } + + return $ret; + } + + /** + * Determine whether a channel exists in the registry + * @param string Channel Alias + * @return boolean + */ + function _isChannelAlias($alias) + { + return file_exists($this->_getChannelAliasFileName($alias)); + } + + /** + * @param string|null + * @param string|null + * @param string|null + * @return array|null + * @access private + */ + function _packageInfo($package = null, $key = null, $channel = 'pear.php.net') + { + if ($package === null) { + if ($channel === null) { + $channels = $this->_listChannels(); + $ret = array(); + foreach ($channels as $channel) { + $channel = strtolower($channel); + $ret[$channel] = array(); + $packages = $this->_listPackages($channel); + foreach ($packages as $package) { + $ret[$channel][] = $this->_packageInfo($package, null, $channel); + } + } + + return $ret; + } + + $ps = $this->_listPackages($channel); + if (!count($ps)) { + return array(); + } + return array_map(array(&$this, '_packageInfo'), + $ps, array_fill(0, count($ps), null), + array_fill(0, count($ps), $channel)); + } + + $fp = $this->_openPackageFile($package, 'r', $channel); + if ($fp === null) { + return null; + } + + $rt = get_magic_quotes_runtime(); + set_magic_quotes_runtime(0); + clearstatcache(); + $this->_closePackageFile($fp); + $data = file_get_contents($this->_packageFileName($package, $channel)); + set_magic_quotes_runtime($rt); + $data = unserialize($data); + if ($key === null) { + return $data; + } + + // compatibility for package.xml version 2.0 + if (isset($data['old'][$key])) { + return $data['old'][$key]; + } + + if (isset($data[$key])) { + return $data[$key]; + } + + return null; + } + + /** + * @param string Channel name + * @param bool whether to strictly retrieve info of channels, not just aliases + * @return array|null + */ + function _channelInfo($channel, $noaliases = false) + { + if (!$this->_channelExists($channel, $noaliases)) { + return null; + } + + $fp = $this->_openChannelFile($channel, 'r'); + if ($fp === null) { + return null; + } + + $rt = get_magic_quotes_runtime(); + set_magic_quotes_runtime(0); + clearstatcache(); + $this->_closeChannelFile($fp); + $data = file_get_contents($this->_channelFileName($channel)); + set_magic_quotes_runtime($rt); + $data = unserialize($data); + return $data; + } + + function _listChannels() + { + $channellist = array(); + if (!file_exists($this->channelsdir) || !is_dir($this->channelsdir)) { + return array('pear.php.net', 'pecl.php.net', 'doc.php.net', '__uri'); + } + + $dp = opendir($this->channelsdir); + while ($ent = readdir($dp)) { + if ($ent{0} == '.' || substr($ent, -4) != '.reg') { + continue; + } + + if ($ent == '__uri.reg') { + $channellist[] = '__uri'; + continue; + } + + $channellist[] = str_replace('_', '/', substr($ent, 0, -4)); + } + + closedir($dp); + if (!in_array('pear.php.net', $channellist)) { + $channellist[] = 'pear.php.net'; + } + + if (!in_array('pecl.php.net', $channellist)) { + $channellist[] = 'pecl.php.net'; + } + + if (!in_array('doc.php.net', $channellist)) { + $channellist[] = 'doc.php.net'; + } + + + if (!in_array('__uri', $channellist)) { + $channellist[] = '__uri'; + } + + natsort($channellist); + return $channellist; + } + + function _listPackages($channel = false) + { + if ($channel && $this->_getChannelFromAlias($channel) != 'pear.php.net') { + return $this->_listChannelPackages($channel); + } + + if (!file_exists($this->statedir) || !is_dir($this->statedir)) { + return array(); + } + + $pkglist = array(); + $dp = opendir($this->statedir); + if (!$dp) { + return $pkglist; + } + + while ($ent = readdir($dp)) { + if ($ent{0} == '.' || substr($ent, -4) != '.reg') { + continue; + } + + $pkglist[] = substr($ent, 0, -4); + } + closedir($dp); + return $pkglist; + } + + function _listChannelPackages($channel) + { + $pkglist = array(); + if (!file_exists($this->_channelDirectoryName($channel)) || + !is_dir($this->_channelDirectoryName($channel))) { + return array(); + } + + $dp = opendir($this->_channelDirectoryName($channel)); + if (!$dp) { + return $pkglist; + } + + while ($ent = readdir($dp)) { + if ($ent{0} == '.' || substr($ent, -4) != '.reg') { + continue; + } + $pkglist[] = substr($ent, 0, -4); + } + + closedir($dp); + return $pkglist; + } + + function _listAllPackages() + { + $ret = array(); + foreach ($this->_listChannels() as $channel) { + $ret[$channel] = $this->_listPackages($channel); + } + + return $ret; + } + + /** + * Add an installed package to the registry + * @param string package name + * @param array package info (parsed by PEAR_Common::infoFrom*() methods) + * @return bool success of saving + * @access private + */ + function _addPackage($package, $info) + { + if ($this->_packageExists($package)) { + return false; + } + + $fp = $this->_openPackageFile($package, 'wb'); + if ($fp === null) { + return false; + } + + $info['_lastmodified'] = time(); + fwrite($fp, serialize($info)); + $this->_closePackageFile($fp); + if (isset($info['filelist'])) { + $this->_rebuildFileMap(); + } + + return true; + } + + /** + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2 + * @return bool + * @access private + */ + function _addPackage2($info) + { + if (!is_a($info, 'PEAR_PackageFile_v1') && !is_a($info, 'PEAR_PackageFile_v2')) { + return false; + } + + if (!$info->validate()) { + if (class_exists('PEAR_Common')) { + $ui = PEAR_Frontend::singleton(); + if ($ui) { + foreach ($info->getValidationWarnings() as $err) { + $ui->log($err['message'], true); + } + } + } + return false; + } + + $channel = $info->getChannel(); + $package = $info->getPackage(); + $save = $info; + if ($this->_packageExists($package, $channel)) { + return false; + } + + if (!$this->_channelExists($channel, true)) { + return false; + } + + $info = $info->toArray(true); + if (!$info) { + return false; + } + + $fp = $this->_openPackageFile($package, 'wb', $channel); + if ($fp === null) { + return false; + } + + $info['_lastmodified'] = time(); + fwrite($fp, serialize($info)); + $this->_closePackageFile($fp); + $this->_rebuildFileMap(); + return true; + } + + /** + * @param string Package name + * @param array parsed package.xml 1.0 + * @param bool this parameter is only here for BC. Don't use it. + * @access private + */ + function _updatePackage($package, $info, $merge = true) + { + $oldinfo = $this->_packageInfo($package); + if (empty($oldinfo)) { + return false; + } + + $fp = $this->_openPackageFile($package, 'w'); + if ($fp === null) { + return false; + } + + if (is_object($info)) { + $info = $info->toArray(); + } + $info['_lastmodified'] = time(); + + $newinfo = $info; + if ($merge) { + $info = array_merge($oldinfo, $info); + } else { + $diff = $info; + } + + fwrite($fp, serialize($info)); + $this->_closePackageFile($fp); + if (isset($newinfo['filelist'])) { + $this->_rebuildFileMap(); + } + + return true; + } + + /** + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2 + * @return bool + * @access private + */ + function _updatePackage2($info) + { + if (!$this->_packageExists($info->getPackage(), $info->getChannel())) { + return false; + } + + $fp = $this->_openPackageFile($info->getPackage(), 'w', $info->getChannel()); + if ($fp === null) { + return false; + } + + $save = $info; + $info = $save->getArray(true); + $info['_lastmodified'] = time(); + fwrite($fp, serialize($info)); + $this->_closePackageFile($fp); + $this->_rebuildFileMap(); + return true; + } + + /** + * @param string Package name + * @param string Channel name + * @return PEAR_PackageFile_v1|PEAR_PackageFile_v2|null + * @access private + */ + function &_getPackage($package, $channel = 'pear.php.net') + { + $info = $this->_packageInfo($package, null, $channel); + if ($info === null) { + return $info; + } + + $a = $this->_config; + if (!$a) { + $this->_config = &new PEAR_Config; + $this->_config->set('php_dir', $this->statedir); + } + + if (!class_exists('PEAR_PackageFile')) { + require_once 'PEAR/PackageFile.php'; + } + + $pkg = &new PEAR_PackageFile($this->_config); + $pf = &$pkg->fromArray($info); + return $pf; + } + + /** + * @param string channel name + * @param bool whether to strictly retrieve channel names + * @return PEAR_ChannelFile|PEAR_Error + * @access private + */ + function &_getChannel($channel, $noaliases = false) + { + $ch = false; + if ($this->_channelExists($channel, $noaliases)) { + $chinfo = $this->_channelInfo($channel, $noaliases); + if ($chinfo) { + if (!class_exists('PEAR_ChannelFile')) { + require_once 'PEAR/ChannelFile.php'; + } + + $ch = &PEAR_ChannelFile::fromArrayWithErrors($chinfo); + } + } + + if ($ch) { + if ($ch->validate()) { + return $ch; + } + + foreach ($ch->getErrors(true) as $err) { + $message = $err['message'] . "\n"; + } + + $ch = PEAR::raiseError($message); + return $ch; + } + + if ($this->_getChannelFromAlias($channel) == 'pear.php.net') { + // the registry is not properly set up, so use defaults + if (!class_exists('PEAR_ChannelFile')) { + require_once 'PEAR/ChannelFile.php'; + } + + $pear_channel = new PEAR_ChannelFile; + $pear_channel->setServer('pear.php.net'); + $pear_channel->setAlias('pear'); + $pear_channel->setSummary('PHP Extension and Application Repository'); + $pear_channel->setDefaultPEARProtocols(); + $pear_channel->setBaseURL('REST1.0', 'http://pear.php.net/rest/'); + $pear_channel->setBaseURL('REST1.1', 'http://pear.php.net/rest/'); + $pear_channel->setBaseURL('REST1.3', 'http://pear.php.net/rest/'); + return $pear_channel; + } + + if ($this->_getChannelFromAlias($channel) == 'pecl.php.net') { + // the registry is not properly set up, so use defaults + if (!class_exists('PEAR_ChannelFile')) { + require_once 'PEAR/ChannelFile.php'; + } + $pear_channel = new PEAR_ChannelFile; + $pear_channel->setServer('pecl.php.net'); + $pear_channel->setAlias('pecl'); + $pear_channel->setSummary('PHP Extension Community Library'); + $pear_channel->setDefaultPEARProtocols(); + $pear_channel->setBaseURL('REST1.0', 'http://pecl.php.net/rest/'); + $pear_channel->setBaseURL('REST1.1', 'http://pecl.php.net/rest/'); + $pear_channel->setValidationPackage('PEAR_Validator_PECL', '1.0'); + return $pear_channel; + } + + if ($this->_getChannelFromAlias($channel) == 'doc.php.net') { + // the registry is not properly set up, so use defaults + if (!class_exists('PEAR_ChannelFile')) { + require_once 'PEAR/ChannelFile.php'; + } + + $doc_channel = new PEAR_ChannelFile; + $doc_channel->setServer('doc.php.net'); + $doc_channel->setAlias('phpdocs'); + $doc_channel->setSummary('PHP Documentation Team'); + $doc_channel->setDefaultPEARProtocols(); + $doc_channel->setBaseURL('REST1.0', 'http://doc.php.net/rest/'); + $doc_channel->setBaseURL('REST1.1', 'http://doc.php.net/rest/'); + $doc_channel->setBaseURL('REST1.3', 'http://doc.php.net/rest/'); + return $doc_channel; + } + + + if ($this->_getChannelFromAlias($channel) == '__uri') { + // the registry is not properly set up, so use defaults + if (!class_exists('PEAR_ChannelFile')) { + require_once 'PEAR/ChannelFile.php'; + } + + $private = new PEAR_ChannelFile; + $private->setName('__uri'); + $private->setDefaultPEARProtocols(); + $private->setBaseURL('REST1.0', '****'); + $private->setSummary('Pseudo-channel for static packages'); + return $private; + } + + return $ch; + } + + /** + * @param string Package name + * @param string Channel name + * @return bool + */ + function packageExists($package, $channel = 'pear.php.net') + { + if (PEAR::isError($e = $this->_lock(LOCK_SH))) { + return $e; + } + $ret = $this->_packageExists($package, $channel); + $this->_unlock(); + return $ret; + } + + // }}} + + // {{{ channelExists() + + /** + * @param string channel name + * @param bool if true, then aliases will be ignored + * @return bool + */ + function channelExists($channel, $noaliases = false) + { + if (PEAR::isError($e = $this->_lock(LOCK_SH))) { + return $e; + } + $ret = $this->_channelExists($channel, $noaliases); + $this->_unlock(); + return $ret; + } + + // }}} + + /** + * @param string channel name mirror is in + * @param string mirror name + * + * @return bool + */ + function mirrorExists($channel, $mirror) + { + if (PEAR::isError($e = $this->_lock(LOCK_SH))) { + return $e; + } + + $ret = $this->_mirrorExists($channel, $mirror); + $this->_unlock(); + return $ret; + } + + // {{{ isAlias() + + /** + * Determines whether the parameter is an alias of a channel + * @param string + * @return bool + */ + function isAlias($alias) + { + if (PEAR::isError($e = $this->_lock(LOCK_SH))) { + return $e; + } + $ret = $this->_isChannelAlias($alias); + $this->_unlock(); + return $ret; + } + + // }}} + // {{{ packageInfo() + + /** + * @param string|null + * @param string|null + * @param string + * @return array|null + */ + function packageInfo($package = null, $key = null, $channel = 'pear.php.net') + { + if (PEAR::isError($e = $this->_lock(LOCK_SH))) { + return $e; + } + $ret = $this->_packageInfo($package, $key, $channel); + $this->_unlock(); + return $ret; + } + + // }}} + // {{{ channelInfo() + + /** + * Retrieve a raw array of channel data. + * + * Do not use this, instead use {@link getChannel()} for normal + * operations. Array structure is undefined in this method + * @param string channel name + * @param bool whether to strictly retrieve information only on non-aliases + * @return array|null|PEAR_Error + */ + function channelInfo($channel = null, $noaliases = false) + { + if (PEAR::isError($e = $this->_lock(LOCK_SH))) { + return $e; + } + $ret = $this->_channelInfo($channel, $noaliases); + $this->_unlock(); + return $ret; + } + + // }}} + + /** + * @param string + */ + function channelName($channel) + { + if (PEAR::isError($e = $this->_lock(LOCK_SH))) { + return $e; + } + $ret = $this->_getChannelFromAlias($channel); + $this->_unlock(); + return $ret; + } + + /** + * @param string + */ + function channelAlias($channel) + { + if (PEAR::isError($e = $this->_lock(LOCK_SH))) { + return $e; + } + $ret = $this->_getAlias($channel); + $this->_unlock(); + return $ret; + } + // {{{ listPackages() + + function listPackages($channel = false) + { + if (PEAR::isError($e = $this->_lock(LOCK_SH))) { + return $e; + } + $ret = $this->_listPackages($channel); + $this->_unlock(); + return $ret; + } + + // }}} + // {{{ listAllPackages() + + function listAllPackages() + { + if (PEAR::isError($e = $this->_lock(LOCK_SH))) { + return $e; + } + $ret = $this->_listAllPackages(); + $this->_unlock(); + return $ret; + } + + // }}} + // {{{ listChannel() + + function listChannels() + { + if (PEAR::isError($e = $this->_lock(LOCK_SH))) { + return $e; + } + $ret = $this->_listChannels(); + $this->_unlock(); + return $ret; + } + + // }}} + // {{{ addPackage() + + /** + * Add an installed package to the registry + * @param string|PEAR_PackageFile_v1|PEAR_PackageFile_v2 package name or object + * that will be passed to {@link addPackage2()} + * @param array package info (parsed by PEAR_Common::infoFrom*() methods) + * @return bool success of saving + */ + function addPackage($package, $info) + { + if (is_object($info)) { + return $this->addPackage2($info); + } + if (PEAR::isError($e = $this->_lock(LOCK_EX))) { + return $e; + } + $ret = $this->_addPackage($package, $info); + $this->_unlock(); + if ($ret) { + if (!class_exists('PEAR_PackageFile_v1')) { + require_once 'PEAR/PackageFile/v1.php'; + } + $pf = new PEAR_PackageFile_v1; + $pf->setConfig($this->_config); + $pf->fromArray($info); + $this->_dependencyDB->uninstallPackage($pf); + $this->_dependencyDB->installPackage($pf); + } + return $ret; + } + + // }}} + // {{{ addPackage2() + + function addPackage2($info) + { + if (!is_object($info)) { + return $this->addPackage($info['package'], $info); + } + if (PEAR::isError($e = $this->_lock(LOCK_EX))) { + return $e; + } + $ret = $this->_addPackage2($info); + $this->_unlock(); + if ($ret) { + $this->_dependencyDB->uninstallPackage($info); + $this->_dependencyDB->installPackage($info); + } + return $ret; + } + + // }}} + // {{{ updateChannel() + + /** + * For future expandibility purposes, separate this + * @param PEAR_ChannelFile + */ + function updateChannel($channel, $lastmodified = null) + { + if ($channel->getName() == '__uri') { + return false; + } + return $this->addChannel($channel, $lastmodified, true); + } + + // }}} + // {{{ deleteChannel() + + /** + * Deletion fails if there are any packages installed from the channel + * @param string|PEAR_ChannelFile channel name + * @return boolean|PEAR_Error True on deletion, false if it doesn't exist + */ + function deleteChannel($channel) + { + if (PEAR::isError($e = $this->_lock(LOCK_EX))) { + return $e; + } + + $ret = $this->_deleteChannel($channel); + $this->_unlock(); + if ($ret && is_a($this->_config, 'PEAR_Config')) { + $this->_config->setChannels($this->listChannels()); + } + + return $ret; + } + + // }}} + // {{{ addChannel() + + /** + * @param PEAR_ChannelFile Channel object + * @param string Last-Modified header from HTTP for caching + * @return boolean|PEAR_Error True on creation, false if it already exists + */ + function addChannel($channel, $lastmodified = false, $update = false) + { + if (!is_a($channel, 'PEAR_ChannelFile') || !$channel->validate()) { + return false; + } + + if (PEAR::isError($e = $this->_lock(LOCK_EX))) { + return $e; + } + + $ret = $this->_addChannel($channel, $update, $lastmodified); + $this->_unlock(); + if (!$update && $ret && is_a($this->_config, 'PEAR_Config')) { + $this->_config->setChannels($this->listChannels()); + } + + return $ret; + } + + // }}} + // {{{ deletePackage() + + function deletePackage($package, $channel = 'pear.php.net') + { + if (PEAR::isError($e = $this->_lock(LOCK_EX))) { + return $e; + } + + $file = $this->_packageFileName($package, $channel); + $ret = file_exists($file) ? @unlink($file) : false; + $this->_rebuildFileMap(); + $this->_unlock(); + $p = array('channel' => $channel, 'package' => $package); + $this->_dependencyDB->uninstallPackage($p); + return $ret; + } + + // }}} + // {{{ updatePackage() + + function updatePackage($package, $info, $merge = true) + { + if (is_object($info)) { + return $this->updatePackage2($info, $merge); + } + if (PEAR::isError($e = $this->_lock(LOCK_EX))) { + return $e; + } + $ret = $this->_updatePackage($package, $info, $merge); + $this->_unlock(); + if ($ret) { + if (!class_exists('PEAR_PackageFile_v1')) { + require_once 'PEAR/PackageFile/v1.php'; + } + $pf = new PEAR_PackageFile_v1; + $pf->setConfig($this->_config); + $pf->fromArray($this->packageInfo($package)); + $this->_dependencyDB->uninstallPackage($pf); + $this->_dependencyDB->installPackage($pf); + } + return $ret; + } + + // }}} + // {{{ updatePackage2() + + function updatePackage2($info) + { + + if (!is_object($info)) { + return $this->updatePackage($info['package'], $info, $merge); + } + + if (!$info->validate(PEAR_VALIDATE_DOWNLOADING)) { + return false; + } + + if (PEAR::isError($e = $this->_lock(LOCK_EX))) { + return $e; + } + + $ret = $this->_updatePackage2($info); + $this->_unlock(); + if ($ret) { + $this->_dependencyDB->uninstallPackage($info); + $this->_dependencyDB->installPackage($info); + } + + return $ret; + } + + // }}} + // {{{ getChannel() + /** + * @param string channel name + * @param bool whether to strictly return raw channels (no aliases) + * @return PEAR_ChannelFile|PEAR_Error + */ + function &getChannel($channel, $noaliases = false) + { + if (PEAR::isError($e = $this->_lock(LOCK_SH))) { + return $e; + } + $ret = &$this->_getChannel($channel, $noaliases); + $this->_unlock(); + if (!$ret) { + return PEAR::raiseError('Unknown channel: ' . $channel); + } + return $ret; + } + + // }}} + // {{{ getPackage() + /** + * @param string package name + * @param string channel name + * @return PEAR_PackageFile_v1|PEAR_PackageFile_v2|null + */ + function &getPackage($package, $channel = 'pear.php.net') + { + if (PEAR::isError($e = $this->_lock(LOCK_SH))) { + return $e; + } + $pf = &$this->_getPackage($package, $channel); + $this->_unlock(); + return $pf; + } + + // }}} + + /** + * Get PEAR_PackageFile_v[1/2] objects representing the contents of + * a dependency group that are installed. + * + * This is used at uninstall-time + * @param array + * @return array|false + */ + function getInstalledGroup($group) + { + $ret = array(); + if (isset($group['package'])) { + if (!isset($group['package'][0])) { + $group['package'] = array($group['package']); + } + foreach ($group['package'] as $package) { + $depchannel = isset($package['channel']) ? $package['channel'] : '__uri'; + $p = &$this->getPackage($package['name'], $depchannel); + if ($p) { + $save = &$p; + $ret[] = &$save; + } + } + } + if (isset($group['subpackage'])) { + if (!isset($group['subpackage'][0])) { + $group['subpackage'] = array($group['subpackage']); + } + foreach ($group['subpackage'] as $package) { + $depchannel = isset($package['channel']) ? $package['channel'] : '__uri'; + $p = &$this->getPackage($package['name'], $depchannel); + if ($p) { + $save = &$p; + $ret[] = &$save; + } + } + } + if (!count($ret)) { + return false; + } + return $ret; + } + + // {{{ getChannelValidator() + /** + * @param string channel name + * @return PEAR_Validate|false + */ + function &getChannelValidator($channel) + { + $chan = $this->getChannel($channel); + if (PEAR::isError($chan)) { + return $chan; + } + $val = $chan->getValidationObject(); + return $val; + } + // }}} + // {{{ getChannels() + /** + * @param string channel name + * @return array an array of PEAR_ChannelFile objects representing every installed channel + */ + function &getChannels() + { + $ret = array(); + if (PEAR::isError($e = $this->_lock(LOCK_SH))) { + return $e; + } + foreach ($this->_listChannels() as $channel) { + $e = &$this->_getChannel($channel); + if (!$e || PEAR::isError($e)) { + continue; + } + $ret[] = $e; + } + $this->_unlock(); + return $ret; + } + + // }}} + // {{{ checkFileMap() + + /** + * Test whether a file or set of files belongs to a package. + * + * If an array is passed in + * @param string|array file path, absolute or relative to the pear + * install dir + * @param string|array name of PEAR package or array('package' => name, 'channel' => + * channel) of a package that will be ignored + * @param string API version - 1.1 will exclude any files belonging to a package + * @param array private recursion variable + * @return array|false which package and channel the file belongs to, or an empty + * string if the file does not belong to an installed package, + * or belongs to the second parameter's package + */ + function checkFileMap($path, $package = false, $api = '1.0', $attrs = false) + { + if (is_array($path)) { + static $notempty; + if (empty($notempty)) { + if (!class_exists('PEAR_Installer_Role')) { + require_once 'PEAR/Installer/Role.php'; + } + $notempty = create_function('$a','return !empty($a);'); + } + $package = is_array($package) ? array(strtolower($package[0]), strtolower($package[1])) + : strtolower($package); + $pkgs = array(); + foreach ($path as $name => $attrs) { + if (is_array($attrs)) { + if (isset($attrs['install-as'])) { + $name = $attrs['install-as']; + } + if (!in_array($attrs['role'], PEAR_Installer_Role::getInstallableRoles())) { + // these are not installed + continue; + } + if (!in_array($attrs['role'], PEAR_Installer_Role::getBaseinstallRoles())) { + $attrs['baseinstalldir'] = is_array($package) ? $package[1] : $package; + } + if (isset($attrs['baseinstalldir'])) { + $name = $attrs['baseinstalldir'] . DIRECTORY_SEPARATOR . $name; + } + } + $pkgs[$name] = $this->checkFileMap($name, $package, $api, $attrs); + if (PEAR::isError($pkgs[$name])) { + return $pkgs[$name]; + } + } + return array_filter($pkgs, $notempty); + } + if (empty($this->filemap_cache)) { + if (PEAR::isError($e = $this->_lock(LOCK_SH))) { + return $e; + } + $err = $this->_readFileMap(); + $this->_unlock(); + if (PEAR::isError($err)) { + return $err; + } + } + if (!$attrs) { + $attrs = array('role' => 'php'); // any old call would be for PHP role only + } + if (isset($this->filemap_cache[$attrs['role']][$path])) { + if ($api >= '1.1' && $this->filemap_cache[$attrs['role']][$path] == $package) { + return false; + } + return $this->filemap_cache[$attrs['role']][$path]; + } + $l = strlen($this->install_dir); + if (substr($path, 0, $l) == $this->install_dir) { + $path = preg_replace('!^'.DIRECTORY_SEPARATOR.'+!', '', substr($path, $l)); + } + if (isset($this->filemap_cache[$attrs['role']][$path])) { + if ($api >= '1.1' && $this->filemap_cache[$attrs['role']][$path] == $package) { + return false; + } + return $this->filemap_cache[$attrs['role']][$path]; + } + return false; + } + + // }}} + // {{{ flush() + /** + * Force a reload of the filemap + * @since 1.5.0RC3 + */ + function flushFileMap() + { + $this->filemap_cache = null; + clearstatcache(); // ensure that the next read gets the full, current filemap + } + + // }}} + // {{{ apiVersion() + /** + * Get the expected API version. Channels API is version 1.1, as it is backwards + * compatible with 1.0 + * @return string + */ + function apiVersion() + { + return '1.1'; + } + // }}} + + + /** + * Parse a package name, or validate a parsed package name array + * @param string|array pass in an array of format + * array( + * 'package' => 'pname', + * ['channel' => 'channame',] + * ['version' => 'version',] + * ['state' => 'state',] + * ['group' => 'groupname']) + * or a string of format + * [channel://][channame/]pname[-version|-state][/group=groupname] + * @return array|PEAR_Error + */ + function parsePackageName($param, $defaultchannel = 'pear.php.net') + { + $saveparam = $param; + if (is_array($param)) { + // convert to string for error messages + $saveparam = $this->parsedPackageNameToString($param); + // process the array + if (!isset($param['package'])) { + return PEAR::raiseError('parsePackageName(): array $param ' . + 'must contain a valid package name in index "param"', + 'package', null, null, $param); + } + if (!isset($param['uri'])) { + if (!isset($param['channel'])) { + $param['channel'] = $defaultchannel; + } + } else { + $param['channel'] = '__uri'; + } + } else { + $components = @parse_url((string) $param); + if (isset($components['scheme'])) { + if ($components['scheme'] == 'http') { + // uri package + $param = array('uri' => $param, 'channel' => '__uri'); + } elseif($components['scheme'] != 'channel') { + return PEAR::raiseError('parsePackageName(): only channel:// uris may ' . + 'be downloaded, not "' . $param . '"', 'invalid', null, null, $param); + } + } + if (!isset($components['path'])) { + return PEAR::raiseError('parsePackageName(): array $param ' . + 'must contain a valid package name in "' . $param . '"', + 'package', null, null, $param); + } + if (isset($components['host'])) { + // remove the leading "/" + $components['path'] = substr($components['path'], 1); + } + if (!isset($components['scheme'])) { + if (strpos($components['path'], '/') !== false) { + if ($components['path']{0} == '/') { + return PEAR::raiseError('parsePackageName(): this is not ' . + 'a package name, it begins with "/" in "' . $param . '"', + 'invalid', null, null, $param); + } + $parts = explode('/', $components['path']); + $components['host'] = array_shift($parts); + if (count($parts) > 1) { + $components['path'] = array_pop($parts); + $components['host'] .= '/' . implode('/', $parts); + } else { + $components['path'] = implode('/', $parts); + } + } else { + $components['host'] = $defaultchannel; + } + } else { + if (strpos($components['path'], '/')) { + $parts = explode('/', $components['path']); + $components['path'] = array_pop($parts); + $components['host'] .= '/' . implode('/', $parts); + } + } + + if (is_array($param)) { + $param['package'] = $components['path']; + } else { + $param = array( + 'package' => $components['path'] + ); + if (isset($components['host'])) { + $param['channel'] = $components['host']; + } + } + if (isset($components['fragment'])) { + $param['group'] = $components['fragment']; + } + if (isset($components['user'])) { + $param['user'] = $components['user']; + } + if (isset($components['pass'])) { + $param['pass'] = $components['pass']; + } + if (isset($components['query'])) { + parse_str($components['query'], $param['opts']); + } + // check for extension + $pathinfo = pathinfo($param['package']); + if (isset($pathinfo['extension']) && + in_array(strtolower($pathinfo['extension']), array('tgz', 'tar'))) { + $param['extension'] = $pathinfo['extension']; + $param['package'] = substr($pathinfo['basename'], 0, + strlen($pathinfo['basename']) - 4); + } + // check for version + if (strpos($param['package'], '-')) { + $test = explode('-', $param['package']); + if (count($test) != 2) { + return PEAR::raiseError('parsePackageName(): only one version/state ' . + 'delimiter "-" is allowed in "' . $saveparam . '"', + 'version', null, null, $param); + } + list($param['package'], $param['version']) = $test; + } + } + // validation + $info = $this->channelExists($param['channel']); + if (PEAR::isError($info)) { + return $info; + } + if (!$info) { + return PEAR::raiseError('unknown channel "' . $param['channel'] . + '" in "' . $saveparam . '"', 'channel', null, null, $param); + } + $chan = $this->getChannel($param['channel']); + if (PEAR::isError($chan)) { + return $chan; + } + if (!$chan) { + return PEAR::raiseError("Exception: corrupt registry, could not " . + "retrieve channel " . $param['channel'] . " information", + 'registry', null, null, $param); + } + $param['channel'] = $chan->getName(); + $validate = $chan->getValidationObject(); + $vpackage = $chan->getValidationPackage(); + // validate package name + if (!$validate->validPackageName($param['package'], $vpackage['_content'])) { + return PEAR::raiseError('parsePackageName(): invalid package name "' . + $param['package'] . '" in "' . $saveparam . '"', + 'package', null, null, $param); + } + if (isset($param['group'])) { + if (!PEAR_Validate::validGroupName($param['group'])) { + return PEAR::raiseError('parsePackageName(): dependency group "' . $param['group'] . + '" is not a valid group name in "' . $saveparam . '"', 'group', null, null, + $param); + } + } + if (isset($param['state'])) { + if (!in_array(strtolower($param['state']), $validate->getValidStates())) { + return PEAR::raiseError('parsePackageName(): state "' . $param['state'] + . '" is not a valid state in "' . $saveparam . '"', + 'state', null, null, $param); + } + } + if (isset($param['version'])) { + if (isset($param['state'])) { + return PEAR::raiseError('parsePackageName(): cannot contain both ' . + 'a version and a stability (state) in "' . $saveparam . '"', + 'version/state', null, null, $param); + } + // check whether version is actually a state + if (in_array(strtolower($param['version']), $validate->getValidStates())) { + $param['state'] = strtolower($param['version']); + unset($param['version']); + } else { + if (!$validate->validVersion($param['version'])) { + return PEAR::raiseError('parsePackageName(): "' . $param['version'] . + '" is neither a valid version nor a valid state in "' . + $saveparam . '"', 'version/state', null, null, $param); + } + } + } + return $param; + } + + /** + * @param array + * @return string + */ + function parsedPackageNameToString($parsed, $brief = false) + { + if (is_string($parsed)) { + return $parsed; + } + if (is_object($parsed)) { + $p = $parsed; + $parsed = array( + 'package' => $p->getPackage(), + 'channel' => $p->getChannel(), + 'version' => $p->getVersion(), + ); + } + if (isset($parsed['uri'])) { + return $parsed['uri']; + } + if ($brief) { + if ($channel = $this->channelAlias($parsed['channel'])) { + return $channel . '/' . $parsed['package']; + } + } + $upass = ''; + if (isset($parsed['user'])) { + $upass = $parsed['user']; + if (isset($parsed['pass'])) { + $upass .= ':' . $parsed['pass']; + } + $upass = "$upass@"; + } + $ret = 'channel://' . $upass . $parsed['channel'] . '/' . $parsed['package']; + if (isset($parsed['version']) || isset($parsed['state'])) { + $ver = isset($parsed['version']) ? $parsed['version'] : ''; + $ver .= isset($parsed['state']) ? $parsed['state'] : ''; + $ret .= '-' . $ver; + } + if (isset($parsed['extension'])) { + $ret .= '.' . $parsed['extension']; + } + if (isset($parsed['opts'])) { + $ret .= '?'; + foreach ($parsed['opts'] as $name => $value) { + $parsed['opts'][$name] = "$name=$value"; + } + $ret .= implode('&', $parsed['opts']); + } + if (isset($parsed['group'])) { + $ret .= '#' . $parsed['group']; + } + return $ret; + } +} \ No newline at end of file diff --git a/library/pear/PEAR/RunTest.php b/library/pear/PEAR/RunTest.php new file mode 100644 index 000000000..6db03527f --- /dev/null +++ b/library/pear/PEAR/RunTest.php @@ -0,0 +1,962 @@ + + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: RunTest.php 297621 2010-04-07 15:09:33Z sebastian $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.3.3 + */ + +/** + * for error handling + */ +require_once 'PEAR.php'; +require_once 'PEAR/Config.php'; + +define('DETAILED', 1); +putenv("PHP_PEAR_RUNTESTS=1"); + +/** + * Simplified version of PHP's test suite + * + * Try it with: + * + * $ php -r 'include "../PEAR/RunTest.php"; $t=new PEAR_RunTest; $o=$t->run("./pear_system.phpt");print_r($o);' + * + * + * @category pear + * @package PEAR + * @author Tomas V.V.Cox + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.1 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.3.3 + */ +class PEAR_RunTest +{ + var $_headers = array(); + var $_logger; + var $_options; + var $_php; + var $tests_count; + var $xdebug_loaded; + /** + * Saved value of php executable, used to reset $_php when we + * have a test that uses cgi + * + * @var unknown_type + */ + var $_savephp; + var $ini_overwrites = array( + 'output_handler=', + 'open_basedir=', + 'safe_mode=0', + 'disable_functions=', + 'output_buffering=Off', + 'display_errors=1', + 'log_errors=0', + 'html_errors=0', + 'track_errors=1', + 'report_memleaks=0', + 'report_zend_debug=0', + 'docref_root=', + 'docref_ext=.html', + 'error_prepend_string=', + 'error_append_string=', + 'auto_prepend_file=', + 'auto_append_file=', + 'magic_quotes_runtime=0', + 'xdebug.default_enable=0', + 'allow_url_fopen=1', + ); + + /** + * An object that supports the PEAR_Common->log() signature, or null + * @param PEAR_Common|null + */ + function PEAR_RunTest($logger = null, $options = array()) + { + if (!defined('E_DEPRECATED')) { + define('E_DEPRECATED', 0); + } + if (!defined('E_STRICT')) { + define('E_STRICT', 0); + } + $this->ini_overwrites[] = 'error_reporting=' . (E_ALL & ~(E_DEPRECATED | E_STRICT)); + if (is_null($logger)) { + require_once 'PEAR/Common.php'; + $logger = new PEAR_Common; + } + $this->_logger = $logger; + $this->_options = $options; + + $conf = &PEAR_Config::singleton(); + $this->_php = $conf->get('php_bin'); + } + + /** + * Taken from php-src/run-tests.php + * + * @param string $commandline command name + * @param array $env + * @param string $stdin standard input to pass to the command + * @return unknown + */ + function system_with_timeout($commandline, $env = null, $stdin = null) + { + $data = ''; + if (version_compare(phpversion(), '5.0.0', '<')) { + $proc = proc_open($commandline, array( + 0 => array('pipe', 'r'), + 1 => array('pipe', 'w'), + 2 => array('pipe', 'w') + ), $pipes); + } else { + $proc = proc_open($commandline, array( + 0 => array('pipe', 'r'), + 1 => array('pipe', 'w'), + 2 => array('pipe', 'w') + ), $pipes, null, $env, array('suppress_errors' => true)); + } + + if (!$proc) { + return false; + } + + if (is_string($stdin)) { + fwrite($pipes[0], $stdin); + } + fclose($pipes[0]); + + while (true) { + /* hide errors from interrupted syscalls */ + $r = $pipes; + $e = $w = null; + $n = @stream_select($r, $w, $e, 60); + + if ($n === 0) { + /* timed out */ + $data .= "\n ** ERROR: process timed out **\n"; + proc_terminate($proc); + return array(1234567890, $data); + } else if ($n > 0) { + $line = fread($pipes[1], 8192); + if (strlen($line) == 0) { + /* EOF */ + break; + } + $data .= $line; + } + } + if (function_exists('proc_get_status')) { + $stat = proc_get_status($proc); + if ($stat['signaled']) { + $data .= "\nTermsig=".$stat['stopsig']; + } + } + $code = proc_close($proc); + if (function_exists('proc_get_status')) { + $code = $stat['exitcode']; + } + return array($code, $data); + } + + /** + * Turns a PHP INI string into an array + * + * Turns -d "include_path=/foo/bar" into this: + * array( + * 'include_path' => array( + * 'operator' => '-d', + * 'value' => '/foo/bar', + * ) + * ) + * Works both with quotes and without + * + * @param string an PHP INI string, -d "include_path=/foo/bar" + * @return array + */ + function iniString2array($ini_string) + { + if (!$ini_string) { + return array(); + } + $split = preg_split('/[\s]|=/', $ini_string, -1, PREG_SPLIT_NO_EMPTY); + $key = $split[1][0] == '"' ? substr($split[1], 1) : $split[1]; + $value = $split[2][strlen($split[2]) - 1] == '"' ? substr($split[2], 0, -1) : $split[2]; + // FIXME review if this is really the struct to go with + $array = array($key => array('operator' => $split[0], 'value' => $value)); + return $array; + } + + function settings2array($settings, $ini_settings) + { + foreach ($settings as $setting) { + if (strpos($setting, '=') !== false) { + $setting = explode('=', $setting, 2); + $name = trim(strtolower($setting[0])); + $value = trim($setting[1]); + $ini_settings[$name] = $value; + } + } + return $ini_settings; + } + + function settings2params($ini_settings) + { + $settings = ''; + foreach ($ini_settings as $name => $value) { + if (is_array($value)) { + $operator = $value['operator']; + $value = $value['value']; + } else { + $operator = '-d'; + } + $value = addslashes($value); + $settings .= " $operator \"$name=$value\""; + } + return $settings; + } + + function _preparePhpBin($php, $file, $ini_settings) + { + $file = escapeshellarg($file); + // This was fixed in php 5.3 and is not needed after that + if (OS_WINDOWS && version_compare(PHP_VERSION, '5.3', '<')) { + $cmd = '"'.escapeshellarg($php).' '.$ini_settings.' -f ' . $file .'"'; + } else { + $cmd = $php . $ini_settings . ' -f ' . $file; + } + + return $cmd; + } + + function runPHPUnit($file, $ini_settings = '') + { + if (!file_exists($file) && file_exists(getcwd() . DIRECTORY_SEPARATOR . $file)) { + $file = realpath(getcwd() . DIRECTORY_SEPARATOR . $file); + } elseif (file_exists($file)) { + $file = realpath($file); + } + + $cmd = $this->_preparePhpBin($this->_php, $file, $ini_settings); + if (isset($this->_logger)) { + $this->_logger->log(2, 'Running command "' . $cmd . '"'); + } + + $savedir = getcwd(); // in case the test moves us around + chdir(dirname($file)); + echo `$cmd`; + chdir($savedir); + return 'PASSED'; // we have no way of knowing this information so assume passing + } + + /** + * Runs an individual test case. + * + * @param string The filename of the test + * @param array|string INI settings to be applied to the test run + * @param integer Number what the current running test is of the + * whole test suite being runned. + * + * @return string|object Returns PASSED, WARNED, FAILED depending on how the + * test came out. + * PEAR Error when the tester it self fails + */ + function run($file, $ini_settings = array(), $test_number = 1) + { + if (isset($this->_savephp)) { + $this->_php = $this->_savephp; + unset($this->_savephp); + } + if (empty($this->_options['cgi'])) { + // try to see if php-cgi is in the path + $res = $this->system_with_timeout('php-cgi -v'); + if (false !== $res && !(is_array($res) && $res === array(127, ''))) { + $this->_options['cgi'] = 'php-cgi'; + } + } + if (1 < $len = strlen($this->tests_count)) { + $test_number = str_pad($test_number, $len, ' ', STR_PAD_LEFT); + $test_nr = "[$test_number/$this->tests_count] "; + } else { + $test_nr = ''; + } + + $file = realpath($file); + $section_text = $this->_readFile($file); + if (PEAR::isError($section_text)) { + return $section_text; + } + + if (isset($section_text['POST_RAW']) && isset($section_text['UPLOAD'])) { + return PEAR::raiseError("Cannot contain both POST_RAW and UPLOAD in test file: $file"); + } + + $cwd = getcwd(); + + $pass_options = ''; + if (!empty($this->_options['ini'])) { + $pass_options = $this->_options['ini']; + } + + if (is_string($ini_settings)) { + $ini_settings = $this->iniString2array($ini_settings); + } + + $ini_settings = $this->settings2array($this->ini_overwrites, $ini_settings); + if ($section_text['INI']) { + if (strpos($section_text['INI'], '{PWD}') !== false) { + $section_text['INI'] = str_replace('{PWD}', dirname($file), $section_text['INI']); + } + $ini = preg_split( "/[\n\r]+/", $section_text['INI']); + $ini_settings = $this->settings2array($ini, $ini_settings); + } + $ini_settings = $this->settings2params($ini_settings); + $shortname = str_replace($cwd . DIRECTORY_SEPARATOR, '', $file); + + $tested = trim($section_text['TEST']); + $tested.= !isset($this->_options['simple']) ? "[$shortname]" : ' '; + + if (!empty($section_text['POST']) || !empty($section_text['POST_RAW']) || + !empty($section_text['UPLOAD']) || !empty($section_text['GET']) || + !empty($section_text['COOKIE']) || !empty($section_text['EXPECTHEADERS'])) { + if (empty($this->_options['cgi'])) { + if (!isset($this->_options['quiet'])) { + $this->_logger->log(0, "SKIP $test_nr$tested (reason: --cgi option needed for this test, type 'pear help run-tests')"); + } + if (isset($this->_options['tapoutput'])) { + return array('ok', ' # skip --cgi option needed for this test, "pear help run-tests" for info'); + } + return 'SKIPPED'; + } + $this->_savephp = $this->_php; + $this->_php = $this->_options['cgi']; + } + + $temp_dir = realpath(dirname($file)); + $main_file_name = basename($file, 'phpt'); + $diff_filename = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name.'diff'; + $log_filename = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name.'log'; + $exp_filename = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name.'exp'; + $output_filename = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name.'out'; + $memcheck_filename = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name.'mem'; + $temp_file = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name.'php'; + $temp_skipif = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name.'skip.php'; + $temp_clean = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name.'clean.php'; + $tmp_post = $temp_dir . DIRECTORY_SEPARATOR . uniqid('phpt.'); + + // unlink old test results + $this->_cleanupOldFiles($file); + + // Check if test should be skipped. + $res = $this->_runSkipIf($section_text, $temp_skipif, $tested, $ini_settings); + if (count($res) != 2) { + return $res; + } + $info = $res['info']; + $warn = $res['warn']; + + // We've satisfied the preconditions - run the test! + if (isset($this->_options['coverage']) && $this->xdebug_loaded) { + $xdebug_file = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name . 'xdebug'; + $text = "\n" . 'function coverage_shutdown() {' . + "\n" . ' $xdebug = var_export(xdebug_get_code_coverage(), true);'; + if (!function_exists('file_put_contents')) { + $text .= "\n" . ' $fh = fopen(\'' . $xdebug_file . '\', "wb");' . + "\n" . ' if ($fh !== false) {' . + "\n" . ' fwrite($fh, $xdebug);' . + "\n" . ' fclose($fh);' . + "\n" . ' }'; + } else { + $text .= "\n" . ' file_put_contents(\'' . $xdebug_file . '\', $xdebug);'; + } + + $text .= "\n" . 'xdebug_stop_code_coverage();' . + "\n" . '} // end coverage_shutdown()' . + "\n" . 'register_shutdown_function("coverage_shutdown");'; + $text .= "\n" . 'xdebug_start_code_coverage(XDEBUG_CC_UNUSED | XDEBUG_CC_DEAD_CODE);' . "\n?>"; + + // Workaround for http://pear.php.net/bugs/bug.php?id=17292 + $lines = explode("\n", $section_text['FILE']); + $numLines = count($lines); + $namespace = ''; + + for ($i = 0; $i < $numLines; $i++) { + $lines[$i] = trim($lines[$i]); + + if ($lines[$i] == 'save_text($temp_file, "save_text($temp_file, $section_text['FILE']); + } + + $args = $section_text['ARGS'] ? ' -- '.$section_text['ARGS'] : ''; + $cmd = $this->_preparePhpBin($this->_php, $temp_file, $ini_settings); + $cmd.= "$args 2>&1"; + if (isset($this->_logger)) { + $this->_logger->log(2, 'Running command "' . $cmd . '"'); + } + + // Reset environment from any previous test. + $env = $this->_resetEnv($section_text, $temp_file); + + $section_text = $this->_processUpload($section_text, $file); + if (PEAR::isError($section_text)) { + return $section_text; + } + + if (array_key_exists('POST_RAW', $section_text) && !empty($section_text['POST_RAW'])) { + $post = trim($section_text['POST_RAW']); + $raw_lines = explode("\n", $post); + + $request = ''; + $started = false; + foreach ($raw_lines as $i => $line) { + if (empty($env['CONTENT_TYPE']) && + preg_match('/^Content-Type:(.*)/i', $line, $res)) { + $env['CONTENT_TYPE'] = trim(str_replace("\r", '', $res[1])); + continue; + } + if ($started) { + $request .= "\n"; + } + $started = true; + $request .= $line; + } + + $env['CONTENT_LENGTH'] = strlen($request); + $env['REQUEST_METHOD'] = 'POST'; + + $this->save_text($tmp_post, $request); + $cmd = "$this->_php$pass_options$ini_settings \"$temp_file\" 2>&1 < $tmp_post"; + } elseif (array_key_exists('POST', $section_text) && !empty($section_text['POST'])) { + $post = trim($section_text['POST']); + $this->save_text($tmp_post, $post); + $content_length = strlen($post); + + $env['REQUEST_METHOD'] = 'POST'; + $env['CONTENT_TYPE'] = 'application/x-www-form-urlencoded'; + $env['CONTENT_LENGTH'] = $content_length; + + $cmd = "$this->_php$pass_options$ini_settings \"$temp_file\" 2>&1 < $tmp_post"; + } else { + $env['REQUEST_METHOD'] = 'GET'; + $env['CONTENT_TYPE'] = ''; + $env['CONTENT_LENGTH'] = ''; + } + + if (OS_WINDOWS && isset($section_text['RETURNS'])) { + ob_start(); + system($cmd, $return_value); + $out = ob_get_contents(); + ob_end_clean(); + $section_text['RETURNS'] = (int) trim($section_text['RETURNS']); + $returnfail = ($return_value != $section_text['RETURNS']); + } else { + $returnfail = false; + $stdin = isset($section_text['STDIN']) ? $section_text['STDIN'] : null; + $out = $this->system_with_timeout($cmd, $env, $stdin); + $return_value = $out[0]; + $out = $out[1]; + } + + $output = preg_replace('/\r\n/', "\n", trim($out)); + + if (isset($tmp_post) && realpath($tmp_post) && file_exists($tmp_post)) { + @unlink(realpath($tmp_post)); + } + chdir($cwd); // in case the test moves us around + + $this->_testCleanup($section_text, $temp_clean); + + /* when using CGI, strip the headers from the output */ + $output = $this->_stripHeadersCGI($output); + + if (isset($section_text['EXPECTHEADERS'])) { + $testheaders = $this->_processHeaders($section_text['EXPECTHEADERS']); + $missing = array_diff_assoc($testheaders, $this->_headers); + $changed = ''; + foreach ($missing as $header => $value) { + if (isset($this->_headers[$header])) { + $changed .= "-$header: $value\n+$header: "; + $changed .= $this->_headers[$header]; + } else { + $changed .= "-$header: $value\n"; + } + } + if ($missing) { + // tack on failed headers to output: + $output .= "\n====EXPECTHEADERS FAILURE====:\n$changed"; + } + } + // Does the output match what is expected? + do { + if (isset($section_text['EXPECTF']) || isset($section_text['EXPECTREGEX'])) { + if (isset($section_text['EXPECTF'])) { + $wanted = trim($section_text['EXPECTF']); + } else { + $wanted = trim($section_text['EXPECTREGEX']); + } + $wanted_re = preg_replace('/\r\n/', "\n", $wanted); + if (isset($section_text['EXPECTF'])) { + $wanted_re = preg_quote($wanted_re, '/'); + // Stick to basics + $wanted_re = str_replace("%s", ".+?", $wanted_re); //not greedy + $wanted_re = str_replace("%i", "[+\-]?[0-9]+", $wanted_re); + $wanted_re = str_replace("%d", "[0-9]+", $wanted_re); + $wanted_re = str_replace("%x", "[0-9a-fA-F]+", $wanted_re); + $wanted_re = str_replace("%f", "[+\-]?\.?[0-9]+\.?[0-9]*(E-?[0-9]+)?", $wanted_re); + $wanted_re = str_replace("%c", ".", $wanted_re); + // %f allows two points "-.0.0" but that is the best *simple* expression + } + + /* DEBUG YOUR REGEX HERE + var_dump($wanted_re); + print(str_repeat('=', 80) . "\n"); + var_dump($output); + */ + if (!$returnfail && preg_match("/^$wanted_re\$/s", $output)) { + if (file_exists($temp_file)) { + unlink($temp_file); + } + if (array_key_exists('FAIL', $section_text)) { + break; + } + if (!isset($this->_options['quiet'])) { + $this->_logger->log(0, "PASS $test_nr$tested$info"); + } + if (isset($this->_options['tapoutput'])) { + return array('ok', ' - ' . $tested); + } + return 'PASSED'; + } + } else { + if (isset($section_text['EXPECTFILE'])) { + $f = $temp_dir . '/' . trim($section_text['EXPECTFILE']); + if (!($fp = @fopen($f, 'rb'))) { + return PEAR::raiseError('--EXPECTFILE-- section file ' . + $f . ' not found'); + } + fclose($fp); + $section_text['EXPECT'] = file_get_contents($f); + } + + if (isset($section_text['EXPECT'])) { + $wanted = preg_replace('/\r\n/', "\n", trim($section_text['EXPECT'])); + } else { + $wanted = ''; + } + + // compare and leave on success + if (!$returnfail && 0 == strcmp($output, $wanted)) { + if (file_exists($temp_file)) { + unlink($temp_file); + } + if (array_key_exists('FAIL', $section_text)) { + break; + } + if (!isset($this->_options['quiet'])) { + $this->_logger->log(0, "PASS $test_nr$tested$info"); + } + if (isset($this->_options['tapoutput'])) { + return array('ok', ' - ' . $tested); + } + return 'PASSED'; + } + } + } while (false); + + if (array_key_exists('FAIL', $section_text)) { + // we expect a particular failure + // this is only used for testing PEAR_RunTest + $expectf = isset($section_text['EXPECTF']) ? $wanted_re : null; + $faildiff = $this->generate_diff($wanted, $output, null, $expectf); + $faildiff = preg_replace('/\r/', '', $faildiff); + $wanted = preg_replace('/\r/', '', trim($section_text['FAIL'])); + if ($faildiff == $wanted) { + if (!isset($this->_options['quiet'])) { + $this->_logger->log(0, "PASS $test_nr$tested$info"); + } + if (isset($this->_options['tapoutput'])) { + return array('ok', ' - ' . $tested); + } + return 'PASSED'; + } + unset($section_text['EXPECTF']); + $output = $faildiff; + if (isset($section_text['RETURNS'])) { + return PEAR::raiseError('Cannot have both RETURNS and FAIL in the same test: ' . + $file); + } + } + + // Test failed so we need to report details. + $txt = $warn ? 'WARN ' : 'FAIL '; + $this->_logger->log(0, $txt . $test_nr . $tested . $info); + + // write .exp + $res = $this->_writeLog($exp_filename, $wanted); + if (PEAR::isError($res)) { + return $res; + } + + // write .out + $res = $this->_writeLog($output_filename, $output); + if (PEAR::isError($res)) { + return $res; + } + + // write .diff + $returns = isset($section_text['RETURNS']) ? + array(trim($section_text['RETURNS']), $return_value) : null; + $expectf = isset($section_text['EXPECTF']) ? $wanted_re : null; + $data = $this->generate_diff($wanted, $output, $returns, $expectf); + $res = $this->_writeLog($diff_filename, $data); + if (PEAR::isError($res)) { + return $res; + } + + // write .log + $data = " +---- EXPECTED OUTPUT +$wanted +---- ACTUAL OUTPUT +$output +---- FAILED +"; + + if ($returnfail) { + $data .= " +---- EXPECTED RETURN +$section_text[RETURNS] +---- ACTUAL RETURN +$return_value +"; + } + + $res = $this->_writeLog($log_filename, $data); + if (PEAR::isError($res)) { + return $res; + } + + if (isset($this->_options['tapoutput'])) { + $wanted = explode("\n", $wanted); + $wanted = "# Expected output:\n#\n#" . implode("\n#", $wanted); + $output = explode("\n", $output); + $output = "#\n#\n# Actual output:\n#\n#" . implode("\n#", $output); + return array($wanted . $output . 'not ok', ' - ' . $tested); + } + return $warn ? 'WARNED' : 'FAILED'; + } + + function generate_diff($wanted, $output, $rvalue, $wanted_re) + { + $w = explode("\n", $wanted); + $o = explode("\n", $output); + $wr = explode("\n", $wanted_re); + $w1 = array_diff_assoc($w, $o); + $o1 = array_diff_assoc($o, $w); + $o2 = $w2 = array(); + foreach ($w1 as $idx => $val) { + if (!$wanted_re || !isset($wr[$idx]) || !isset($o1[$idx]) || + !preg_match('/^' . $wr[$idx] . '\\z/', $o1[$idx])) { + $w2[sprintf("%03d<", $idx)] = sprintf("%03d- ", $idx + 1) . $val; + } + } + foreach ($o1 as $idx => $val) { + if (!$wanted_re || !isset($wr[$idx]) || + !preg_match('/^' . $wr[$idx] . '\\z/', $val)) { + $o2[sprintf("%03d>", $idx)] = sprintf("%03d+ ", $idx + 1) . $val; + } + } + $diff = array_merge($w2, $o2); + ksort($diff); + $extra = $rvalue ? "##EXPECTED: $rvalue[0]\r\n##RETURNED: $rvalue[1]" : ''; + return implode("\r\n", $diff) . $extra; + } + + // Write the given text to a temporary file, and return the filename. + function save_text($filename, $text) + { + if (!$fp = fopen($filename, 'w')) { + return PEAR::raiseError("Cannot open file '" . $filename . "' (save_text)"); + } + fwrite($fp, $text); + fclose($fp); + if (1 < DETAILED) echo " +FILE $filename {{{ +$text +}}} +"; + } + + function _cleanupOldFiles($file) + { + $temp_dir = realpath(dirname($file)); + $mainFileName = basename($file, 'phpt'); + $diff_filename = $temp_dir . DIRECTORY_SEPARATOR . $mainFileName.'diff'; + $log_filename = $temp_dir . DIRECTORY_SEPARATOR . $mainFileName.'log'; + $exp_filename = $temp_dir . DIRECTORY_SEPARATOR . $mainFileName.'exp'; + $output_filename = $temp_dir . DIRECTORY_SEPARATOR . $mainFileName.'out'; + $memcheck_filename = $temp_dir . DIRECTORY_SEPARATOR . $mainFileName.'mem'; + $temp_file = $temp_dir . DIRECTORY_SEPARATOR . $mainFileName.'php'; + $temp_skipif = $temp_dir . DIRECTORY_SEPARATOR . $mainFileName.'skip.php'; + $temp_clean = $temp_dir . DIRECTORY_SEPARATOR . $mainFileName.'clean.php'; + $tmp_post = $temp_dir . DIRECTORY_SEPARATOR . uniqid('phpt.'); + + // unlink old test results + @unlink($diff_filename); + @unlink($log_filename); + @unlink($exp_filename); + @unlink($output_filename); + @unlink($memcheck_filename); + @unlink($temp_file); + @unlink($temp_skipif); + @unlink($tmp_post); + @unlink($temp_clean); + } + + function _runSkipIf($section_text, $temp_skipif, $tested, $ini_settings) + { + $info = ''; + $warn = false; + if (array_key_exists('SKIPIF', $section_text) && trim($section_text['SKIPIF'])) { + $this->save_text($temp_skipif, $section_text['SKIPIF']); + $output = $this->system_with_timeout("$this->_php$ini_settings -f \"$temp_skipif\""); + $output = $output[1]; + $loutput = ltrim($output); + unlink($temp_skipif); + if (!strncasecmp('skip', $loutput, 4)) { + $skipreason = "SKIP $tested"; + if (preg_match('/^\s*skip\s*(.+)\s*/i', $output, $m)) { + $skipreason .= '(reason: ' . $m[1] . ')'; + } + if (!isset($this->_options['quiet'])) { + $this->_logger->log(0, $skipreason); + } + if (isset($this->_options['tapoutput'])) { + return array('ok', ' # skip ' . $reason); + } + return 'SKIPPED'; + } + + if (!strncasecmp('info', $loutput, 4) + && preg_match('/^\s*info\s*(.+)\s*/i', $output, $m)) { + $info = " (info: $m[1])"; + } + + if (!strncasecmp('warn', $loutput, 4) + && preg_match('/^\s*warn\s*(.+)\s*/i', $output, $m)) { + $warn = true; /* only if there is a reason */ + $info = " (warn: $m[1])"; + } + } + + return array('warn' => $warn, 'info' => $info); + } + + function _stripHeadersCGI($output) + { + $this->headers = array(); + if (!empty($this->_options['cgi']) && + $this->_php == $this->_options['cgi'] && + preg_match("/^(.*?)(?:\n\n(.*)|\\z)/s", $output, $match)) { + $output = isset($match[2]) ? trim($match[2]) : ''; + $this->_headers = $this->_processHeaders($match[1]); + } + + return $output; + } + + /** + * Return an array that can be used with array_diff() to compare headers + * + * @param string $text + */ + function _processHeaders($text) + { + $headers = array(); + $rh = preg_split("/[\n\r]+/", $text); + foreach ($rh as $line) { + if (strpos($line, ':')!== false) { + $line = explode(':', $line, 2); + $headers[trim($line[0])] = trim($line[1]); + } + } + return $headers; + } + + function _readFile($file) + { + // Load the sections of the test file. + $section_text = array( + 'TEST' => '(unnamed test)', + 'SKIPIF' => '', + 'GET' => '', + 'COOKIE' => '', + 'POST' => '', + 'ARGS' => '', + 'INI' => '', + 'CLEAN' => '', + ); + + if (!is_file($file) || !$fp = fopen($file, "r")) { + return PEAR::raiseError("Cannot open test file: $file"); + } + + $section = ''; + while (!feof($fp)) { + $line = fgets($fp); + + // Match the beginning of a section. + if (preg_match('/^--([_A-Z]+)--/', $line, $r)) { + $section = $r[1]; + $section_text[$section] = ''; + continue; + } elseif (empty($section)) { + fclose($fp); + return PEAR::raiseError("Invalid sections formats in test file: $file"); + } + + // Add to the section text. + $section_text[$section] .= $line; + } + fclose($fp); + + return $section_text; + } + + function _writeLog($logname, $data) + { + if (!$log = fopen($logname, 'w')) { + return PEAR::raiseError("Cannot create test log - $logname"); + } + fwrite($log, $data); + fclose($log); + } + + function _resetEnv($section_text, $temp_file) + { + $env = $_ENV; + $env['REDIRECT_STATUS'] = ''; + $env['QUERY_STRING'] = ''; + $env['PATH_TRANSLATED'] = ''; + $env['SCRIPT_FILENAME'] = ''; + $env['REQUEST_METHOD'] = ''; + $env['CONTENT_TYPE'] = ''; + $env['CONTENT_LENGTH'] = ''; + if (!empty($section_text['ENV'])) { + if (strpos($section_text['ENV'], '{PWD}') !== false) { + $section_text['ENV'] = str_replace('{PWD}', dirname($temp_file), $section_text['ENV']); + } + foreach (explode("\n", trim($section_text['ENV'])) as $e) { + $e = explode('=', trim($e), 2); + if (!empty($e[0]) && isset($e[1])) { + $env[$e[0]] = $e[1]; + } + } + } + if (array_key_exists('GET', $section_text)) { + $env['QUERY_STRING'] = trim($section_text['GET']); + } else { + $env['QUERY_STRING'] = ''; + } + if (array_key_exists('COOKIE', $section_text)) { + $env['HTTP_COOKIE'] = trim($section_text['COOKIE']); + } else { + $env['HTTP_COOKIE'] = ''; + } + $env['REDIRECT_STATUS'] = '1'; + $env['PATH_TRANSLATED'] = $temp_file; + $env['SCRIPT_FILENAME'] = $temp_file; + + return $env; + } + + function _processUpload($section_text, $file) + { + if (array_key_exists('UPLOAD', $section_text) && !empty($section_text['UPLOAD'])) { + $upload_files = trim($section_text['UPLOAD']); + $upload_files = explode("\n", $upload_files); + + $request = "Content-Type: multipart/form-data; boundary=---------------------------20896060251896012921717172737\n" . + "-----------------------------20896060251896012921717172737\n"; + foreach ($upload_files as $fileinfo) { + $fileinfo = explode('=', $fileinfo); + if (count($fileinfo) != 2) { + return PEAR::raiseError("Invalid UPLOAD section in test file: $file"); + } + if (!realpath(dirname($file) . '/' . $fileinfo[1])) { + return PEAR::raiseError("File for upload does not exist: $fileinfo[1] " . + "in test file: $file"); + } + $file_contents = file_get_contents(dirname($file) . '/' . $fileinfo[1]); + $fileinfo[1] = basename($fileinfo[1]); + $request .= "Content-Disposition: form-data; name=\"$fileinfo[0]\"; filename=\"$fileinfo[1]\"\n"; + $request .= "Content-Type: text/plain\n\n"; + $request .= $file_contents . "\n" . + "-----------------------------20896060251896012921717172737\n"; + } + + if (array_key_exists('POST', $section_text) && !empty($section_text['POST'])) { + // encode POST raw + $post = trim($section_text['POST']); + $post = explode('&', $post); + foreach ($post as $i => $post_info) { + $post_info = explode('=', $post_info); + if (count($post_info) != 2) { + return PEAR::raiseError("Invalid POST data in test file: $file"); + } + $post_info[0] = rawurldecode($post_info[0]); + $post_info[1] = rawurldecode($post_info[1]); + $post[$i] = $post_info; + } + foreach ($post as $post_info) { + $request .= "Content-Disposition: form-data; name=\"$post_info[0]\"\n\n"; + $request .= $post_info[1] . "\n" . + "-----------------------------20896060251896012921717172737\n"; + } + unset($section_text['POST']); + } + $section_text['POST_RAW'] = $request; + } + + return $section_text; + } + + function _testCleanup($section_text, $temp_clean) + { + if ($section_text['CLEAN']) { + // perform test cleanup + $this->save_text($temp_clean, $section_text['CLEAN']); + $output = $this->system_with_timeout("$this->_php $temp_clean 2>&1"); + if (strlen($output[1])) { + echo "BORKED --CLEAN-- section! output:\n", $output[1]; + } + if (file_exists($temp_clean)) { + unlink($temp_clean); + } + } + } +} diff --git a/library/pear/PEAR/Task/Common.php b/library/pear/PEAR/Task/Common.php new file mode 100644 index 000000000..6a8b9f28c --- /dev/null +++ b/library/pear/PEAR/Task/Common.php @@ -0,0 +1,202 @@ + + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Common.php 276394 2009-02-25 00:15:49Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ +/**#@+ + * Error codes for task validation routines + */ +define('PEAR_TASK_ERROR_NOATTRIBS', 1); +define('PEAR_TASK_ERROR_MISSING_ATTRIB', 2); +define('PEAR_TASK_ERROR_WRONG_ATTRIB_VALUE', 3); +define('PEAR_TASK_ERROR_INVALID', 4); +/**#@-*/ +define('PEAR_TASK_PACKAGE', 1); +define('PEAR_TASK_INSTALL', 2); +define('PEAR_TASK_PACKAGEANDINSTALL', 3); +/** + * A task is an operation that manipulates the contents of a file. + * + * Simple tasks operate on 1 file. Multiple tasks are executed after all files have been + * processed and installed, and are designed to operate on all files containing the task. + * The Post-install script task simply takes advantage of the fact that it will be run + * after installation, replace is a simple task. + * + * Combining tasks is possible, but ordering is significant. + * + * + * + * + * + * + * This will first replace any instance of @data-dir@ in the test.php file + * with the path to the current data directory. Then, it will include the + * test.php file and run the script it contains to configure the package post-installation. + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.1 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + * @abstract + */ +class PEAR_Task_Common +{ + /** + * Valid types for this version are 'simple' and 'multiple' + * + * - simple tasks operate on the contents of a file and write out changes to disk + * - multiple tasks operate on the contents of many files and write out the + * changes directly to disk + * + * Child task classes must override this property. + * @access protected + */ + var $type = 'simple'; + /** + * Determines which install phase this task is executed under + */ + var $phase = PEAR_TASK_INSTALL; + /** + * @access protected + */ + var $config; + /** + * @access protected + */ + var $registry; + /** + * @access protected + */ + var $logger; + /** + * @access protected + */ + var $installphase; + /** + * @param PEAR_Config + * @param PEAR_Common + */ + function PEAR_Task_Common(&$config, &$logger, $phase) + { + $this->config = &$config; + $this->registry = &$config->getRegistry(); + $this->logger = &$logger; + $this->installphase = $phase; + if ($this->type == 'multiple') { + $GLOBALS['_PEAR_TASK_POSTINSTANCES'][get_class($this)][] = &$this; + } + } + + /** + * Validate the basic contents of a task tag. + * @param PEAR_PackageFile_v2 + * @param array + * @param PEAR_Config + * @param array the entire parsed tag + * @return true|array On error, return an array in format: + * array(PEAR_TASK_ERROR_???[, param1][, param2][, ...]) + * + * For PEAR_TASK_ERROR_MISSING_ATTRIB, pass the attribute name in + * For PEAR_TASK_ERROR_WRONG_ATTRIB_VALUE, pass the attribute name and an array + * of legal values in + * @static + * @abstract + */ + function validateXml($pkg, $xml, $config, $fileXml) + { + } + + /** + * Initialize a task instance with the parameters + * @param array raw, parsed xml + * @param array attributes from the tag containing this task + * @param string|null last installed version of this package + * @abstract + */ + function init($xml, $fileAttributes, $lastVersion) + { + } + + /** + * Begin a task processing session. All multiple tasks will be processed after each file + * has been successfully installed, all simple tasks should perform their task here and + * return any errors using the custom throwError() method to allow forward compatibility + * + * This method MUST NOT write out any changes to disk + * @param PEAR_PackageFile_v2 + * @param string file contents + * @param string the eventual final file location (informational only) + * @return string|false|PEAR_Error false to skip this file, PEAR_Error to fail + * (use $this->throwError), otherwise return the new contents + * @abstract + */ + function startSession($pkg, $contents, $dest) + { + } + + /** + * This method is used to process each of the tasks for a particular multiple class + * type. Simple tasks need not implement this method. + * @param array an array of tasks + * @access protected + * @static + * @abstract + */ + function run($tasks) + { + } + + /** + * @static + * @final + */ + function hasPostinstallTasks() + { + return isset($GLOBALS['_PEAR_TASK_POSTINSTANCES']); + } + + /** + * @static + * @final + */ + function runPostinstallTasks() + { + foreach ($GLOBALS['_PEAR_TASK_POSTINSTANCES'] as $class => $tasks) { + $err = call_user_func(array($class, 'run'), + $GLOBALS['_PEAR_TASK_POSTINSTANCES'][$class]); + if ($err) { + return PEAR_Task_Common::throwError($err); + } + } + unset($GLOBALS['_PEAR_TASK_POSTINSTANCES']); + } + + /** + * Determines whether a role is a script + * @return bool + */ + function isScript() + { + return $this->type == 'script'; + } + + function throwError($msg, $code = -1) + { + include_once 'PEAR.php'; + return PEAR::raiseError($msg, $code); + } +} +?> \ No newline at end of file diff --git a/library/pear/PEAR/Task/Postinstallscript.php b/library/pear/PEAR/Task/Postinstallscript.php new file mode 100644 index 000000000..d1376b140 --- /dev/null +++ b/library/pear/PEAR/Task/Postinstallscript.php @@ -0,0 +1,323 @@ + + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Postinstallscript.php 276385 2009-02-24 23:46:03Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ +/** + * Base class + */ +require_once 'PEAR/Task/Common.php'; +/** + * Implements the postinstallscript file task. + * + * Note that post-install scripts are handled separately from installation, by the + * "pear run-scripts" command + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.1 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_Task_Postinstallscript extends PEAR_Task_Common +{ + var $type = 'script'; + var $_class; + var $_params; + var $_obj; + /** + * + * @var PEAR_PackageFile_v2 + */ + var $_pkg; + var $_contents; + var $phase = PEAR_TASK_INSTALL; + + /** + * Validate the raw xml at parsing-time. + * + * This also attempts to validate the script to make sure it meets the criteria + * for a post-install script + * @param PEAR_PackageFile_v2 + * @param array The XML contents of the tag + * @param PEAR_Config + * @param array the entire parsed tag + * @static + */ + function validateXml($pkg, $xml, $config, $fileXml) + { + if ($fileXml['role'] != 'php') { + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . + $fileXml['name'] . '" must be role="php"'); + } + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $file = $pkg->getFileContents($fileXml['name']); + if (PEAR::isError($file)) { + PEAR::popErrorHandling(); + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . + $fileXml['name'] . '" is not valid: ' . + $file->getMessage()); + } elseif ($file === null) { + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . + $fileXml['name'] . '" could not be retrieved for processing!'); + } else { + $analysis = $pkg->analyzeSourceCode($file, true); + if (!$analysis) { + PEAR::popErrorHandling(); + $warnings = ''; + foreach ($pkg->getValidationWarnings() as $warn) { + $warnings .= $warn['message'] . "\n"; + } + return array(PEAR_TASK_ERROR_INVALID, 'Analysis of post-install script "' . + $fileXml['name'] . '" failed: ' . $warnings); + } + if (count($analysis['declared_classes']) != 1) { + PEAR::popErrorHandling(); + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . + $fileXml['name'] . '" must declare exactly 1 class'); + } + $class = $analysis['declared_classes'][0]; + if ($class != str_replace(array('/', '.php'), array('_', ''), + $fileXml['name']) . '_postinstall') { + PEAR::popErrorHandling(); + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . + $fileXml['name'] . '" class "' . $class . '" must be named "' . + str_replace(array('/', '.php'), array('_', ''), + $fileXml['name']) . '_postinstall"'); + } + if (!isset($analysis['declared_methods'][$class])) { + PEAR::popErrorHandling(); + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . + $fileXml['name'] . '" must declare methods init() and run()'); + } + $methods = array('init' => 0, 'run' => 1); + foreach ($analysis['declared_methods'][$class] as $method) { + if (isset($methods[$method])) { + unset($methods[$method]); + } + } + if (count($methods)) { + PEAR::popErrorHandling(); + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . + $fileXml['name'] . '" must declare methods init() and run()'); + } + } + PEAR::popErrorHandling(); + $definedparams = array(); + $tasksNamespace = $pkg->getTasksNs() . ':'; + if (!isset($xml[$tasksNamespace . 'paramgroup']) && isset($xml['paramgroup'])) { + // in order to support the older betas, which did not expect internal tags + // to also use the namespace + $tasksNamespace = ''; + } + if (isset($xml[$tasksNamespace . 'paramgroup'])) { + $params = $xml[$tasksNamespace . 'paramgroup']; + if (!is_array($params) || !isset($params[0])) { + $params = array($params); + } + foreach ($params as $param) { + if (!isset($param[$tasksNamespace . 'id'])) { + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . + $fileXml['name'] . '" must have ' . + 'an ' . $tasksNamespace . 'id> tag'); + } + if (isset($param[$tasksNamespace . 'name'])) { + if (!in_array($param[$tasksNamespace . 'name'], $definedparams)) { + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . + $fileXml['name'] . '" ' . $tasksNamespace . + 'paramgroup> id "' . $param[$tasksNamespace . 'id'] . + '" parameter "' . $param[$tasksNamespace . 'name'] . + '" has not been previously defined'); + } + if (!isset($param[$tasksNamespace . 'conditiontype'])) { + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . + $fileXml['name'] . '" ' . $tasksNamespace . + 'paramgroup> id "' . $param[$tasksNamespace . 'id'] . + '" must have a ' . $tasksNamespace . + 'conditiontype> tag containing either "=", ' . + '"!=", or "preg_match"'); + } + if (!in_array($param[$tasksNamespace . 'conditiontype'], + array('=', '!=', 'preg_match'))) { + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . + $fileXml['name'] . '" ' . $tasksNamespace . + 'paramgroup> id "' . $param[$tasksNamespace . 'id'] . + '" must have a ' . $tasksNamespace . + 'conditiontype> tag containing either "=", ' . + '"!=", or "preg_match"'); + } + if (!isset($param[$tasksNamespace . 'value'])) { + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . + $fileXml['name'] . '" ' . $tasksNamespace . + 'paramgroup> id "' . $param[$tasksNamespace . 'id'] . + '" must have a ' . $tasksNamespace . + 'value> tag containing expected parameter value'); + } + } + if (isset($param[$tasksNamespace . 'instructions'])) { + if (!is_string($param[$tasksNamespace . 'instructions'])) { + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . + $fileXml['name'] . '" ' . $tasksNamespace . + 'paramgroup> id "' . $param[$tasksNamespace . 'id'] . + '" ' . $tasksNamespace . 'instructions> must be simple text'); + } + } + if (!isset($param[$tasksNamespace . 'param'])) { + continue; // is no longer required + } + $subparams = $param[$tasksNamespace . 'param']; + if (!is_array($subparams) || !isset($subparams[0])) { + $subparams = array($subparams); + } + foreach ($subparams as $subparam) { + if (!isset($subparam[$tasksNamespace . 'name'])) { + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . + $fileXml['name'] . '" parameter for ' . + $tasksNamespace . 'paramgroup> id "' . + $param[$tasksNamespace . 'id'] . '" must have ' . + 'a ' . $tasksNamespace . 'name> tag'); + } + if (!preg_match('/[a-zA-Z0-9]+/', + $subparam[$tasksNamespace . 'name'])) { + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . + $fileXml['name'] . '" parameter "' . + $subparam[$tasksNamespace . 'name'] . + '" for ' . $tasksNamespace . 'paramgroup> id "' . + $param[$tasksNamespace . 'id'] . + '" is not a valid name. Must contain only alphanumeric characters'); + } + if (!isset($subparam[$tasksNamespace . 'prompt'])) { + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . + $fileXml['name'] . '" parameter "' . + $subparam[$tasksNamespace . 'name'] . + '" for ' . $tasksNamespace . 'paramgroup> id "' . + $param[$tasksNamespace . 'id'] . + '" must have a ' . $tasksNamespace . 'prompt> tag'); + } + if (!isset($subparam[$tasksNamespace . 'type'])) { + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . + $fileXml['name'] . '" parameter "' . + $subparam[$tasksNamespace . 'name'] . + '" for ' . $tasksNamespace . 'paramgroup> id "' . + $param[$tasksNamespace . 'id'] . + '" must have a ' . $tasksNamespace . 'type> tag'); + } + $definedparams[] = $param[$tasksNamespace . 'id'] . '::' . + $subparam[$tasksNamespace . 'name']; + } + } + } + return true; + } + + /** + * Initialize a task instance with the parameters + * @param array raw, parsed xml + * @param array attributes from the tag containing this task + * @param string|null last installed version of this package, if any (useful for upgrades) + */ + function init($xml, $fileattribs, $lastversion) + { + $this->_class = str_replace('/', '_', $fileattribs['name']); + $this->_filename = $fileattribs['name']; + $this->_class = str_replace ('.php', '', $this->_class) . '_postinstall'; + $this->_params = $xml; + $this->_lastversion = $lastversion; + } + + /** + * Strip the tasks: namespace from internal params + * + * @access private + */ + function _stripNamespace($params = null) + { + if ($params === null) { + $params = array(); + if (!is_array($this->_params)) { + return; + } + foreach ($this->_params as $i => $param) { + if (is_array($param)) { + $param = $this->_stripNamespace($param); + } + $params[str_replace($this->_pkg->getTasksNs() . ':', '', $i)] = $param; + } + $this->_params = $params; + } else { + $newparams = array(); + foreach ($params as $i => $param) { + if (is_array($param)) { + $param = $this->_stripNamespace($param); + } + $newparams[str_replace($this->_pkg->getTasksNs() . ':', '', $i)] = $param; + } + return $newparams; + } + } + + /** + * Unlike other tasks, the installed file name is passed in instead of the file contents, + * because this task is handled post-installation + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2 + * @param string file name + * @return bool|PEAR_Error false to skip this file, PEAR_Error to fail + * (use $this->throwError) + */ + function startSession($pkg, $contents) + { + if ($this->installphase != PEAR_TASK_INSTALL) { + return false; + } + // remove the tasks: namespace if present + $this->_pkg = $pkg; + $this->_stripNamespace(); + $this->logger->log(0, 'Including external post-installation script "' . + $contents . '" - any errors are in this script'); + include_once $contents; + if (class_exists($this->_class)) { + $this->logger->log(0, 'Inclusion succeeded'); + } else { + return $this->throwError('init of post-install script class "' . $this->_class + . '" failed'); + } + $this->_obj = new $this->_class; + $this->logger->log(1, 'running post-install script "' . $this->_class . '->init()"'); + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $res = $this->_obj->init($this->config, $pkg, $this->_lastversion); + PEAR::popErrorHandling(); + if ($res) { + $this->logger->log(0, 'init succeeded'); + } else { + return $this->throwError('init of post-install script "' . $this->_class . + '->init()" failed'); + } + $this->_contents = $contents; + return true; + } + + /** + * No longer used + * @see PEAR_PackageFile_v2::runPostinstallScripts() + * @param array an array of tasks + * @param string install or upgrade + * @access protected + * @static + */ + function run() + { + } +} +?> \ No newline at end of file diff --git a/library/pear/PEAR/Task/Postinstallscript/rw.php b/library/pear/PEAR/Task/Postinstallscript/rw.php new file mode 100644 index 000000000..330dcbd64 --- /dev/null +++ b/library/pear/PEAR/Task/Postinstallscript/rw.php @@ -0,0 +1,169 @@ + - read/write version + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: rw.php 276385 2009-02-24 23:46:03Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a10 + */ +/** + * Base class + */ +require_once 'PEAR/Task/Postinstallscript.php'; +/** + * Abstracts the postinstallscript file task xml. + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.1 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a10 + */ +class PEAR_Task_Postinstallscript_rw extends PEAR_Task_Postinstallscript +{ + /** + * parent package file object + * + * @var PEAR_PackageFile_v2_rw + */ + var $_pkg; + /** + * Enter description here... + * + * @param PEAR_PackageFile_v2_rw $pkg + * @param PEAR_Config $config + * @param PEAR_Frontend $logger + * @param array $fileXml + * @return PEAR_Task_Postinstallscript_rw + */ + function PEAR_Task_Postinstallscript_rw(&$pkg, &$config, &$logger, $fileXml) + { + parent::PEAR_Task_Common($config, $logger, PEAR_TASK_PACKAGE); + $this->_contents = $fileXml; + $this->_pkg = &$pkg; + $this->_params = array(); + } + + function validate() + { + return $this->validateXml($this->_pkg, $this->_params, $this->config, $this->_contents); + } + + function getName() + { + return 'postinstallscript'; + } + + /** + * add a simple to the post-install script + * + * Order is significant, so call this method in the same + * sequence the users should see the paramgroups. The $params + * parameter should either be the result of a call to {@link getParam()} + * or an array of calls to getParam(). + * + * Use {@link addConditionTypeGroup()} to add a containing + * a tag + * @param string $id id as seen by the script + * @param array|false $params array of getParam() calls, or false for no params + * @param string|false $instructions + */ + function addParamGroup($id, $params = false, $instructions = false) + { + if ($params && isset($params[0]) && !isset($params[1])) { + $params = $params[0]; + } + $stuff = + array( + $this->_pkg->getTasksNs() . ':id' => $id, + ); + if ($instructions) { + $stuff[$this->_pkg->getTasksNs() . ':instructions'] = $instructions; + } + if ($params) { + $stuff[$this->_pkg->getTasksNs() . ':param'] = $params; + } + $this->_params[$this->_pkg->getTasksNs() . ':paramgroup'][] = $stuff; + } + + /** + * add a complex to the post-install script with conditions + * + * This inserts a with + * + * Order is significant, so call this method in the same + * sequence the users should see the paramgroups. The $params + * parameter should either be the result of a call to {@link getParam()} + * or an array of calls to getParam(). + * + * Use {@link addParamGroup()} to add a simple + * + * @param string $id id as seen by the script + * @param string $oldgroup id of the section referenced by + * + * @param string $param name of the from the older section referenced + * by + * @param string $value value to match of the parameter + * @param string $conditiontype one of '=', '!=', 'preg_match' + * @param array|false $params array of getParam() calls, or false for no params + * @param string|false $instructions + */ + function addConditionTypeGroup($id, $oldgroup, $param, $value, $conditiontype = '=', + $params = false, $instructions = false) + { + if ($params && isset($params[0]) && !isset($params[1])) { + $params = $params[0]; + } + $stuff = array( + $this->_pkg->getTasksNs() . ':id' => $id, + ); + if ($instructions) { + $stuff[$this->_pkg->getTasksNs() . ':instructions'] = $instructions; + } + $stuff[$this->_pkg->getTasksNs() . ':name'] = $oldgroup . '::' . $param; + $stuff[$this->_pkg->getTasksNs() . ':conditiontype'] = $conditiontype; + $stuff[$this->_pkg->getTasksNs() . ':value'] = $value; + if ($params) { + $stuff[$this->_pkg->getTasksNs() . ':param'] = $params; + } + $this->_params[$this->_pkg->getTasksNs() . ':paramgroup'][] = $stuff; + } + + function getXml() + { + return $this->_params; + } + + /** + * Use to set up a param tag for use in creating a paramgroup + * @static + */ + function getParam($name, $prompt, $type = 'string', $default = null) + { + if ($default !== null) { + return + array( + $this->_pkg->getTasksNs() . ':name' => $name, + $this->_pkg->getTasksNs() . ':prompt' => $prompt, + $this->_pkg->getTasksNs() . ':type' => $type, + $this->_pkg->getTasksNs() . ':default' => $default + ); + } + return + array( + $this->_pkg->getTasksNs() . ':name' => $name, + $this->_pkg->getTasksNs() . ':prompt' => $prompt, + $this->_pkg->getTasksNs() . ':type' => $type, + ); + } +} +?> \ No newline at end of file diff --git a/library/pear/PEAR/Task/Replace.php b/library/pear/PEAR/Task/Replace.php new file mode 100644 index 000000000..83cb2a3fb --- /dev/null +++ b/library/pear/PEAR/Task/Replace.php @@ -0,0 +1,176 @@ + + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Replace.php 276394 2009-02-25 00:15:49Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ +/** + * Base class + */ +require_once 'PEAR/Task/Common.php'; +/** + * Implements the replace file task. + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.1 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_Task_Replace extends PEAR_Task_Common +{ + var $type = 'simple'; + var $phase = PEAR_TASK_PACKAGEANDINSTALL; + var $_replacements; + + /** + * Validate the raw xml at parsing-time. + * @param PEAR_PackageFile_v2 + * @param array raw, parsed xml + * @param PEAR_Config + * @static + */ + function validateXml($pkg, $xml, $config, $fileXml) + { + if (!isset($xml['attribs'])) { + return array(PEAR_TASK_ERROR_NOATTRIBS); + } + if (!isset($xml['attribs']['type'])) { + return array(PEAR_TASK_ERROR_MISSING_ATTRIB, 'type'); + } + if (!isset($xml['attribs']['to'])) { + return array(PEAR_TASK_ERROR_MISSING_ATTRIB, 'to'); + } + if (!isset($xml['attribs']['from'])) { + return array(PEAR_TASK_ERROR_MISSING_ATTRIB, 'from'); + } + if ($xml['attribs']['type'] == 'pear-config') { + if (!in_array($xml['attribs']['to'], $config->getKeys())) { + return array(PEAR_TASK_ERROR_WRONG_ATTRIB_VALUE, 'to', $xml['attribs']['to'], + $config->getKeys()); + } + } elseif ($xml['attribs']['type'] == 'php-const') { + if (defined($xml['attribs']['to'])) { + return true; + } else { + return array(PEAR_TASK_ERROR_WRONG_ATTRIB_VALUE, 'to', $xml['attribs']['to'], + array('valid PHP constant')); + } + } elseif ($xml['attribs']['type'] == 'package-info') { + if (in_array($xml['attribs']['to'], + array('name', 'summary', 'channel', 'notes', 'extends', 'description', + 'release_notes', 'license', 'release-license', 'license-uri', + 'version', 'api-version', 'state', 'api-state', 'release_date', + 'date', 'time'))) { + return true; + } else { + return array(PEAR_TASK_ERROR_WRONG_ATTRIB_VALUE, 'to', $xml['attribs']['to'], + array('name', 'summary', 'channel', 'notes', 'extends', 'description', + 'release_notes', 'license', 'release-license', 'license-uri', + 'version', 'api-version', 'state', 'api-state', 'release_date', + 'date', 'time')); + } + } else { + return array(PEAR_TASK_ERROR_WRONG_ATTRIB_VALUE, 'type', $xml['attribs']['type'], + array('pear-config', 'package-info', 'php-const')); + } + return true; + } + + /** + * Initialize a task instance with the parameters + * @param array raw, parsed xml + * @param unused + */ + function init($xml, $attribs) + { + $this->_replacements = isset($xml['attribs']) ? array($xml) : $xml; + } + + /** + * Do a package.xml 1.0 replacement, with additional package-info fields available + * + * See validateXml() source for the complete list of allowed fields + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2 + * @param string file contents + * @param string the eventual final file location (informational only) + * @return string|false|PEAR_Error false to skip this file, PEAR_Error to fail + * (use $this->throwError), otherwise return the new contents + */ + function startSession($pkg, $contents, $dest) + { + $subst_from = $subst_to = array(); + foreach ($this->_replacements as $a) { + $a = $a['attribs']; + $to = ''; + if ($a['type'] == 'pear-config') { + if ($this->installphase == PEAR_TASK_PACKAGE) { + return false; + } + if ($a['to'] == 'master_server') { + $chan = $this->registry->getChannel($pkg->getChannel()); + if (!PEAR::isError($chan)) { + $to = $chan->getServer(); + } else { + $this->logger->log(0, "$dest: invalid pear-config replacement: $a[to]"); + return false; + } + } else { + if ($this->config->isDefinedLayer('ftp')) { + // try the remote config file first + $to = $this->config->get($a['to'], 'ftp', $pkg->getChannel()); + if (is_null($to)) { + // then default to local + $to = $this->config->get($a['to'], null, $pkg->getChannel()); + } + } else { + $to = $this->config->get($a['to'], null, $pkg->getChannel()); + } + } + if (is_null($to)) { + $this->logger->log(0, "$dest: invalid pear-config replacement: $a[to]"); + return false; + } + } elseif ($a['type'] == 'php-const') { + if ($this->installphase == PEAR_TASK_PACKAGE) { + return false; + } + if (defined($a['to'])) { + $to = constant($a['to']); + } else { + $this->logger->log(0, "$dest: invalid php-const replacement: $a[to]"); + return false; + } + } else { + if ($t = $pkg->packageInfo($a['to'])) { + $to = $t; + } else { + $this->logger->log(0, "$dest: invalid package-info replacement: $a[to]"); + return false; + } + } + if (!is_null($to)) { + $subst_from[] = $a['from']; + $subst_to[] = $to; + } + } + $this->logger->log(3, "doing " . sizeof($subst_from) . + " substitution(s) for $dest"); + if (sizeof($subst_from)) { + $contents = str_replace($subst_from, $subst_to, $contents); + } + return $contents; + } +} +?> \ No newline at end of file diff --git a/library/pear/PEAR/Task/Replace/rw.php b/library/pear/PEAR/Task/Replace/rw.php new file mode 100644 index 000000000..7bdcecd3b --- /dev/null +++ b/library/pear/PEAR/Task/Replace/rw.php @@ -0,0 +1,61 @@ + - read/write version + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: rw.php 276385 2009-02-24 23:46:03Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a10 + */ +/** + * Base class + */ +require_once 'PEAR/Task/Replace.php'; +/** + * Abstracts the replace task xml. + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.1 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a10 + */ +class PEAR_Task_Replace_rw extends PEAR_Task_Replace +{ + function PEAR_Task_Replace_rw(&$pkg, &$config, &$logger, $fileXml) + { + parent::PEAR_Task_Common($config, $logger, PEAR_TASK_PACKAGE); + $this->_contents = $fileXml; + $this->_pkg = &$pkg; + $this->_params = array(); + } + + function validate() + { + return $this->validateXml($this->_pkg, $this->_params, $this->config, $this->_contents); + } + + function setInfo($from, $to, $type) + { + $this->_params = array('attribs' => array('from' => $from, 'to' => $to, 'type' => $type)); + } + + function getName() + { + return 'replace'; + } + + function getXml() + { + return $this->_params; + } +} +?> \ No newline at end of file diff --git a/library/pear/PEAR/Task/Unixeol.php b/library/pear/PEAR/Task/Unixeol.php new file mode 100644 index 000000000..d5a016756 --- /dev/null +++ b/library/pear/PEAR/Task/Unixeol.php @@ -0,0 +1,77 @@ + + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Unixeol.php 276394 2009-02-25 00:15:49Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ +/** + * Base class + */ +require_once 'PEAR/Task/Common.php'; +/** + * Implements the unix line endings file task. + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.1 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_Task_Unixeol extends PEAR_Task_Common +{ + var $type = 'simple'; + var $phase = PEAR_TASK_PACKAGE; + var $_replacements; + + /** + * Validate the raw xml at parsing-time. + * @param PEAR_PackageFile_v2 + * @param array raw, parsed xml + * @param PEAR_Config + * @static + */ + function validateXml($pkg, $xml, $config, $fileXml) + { + if ($xml != '') { + return array(PEAR_TASK_ERROR_INVALID, 'no attributes allowed'); + } + return true; + } + + /** + * Initialize a task instance with the parameters + * @param array raw, parsed xml + * @param unused + */ + function init($xml, $attribs) + { + } + + /** + * Replace all line endings with line endings customized for the current OS + * + * See validateXml() source for the complete list of allowed fields + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2 + * @param string file contents + * @param string the eventual final file location (informational only) + * @return string|false|PEAR_Error false to skip this file, PEAR_Error to fail + * (use $this->throwError), otherwise return the new contents + */ + function startSession($pkg, $contents, $dest) + { + $this->logger->log(3, "replacing all line endings with \\n in $dest"); + return preg_replace("/\r\n|\n\r|\r|\n/", "\n", $contents); + } +} +?> \ No newline at end of file diff --git a/library/pear/PEAR/Task/Unixeol/rw.php b/library/pear/PEAR/Task/Unixeol/rw.php new file mode 100644 index 000000000..073d3b533 --- /dev/null +++ b/library/pear/PEAR/Task/Unixeol/rw.php @@ -0,0 +1,56 @@ + - read/write version + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: rw.php 276385 2009-02-24 23:46:03Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a10 + */ +/** + * Base class + */ +require_once 'PEAR/Task/Unixeol.php'; +/** + * Abstracts the unixeol task xml. + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.1 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a10 + */ +class PEAR_Task_Unixeol_rw extends PEAR_Task_Unixeol +{ + function PEAR_Task_Unixeol_rw(&$pkg, &$config, &$logger, $fileXml) + { + parent::PEAR_Task_Common($config, $logger, PEAR_TASK_PACKAGE); + $this->_contents = $fileXml; + $this->_pkg = &$pkg; + $this->_params = array(); + } + + function validate() + { + return true; + } + + function getName() + { + return 'unixeol'; + } + + function getXml() + { + return ''; + } +} +?> \ No newline at end of file diff --git a/library/pear/PEAR/Task/Windowseol.php b/library/pear/PEAR/Task/Windowseol.php new file mode 100644 index 000000000..0a900759b --- /dev/null +++ b/library/pear/PEAR/Task/Windowseol.php @@ -0,0 +1,77 @@ + + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Windowseol.php 276394 2009-02-25 00:15:49Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ +/** + * Base class + */ +require_once 'PEAR/Task/Common.php'; +/** + * Implements the windows line endsings file task. + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.1 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_Task_Windowseol extends PEAR_Task_Common +{ + var $type = 'simple'; + var $phase = PEAR_TASK_PACKAGE; + var $_replacements; + + /** + * Validate the raw xml at parsing-time. + * @param PEAR_PackageFile_v2 + * @param array raw, parsed xml + * @param PEAR_Config + * @static + */ + function validateXml($pkg, $xml, $config, $fileXml) + { + if ($xml != '') { + return array(PEAR_TASK_ERROR_INVALID, 'no attributes allowed'); + } + return true; + } + + /** + * Initialize a task instance with the parameters + * @param array raw, parsed xml + * @param unused + */ + function init($xml, $attribs) + { + } + + /** + * Replace all line endings with windows line endings + * + * See validateXml() source for the complete list of allowed fields + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2 + * @param string file contents + * @param string the eventual final file location (informational only) + * @return string|false|PEAR_Error false to skip this file, PEAR_Error to fail + * (use $this->throwError), otherwise return the new contents + */ + function startSession($pkg, $contents, $dest) + { + $this->logger->log(3, "replacing all line endings with \\r\\n in $dest"); + return preg_replace("/\r\n|\n\r|\r|\n/", "\r\n", $contents); + } +} +?> \ No newline at end of file diff --git a/library/pear/PEAR/Task/Windowseol/rw.php b/library/pear/PEAR/Task/Windowseol/rw.php new file mode 100644 index 000000000..6e36d7635 --- /dev/null +++ b/library/pear/PEAR/Task/Windowseol/rw.php @@ -0,0 +1,56 @@ + - read/write version + * + * PHP versions 4 and 5 + * + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: rw.php 276385 2009-02-24 23:46:03Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a10 + */ +/** + * Base class + */ +require_once 'PEAR/Task/Windowseol.php'; +/** + * Abstracts the windowseol task xml. + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.1 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a10 + */ +class PEAR_Task_Windowseol_rw extends PEAR_Task_Windowseol +{ + function PEAR_Task_Windowseol_rw(&$pkg, &$config, &$logger, $fileXml) + { + parent::PEAR_Task_Common($config, $logger, PEAR_TASK_PACKAGE); + $this->_contents = $fileXml; + $this->_pkg = &$pkg; + $this->_params = array(); + } + + function validate() + { + return true; + } + + function getName() + { + return 'windowseol'; + } + + function getXml() + { + return ''; + } +} +?> \ No newline at end of file diff --git a/library/pear/PEAR/Validate.php b/library/pear/PEAR/Validate.php new file mode 100644 index 000000000..f1d2afa90 --- /dev/null +++ b/library/pear/PEAR/Validate.php @@ -0,0 +1,629 @@ + + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Validate.php 276383 2009-02-24 23:39:37Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ +/**#@+ + * Constants for install stage + */ +define('PEAR_VALIDATE_INSTALLING', 1); +define('PEAR_VALIDATE_UNINSTALLING', 2); // this is not bit-mapped like the others +define('PEAR_VALIDATE_NORMAL', 3); +define('PEAR_VALIDATE_DOWNLOADING', 4); // this is not bit-mapped like the others +define('PEAR_VALIDATE_PACKAGING', 7); +/**#@-*/ +require_once 'PEAR/Common.php'; +require_once 'PEAR/Validator/PECL.php'; + +/** + * Validation class for package.xml - channel-level advanced validation + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.1 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_Validate +{ + var $packageregex = _PEAR_COMMON_PACKAGE_NAME_PREG; + /** + * @var PEAR_PackageFile_v1|PEAR_PackageFile_v2 + */ + var $_packagexml; + /** + * @var int one of the PEAR_VALIDATE_* constants + */ + var $_state = PEAR_VALIDATE_NORMAL; + /** + * Format: ('error' => array('field' => name, 'reason' => reason), 'warning' => same) + * @var array + * @access private + */ + var $_failures = array('error' => array(), 'warning' => array()); + + /** + * Override this method to handle validation of normal package names + * @param string + * @return bool + * @access protected + */ + function _validPackageName($name) + { + return (bool) preg_match('/^' . $this->packageregex . '\\z/', $name); + } + + /** + * @param string package name to validate + * @param string name of channel-specific validation package + * @final + */ + function validPackageName($name, $validatepackagename = false) + { + if ($validatepackagename) { + if (strtolower($name) == strtolower($validatepackagename)) { + return (bool) preg_match('/^[a-zA-Z0-9_]+(?:\.[a-zA-Z0-9_]+)*\\z/', $name); + } + } + return $this->_validPackageName($name); + } + + /** + * This validates a bundle name, and bundle names must conform + * to the PEAR naming convention, so the method is final and static. + * @param string + * @final + * @static + */ + function validGroupName($name) + { + return (bool) preg_match('/^' . _PEAR_COMMON_PACKAGE_NAME_PREG . '\\z/', $name); + } + + /** + * Determine whether $state represents a valid stability level + * @param string + * @return bool + * @static + * @final + */ + function validState($state) + { + return in_array($state, array('snapshot', 'devel', 'alpha', 'beta', 'stable')); + } + + /** + * Get a list of valid stability levels + * @return array + * @static + * @final + */ + function getValidStates() + { + return array('snapshot', 'devel', 'alpha', 'beta', 'stable'); + } + + /** + * Determine whether a version is a properly formatted version number that can be used + * by version_compare + * @param string + * @return bool + * @static + * @final + */ + function validVersion($ver) + { + return (bool) preg_match(PEAR_COMMON_PACKAGE_VERSION_PREG, $ver); + } + + /** + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2 + */ + function setPackageFile(&$pf) + { + $this->_packagexml = &$pf; + } + + /** + * @access private + */ + function _addFailure($field, $reason) + { + $this->_failures['errors'][] = array('field' => $field, 'reason' => $reason); + } + + /** + * @access private + */ + function _addWarning($field, $reason) + { + $this->_failures['warnings'][] = array('field' => $field, 'reason' => $reason); + } + + function getFailures() + { + $failures = $this->_failures; + $this->_failures = array('warnings' => array(), 'errors' => array()); + return $failures; + } + + /** + * @param int one of the PEAR_VALIDATE_* constants + */ + function validate($state = null) + { + if (!isset($this->_packagexml)) { + return false; + } + if ($state !== null) { + $this->_state = $state; + } + $this->_failures = array('warnings' => array(), 'errors' => array()); + $this->validatePackageName(); + $this->validateVersion(); + $this->validateMaintainers(); + $this->validateDate(); + $this->validateSummary(); + $this->validateDescription(); + $this->validateLicense(); + $this->validateNotes(); + if ($this->_packagexml->getPackagexmlVersion() == '1.0') { + $this->validateState(); + $this->validateFilelist(); + } elseif ($this->_packagexml->getPackagexmlVersion() == '2.0' || + $this->_packagexml->getPackagexmlVersion() == '2.1') { + $this->validateTime(); + $this->validateStability(); + $this->validateDeps(); + $this->validateMainFilelist(); + $this->validateReleaseFilelist(); + //$this->validateGlobalTasks(); + $this->validateChangelog(); + } + return !((bool) count($this->_failures['errors'])); + } + + /** + * @access protected + */ + function validatePackageName() + { + if ($this->_state == PEAR_VALIDATE_PACKAGING || + $this->_state == PEAR_VALIDATE_NORMAL) { + if (($this->_packagexml->getPackagexmlVersion() == '2.0' || + $this->_packagexml->getPackagexmlVersion() == '2.1') && + $this->_packagexml->getExtends()) { + $version = $this->_packagexml->getVersion() . ''; + $name = $this->_packagexml->getPackage(); + $test = array_shift($a = explode('.', $version)); + if ($test == '0') { + return true; + } + $vlen = strlen($test); + $majver = substr($name, strlen($name) - $vlen); + while ($majver && !is_numeric($majver{0})) { + $majver = substr($majver, 1); + } + if ($majver != $test) { + $this->_addWarning('package', "package $name extends package " . + $this->_packagexml->getExtends() . ' and so the name should ' . + 'have a postfix equal to the major version like "' . + $this->_packagexml->getExtends() . $test . '"'); + return true; + } elseif (substr($name, 0, strlen($name) - $vlen) != + $this->_packagexml->getExtends()) { + $this->_addWarning('package', "package $name extends package " . + $this->_packagexml->getExtends() . ' and so the name must ' . + 'be an extension like "' . $this->_packagexml->getExtends() . + $test . '"'); + return true; + } + } + } + if (!$this->validPackageName($this->_packagexml->getPackage())) { + $this->_addFailure('name', 'package name "' . + $this->_packagexml->getPackage() . '" is invalid'); + return false; + } + } + + /** + * @access protected + */ + function validateVersion() + { + if ($this->_state != PEAR_VALIDATE_PACKAGING) { + if (!$this->validVersion($this->_packagexml->getVersion())) { + $this->_addFailure('version', + 'Invalid version number "' . $this->_packagexml->getVersion() . '"'); + } + return false; + } + $version = $this->_packagexml->getVersion(); + $versioncomponents = explode('.', $version); + if (count($versioncomponents) != 3) { + $this->_addWarning('version', + 'A version number should have 3 decimals (x.y.z)'); + return true; + } + $name = $this->_packagexml->getPackage(); + // version must be based upon state + switch ($this->_packagexml->getState()) { + case 'snapshot' : + return true; + case 'devel' : + if ($versioncomponents[0] . 'a' == '0a') { + return true; + } + if ($versioncomponents[0] == 0) { + $versioncomponents[0] = '0'; + $this->_addWarning('version', + 'version "' . $version . '" should be "' . + implode('.' ,$versioncomponents) . '"'); + } else { + $this->_addWarning('version', + 'packages with devel stability must be < version 1.0.0'); + } + return true; + break; + case 'alpha' : + case 'beta' : + // check for a package that extends a package, + // like Foo and Foo2 + if ($this->_state == PEAR_VALIDATE_PACKAGING) { + if (substr($versioncomponents[2], 1, 2) == 'rc') { + $this->_addFailure('version', 'Release Candidate versions ' . + 'must have capital RC, not lower-case rc'); + return false; + } + } + if (!$this->_packagexml->getExtends()) { + if ($versioncomponents[0] == '1') { + if ($versioncomponents[2]{0} == '0') { + if ($versioncomponents[2] == '0') { + // version 1.*.0000 + $this->_addWarning('version', + 'version 1.' . $versioncomponents[1] . + '.0 probably should not be alpha or beta'); + return true; + } elseif (strlen($versioncomponents[2]) > 1) { + // version 1.*.0RC1 or 1.*.0beta24 etc. + return true; + } else { + // version 1.*.0 + $this->_addWarning('version', + 'version 1.' . $versioncomponents[1] . + '.0 probably should not be alpha or beta'); + return true; + } + } else { + $this->_addWarning('version', + 'bugfix versions (1.3.x where x > 0) probably should ' . + 'not be alpha or beta'); + return true; + } + } elseif ($versioncomponents[0] != '0') { + $this->_addWarning('version', + 'major versions greater than 1 are not allowed for packages ' . + 'without an tag or an identical postfix (foo2 v2.0.0)'); + return true; + } + if ($versioncomponents[0] . 'a' == '0a') { + return true; + } + if ($versioncomponents[0] == 0) { + $versioncomponents[0] = '0'; + $this->_addWarning('version', + 'version "' . $version . '" should be "' . + implode('.' ,$versioncomponents) . '"'); + } + } else { + $vlen = strlen($versioncomponents[0] . ''); + $majver = substr($name, strlen($name) - $vlen); + while ($majver && !is_numeric($majver{0})) { + $majver = substr($majver, 1); + } + if (($versioncomponents[0] != 0) && $majver != $versioncomponents[0]) { + $this->_addWarning('version', 'first version number "' . + $versioncomponents[0] . '" must match the postfix of ' . + 'package name "' . $name . '" (' . + $majver . ')'); + return true; + } + if ($versioncomponents[0] == $majver) { + if ($versioncomponents[2]{0} == '0') { + if ($versioncomponents[2] == '0') { + // version 2.*.0000 + $this->_addWarning('version', + "version $majver." . $versioncomponents[1] . + '.0 probably should not be alpha or beta'); + return false; + } elseif (strlen($versioncomponents[2]) > 1) { + // version 2.*.0RC1 or 2.*.0beta24 etc. + return true; + } else { + // version 2.*.0 + $this->_addWarning('version', + "version $majver." . $versioncomponents[1] . + '.0 cannot be alpha or beta'); + return true; + } + } else { + $this->_addWarning('version', + "bugfix versions ($majver.x.y where y > 0) should " . + 'not be alpha or beta'); + return true; + } + } elseif ($versioncomponents[0] != '0') { + $this->_addWarning('version', + "only versions 0.x.y and $majver.x.y are allowed for alpha/beta releases"); + return true; + } + if ($versioncomponents[0] . 'a' == '0a') { + return true; + } + if ($versioncomponents[0] == 0) { + $versioncomponents[0] = '0'; + $this->_addWarning('version', + 'version "' . $version . '" should be "' . + implode('.' ,$versioncomponents) . '"'); + } + } + return true; + break; + case 'stable' : + if ($versioncomponents[0] == '0') { + $this->_addWarning('version', 'versions less than 1.0.0 cannot ' . + 'be stable'); + return true; + } + if (!is_numeric($versioncomponents[2])) { + if (preg_match('/\d+(rc|a|alpha|b|beta)\d*/i', + $versioncomponents[2])) { + $this->_addWarning('version', 'version "' . $version . '" or any ' . + 'RC/beta/alpha version cannot be stable'); + return true; + } + } + // check for a package that extends a package, + // like Foo and Foo2 + if ($this->_packagexml->getExtends()) { + $vlen = strlen($versioncomponents[0] . ''); + $majver = substr($name, strlen($name) - $vlen); + while ($majver && !is_numeric($majver{0})) { + $majver = substr($majver, 1); + } + if (($versioncomponents[0] != 0) && $majver != $versioncomponents[0]) { + $this->_addWarning('version', 'first version number "' . + $versioncomponents[0] . '" must match the postfix of ' . + 'package name "' . $name . '" (' . + $majver . ')'); + return true; + } + } elseif ($versioncomponents[0] > 1) { + $this->_addWarning('version', 'major version x in x.y.z may not be greater than ' . + '1 for any package that does not have an tag'); + } + return true; + break; + default : + return false; + break; + } + } + + /** + * @access protected + */ + function validateMaintainers() + { + // maintainers can only be truly validated server-side for most channels + // but allow this customization for those who wish it + return true; + } + + /** + * @access protected + */ + function validateDate() + { + if ($this->_state == PEAR_VALIDATE_NORMAL || + $this->_state == PEAR_VALIDATE_PACKAGING) { + + if (!preg_match('/(\d\d\d\d)\-(\d\d)\-(\d\d)/', + $this->_packagexml->getDate(), $res) || + count($res) < 4 + || !checkdate($res[2], $res[3], $res[1]) + ) { + $this->_addFailure('date', 'invalid release date "' . + $this->_packagexml->getDate() . '"'); + return false; + } + + if ($this->_state == PEAR_VALIDATE_PACKAGING && + $this->_packagexml->getDate() != date('Y-m-d')) { + $this->_addWarning('date', 'Release Date "' . + $this->_packagexml->getDate() . '" is not today'); + } + } + return true; + } + + /** + * @access protected + */ + function validateTime() + { + if (!$this->_packagexml->getTime()) { + // default of no time value set + return true; + } + + // packager automatically sets time, so only validate if pear validate is called + if ($this->_state = PEAR_VALIDATE_NORMAL) { + if (!preg_match('/\d\d:\d\d:\d\d/', + $this->_packagexml->getTime())) { + $this->_addFailure('time', 'invalid release time "' . + $this->_packagexml->getTime() . '"'); + return false; + } + + $result = preg_match('|\d{2}\:\d{2}\:\d{2}|', $this->_packagexml->getTime(), $matches); + if ($result === false || empty($matches)) { + $this->_addFailure('time', 'invalid release time "' . + $this->_packagexml->getTime() . '"'); + return false; + } + } + + return true; + } + + /** + * @access protected + */ + function validateState() + { + // this is the closest to "final" php4 can get + if (!PEAR_Validate::validState($this->_packagexml->getState())) { + if (strtolower($this->_packagexml->getState() == 'rc')) { + $this->_addFailure('state', 'RC is not a state, it is a version ' . + 'postfix, use ' . $this->_packagexml->getVersion() . 'RC1, state beta'); + } + $this->_addFailure('state', 'invalid release state "' . + $this->_packagexml->getState() . '", must be one of: ' . + implode(', ', PEAR_Validate::getValidStates())); + return false; + } + return true; + } + + /** + * @access protected + */ + function validateStability() + { + $ret = true; + $packagestability = $this->_packagexml->getState(); + $apistability = $this->_packagexml->getState('api'); + if (!PEAR_Validate::validState($packagestability)) { + $this->_addFailure('state', 'invalid release stability "' . + $this->_packagexml->getState() . '", must be one of: ' . + implode(', ', PEAR_Validate::getValidStates())); + $ret = false; + } + $apistates = PEAR_Validate::getValidStates(); + array_shift($apistates); // snapshot is not allowed + if (!in_array($apistability, $apistates)) { + $this->_addFailure('state', 'invalid API stability "' . + $this->_packagexml->getState('api') . '", must be one of: ' . + implode(', ', $apistates)); + $ret = false; + } + return $ret; + } + + /** + * @access protected + */ + function validateSummary() + { + return true; + } + + /** + * @access protected + */ + function validateDescription() + { + return true; + } + + /** + * @access protected + */ + function validateLicense() + { + return true; + } + + /** + * @access protected + */ + function validateNotes() + { + return true; + } + + /** + * for package.xml 2.0 only - channels can't use package.xml 1.0 + * @access protected + */ + function validateDependencies() + { + return true; + } + + /** + * for package.xml 1.0 only + * @access private + */ + function _validateFilelist() + { + return true; // placeholder for now + } + + /** + * for package.xml 2.0 only + * @access protected + */ + function validateMainFilelist() + { + return true; // placeholder for now + } + + /** + * for package.xml 2.0 only + * @access protected + */ + function validateReleaseFilelist() + { + return true; // placeholder for now + } + + /** + * @access protected + */ + function validateChangelog() + { + return true; + } + + /** + * @access protected + */ + function validateFilelist() + { + return true; + } + + /** + * @access protected + */ + function validateDeps() + { + return true; + } +} \ No newline at end of file diff --git a/library/pear/PEAR/Validator/PECL.php b/library/pear/PEAR/Validator/PECL.php new file mode 100644 index 000000000..f9757f978 --- /dev/null +++ b/library/pear/PEAR/Validator/PECL.php @@ -0,0 +1,63 @@ + + * @copyright 1997-2006 The PHP Group + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: PECL.php 276383 2009-02-24 23:39:37Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a5 + */ +/** + * This is the parent class for all validators + */ +require_once 'PEAR/Validate.php'; +/** + * Channel Validator for the pecl.php.net channel + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.9.1 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a5 + */ +class PEAR_Validator_PECL extends PEAR_Validate +{ + function validateVersion() + { + if ($this->_state == PEAR_VALIDATE_PACKAGING) { + $version = $this->_packagexml->getVersion(); + $versioncomponents = explode('.', $version); + $last = array_pop($versioncomponents); + if (substr($last, 1, 2) == 'rc') { + $this->_addFailure('version', 'Release Candidate versions must have ' . + 'upper-case RC, not lower-case rc'); + return false; + } + } + return true; + } + + function validatePackageName() + { + $ret = parent::validatePackageName(); + if ($this->_packagexml->getPackageType() == 'extsrc' || + $this->_packagexml->getPackageType() == 'zendextsrc') { + if (strtolower($this->_packagexml->getPackage()) != + strtolower($this->_packagexml->getProvidesExtension())) { + $this->_addWarning('providesextension', 'package name "' . + $this->_packagexml->getPackage() . '" is different from extension name "' . + $this->_packagexml->getProvidesExtension() . '"'); + } + } + return $ret; + } +} +?> \ No newline at end of file diff --git a/library/pear/PEAR/XMLParser.php b/library/pear/PEAR/XMLParser.php new file mode 100644 index 000000000..0a3b88580 --- /dev/null +++ b/library/pear/PEAR/XMLParser.php @@ -0,0 +1,253 @@ + + * @author Stephan Schmidt (original XML_Unserializer code) + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license New BSD License + * @version CVS: $Id: XMLParser.php 282970 2009-06-28 23:10:07Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 + */ + +/** + * Parser for any xml file + * @category pear + * @package PEAR + * @author Greg Beaver + * @author Stephan Schmidt (original XML_Unserializer code) + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license New BSD License + * @version Release: 1.9.1 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 + */ +class PEAR_XMLParser +{ + /** + * unserilialized data + * @var string $_serializedData + */ + var $_unserializedData = null; + + /** + * name of the root tag + * @var string $_root + */ + var $_root = null; + + /** + * stack for all data that is found + * @var array $_dataStack + */ + var $_dataStack = array(); + + /** + * stack for all values that are generated + * @var array $_valStack + */ + var $_valStack = array(); + + /** + * current tag depth + * @var int $_depth + */ + var $_depth = 0; + + /** + * The XML encoding to use + * @var string $encoding + */ + var $encoding = 'ISO-8859-1'; + + /** + * @return array + */ + function getData() + { + return $this->_unserializedData; + } + + /** + * @param string xml content + * @return true|PEAR_Error + */ + function parse($data) + { + if (!extension_loaded('xml')) { + include_once 'PEAR.php'; + return PEAR::raiseError("XML Extension not found", 1); + } + $this->_dataStack = $this->_valStack = array(); + $this->_depth = 0; + + if ( + strpos($data, 'encoding="UTF-8"') + || strpos($data, 'encoding="utf-8"') + || strpos($data, "encoding='UTF-8'") + || strpos($data, "encoding='utf-8'") + ) { + $this->encoding = 'UTF-8'; + } + + if (version_compare(phpversion(), '5.0.0', 'lt') && $this->encoding == 'UTF-8') { + $data = utf8_decode($data); + $this->encoding = 'ISO-8859-1'; + } + + $xp = xml_parser_create($this->encoding); + xml_parser_set_option($xp, XML_OPTION_CASE_FOLDING, 0); + xml_set_object($xp, $this); + xml_set_element_handler($xp, 'startHandler', 'endHandler'); + xml_set_character_data_handler($xp, 'cdataHandler'); + if (!xml_parse($xp, $data)) { + $msg = xml_error_string(xml_get_error_code($xp)); + $line = xml_get_current_line_number($xp); + xml_parser_free($xp); + include_once 'PEAR.php'; + return PEAR::raiseError("XML Error: '$msg' on line '$line'", 2); + } + xml_parser_free($xp); + return true; + } + + /** + * Start element handler for XML parser + * + * @access private + * @param object $parser XML parser object + * @param string $element XML element + * @param array $attribs attributes of XML tag + * @return void + */ + function startHandler($parser, $element, $attribs) + { + $this->_depth++; + $this->_dataStack[$this->_depth] = null; + + $val = array( + 'name' => $element, + 'value' => null, + 'type' => 'string', + 'childrenKeys' => array(), + 'aggregKeys' => array() + ); + + if (count($attribs) > 0) { + $val['children'] = array(); + $val['type'] = 'array'; + $val['children']['attribs'] = $attribs; + } + + array_push($this->_valStack, $val); + } + + /** + * post-process data + * + * @param string $data + * @param string $element element name + */ + function postProcess($data, $element) + { + return trim($data); + } + + /** + * End element handler for XML parser + * + * @access private + * @param object XML parser object + * @param string + * @return void + */ + function endHandler($parser, $element) + { + $value = array_pop($this->_valStack); + $data = $this->postProcess($this->_dataStack[$this->_depth], $element); + + // adjust type of the value + switch (strtolower($value['type'])) { + // unserialize an array + case 'array': + if ($data !== '') { + $value['children']['_content'] = $data; + } + + $value['value'] = isset($value['children']) ? $value['children'] : array(); + break; + + /* + * unserialize a null value + */ + case 'null': + $data = null; + break; + + /* + * unserialize any scalar value + */ + default: + settype($data, $value['type']); + $value['value'] = $data; + break; + } + + $parent = array_pop($this->_valStack); + if ($parent === null) { + $this->_unserializedData = &$value['value']; + $this->_root = &$value['name']; + return true; + } + + // parent has to be an array + if (!isset($parent['children']) || !is_array($parent['children'])) { + $parent['children'] = array(); + if ($parent['type'] != 'array') { + $parent['type'] = 'array'; + } + } + + if (!empty($value['name'])) { + // there already has been a tag with this name + if (in_array($value['name'], $parent['childrenKeys'])) { + // no aggregate has been created for this tag + if (!in_array($value['name'], $parent['aggregKeys'])) { + if (isset($parent['children'][$value['name']])) { + $parent['children'][$value['name']] = array($parent['children'][$value['name']]); + } else { + $parent['children'][$value['name']] = array(); + } + array_push($parent['aggregKeys'], $value['name']); + } + array_push($parent['children'][$value['name']], $value['value']); + } else { + $parent['children'][$value['name']] = &$value['value']; + array_push($parent['childrenKeys'], $value['name']); + } + } else { + array_push($parent['children'],$value['value']); + } + array_push($this->_valStack, $parent); + + $this->_depth--; + } + + /** + * Handler for character data + * + * @access private + * @param object XML parser object + * @param string CDATA + * @return void + */ + function cdataHandler($parser, $cdata) + { + $this->_dataStack[$this->_depth] .= $cdata; + } +} \ No newline at end of file diff --git a/library/pear/PEAR5.php b/library/pear/PEAR5.php new file mode 100644 index 000000000..428606780 --- /dev/null +++ b/library/pear/PEAR5.php @@ -0,0 +1,33 @@ +. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * * Neither the name of Sebastian Bergmann nor the names of his + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRIC + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * @category Testing + * @package PHPUnit + * @author Sebastian Bergmann + * @copyright 2002-2005 Sebastian Bergmann + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + * @version CVS: $Id: PHPUnit.php,v 1.17 2005/11/10 09:47:11 sebastian Exp $ + * @link http://pear.php.net/package/PHPUnit + * @since File available since Release 1.0.0 + */ + +require_once 'PHPUnit/TestCase.php'; +require_once 'PHPUnit/TestResult.php'; +require_once 'PHPUnit/TestSuite.php'; + +/** + * PHPUnit runs a TestSuite and returns a TestResult object. + * + * Here is an example: + * + * + * PHPUnit_TestCase($name); + * } + * + * function setUp() { + * $this->fValue1 = 2; + * $this->fValue2 = 3; + * } + * + * function testAdd() { + * $this->assertTrue($this->fValue1 + $this->fValue2 == 5); + * } + * } + * + * $suite = new PHPUnit_TestSuite(); + * $suite->addTest(new MathTest('testAdd')); + * + * $result = PHPUnit::run($suite); + * print $result->toHTML(); + * ?> + * + * + * Alternatively, you can pass a class name to the PHPUnit_TestSuite() + * constructor and let it automatically add all methods of that class + * that start with 'test' to the suite: + * + * + * toHTML(); + * ?> + * + * + * @category Testing + * @package PHPUnit + * @author Sebastian Bergmann + * @copyright 2002-2005 Sebastian Bergmann + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/package/PHPUnit + * @since Class available since Release 1.0.0 + */ +class PHPUnit { + /** + * Runs a test(suite). + * + * @param mixed + * @return PHPUnit_TestResult + * @access public + */ + function &run(&$suite) { + $result = new PHPUnit_TestResult(); + $suite->run($result); + + return $result; + } +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * c-hanging-comment-ender-p: nil + * End: + */ +?> diff --git a/library/pear/PHPUnit/Assert.php b/library/pear/PHPUnit/Assert.php new file mode 100644 index 000000000..6895e4d35 --- /dev/null +++ b/library/pear/PHPUnit/Assert.php @@ -0,0 +1,426 @@ +. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * * Neither the name of Sebastian Bergmann nor the names of his + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRIC + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * @category Testing + * @package PHPUnit + * @author Sebastian Bergmann + * @copyright 2002-2005 Sebastian Bergmann + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + * @version CVS: $Id: Assert.php,v 1.29 2005/11/10 09:47:14 sebastian Exp $ + * @link http://pear.php.net/package/PHPUnit + * @since File available since Release 1.0.0 + */ + +/** + * A set of assert methods. + * + * @category Testing + * @package PHPUnit + * @author Sebastian Bergmann + * @copyright 2002-2005 Sebastian Bergmann + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/package/PHPUnit + * @since Class available since Release 1.0.0 + */ +class PHPUnit_Assert { + /** + * @var boolean + * @access private + */ + var $_looselyTyped = FALSE; + + /** + * Asserts that a haystack contains a needle. + * + * @param mixed + * @param mixed + * @param string + * @access public + * @since Method available since Release 1.1.0 + */ + function assertContains($needle, $haystack, $message = '') { + if (is_string($needle) && is_string($haystack)) { + $this->assertTrue(strpos($haystack, $needle) !== FALSE, $message); + } + + else if (is_array($haystack) && !is_object($needle)) { + $this->assertTrue(in_array($needle, $haystack), $message); + } + + else { + $this->fail('Unsupported parameter passed to assertContains().'); + } + } + + /** + * Asserts that a haystack does not contain a needle. + * + * @param mixed + * @param mixed + * @param string + * @access public + * @since Method available since Release 1.1.0 + */ + function assertNotContains($needle, $haystack, $message = '') { + if (is_string($needle) && is_string($haystack)) { + $this->assertFalse(strpos($haystack, $needle) !== FALSE, $message); + } + + else if (is_array($haystack) && !is_object($needle)) { + $this->assertFalse(in_array($needle, $haystack), $message); + } + + else { + $this->fail('Unsupported parameter passed to assertNotContains().'); + } + } + + /** + * Asserts that two variables are equal. + * + * @param mixed + * @param mixed + * @param string + * @param mixed + * @access public + */ + function assertEquals($expected, $actual, $message = '', $delta = 0) { + if ((is_array($actual) && is_array($expected)) || + (is_object($actual) && is_object($expected))) { + if (is_array($actual) && is_array($expected)) { + ksort($actual); + ksort($expected); + } + + if ($this->_looselyTyped) { + $actual = $this->_convertToString($actual); + $expected = $this->_convertToString($expected); + } + + $actual = serialize($actual); + $expected = serialize($expected); + + $message = sprintf( + '%sexpected %s, actual %s', + + !empty($message) ? $message . ' ' : '', + $expected, + $actual + ); + + if ($actual !== $expected) { + return $this->fail($message); + } + } + + elseif (is_numeric($actual) && is_numeric($expected)) { + $message = sprintf( + '%sexpected %s%s, actual %s', + + !empty($message) ? $message . ' ' : '', + $expected, + ($delta != 0) ? ('+/- ' . $delta) : '', + $actual + ); + + if (!($actual >= ($expected - $delta) && $actual <= ($expected + $delta))) { + return $this->fail($message); + } + } + + else { + $message = sprintf( + '%sexpected %s, actual %s', + + !empty($message) ? $message . ' ' : '', + $expected, + $actual + ); + + if ($actual !== $expected) { + return $this->fail($message); + } + } + } + + /** + * Asserts that two variables reference the same object. + * This requires the Zend Engine 2 to work. + * + * @param object + * @param object + * @param string + * @access public + * @deprecated + */ + function assertSame($expected, $actual, $message = '') { + if (!version_compare(phpversion(), '5.0.0', '>=')) { + $this->fail('assertSame() only works with PHP >= 5.0.0.'); + } + + if ((is_object($expected) || is_null($expected)) && + (is_object($actual) || is_null($actual))) { + $message = sprintf( + '%sexpected two variables to reference the same object', + + !empty($message) ? $message . ' ' : '' + ); + + if ($expected !== $actual) { + return $this->fail($message); + } + } else { + $this->fail('Unsupported parameter passed to assertSame().'); + } + } + + /** + * Asserts that two variables do not reference the same object. + * This requires the Zend Engine 2 to work. + * + * @param object + * @param object + * @param string + * @access public + * @deprecated + */ + function assertNotSame($expected, $actual, $message = '') { + if (!version_compare(phpversion(), '5.0.0', '>=')) { + $this->fail('assertNotSame() only works with PHP >= 5.0.0.'); + } + + if ((is_object($expected) || is_null($expected)) && + (is_object($actual) || is_null($actual))) { + $message = sprintf( + '%sexpected two variables to reference different objects', + + !empty($message) ? $message . ' ' : '' + ); + + if ($expected === $actual) { + return $this->fail($message); + } + } else { + $this->fail('Unsupported parameter passed to assertNotSame().'); + } + } + + /** + * Asserts that a variable is not NULL. + * + * @param mixed + * @param string + * @access public + */ + function assertNotNull($actual, $message = '') { + $message = sprintf( + '%sexpected NOT NULL, actual NULL', + + !empty($message) ? $message . ' ' : '' + ); + + if (is_null($actual)) { + return $this->fail($message); + } + } + + /** + * Asserts that a variable is NULL. + * + * @param mixed + * @param string + * @access public + */ + function assertNull($actual, $message = '') { + $message = sprintf( + '%sexpected NULL, actual NOT NULL', + + !empty($message) ? $message . ' ' : '' + ); + + if (!is_null($actual)) { + return $this->fail($message); + } + } + + /** + * Asserts that a condition is true. + * + * @param boolean + * @param string + * @access public + */ + function assertTrue($condition, $message = '') { + $message = sprintf( + '%sexpected TRUE, actual FALSE', + + !empty($message) ? $message . ' ' : '' + ); + + if (!$condition) { + return $this->fail($message); + } + } + + /** + * Asserts that a condition is false. + * + * @param boolean + * @param string + * @access public + */ + function assertFalse($condition, $message = '') { + $message = sprintf( + '%sexpected FALSE, actual TRUE', + + !empty($message) ? $message . ' ' : '' + ); + + if ($condition) { + return $this->fail($message); + } + } + + /** + * Asserts that a string matches a given regular expression. + * + * @param string + * @param string + * @param string + * @access public + */ + function assertRegExp($pattern, $string, $message = '') { + $message = sprintf( + '%s"%s" does not match pattern "%s"', + + !empty($message) ? $message . ' ' : '', + $string, + $pattern + ); + + if (!preg_match($pattern, $string)) { + return $this->fail($message); + } + } + + /** + * Asserts that a string does not match a given regular expression. + * + * @param string + * @param string + * @param string + * @access public + * @since Method available since Release 1.1.0 + */ + function assertNotRegExp($pattern, $string, $message = '') { + $message = sprintf( + '%s"%s" matches pattern "%s"', + + !empty($message) ? $message . ' ' : '', + $string, + $pattern + ); + + if (preg_match($pattern, $string)) { + return $this->fail($message); + } + } + + /** + * Asserts that a variable is of a given type. + * + * @param string $expected + * @param mixed $actual + * @param optional string $message + * @access public + */ + function assertType($expected, $actual, $message = '') { + return $this->assertEquals( + $expected, + gettype($actual), + $message + ); + } + + /** + * Converts a value to a string. + * + * @param mixed $value + * @access private + */ + function _convertToString($value) { + foreach ($value as $k => $v) { + if (is_array($v)) { + $value[$k] = $this->_convertToString($value[$k]); + } else { + settype($value[$k], 'string'); + } + } + + return $value; + } + + /** + * @param boolean $looselyTyped + * @access public + */ + function setLooselyTyped($looselyTyped) { + if (is_bool($looselyTyped)) { + $this->_looselyTyped = $looselyTyped; + } + } + + /** + * Fails a test with the given message. + * + * @param string + * @access protected + * @abstract + */ + function fail($message = '') { /* abstract */ } +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * c-hanging-comment-ender-p: nil + * End: + */ +?> diff --git a/library/pear/PHPUnit/GUI/Gtk.php b/library/pear/PHPUnit/GUI/Gtk.php new file mode 100644 index 000000000..3649dc2db --- /dev/null +++ b/library/pear/PHPUnit/GUI/Gtk.php @@ -0,0 +1,740 @@ +. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * * Neither the name of Sebastian Bergmann nor the names of his + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRIC + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * @category Testing + * @package PHPUnit + * @author Scott Mattocks + * @copyright 2002-2005 Sebastian Bergmann + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + * @version CVS: $Id: Gtk.php,v 1.6 2005/11/10 09:47:15 sebastian Exp $ + * @link http://pear.php.net/package/PHPUnit + * @since File available since Release 1.2.0 + */ + +if (!function_exists('is_a')) { + require_once 'PHP/Compat/Function/is_a.php'; +} + +/** + * GTK GUI interface for PHPUnit. + * + * This class is a PHP port of junit.awtui.testrunner. Documentation + * for junit.awtui.testrunner can be found at + * http://junit.sourceforge.net + * + * Due to the limitations of PHP4 and PHP-Gtk, this class can not + * duplicate all of the functionality of the JUnit GUI. Some of the + * things this class cannot do include: + * - Reloading the class for each run + * - Stopping the test in progress + * + * To use simply intantiate the class and call main() + * $gtk =& new PHPUnit_GUI_Gtk; + * $gtk->main(); + * + * Once the window has finished loading, you can enter the name of + * a class that has been loaded (include/require some where in your + * code, or you can pass the name of the file containing the class. + * + * You can also load classes using the SetupDecorator class. + * require_once 'PHPUnit/GUI/SetupDecorator.php'; + * require_once 'PHPUnit/GUI/Gtk.php'; + * $gui = new PHPUnit_GUI_SetupDecorator(new PHPUnit_GUI_Gtk()); + * $gui->getSuitesFromDir('/path/to/test','.*\.php$',array('index.php','sql.php')); + * $gui->show(); + * + * + * @category Testing + * @package PHPUnit + * @author Scott Mattocks + * @copyright 2002-2005 Sebastian Bergmann + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/package/PHPUnit + * @since Class available since Release 1.2.0 + * @todo Allow file drop. (Gtk_FileDrop) + */ +class PHPUnit_GUI_Gtk { + + /** + * The main gtk window + * @var object + */ + var $gui; + /** + * The text entry that contains the name of the + * file that holds the test(s)/suite(s). + * @var object + */ + var $suiteField; + /** + * The label that shows the number of tests that + * were run. + * @var object + */ + var $numberOfRuns; + /** + * The label that shows the number of errors that + * were encountered. + * @var object + */ + var $numberOfErrors; + /** + * The label that shows the number of failures + * that were encountered. + * @var object + */ + var $numberOfFailures; + /** + * The label for reporting user messages. + * @var object + */ + var $statusLine; + /** + * The text area for reporting messages from successful + * test runs. (not necessarily successful tests) + * @var object + */ + var $reportArea; + /** + * The text area for reporting errors encountered when + * running tests. + * @var object + */ + var $dumpArea; + /** + * The progress bar indicator. Shows the percentage of + * passed tests. + * @var object + */ + var $progress; + /** + * A checkbox for the user to indicate whether or not they + * would like to see results from all tests or just failures. + * @object + */ + var $showPassed; + + /** + * Constructor. + * + * The constructor checks for the gtk extension and loads it + * if needed. Then it creates the GUI. The GUI is not shown + * nor is the main gtk loop started until main() is called. + * + * @access public + * @param none + * @return void + */ + function PHPUnit_GUI_Gtk() + { + // Check for php-gtk extension. + if (!extension_loaded('gtk')) { + dl( 'php_gtk.' . PHP_SHLIB_SUFFIX); + } + + // Create the interface but don't start the loop + $this->_createUI(); + } + /** + * Start the main gtk loop. + * + * main() first sets the default state of the showPassed + * check box. Next all widgets that are part of the GUI + * are shown. Finally the main gtk loop is started. + * + * @access public + * @param boolean $showPassed + * @return void + */ + function main($showPassed = true) + { + $this->showPassed->set_active($showPassed); + $this->gui->show_all(); + + gtk::main(); + } + /** + * Create the user interface. + * + * The user interface is pretty simple. It consists of a + * menu, text entry, run button, some labels, a progress + * indicator, and a couple of areas for notification of + * any messages. + * + * @access private + * @param none + * @return void + */ + function _createUI() + { + // Create a window. + $window =& new GtkWindow; + $window->set_title('PHPUnit Gtk'); + $window->set_usize(400, -1); + + // Create the main box. + $mainBox =& new GtkVBox; + $window->add($mainBox); + + // Start with the menu. + $mainBox->pack_start($this->_createMenu()); + + // Then add the suite field entry. + $mainBox->pack_start($this->_createSuiteEntry()); + + // Then add the report labels. + $mainBox->pack_start($this->_createReportLabels()); + + // Next add the progress bar. + $mainBox->pack_start($this->_createProgressBar()); + + // Then add the report area and the dump area. + $mainBox->pack_start($this->_createReportAreas()); + + // Finish off with the status line. + $mainBox->pack_start($this->_createStatusLine()); + + // Connect the destroy signal. + $window->connect_object('destroy', array('gtk', 'main_quit')); + + // Assign the member. + $this->gui =& $window; + } + /** + * Create the menu. + * + * The menu is very simple. It an exit menu item, which exits + * the application, and an about menu item, which shows some + * basic information about the application itself. + * + * @access private + * @param none + * @return &object The GtkMenuBar + */ + function &_createMenu() + { + // Create the menu bar. + $menuBar =& new GtkMenuBar; + + // Create the main (only) menu item. + $phpHeader =& new GtkMenuItem('PHPUnit'); + + // Add the menu item to the menu bar. + $menuBar->append($phpHeader); + + // Create the PHPUnit menu. + $phpMenu =& new GtkMenu; + + // Add the menu items + $about =& new GtkMenuItem('About...'); + $about->connect('activate', array(&$this, 'about')); + $phpMenu->append($about); + + $exit =& new GtkMenuItem('Exit'); + $exit->connect_object('activate', array('gtk', 'main_quit')); + $phpMenu->append($exit); + + // Complete the menu. + $phpHeader->set_submenu($phpMenu); + + return $menuBar; + } + /** + * Create the suite entry and related widgets. + * + * The suite entry has some supporting components such as a + * label, the show passed check box and the run button. All + * of these items are packed into two nested boxes. + * + * @access private + * @param none + * @return &object A box that contains all of the suite entry pieces. + */ + function &_createSuiteEntry() + { + // Create the outermost box. + $outerBox =& new GtkVBox; + + // Create the suite label, box, and field. + $suiteLabel =& new GtkLabel('Test class name:'); + $suiteBox =& new GtkHBox; + $this->suiteField =& new GtkEntry; + $this->suiteField->set_text($suiteName != NULL ? $suiteName : ''); + + // Create the button the user will use to start the test. + $runButton =& new GtkButton('Run'); + $runButton->connect_object('clicked', array(&$this, 'run')); + + // Create the check box that lets the user show only failures. + $this->showPassed =& new GtkCheckButton('Show passed tests'); + + // Add the components to their respective boxes. + $suiteLabel->set_alignment(0, 0); + $outerBox->pack_start($suiteLabel); + $outerBox->pack_start($suiteBox); + $outerBox->pack_start($this->showPassed); + + $suiteBox->pack_start($this->suiteField); + $suiteBox->pack_start($runButton); + + return $outerBox; + } + + /** + * Create the labels that tell the user what has happened. + * + * There are three labels, one each for total runs, errors and + * failures. There is also one label for each of these that + * describes what the label is. It could be done with one label + * instead of two but that would make updates much harder. + * + * @access private + * @param none + * @return &object A box containing the labels. + */ + function &_createReportLabels() + { + // Create a box to hold everything. + $labelBox =& new GtkHBox; + + // Create the non-updated labels. + $numberOfRuns =& new GtkLabel('Runs:'); + $numberOfErrors =& new GtkLabel('Errors:'); + $numberOfFailures =& new GtkLabel('Failures:'); + + // Create the labels that will be updated. + // These are asssigned to members to make it easier to + // set their values later. + $this->numberOfRuns =& new GtkLabel(0); + $this->numberOfErrors =& new GtkLabel(0); + $this->numberOfFailures =& new GtkLabel(0); + + // Pack everything in. + $labelBox->pack_start($numberOfRuns); + $labelBox->pack_start($this->numberOfRuns); + $labelBox->pack_start($numberOfErrors); + $labelBox->pack_start($this->numberOfErrors); + $labelBox->pack_start($numberOfFailures); + $labelBox->pack_start($this->numberOfFailures); + + return $labelBox; + } + + /** + * Create the success/failure indicator. + * + * A GtkProgressBar is used to visually indicate how many + * tests were successful compared to how many were not. The + * progress bar shows the percentage of success and will + * change from green to red if there are any failures. + * + * @access private + * @param none + * @return &object The progress bar + */ + function &_createProgressBar() + { + // Create the progress bar. + $this->progress =& new GtkProgressBar(new GtkAdjustment(0, 0, 1, .1, 1, 0)); + + // Set the progress bar to print the percentage. + $this->progress->set_show_text(true); + + return $this->progress; + } + + /** + * Create the report text areas. + * + * The report area consists of one text area for failures, one + * text area for errors and one label for identification purposes. + * All three widgets are packed into a box. + * + * @access private + * @param none + * @return &object The box containing the report areas. + */ + function &_createReportAreas() + { + // Create the containing box. + $reportBox =& new GtkVBox; + + // Create the identification label + $reportLabel =& new GtkLabel('Errors and Failures:'); + $reportLabel->set_alignment(0, 0); + + // Create the scrolled windows for the text areas. + $reportScroll =& new GtkScrolledWindow; + $dumpScroll =& new GtkScrolledWindow; + + // Make the scroll areas big enough. + $reportScroll->set_usize(-1, 150); + $dumpScroll->set_usize(-1, 150); + + // Only show the vertical scroll bar when needed. + // Never show the horizontal scroll bar. + $reportScroll->set_policy(GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC); + $dumpScroll->set_policy(GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC); + + // Create the text areas. + $this->reportArea =& new GtkText; + $this->dumpArea =& new GtkText; + + // Don't let words get broken. + $this->reportArea->set_word_wrap(true); + $this->dumpArea->set_word_wrap(true); + + // Pack everything in. + $reportBox->pack_start($reportLabel); + $reportScroll->add($this->reportArea); + $reportBox->pack_start($reportScroll); + $dumpScroll->add($this->dumpArea); + $reportBox->pack_start($dumpScroll); + + return $reportBox; + } + + /** + * Create a status label. + * + * A status line at the bottom of the application is used + * to notify the user of non-test related messages such as + * failures loading a test suite. + * + * @access private + * @param none + * @return &object The status label. + */ + function &_createStatusLine() + { + // Create the status label. + $this->statusLine =& new GtkLabel(''); + $this->statusLine->set_alignment(0, 0); + + return $this->statusLine; + } + + /** + * Show a popup with information about the application. + * + * The popup should show information about the version, + * the author, the license, where to get the latest + * version and a short description. + * + * @access public + * @param none + * @return void + */ + function about() + { + // Create the new window. + $about =& new GtkWindow; + $about->set_title('About PHPUnit GUI Gtk'); + $about->set_usize(250, -1); + + // Put two vboxes in the hbox. + $vBox =& new GtkVBox; + $about->add($vBox); + + // Create the labels. + $version =& new GtkLabel(" Version: 1.0"); + $license =& new GtkLabel(" License: PHP License v3.0"); + $where =& new GtkLabel(" Download from: http://pear.php.net/PHPUnit/"); + $unitAuth =& new GtkLabel(" PHPUnit Author: Sebastian Bergman"); + $gtkAuth =& new GtkLabel(" Gtk GUI Author: Scott Mattocks"); + + // Align everything to the left + $where->set_alignment(0, .5); + $version->set_alignment(0, .5); + $license->set_alignment(0, .5); + $gtkAuth->set_alignment(0, .5); + $unitAuth->set_alignment(0, .5); + + // Pack everything into the vBox; + $vBox->pack_start($version); + $vBox->pack_start($license); + $vBox->pack_start($where); + $vBox->pack_start($unitAuth); + $vBox->pack_start($gtkAuth); + + // Connect the destroy signal. + $about->connect('destroy', array('gtk', 'true')); + + // Show the goods. + $about->show_all(); + } + + /** + * Load the test suite. + * + * This method tries to load test suite based on the user + * info. If the user passes the name of a tests suite, it + * is instantiated and a new object is returned. If the + * user passes a file that contains a test suite, the class + * is instantiated and a new object is returned. If the user + * passes a file that contains a test case, the test case is + * passed to a new test suite and the new suite object is + * returned. + * + * @access public + * @param string The file that contains a test case/suite or the classname. + * @return &object The new test suite. + */ + function &loadTest(&$file) + { + // Check to see if a class name was given. + if (is_a($file, 'PHPUnit_TestSuite')) { + return $file; + } elseif (class_exists($file)) { + require_once 'PHPUnit/TestSuite.php'; + return new PHPUnit_TestSuite($file); + } + + // Check that the file exists. + if (!@is_readable($file)) { + $this->_showStatus('Cannot find file: ' . $file); + return false; + } + + $this->_showStatus('Loading test suite...'); + + // Instantiate the class. + // If the path is /path/to/test/TestClass.php + // the class name should be test_TestClass + require_once $file; + $className = str_replace(DIRECTORY_SEPARATOR, '_', $file); + $className = substr($className, 0, strpos($className, '.')); + + require_once 'PHPUnit/TestSuite.php'; + return new PHPUnit_TestSuite($className); + } + + /** + * Run the test suite. + * + * This method runs the test suite and updates the messages + * for the user. When finished it changes the status line + * to 'Test Complete' + * + * @access public + * @param none + * @return void + */ + function runTest() + { + // Notify the user that the test is running. + $this->_showStatus('Running Test...'); + + // Run the test. + $result = PHPUnit::run($this->suite); + + // Update the labels. + $this->_setLabelValue($this->numberOfRuns, $result->runCount()); + $this->_setLabelValue($this->numberOfErrors, $result->errorCount()); + $this->_setLabelValue($this->numberOfFailures, $result->failureCount()); + + // Update the progress bar. + $this->_updateProgress($result->runCount(), + $result->errorCount(), + $result->failureCount() + ); + + // Show the errors. + $this->_showFailures($result->errors(), $this->dumpArea); + + // Show the messages from the tests. + if ($this->showPassed->get_active()) { + // Show failures and success. + $this->_showAll($result, $this->reportArea); + } else { + // Show only failures. + $this->_showFailures($result->failures(), $this->reportArea); + } + + // Update the status message. + $this->_showStatus('Test complete'); + } + + /** + * Set the text of a label. + * + * Change the text of a given label. + * + * @access private + * @param widget &$label The label whose value is to be changed. + * @param string $value The new text of the label. + * @return void + */ + function _setLabelValue(&$label, $value) + { + $label->set_text($value); + } + + /** + * The main work of the application. + * + * Load the test suite and then execute the tests. + * + * @access public + * @param none + * @return void + */ + function run() + { + // Load the test suite. + $this->suite =& $this->loadTest($this->suiteField->get_text()); + + // Check to make sure the suite was loaded properly. + if (!is_object($this->suite)) { + // Raise an error. + $this->_showStatus('Could not load test suite.'); + return false; + } + + // Run the tests. + $this->runTest(); + } + + /** + * Update the status message. + * + * @access private + * @param string $status The new message. + * @return void + */ + function _showStatus($status) + { + $this->statusLine->set_text($status); + } + + /** + * Alias for main() + * + * @see main + */ + function show($showPassed = true) + { + $this->main($showPassed); + } + + /** + * Add a suite to the tests. + * + * This method is require by SetupDecorator. It adds a + * suite to the the current set of suites. + * + * @access public + * @param object $testSuite The suite to add. + * @return void + */ + function addSuites($testSuite) + { + if (!is_array($testSuite)) { + settype($testSuite, 'array'); + } + + foreach ($testSuite as $suite) { + + if (is_a($this->suite, 'PHPUnit_TestSuite')) { + $this->suite->addTestSuite($suite->getName()); + } else { + $this->suite =& $this->loadTest($suite); + } + + // Set the suite field. + $text = $this->suiteField->get_text(); + if (empty($text)) { + $this->suiteField->set_text($this->suite->getName()); + } + } + } + + /** + * Show all test messages. + * + * @access private + * @param object The TestResult from the test suite. + * @return void + */ + function _showAll(&$result) + { + // Clear the area first. + $this->reportArea->delete_text(0, -1); + $this->reportArea->insert_text($result->toString(), 0); + } + + /** + * Show failure/error messages in the given text area. + * + * @access private + * @param object &$results The results of the test. + * @param widget &$area The area to show the results in. + */ + function _showFailures(&$results, &$area) + { + $area->delete_text(0, -1); + foreach (array_reverse($results, true) as $result) { + $area->insert_text($result->toString(), 0); + } + } + + /** + * Update the progress indicator. + * + * @access private + * @param integer $runs + * @param integer $errors + * @param integer $failures + * @return void + */ + function _updateProgress($runs, $errors, $failures) + { + $percentage = 1 - (($errors + $failures) / $runs); + $this->progress->set_percentage($percentage); + } +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * c-hanging-comment-ender-p: nil + * End: + */ +?> diff --git a/library/pear/PHPUnit/GUI/HTML.php b/library/pear/PHPUnit/GUI/HTML.php new file mode 100644 index 000000000..f2816ef4c --- /dev/null +++ b/library/pear/PHPUnit/GUI/HTML.php @@ -0,0 +1,252 @@ +. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * * Neither the name of Sebastian Bergmann nor the names of his + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRIC + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * @category Testing + * @package PHPUnit + * @author Wolfram Kriesing + * @copyright 2002-2005 Sebastian Bergmann + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + * @version CVS: $Id: HTML.php,v 1.19 2005/11/10 09:47:15 sebastian Exp $ + * @link http://pear.php.net/package/PHPUnit + * @since File available since Release 1.0.0 + */ + +/** + * HTML GUI. + * + * @category Testing + * @package PHPUnit + * @author Wolfram Kriesing + * @copyright 2002-2005 Sebastian Bergmann + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/package/PHPUnit + * @since Class available since Release 1.0.0 + */ +class PHPUnit_GUI_HTML +{ + var $_suites = array(); + + /** + * the current implementation of PHPUnit is designed + * this way that adding a suite to another suite only + * grabs all the tests and adds them to the suite, so you + * have no chance to find out which test goes with which suite + * therefore you can simply pass an array of suites to this constructor here + * + * @param array The suites to be tested. If not given, then you might + * be using the SetupDecorator, which detects them automatically + * when calling getSuitesFromDir() + */ + function PHPUnit_GUI_HTML($suites = array()) + { + if (!is_array($suites)) { + $this->_suites = array($suites); + } else { + $this->_suites = $suites; + } + } + + /** + * Add suites to the GUI + * + * @param object this should be an instance of PHPUnit_TestSuite + */ + function addSuites($suites) + { + $this->_suites = array_merge($this->_suites,$suites); + } + + /** + * this prints the HTML code straight out + * + */ + function show() + { + $request = $_REQUEST; + $showPassed = FALSE; + $submitted = @$request['submitted']; + + if ($submitted) { + $showPassed = @$request['showOK'] ? TRUE : FALSE; + } + + $suiteResults = array(); + + foreach ($this->_suites as $aSuite) { + $aSuiteResult = array(); + + // remove the first directory's name from the test-suite name, since it + // mostly is something like 'tests' or alike + $removablePrefix = explode('_',$aSuite->getName()); + $aSuiteResult['name'] = str_replace($removablePrefix[0].'_', '', $aSuite->getName()); + + if ($submitted && isset($request[$aSuiteResult['name']])) { + $result = PHPUnit::run($aSuite); + + $aSuiteResult['counts']['run'] = $result->runCount(); + $aSuiteResult['counts']['error'] = $result->errorCount(); + $aSuiteResult['counts']['failure'] = $result->failureCount(); + + $aSuiteResult['results'] = $this->_prepareResult($result,$showPassed); + + $per = 100/$result->runCount(); + $failed = ($per*$result->errorCount())+($per*$result->failureCount()); + $aSuiteResult['percent'] = round(100-$failed,2); + } else { + $aSuiteResult['addInfo'] = 'NOT EXECUTED'; + } + + $suiteResults[] = $aSuiteResult; + } + + $final['name'] = 'OVERALL RESULT'; + $final['counts'] = array(); + $final['percent'] = 0; + $numExecutedTests = 0; + + foreach ($suiteResults as $aSuiteResult) { + if (sizeof(@$aSuiteResult['counts'])) { + foreach ($aSuiteResult['counts'] as $key=>$aCount) { + if (!isset($final['counts'][$key])) { + $final['counts'][$key] = 0; + } + + $final['counts'][$key] += $aCount; + } + } + } + + if (isset($final['counts']['run'])) { + $per = 100/$final['counts']['run']; + $failed = ($per*$final['counts']['error'])+($per*$final['counts']['failure']); + $final['percent'] = round(100-$failed,2); + } else { + $final['percent'] = 0; + } + + array_unshift($suiteResults,$final); + + include 'PHPUnit/GUI/HTML.tpl'; + } + + function _prepareResult($result,$showPassed) + { + $ret = array(); + $failures = $result->failures(); + + foreach($failures as $aFailure) { + $ret['failures'][] = $this->_prepareFailure($aFailure); + } + + $errors = $result->errors(); + + foreach($errors as $aError) { + $ret['errors'][] = $this->_prepareErrors($aError); + } + + if ($showPassed) { + $passed = $result->passedTests(); + + foreach($passed as $aPassed) { + $ret['passed'][] = $this->_preparePassedTests($aPassed); + } + } + + return $ret; + } + + function _prepareFailure($failure) + { + $test = $failure->failedTest(); + $ret['testName'] = $test->getName(); + $exception = $failure->thrownException(); + + // a serialized string starts with a 'character:decimal:{' + // if so we try to unserialize it + // this piece of the regular expression is for detecting a serialized + // type like 'a:3:' for an array with three element or an object i.e. 'O:12:"class":3' + $serialized = '(\w:\d+:(?:"[^"]+":\d+:)?\{.*\})'; + + // Spaces might make a diff, so we shall show them properly (since a + // user agent ignores them). + if (preg_match('/^(.*)expected ' . $serialized . ', actual ' . $serialized . '$/sU', $exception, $matches)) { + ob_start(); + print_r(unserialize($matches[2])); + $ret['expected'] = htmlspecialchars($matches[1]) . "
    " . htmlspecialchars(rtrim(ob_get_contents())) . "
    "; + // Improved compatibility, ob_clean() would be PHP >= 4.2.0 only. + ob_end_clean(); + + ob_start(); + print_r(unserialize($matches[3])); + $ret['actual'] = htmlspecialchars($matches[1]) . "
    " . htmlspecialchars(rtrim(ob_get_contents())) . "
    "; + ob_end_clean(); + } + + else if (preg_match('/^(.*)expected (.*), actual (.*)$/sU', $exception, $matches)) { + $ret['expected'] = nl2br(str_replace(" ", " ", htmlspecialchars($matches[1] . $matches[2]))); + $ret['actual'] = nl2br(str_replace(" ", " ", htmlspecialchars($matches[1] . $matches[3]))); + } else { + $ret['message'] = nl2br(str_replace(" ", " ", htmlspecialchars($exception))); + } + + return $ret; + } + + function _preparePassedTests($passed) + { + $ret['testName'] = $passed->getName(); + return $ret; + } + + function _prepareError($error) + { + $ret['testName'] = $error->getName(); + $ret['message'] = $error->toString(); + return $ret; + } +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * c-hanging-comment-ender-p: nil + * End: + */ +?> diff --git a/library/pear/PHPUnit/GUI/HTML.tpl b/library/pear/PHPUnit/GUI/HTML.tpl new file mode 100644 index 000000000..edf7f2539 --- /dev/null +++ b/library/pear/PHPUnit/GUI/HTML.tpl @@ -0,0 +1,156 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + Options +
    + + (un)check all +     + show OK > +     + +
    + > + +   + + + +
    + + + + + +
    + + + +
    +
    + $value): ?> + s =         + +
    + + + + + + + + + + + + +
    expected
    actual
    + +
    + +
    OK
    + + + + + + diff --git a/library/pear/PHPUnit/GUI/SetupDecorator.php b/library/pear/PHPUnit/GUI/SetupDecorator.php new file mode 100644 index 000000000..8c871fa43 --- /dev/null +++ b/library/pear/PHPUnit/GUI/SetupDecorator.php @@ -0,0 +1,209 @@ +. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * * Neither the name of Sebastian Bergmann nor the names of his + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRIC + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * @category Testing + * @package PHPUnit + * @author Wolfram Kriesing + * @copyright 2002-2005 Sebastian Bergmann + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + * @version CVS: $Id: SetupDecorator.php,v 1.15 2005/11/10 09:47:15 sebastian Exp $ + * @link http://pear.php.net/package/PHPUnit + * @since File available since Release 1.0.0 + */ + +/** + * This decorator actually just adds the functionality to read the + * test-suite classes from a given directory and instanciate them + * automatically, use it as given in the example below. + * + * + * getSuitesFromDir('/path/to/dir/tests','.*\.php$',array('index.php','sql.php')); + * $gui->show(); + * ?> + * + * + * The example calls this class and tells it to: + * + * - find all file under the directory /path/to/dir/tests + * - for files, which end with '.php' (this is a piece of a regexp, that's why the . is escaped) + * - and to exclude the files 'index.php' and 'sql.php' + * - and include all the files that are left in the tests. + * + * Given that the path (the first parameter) ends with 'tests' it will be assumed + * that the classes are named tests_* where * is the directory plus the filename, + * according to PEAR standards. + * + * So that: + * + * - 'testMe.php' in the dir 'tests' bill be assumed to contain a class tests_testMe + * - '/moretests/aTest.php' should contain a class 'tests_moretests_aTest' + * + * @category Testing + * @package PHPUnit + * @author Wolfram Kriesing + * @copyright 2002-2005 Sebastian Bergmann + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/package/PHPUnit + * @since Class available since Release 1.0.0 + */ +class PHPUnit_GUI_SetupDecorator +{ + /** + * + * + */ + function PHPUnit_GUI_SetupDecorator(&$gui) + { + $this->_gui = &$gui; + } + + /** + * just forwarding the action to the decorated class. + * + */ + function show($showPassed=TRUE) + { + $this->_gui->show($showPassed); + } + + /** + * Setup test suites that can be found in the given directory + * Using the second parameter you can also choose a subsets of the files found + * in the given directory. I.e. only all the files that contain '_UnitTest_', + * in order to do this simply call it like this: + * getSuitesFromDir($dir,'.*_UnitTest_.*'). + * There you can already see that the pattern is built for the use within a regular expression. + * + * @param string the directory where to search for test-suite files + * @param string the pattern (a regexp) by which to find the files + * @param array an array of file names that shall be excluded + */ + function getSuitesFromDir($dir, $filenamePattern = '', $exclude = array()) + { + if ($dir{strlen($dir)-1} == DIRECTORY_SEPARATOR) { + $dir = substr($dir, 0, -1); + } + + $files = $this->_getFiles(realpath($dir), $filenamePattern, $exclude, realpath($dir . '/..')); + asort($files); + + foreach ($files as $className => $aFile) { + include_once($aFile); + + if (class_exists($className)) { + $suites[] =& new PHPUnit_TestSuite($className); + } else { + trigger_error("$className could not be found in $dir$aFile!"); + } + } + + $this->_gui->addSuites($suites); + } + + /** + * This method searches recursively through the directories + * to find all the files that shall be added to the be visible. + * + * @param string the path where find the files + * @param srting the string pattern by which to find the files + * @param string the file names to be excluded + * @param string the root directory, which serves as the prefix to the fully qualified filename + * @access private + */ + function _getFiles($dir, $filenamePattern, $exclude, $rootDir) + { + $files = array(); + + if ($dp = opendir($dir)) { + while (FALSE !== ($file = readdir($dp))) { + $filename = $dir . DIRECTORY_SEPARATOR . $file; + $match = TRUE; + + if ($filenamePattern && !preg_match("~$filenamePattern~", $file)) { + $match = FALSE; + } + + if (sizeof($exclude)) { + foreach ($exclude as $aExclude) { + if (strpos($file, $aExclude) !== FALSE) { + $match = FALSE; + break; + } + } + } + + if (is_file($filename) && $match) { + $tmp = str_replace($rootDir, '', $filename); + + if (strpos($tmp, DIRECTORY_SEPARATOR) === 0) { + $tmp = substr($tmp, 1); + } + + if (strpos($tmp, '/') === 0) { + $tmp = substr($tmp, 1); + } + + $className = str_replace(DIRECTORY_SEPARATOR, '_', $tmp); + $className = basename($className, '.php'); + + $files[$className] = $filename; + } + + if ($file != '.' && $file != '..' && is_dir($filename)) { + $files = array_merge($files, $this->_getFiles($filename, $filenamePattern, $exclude, $rootDir)); + } + } + + closedir($dp); + } + + return $files; + } +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * c-hanging-comment-ender-p: nil + * End: + */ +?> diff --git a/library/pear/PHPUnit/RepeatedTest.php b/library/pear/PHPUnit/RepeatedTest.php new file mode 100644 index 000000000..e014eb5ae --- /dev/null +++ b/library/pear/PHPUnit/RepeatedTest.php @@ -0,0 +1,154 @@ +. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * * Neither the name of Sebastian Bergmann nor the names of his + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRIC + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * @category Testing + * @package PHPUnit + * @author Sebastian Bergmann + * @copyright 2002-2005 Sebastian Bergmann + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + * @version CVS: $Id: RepeatedTest.php,v 1.13 2005/11/10 09:47:14 sebastian Exp $ + * @link http://pear.php.net/package/PHPUnit + * @since File available since Release 1.0.0 + */ + +require_once 'PHPUnit/TestDecorator.php'; + +/** + * A Decorator that runs a test repeatedly. + * + * Here is an example: + * + * + * PHPUnit_TestCase($name); + * } + * + * function setUp() { + * $this->fValue1 = 2; + * $this->fValue2 = 3; + * } + * + * function testAdd() { + * $this->assertTrue($this->fValue1 + $this->fValue2 == 5); + * } + * } + * + * $suite = new PHPUnit_TestSuite; + * + * $suite->addTest( + * new PHPUnit_RepeatedTest( + * new MathTest('testAdd'), + * 10 + * ) + * ); + * + * $result = PHPUnit::run($suite); + * print $result->toString(); + * ?> + * + * + * @category Testing + * @package PHPUnit + * @author Sebastian Bergmann + * @copyright 2002-2005 Sebastian Bergmann + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/package/PHPUnit + * @since Class available since Release 1.0.0 + */ +class PHPUnit_RepeatedTest extends PHPUnit_TestDecorator { + /** + * @var integer + * @access private + */ + var $_timesRepeat = 1; + + /** + * Constructor. + * + * @param object + * @param integer + * @access public + */ + function PHPUnit_RepeatedTest(&$test, $timesRepeat = 1) { + $this->PHPUnit_TestDecorator($test); + $this->_timesRepeat = $timesRepeat; + } + + /** + * Counts the number of test cases that + * will be run by this test. + * + * @return integer + * @access public + */ + function countTestCases() { + return $this->_timesRepeat * $this->_test->countTestCases(); + } + + /** + * Runs the decorated test and collects the + * result in a TestResult. + * + * @param object + * @access public + * @abstract + */ + function run(&$result) { + for ($i = 0; $i < $this->_timesRepeat; $i++) { + $this->_test->run($result); + } + } +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * c-hanging-comment-ender-p: nil + * End: + */ +?> diff --git a/library/pear/PHPUnit/Skeleton.php b/library/pear/PHPUnit/Skeleton.php new file mode 100644 index 000000000..e316373e0 --- /dev/null +++ b/library/pear/PHPUnit/Skeleton.php @@ -0,0 +1,448 @@ +. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * * Neither the name of Sebastian Bergmann nor the names of his + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRIC + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * @category Testing + * @package PHPUnit + * @author Scott Mattocks + * @copyright 2002-2005 Sebastian Bergmann + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + * @version CVS: $Id: Skeleton.php,v 1.8 2005/11/10 09:47:14 sebastian Exp $ + * @link http://pear.php.net/package/PHPUnit + * @since File available since Release 1.1.0 + */ + +/** + * Class for creating a PHPUnit_TestCase skeleton file. + * + * This class will take a classname as a parameter on construction and will + * create a PHP file that contains the skeleton of a PHPUnit_TestCase + * subclass. The test case will contain a test foreach method of the class. + * Methods of the parent class will, by default, be excluded from the test + * class. Passing and optional construction parameter will include them. + * + * Example + * + * createTestClass(); + * + * // Write the new test class to file. + * // By default, code to run the test will be included. + * $ps->writeTestClass(); + * ?> + * + * Now open the skeleton class and fill in the details. + * If you run the test as is, all tests will fail and + * you will see plenty of undefined constant errors. + * + * @category Testing + * @package PHPUnit + * @author Scott Mattocks + * @copyright 2002-2005 Sebastian Bergmann + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/package/PHPUnit + * @since Class available since Release 1.1.0 + */ +class PHPUnit_Skeleton { + /** + * Path to the class file to create a skeleton for. + * @var string + */ + var $classPath; + + /** + * The name of the class + * @var string + */ + var $className; + + /** + * Path to the configuration file needed by class to test. + * @var string + */ + var $configFile; + + /** + * Whether or not to include the methods of the parent class when testing. + * @var boolean + */ + var $includeParents; + + /** + * Whether or not to test private methods. + * @var boolean + */ + var $includePrivate; + + /** + * The test class that will be created. + * @var string + */ + var $testClass; + + /** + * Constructor. Sets the class members and check that the class + * to test is accessible. + * + * @access public + * @param string $className + * @param string $classPath + * @param boolean $includeParents Wheter to include the parent's methods in the test. + * @return void + */ + function PHPUnit_Skeleton($className, $classPath, $includeParents = FALSE, $includePrivate = TRUE) { + // Set up the members. + if (@is_readable($classPath)) { + $this->className = $className; + $this->classPath = $classPath; + } else { + $this->_handleErrors($classPath . ' is not readable. Cannot create test class.'); + } + + // Do we want to include parent methods? + $this->includeParents = $includeParents; + + // Do we want to allow private methods? + $this->includePrivate = $includePrivate; + } + + /** + * The class to test may require a special config file before it can be + * instantiated. This method lets you set that file. + * + * @access public + * @param string $configPath + * @return void + */ + function setConfigFile($configFile) { + // Check that the file is readable + if (@is_readable($configFile)) { + $this->configFile = $configFile; + } else { + $this->_handleErrors($configFile . ' is not readable. Cannot create test class.'); + } + } + + /** + * Create the code that will be the skeleton of the test case. + * + * The test case must have a clss definition, one var, a constructor + * setUp, tearDown, and methods. Optionally and by default the code + * to run the test is added when the class is written to file. + * + * @access public + * @param none + * @return void + */ + function createTestClass() { + // Instantiate the object. + if (isset($this->configFile)) { + require_once $this->configFile; + } + + require_once $this->classPath; + + // Get the methods. + $classMethods = get_class_methods($this->className); + + // Remove the parent methods if needed. + if (!$this->includeParents) { + $parentMethods = get_class_methods(get_parent_class($this->className)); + + if (count($parentMethods)) { + $classMethods = array_diff($classMethods, $parentMethods); + } + } + + // Create the class definition, constructor, setUp and tearDown. + $this->_createDefinition(); + $this->_createConstructor(); + $this->_createSetUpTearDown(); + + if (count($classMethods)) { + // Foreach method create a test case. + foreach ($classMethods as $method) { + // Unless it is the constructor. + if (strcasecmp($this->className, $method) !== 0) { + // Check for private methods. + if (!$this->includePrivate && strpos($method, '_') === 0) { + continue; + } else { + $this->_createMethod($method); + } + } + } + } + + // Finis off the class. + $this->_finishClass(); + } + + /** + * Create the class definition. + * + * The definition consist of a header comment, require statment + * for getting the PHPUnit file, the actual class definition, + * and the definition of the class member variable. + * + * All of the code needed for the new class is stored in the + * testClass member. + * + * @access private + * @param none + * @return void + */ + function _createDefinition() { + // Create header comment. + $this->testClass = + "/**\n" . + " * PHPUnit test case for " . $this->className . "\n" . + " * \n" . + " * The method skeletons below need to be filled in with \n" . + " * real data so that the tests will run correctly. Replace \n" . + " * all EXPECTED_VAL and PARAM strings with real data. \n" . + " * \n" . + " * Created with PHPUnit_Skeleton on " . date('Y-m-d') . "\n" . + " */\n"; + + // Add the require statements. + $this->testClass .= "require_once 'PHPUnit.php';\n"; + + // Add the class definition and variable definition. + $this->testClass .= + "class " . $this->className . "Test extends PHPUnit_TestCase {\n\n" . + " var \$" . $this->className . ";\n\n"; + } + + /** + * Create the class constructor. (PHP4 style) + * + * The constructor simply calls the PHPUnit_TestCase method. + * This code is taken from the PHPUnit documentation. + * + * All of the code needed for the new class is stored in the + * testClass member. + * + * @access private + * @param none + * @return void + */ + function _createConstructor() { + // Create the test class constructor. + $this->testClass.= + " function " . $this->className . "Test(\$name)\n" . + " {\n" . + " \$this->PHPUnit_TestCase(\$name);\n" . + " }\n\n"; + } + + /** + * Create setUp and tearDown methods. + * + * The setUp method creates the instance of the object to test. + * The tearDown method releases the instance. + * This code is taken from the PHPUnit documentation. + * + * All of the code needed for the new class is stored in the + * testClass member. + * + * @access private + * @param none + * @return void + */ + function _createSetUpTearDown() { + // Create the setUp method. + $this->testClass .= + " function setUp()\n" . + " {\n"; + + if (isset($this->configFile)) { + $this->testClass .= + " require_once '" . $this->configFile . "';\n"; + } + + $this->testClass .= + " require_once '" . $this->classPath . "';\n" . + " \$this->" . $this->className . " =& new " . $this->className . "(PARAM);\n" . + " }\n\n"; + + // Create the tearDown method. + $this->testClass .= + " function tearDown()\n" . + " {\n" . + " unset(\$this->" . $this->className . ");\n" . + " }\n\n"; + } + + /** + * Create a basic skeleton for test methods. + * + * This code is taken from the PHPUnit documentation. + * + * All of the code needed for the new class is stored in the + * testClass member. + * + * @access private + * @param none + * @return void + */ + function _createMethod($methodName) { + // Create a test method. + $this->testClass .= + " function test" . $methodName . "()\n" . + " {\n" . + " \$result = \$this->" . $this->className . "->" . $methodName . "(PARAM);\n" . + " \$expected = EXPECTED_VAL;\n" . + " \$this->assertEquals(\$expected, \$result);\n" . + " }\n\n"; + } + + /** + * Add the closing brace needed for a proper class definition. + * + * All of the code needed for the new class is stored in the + * testClass member. + * + * @access private + * @param none + * @return void + */ + function _finishClass() { + // Close off the class. + $this->testClass.= "}\n"; + } + + /** + * Create the code that will actually run the test. + * + * This code is added by default so that the test can be run + * just by running the file. To have it not added pass false + * as the second parameter to the writeTestClass method. + * This code is taken from the PHPUnit documentation. + * + * All of the code needed for the new class is stored in the + * testClass member. + * + * @access private + * @param none + * @return void + */ + function _createTest() { + // Create a call to the test. + $test = + "// Running the test.\n" . + "\$suite = new PHPUnit_TestSuite('" . $this->className . "Test');\n" . + "\$result = PHPUnit::run(\$suite);\n" . + "echo \$result->toString();\n"; + + return $test; + } + + /** + * Write the test class to file. + * + * This will write the test class created using the createTestClass + * method to a file called Test.php. By default the file + * is written to the current directory and will have code to run + * the test appended to the bottom of the file. + * + * @access public + * @param string $destination The directory to write the file to. + * @param boolean $addTest Wheter to add the test running code. + * @return void + */ + function writeTestClass($destination = './', $addTest = TRUE) { + // Check for something to write to file. + if (!isset($this->testClass)) { + $this->_handleErrors('Noting to write.', PHPUS_WARNING); + return; + } + + // Open the destination file. + $fp = fopen($destination . $this->className . 'Test.php', 'w'); + fwrite($fp, "testClass); + + // Add the call to test the class in the file if we were asked to. + if ($addTest) { + fwrite($fp, $this->_createTest()); + } + + // Close the file. + fwrite($fp, "?>\n"); + fclose($fp); + } + + /** + * Error handler. + * + * This method should be rewritten to use the prefered error + * handling method. (PEAR_ErrorStack) + * + * @access private + * @param string $message The error message. + * @param integer $type An indication of the severity of the error. + * @return void Code may cause PHP to exit. + */ + function _handleErrors($message, $type = E_USER_ERROR) { + // For now just echo the message. + echo $message; + + // Check to see if we should quit. + if ($type == E_USER_ERROR) { + exit; + } + } +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * c-hanging-comment-ender-p: nil + * End: + */ +?> diff --git a/library/pear/PHPUnit/TestCase.php b/library/pear/PHPUnit/TestCase.php new file mode 100644 index 000000000..78a23aa8c --- /dev/null +++ b/library/pear/PHPUnit/TestCase.php @@ -0,0 +1,293 @@ +. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * * Neither the name of Sebastian Bergmann nor the names of his + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRIC + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * @category Testing + * @package PHPUnit + * @author Sebastian Bergmann + * @copyright 2002-2005 Sebastian Bergmann + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + * @version CVS: $Id: TestCase.php,v 1.21 2005/11/10 09:47:14 sebastian Exp $ + * @link http://pear.php.net/package/PHPUnit + * @since File available since Release 1.0.0 + */ + +require_once 'PHPUnit/Assert.php'; +require_once 'PHPUnit/TestResult.php'; + +/** + * A TestCase defines the fixture to run multiple tests. + * + * To define a TestCase + * + * 1) Implement a subclass of PHPUnit_TestCase. + * 2) Define instance variables that store the state of the fixture. + * 3) Initialize the fixture state by overriding setUp(). + * 4) Clean-up after a test by overriding tearDown(). + * + * Each test runs in its own fixture so there can be no side effects + * among test runs. + * + * Here is an example: + * + * + * PHPUnit_TestCase($name); + * } + * + * function setUp() { + * $this->fValue1 = 2; + * $this->fValue2 = 3; + * } + * } + * ?> + * + * + * For each test implement a method which interacts with the fixture. + * Verify the expected results with assertions specified by calling + * assert with a boolean. + * + * + * assertTrue($this->fValue1 + $this->fValue2 == 5); + * } + * ?> + * + * + * @category Testing + * @package PHPUnit + * @author Sebastian Bergmann + * @copyright 2002-2005 Sebastian Bergmann + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/package/PHPUnit + * @since Class available since Release 1.0.0 + */ +class PHPUnit_TestCase extends PHPUnit_Assert { + /** + * @var boolean + * @access private + */ + var $_failed = FALSE; + + /** + * The name of the test case. + * + * @var string + * @access private + */ + var $_name = ''; + + /** + * PHPUnit_TestResult object + * + * @var object + * @access private + */ + var $_result; + + /** + * Constructs a test case with the given name. + * + * @param string + * @access public + */ + function PHPUnit_TestCase($name = FALSE) { + if ($name !== FALSE) { + $this->setName($name); + } + } + + /** + * Counts the number of test cases executed by run(TestResult result). + * + * @return integer + * @access public + */ + function countTestCases() { + return 1; + } + + /** + * Gets the name of a TestCase. + * + * @return string + * @access public + */ + function getName() { + return $this->_name; + } + + /** + * Runs the test case and collects the results in a given TestResult object. + * + * @param object + * @return object + * @access public + */ + function run(&$result) { + $this->_result = &$result; + $this->_result->run($this); + + return $this->_result; + } + + /** + * Runs the bare test sequence. + * + * @access public + */ + function runBare() { + $this->setUp(); + $this->runTest(); + $this->tearDown(); + $this->pass(); + } + + /** + * Override to run the test and assert its state. + * + * @access protected + */ + function runTest() { + call_user_func( + array( + &$this, + $this->_name + ) + ); + } + + /** + * Sets the name of a TestCase. + * + * @param string + * @access public + */ + function setName($name) { + $this->_name = $name; + } + + /** + * Returns a string representation of the test case. + * + * @return string + * @access public + */ + function toString() { + return ''; + } + + /** + * Creates a default TestResult object. + * + * @return object + * @access protected + */ + function &createResult() { + return new PHPUnit_TestResult; + } + + /** + * Fails a test with the given message. + * + * @param string + * @access protected + */ + function fail($message = '') { + if (function_exists('debug_backtrace')) { + $trace = debug_backtrace(); + + if (isset($trace['1']['file'])) { + $message = sprintf( + "%s in %s:%s", + + $message, + $trace['1']['file'], + $trace['1']['line'] + ); + } + } + + $this->_result->addFailure($this, $message); + $this->_failed = TRUE; + } + + /** + * Passes a test. + * + * @access protected + */ + function pass() { + if (!$this->_failed) { + $this->_result->addPassedTest($this); + } + } + + /** + * Sets up the fixture, for example, open a network connection. + * This method is called before a test is executed. + * + * @access protected + * @abstract + */ + function setUp() { /* abstract */ } + + /** + * Tears down the fixture, for example, close a network connection. + * This method is called after a test is executed. + * + * @access protected + * @abstract + */ + function tearDown() { /* abstract */ } +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * c-hanging-comment-ender-p: nil + * End: + */ +?> diff --git a/library/pear/PHPUnit/TestDecorator.php b/library/pear/PHPUnit/TestDecorator.php new file mode 100644 index 000000000..41332103c --- /dev/null +++ b/library/pear/PHPUnit/TestDecorator.php @@ -0,0 +1,156 @@ +. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * * Neither the name of Sebastian Bergmann nor the names of his + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRIC + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * @category Testing + * @package PHPUnit + * @author Sebastian Bergmann + * @copyright 2002-2005 Sebastian Bergmann + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + * @version CVS: $Id: TestDecorator.php,v 1.17 2005/11/10 09:47:14 sebastian Exp $ + * @link http://pear.php.net/package/PHPUnit + * @since File available since Release 1.0.0 + */ + +require_once 'PHPUnit/TestCase.php'; +require_once 'PHPUnit/TestSuite.php'; + +if (!function_exists('is_a')) { + require_once 'PHP/Compat/Function/is_a.php'; +} + +/** + * A Decorator for Tests. + * + * Use TestDecorator as the base class for defining new + * test decorators. Test decorator subclasses can be introduced + * to add behaviour before or after a test is run. + * + * @category Testing + * @package PHPUnit + * @author Sebastian Bergmann + * @copyright 2002-2005 Sebastian Bergmann + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/package/PHPUnit + * @since Class available since Release 1.0.0 + */ +class PHPUnit_TestDecorator { + /** + * The Test to be decorated. + * + * @var object + * @access protected + */ + var $_test = NULL; + + /** + * Constructor. + * + * @param object + * @access public + */ + function PHPUnit_TestDecorator(&$test) { + if (is_object($test) && + (is_a($test, 'PHPUnit_TestCase') || + is_a($test, 'PHPUnit_TestSuite'))) { + + $this->_test = &$test; + } + } + + /** + * Runs the test and collects the + * result in a TestResult. + * + * @param object + * @access public + */ + function basicRun(&$result) { + $this->_test->run($result); + } + + /** + * Counts the number of test cases that + * will be run by this test. + * + * @return integer + * @access public + */ + function countTestCases() { + return $this->_test->countTestCases(); + } + + /** + * Returns the test to be run. + * + * @return object + * @access public + */ + function &getTest() { + return $this->_test; + } + + /** + * Runs the decorated test and collects the + * result in a TestResult. + * + * @param object + * @access public + * @abstract + */ + function run(&$result) { /* abstract */ } + + /** + * Returns a string representation of the test. + * + * @return string + * @access public + */ + function toString() { + return $this->_test->toString(); + } +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * c-hanging-comment-ender-p: nil + * End: + */ +?> diff --git a/library/pear/PHPUnit/TestFailure.php b/library/pear/PHPUnit/TestFailure.php new file mode 100644 index 000000000..7839e4465 --- /dev/null +++ b/library/pear/PHPUnit/TestFailure.php @@ -0,0 +1,130 @@ +. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * * Neither the name of Sebastian Bergmann nor the names of his + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRIC + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * @category Testing + * @package PHPUnit + * @author Sebastian Bergmann + * @copyright 2002-2005 Sebastian Bergmann + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + * @version CVS: $Id: TestFailure.php,v 1.13 2005/11/10 09:47:14 sebastian Exp $ + * @link http://pear.php.net/package/PHPUnit + * @since File available since Release 1.0.0 + */ + +/** + * A TestFailure collects a failed test together with the caught exception. + * + * @category Testing + * @package PHPUnit + * @author Sebastian Bergmann + * @copyright 2002-2005 Sebastian Bergmann + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/package/PHPUnit + * @since Class available since Release 1.0.0 + */ +class PHPUnit_TestFailure { + /** + * @var object + * @access private + */ + var $_failedTest; + + /** + * @var string + * @access private + */ + var $_thrownException; + + /** + * Constructs a TestFailure with the given test and exception. + * + * @param object + * @param string + * @access public + */ + function PHPUnit_TestFailure(&$failedTest, &$thrownException) { + $this->_failedTest = &$failedTest; + $this->_thrownException = &$thrownException; + } + + /** + * Gets the failed test. + * + * @return object + * @access public + */ + function &failedTest() { + return $this->_failedTest; + } + + /** + * Gets the thrown exception. + * + * @return object + * @access public + */ + function &thrownException() { + return $this->_thrownException; + } + + /** + * Returns a short description of the failure. + * + * @return string + * @access public + */ + function toString() { + return sprintf( + "TestCase %s->%s() failed: %s\n", + + get_class($this->_failedTest), + $this->_failedTest->getName(), + $this->_thrownException + ); + } +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * c-hanging-comment-ender-p: nil + * End: + */ +?> diff --git a/library/pear/PHPUnit/TestListener.php b/library/pear/PHPUnit/TestListener.php new file mode 100644 index 000000000..9cff52cb2 --- /dev/null +++ b/library/pear/PHPUnit/TestListener.php @@ -0,0 +1,162 @@ +. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * * Neither the name of Sebastian Bergmann nor the names of his + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRIC + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * @category Testing + * @package PHPUnit + * @author Sebastian Bergmann + * @copyright 2002-2005 Sebastian Bergmann + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + * @version CVS: $Id: TestListener.php,v 1.12 2005/11/10 09:47:15 sebastian Exp $ + * @link http://pear.php.net/package/PHPUnit + * @since File available since Release 1.0.0 + */ + +/** + * A Listener for test progress. + * + * Here is an example: + * + * + * PHPUnit_TestCase($name); + * } + * + * function setUp() { + * $this->fValue1 = 2; + * $this->fValue2 = 3; + * } + * + * function testAdd() { + * $this->assertTrue($this->fValue1 + $this->fValue2 == 4); + * } + * } + * + * class MyListener extends PHPUnit_TestListener { + * function addError(&$test, &$t) { + * print "MyListener::addError() called.\n"; + * } + * + * function addFailure(&$test, &$t) { + * print "MyListener::addFailure() called.\n"; + * } + * + * function endTest(&$test) { + * print "MyListener::endTest() called.\n"; + * } + * + * function startTest(&$test) { + * print "MyListener::startTest() called.\n"; + * } + * } + * + * $suite = new PHPUnit_TestSuite; + * $suite->addTest(new MathTest('testAdd')); + * + * $result = new PHPUnit_TestResult; + * $result->addListener(new MyListener); + * + * $suite->run($result); + * print $result->toString(); + * ?> + * + * + * @category Testing + * @package PHPUnit + * @author Sebastian Bergmann + * @copyright 2002-2005 Sebastian Bergmann + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/package/PHPUnit + * @since Class available since Release 1.0.0 + */ +class PHPUnit_TestListener { + /** + * An error occurred. + * + * @param object + * @param object + * @access public + * @abstract + */ + function addError(&$test, &$t) { /*abstract */ } + + /** + * A failure occurred. + * + * @param object + * @param object + * @access public + * @abstract + */ + function addFailure(&$test, &$t) { /*abstract */ } + + /** + * A test ended. + * + * @param object + * @access public + * @abstract + */ + function endTest(&$test) { /*abstract */ } + + /** + * A test started. + * + * @param object + * @access public + * @abstract + */ + function startTest(&$test) { /*abstract */ } +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * c-hanging-comment-ender-p: nil + * End: + */ +?> diff --git a/library/pear/PHPUnit/TestResult.php b/library/pear/PHPUnit/TestResult.php new file mode 100644 index 000000000..eb42121e1 --- /dev/null +++ b/library/pear/PHPUnit/TestResult.php @@ -0,0 +1,347 @@ +. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * * Neither the name of Sebastian Bergmann nor the names of his + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRIC + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * @category Testing + * @package PHPUnit + * @author Sebastian Bergmann + * @copyright 2002-2005 Sebastian Bergmann + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + * @version CVS: $Id: TestResult.php,v 1.18 2005/11/10 09:47:15 sebastian Exp $ + * @link http://pear.php.net/package/PHPUnit + * @since File available since Release 1.0.0 + */ + +require_once 'PHPUnit/TestFailure.php'; +require_once 'PHPUnit/TestListener.php'; + +if (!function_exists('is_a')) { + require_once 'PHP/Compat/Function/is_a.php'; +} + +/** + * A TestResult collects the results of executing a test case. + * + * @category Testing + * @package PHPUnit + * @author Sebastian Bergmann + * @copyright 2002-2005 Sebastian Bergmann + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/package/PHPUnit + * @since Class available since Release 1.0.0 + */ +class PHPUnit_TestResult { + /** + * @var array + * @access protected + */ + var $_errors = array(); + + /** + * @var array + * @access protected + */ + var $_failures = array(); + + /** + * @var array + * @access protected + */ + var $_listeners = array(); + + /** + * @var array + * @access protected + */ + var $_passedTests = array(); + + /** + * @var integer + * @access protected + */ + var $_runTests = 0; + + /** + * @var boolean + * @access private + */ + var $_stop = FALSE; + + /** + * Adds an error to the list of errors. + * The passed in exception caused the error. + * + * @param object + * @param object + * @access public + */ + function addError(&$test, &$t) { + $this->_errors[] = new PHPUnit_TestFailure($test, $t); + + for ($i = 0; $i < sizeof($this->_listeners); $i++) { + $this->_listeners[$i]->addError($test, $t); + } + } + + /** + * Adds a failure to the list of failures. + * The passed in exception caused the failure. + * + * @param object + * @param object + * @access public + */ + function addFailure(&$test, &$t) { + $this->_failures[] = new PHPUnit_TestFailure($test, $t); + + for ($i = 0; $i < sizeof($this->_listeners); $i++) { + $this->_listeners[$i]->addFailure($test, $t); + } + } + + /** + * Registers a TestListener. + * + * @param object + * @access public + */ + function addListener(&$listener) { + if (is_object($listener) && + is_a($listener, 'PHPUnit_TestListener')) { + $this->_listeners[] = &$listener; + } + } + + /** + * Adds a passed test to the list of passed tests. + * + * @param object + * @access public + */ + function addPassedTest(&$test) { + $this->_passedTests[] = &$test; + } + + /** + * Informs the result that a test was completed. + * + * @param object + * @access public + */ + function endTest(&$test) { + for ($i = 0; $i < sizeof($this->_listeners); $i++) { + $this->_listeners[$i]->endTest($test); + } + } + + /** + * Gets the number of detected errors. + * + * @return integer + * @access public + */ + function errorCount() { + return sizeof($this->_errors); + } + + /** + * Returns an Enumeration for the errors. + * + * @return array + * @access public + */ + function &errors() { + return $this->_errors; + } + + /** + * Gets the number of detected failures. + * + * @return integer + * @access public + */ + function failureCount() { + return sizeof($this->_failures); + } + + /** + * Returns an Enumeration for the failures. + * + * @return array + * @access public + */ + function &failures() { + return $this->_failures; + } + + /** + * Returns an Enumeration for the passed tests. + * + * @return array + * @access public + */ + function &passedTests() { + return $this->_passedTests; + } + + /** + * Unregisters a TestListener. + * This requires the Zend Engine 2 (to work properly). + * + * @param object + * @access public + */ + function removeListener(&$listener) { + for ($i = 0; $i < sizeof($this->_listeners); $i++) { + if ($this->_listeners[$i] === $listener) { + unset($this->_listeners[$i]); + } + } + } + + /** + * Runs a TestCase. + * + * @param object + * @access public + */ + function run(&$test) { + $this->startTest($test); + $this->_runTests++; + $test->runBare(); + $this->endTest($test); + } + + /** + * Gets the number of run tests. + * + * @return integer + * @access public + */ + function runCount() { + return $this->_runTests; + } + + /** + * Checks whether the test run should stop. + * + * @access public + */ + function shouldStop() { + return $this->_stop; + } + + /** + * Informs the result that a test will be started. + * + * @param object + * @access public + */ + function startTest(&$test) { + for ($i = 0; $i < sizeof($this->_listeners); $i++) { + $this->_listeners[$i]->startTest($test); + } + } + + /** + * Marks that the test run should stop. + * + * @access public + */ + function stop() { + $this->_stop = TRUE; + } + + /** + * Returns a HTML representation of the test result. + * + * @return string + * @access public + */ + function toHTML() { + return '
    ' . htmlspecialchars($this->toString()) . '
    '; + } + + /** + * Returns a text representation of the test result. + * + * @return string + * @access public + */ + function toString() { + $result = ''; + + foreach ($this->_passedTests as $passedTest) { + $result .= sprintf( + "TestCase %s->%s() passed\n", + + get_class($passedTest), + $passedTest->getName() + ); + } + + foreach ($this->_failures as $failedTest) { + $result .= $failedTest->toString(); + } + + return $result; + } + + /** + * Returns whether the entire test was successful or not. + * + * @return boolean + * @access public + */ + function wasSuccessful() { + if (empty($this->_errors) && empty($this->_failures)) { + return TRUE; + } else { + return FALSE; + } + } +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * c-hanging-comment-ender-p: nil + * End: + */ +?> diff --git a/library/pear/PHPUnit/TestSuite.php b/library/pear/PHPUnit/TestSuite.php new file mode 100644 index 000000000..c0e069eec --- /dev/null +++ b/library/pear/PHPUnit/TestSuite.php @@ -0,0 +1,262 @@ +. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * * Neither the name of Sebastian Bergmann nor the names of his + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRIC + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * @category Testing + * @package PHPUnit + * @author Sebastian Bergmann + * @copyright 2002-2005 Sebastian Bergmann + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + * @version CVS: $Id: TestSuite.php,v 1.17 2005/11/10 09:47:15 sebastian Exp $ + * @link http://pear.php.net/package/PHPUnit + * @since File available since Release 1.0.0 + */ + +require_once 'PHPUnit/TestCase.php'; + +/** + * A TestSuite is a Composite of Tests. It runs a collection of test cases. + * + * Here is an example using the dynamic test definition. + * + * + * addTest(new MathTest('testPass')); + * ?> + * + * + * Alternatively, a TestSuite can extract the tests to be run automatically. + * To do so you pass the classname of your TestCase class to the TestSuite + * constructor. + * + * + * + * + * + * This constructor creates a suite with all the methods starting with + * "test" that take no arguments. + * + * @category Testing + * @package PHPUnit + * @author Sebastian Bergmann + * @copyright 2002-2005 Sebastian Bergmann + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/package/PHPUnit + * @since Class available since Release 1.0.0 + */ +class PHPUnit_TestSuite { + /** + * The name of the test suite. + * + * @var string + * @access private + */ + var $_name = ''; + + /** + * The tests in the test suite. + * + * @var array + * @access private + */ + var $_tests = array(); + + /** + * Constructs a TestSuite. + * + * @param mixed + * @access public + */ + function PHPUnit_TestSuite($test = FALSE) { + if ($test !== FALSE) { + $this->setName($test); + $this->addTestSuite($test); + } + } + + /** + * Adds a test to the suite. + * + * @param object + * @access public + */ + function addTest(&$test) { + $this->_tests[] = &$test; + } + + /** + * Adds the tests from the given class to the suite. + * + * @param string + * @access public + */ + function addTestSuite($testClass) { + if (class_exists($testClass)) { + $methods = get_class_methods($testClass); + $parentClasses = array(strtolower($testClass)); + $parentClass = $testClass; + + while(is_string($parentClass = get_parent_class($parentClass))) { + $parentClasses[] = $parentClass; + } + + foreach ($methods as $method) { + if (substr($method, 0, 4) == 'test' && + !in_array($method, $parentClasses)) { + $this->addTest(new $testClass($method)); + } + } + } + } + + /** + * Counts the number of test cases that will be run by this test. + * + * @return integer + * @access public + */ + function countTestCases() { + $count = 0; + + foreach ($this->_tests as $test) { + $count += $test->countTestCases(); + } + + return $count; + } + + /** + * Returns the name of the suite. + * + * @return string + * @access public + */ + function getName() { + return $this->_name; + } + + /** + * Runs the tests and collects their result in a TestResult. + * + * @param object + * @access public + */ + function run(&$result) { + for ($i = 0; $i < sizeof($this->_tests) && !$result->shouldStop(); $i++) { + $this->_tests[$i]->run($result); + } + } + + /** + * Runs a test. + * + * @param object + * @param object + * @access public + */ + function runTest(&$test, &$result) { + $test->run($result); + } + + /** + * Sets the name of the suite. + * + * @param string + * @access public + */ + function setName($name) { + $this->_name = $name; + } + + /** + * Returns the test at the given index. + * + * @param integer + * @return object + * @access public + */ + function &testAt($index) { + if (isset($this->_tests[$index])) { + return $this->_tests[$index]; + } else { + return FALSE; + } + } + + /** + * Returns the number of tests in this suite. + * + * @return integer + * @access public + */ + function testCount() { + return sizeof($this->_tests); + } + + /** + * Returns the tests as an enumeration. + * + * @return array + * @access public + */ + function &tests() { + return $this->_tests; + } + + /** + * Returns a string representation of the test suite. + * + * @return string + * @access public + */ + function toString() { + return ''; + } +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * c-hanging-comment-ender-p: nil + * End: + */ +?> diff --git a/library/pear/System.php b/library/pear/System.php new file mode 100644 index 000000000..af5718217 --- /dev/null +++ b/library/pear/System.php @@ -0,0 +1,621 @@ + + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: System.php 276386 2009-02-24 23:52:56Z dufuz $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 0.1 + */ + +/** + * base class + */ +require_once 'PEAR.php'; +require_once 'Console/Getopt.php'; + +$GLOBALS['_System_temp_files'] = array(); + +/** +* System offers cross plattform compatible system functions +* +* Static functions for different operations. Should work under +* Unix and Windows. The names and usage has been taken from its respectively +* GNU commands. The functions will return (bool) false on error and will +* trigger the error with the PHP trigger_error() function (you can silence +* the error by prefixing a '@' sign after the function call, but this +* is not recommended practice. Instead use an error handler with +* {@link set_error_handler()}). +* +* Documentation on this class you can find in: +* http://pear.php.net/manual/ +* +* Example usage: +* if (!@System::rm('-r file1 dir1')) { +* print "could not delete file1 or dir1"; +* } +* +* In case you need to to pass file names with spaces, +* pass the params as an array: +* +* System::rm(array('-r', $file1, $dir1)); +* +* @category pear +* @package System +* @author Tomas V.V. Cox +* @copyright 1997-2006 The PHP Group +* @license http://opensource.org/licenses/bsd-license.php New BSD License +* @version Release: 1.9.1 +* @link http://pear.php.net/package/PEAR +* @since Class available since Release 0.1 +* @static +*/ +class System +{ + /** + * returns the commandline arguments of a function + * + * @param string $argv the commandline + * @param string $short_options the allowed option short-tags + * @param string $long_options the allowed option long-tags + * @return array the given options and there values + * @static + * @access private + */ + function _parseArgs($argv, $short_options, $long_options = null) + { + if (!is_array($argv) && $argv !== null) { + $argv = preg_split('/\s+/', $argv, -1, PREG_SPLIT_NO_EMPTY); + } + return Console_Getopt::getopt2($argv, $short_options); + } + + /** + * Output errors with PHP trigger_error(). You can silence the errors + * with prefixing a "@" sign to the function call: @System::mkdir(..); + * + * @param mixed $error a PEAR error or a string with the error message + * @return bool false + * @static + * @access private + */ + function raiseError($error) + { + if (PEAR::isError($error)) { + $error = $error->getMessage(); + } + trigger_error($error, E_USER_WARNING); + return false; + } + + /** + * Creates a nested array representing the structure of a directory + * + * System::_dirToStruct('dir1', 0) => + * Array + * ( + * [dirs] => Array + * ( + * [0] => dir1 + * ) + * + * [files] => Array + * ( + * [0] => dir1/file2 + * [1] => dir1/file3 + * ) + * ) + * @param string $sPath Name of the directory + * @param integer $maxinst max. deep of the lookup + * @param integer $aktinst starting deep of the lookup + * @param bool $silent if true, do not emit errors. + * @return array the structure of the dir + * @static + * @access private + */ + function _dirToStruct($sPath, $maxinst, $aktinst = 0, $silent = false) + { + $struct = array('dirs' => array(), 'files' => array()); + if (($dir = @opendir($sPath)) === false) { + if (!$silent) { + System::raiseError("Could not open dir $sPath"); + } + return $struct; // XXX could not open error + } + + $struct['dirs'][] = $sPath = realpath($sPath); // XXX don't add if '.' or '..' ? + $list = array(); + while (false !== ($file = readdir($dir))) { + if ($file != '.' && $file != '..') { + $list[] = $file; + } + } + + closedir($dir); + natsort($list); + if ($aktinst < $maxinst || $maxinst == 0) { + foreach ($list as $val) { + $path = $sPath . DIRECTORY_SEPARATOR . $val; + if (is_dir($path) && !is_link($path)) { + $tmp = System::_dirToStruct($path, $maxinst, $aktinst+1, $silent); + $struct = array_merge_recursive($struct, $tmp); + } else { + $struct['files'][] = $path; + } + } + } + + return $struct; + } + + /** + * Creates a nested array representing the structure of a directory and files + * + * @param array $files Array listing files and dirs + * @return array + * @static + * @see System::_dirToStruct() + */ + function _multipleToStruct($files) + { + $struct = array('dirs' => array(), 'files' => array()); + settype($files, 'array'); + foreach ($files as $file) { + if (is_dir($file) && !is_link($file)) { + $tmp = System::_dirToStruct($file, 0); + $struct = array_merge_recursive($tmp, $struct); + } else { + if (!in_array($file, $struct['files'])) { + $struct['files'][] = $file; + } + } + } + return $struct; + } + + /** + * The rm command for removing files. + * Supports multiple files and dirs and also recursive deletes + * + * @param string $args the arguments for rm + * @return mixed PEAR_Error or true for success + * @static + * @access public + */ + function rm($args) + { + $opts = System::_parseArgs($args, 'rf'); // "f" does nothing but I like it :-) + if (PEAR::isError($opts)) { + return System::raiseError($opts); + } + foreach ($opts[0] as $opt) { + if ($opt[0] == 'r') { + $do_recursive = true; + } + } + $ret = true; + if (isset($do_recursive)) { + $struct = System::_multipleToStruct($opts[1]); + foreach ($struct['files'] as $file) { + if (!@unlink($file)) { + $ret = false; + } + } + + rsort($struct['dirs']); + foreach ($struct['dirs'] as $dir) { + if (!@rmdir($dir)) { + $ret = false; + } + } + } else { + foreach ($opts[1] as $file) { + $delete = (is_dir($file)) ? 'rmdir' : 'unlink'; + if (!@$delete($file)) { + $ret = false; + } + } + } + return $ret; + } + + /** + * Make directories. + * + * The -p option will create parent directories + * @param string $args the name of the director(y|ies) to create + * @return bool True for success + * @static + * @access public + */ + function mkDir($args) + { + $opts = System::_parseArgs($args, 'pm:'); + if (PEAR::isError($opts)) { + return System::raiseError($opts); + } + + $mode = 0777; // default mode + foreach ($opts[0] as $opt) { + if ($opt[0] == 'p') { + $create_parents = true; + } elseif ($opt[0] == 'm') { + // if the mode is clearly an octal number (starts with 0) + // convert it to decimal + if (strlen($opt[1]) && $opt[1]{0} == '0') { + $opt[1] = octdec($opt[1]); + } else { + // convert to int + $opt[1] += 0; + } + $mode = $opt[1]; + } + } + + $ret = true; + if (isset($create_parents)) { + foreach ($opts[1] as $dir) { + $dirstack = array(); + while ((!file_exists($dir) || !is_dir($dir)) && + $dir != DIRECTORY_SEPARATOR) { + array_unshift($dirstack, $dir); + $dir = dirname($dir); + } + + while ($newdir = array_shift($dirstack)) { + if (!is_writeable(dirname($newdir))) { + $ret = false; + break; + } + + if (!mkdir($newdir, $mode)) { + $ret = false; + } + } + } + } else { + foreach($opts[1] as $dir) { + if ((@file_exists($dir) || !is_dir($dir)) && !mkdir($dir, $mode)) { + $ret = false; + } + } + } + + return $ret; + } + + /** + * Concatenate files + * + * Usage: + * 1) $var = System::cat('sample.txt test.txt'); + * 2) System::cat('sample.txt test.txt > final.txt'); + * 3) System::cat('sample.txt test.txt >> final.txt'); + * + * Note: as the class use fopen, urls should work also (test that) + * + * @param string $args the arguments + * @return boolean true on success + * @static + * @access public + */ + function &cat($args) + { + $ret = null; + $files = array(); + if (!is_array($args)) { + $args = preg_split('/\s+/', $args, -1, PREG_SPLIT_NO_EMPTY); + } + + $count_args = count($args); + for ($i = 0; $i < $count_args; $i++) { + if ($args[$i] == '>') { + $mode = 'wb'; + $outputfile = $args[$i+1]; + break; + } elseif ($args[$i] == '>>') { + $mode = 'ab+'; + $outputfile = $args[$i+1]; + break; + } else { + $files[] = $args[$i]; + } + } + $outputfd = false; + if (isset($mode)) { + if (!$outputfd = fopen($outputfile, $mode)) { + $err = System::raiseError("Could not open $outputfile"); + return $err; + } + $ret = true; + } + foreach ($files as $file) { + if (!$fd = fopen($file, 'r')) { + System::raiseError("Could not open $file"); + continue; + } + while ($cont = fread($fd, 2048)) { + if (is_resource($outputfd)) { + fwrite($outputfd, $cont); + } else { + $ret .= $cont; + } + } + fclose($fd); + } + if (is_resource($outputfd)) { + fclose($outputfd); + } + return $ret; + } + + /** + * Creates temporary files or directories. This function will remove + * the created files when the scripts finish its execution. + * + * Usage: + * 1) $tempfile = System::mktemp("prefix"); + * 2) $tempdir = System::mktemp("-d prefix"); + * 3) $tempfile = System::mktemp(); + * 4) $tempfile = System::mktemp("-t /var/tmp prefix"); + * + * prefix -> The string that will be prepended to the temp name + * (defaults to "tmp"). + * -d -> A temporary dir will be created instead of a file. + * -t -> The target dir where the temporary (file|dir) will be created. If + * this param is missing by default the env vars TMP on Windows or + * TMPDIR in Unix will be used. If these vars are also missing + * c:\windows\temp or /tmp will be used. + * + * @param string $args The arguments + * @return mixed the full path of the created (file|dir) or false + * @see System::tmpdir() + * @static + * @access public + */ + function mktemp($args = null) + { + static $first_time = true; + $opts = System::_parseArgs($args, 't:d'); + if (PEAR::isError($opts)) { + return System::raiseError($opts); + } + + foreach ($opts[0] as $opt) { + if ($opt[0] == 'd') { + $tmp_is_dir = true; + } elseif ($opt[0] == 't') { + $tmpdir = $opt[1]; + } + } + + $prefix = (isset($opts[1][0])) ? $opts[1][0] : 'tmp'; + if (!isset($tmpdir)) { + $tmpdir = System::tmpdir(); + } + + if (!System::mkDir(array('-p', $tmpdir))) { + return false; + } + + $tmp = tempnam($tmpdir, $prefix); + if (isset($tmp_is_dir)) { + unlink($tmp); // be careful possible race condition here + if (!mkdir($tmp, 0700)) { + return System::raiseError("Unable to create temporary directory $tmpdir"); + } + } + + $GLOBALS['_System_temp_files'][] = $tmp; + if (isset($tmp_is_dir)) { + //$GLOBALS['_System_temp_files'][] = dirname($tmp); + } + + if ($first_time) { + PEAR::registerShutdownFunc(array('System', '_removeTmpFiles')); + $first_time = false; + } + + return $tmp; + } + + /** + * Remove temporary files created my mkTemp. This function is executed + * at script shutdown time + * + * @static + * @access private + */ + function _removeTmpFiles() + { + if (count($GLOBALS['_System_temp_files'])) { + $delete = $GLOBALS['_System_temp_files']; + array_unshift($delete, '-r'); + System::rm($delete); + $GLOBALS['_System_temp_files'] = array(); + } + } + + /** + * Get the path of the temporal directory set in the system + * by looking in its environments variables. + * Note: php.ini-recommended removes the "E" from the variables_order setting, + * making unavaible the $_ENV array, that s why we do tests with _ENV + * + * @static + * @return string The temporary directory on the system + */ + function tmpdir() + { + if (OS_WINDOWS) { + if ($var = isset($_ENV['TMP']) ? $_ENV['TMP'] : getenv('TMP')) { + return $var; + } + if ($var = isset($_ENV['TEMP']) ? $_ENV['TEMP'] : getenv('TEMP')) { + return $var; + } + if ($var = isset($_ENV['USERPROFILE']) ? $_ENV['USERPROFILE'] : getenv('USERPROFILE')) { + return $var; + } + if ($var = isset($_ENV['windir']) ? $_ENV['windir'] : getenv('windir')) { + return $var; + } + return getenv('SystemRoot') . '\temp'; + } + if ($var = isset($_ENV['TMPDIR']) ? $_ENV['TMPDIR'] : getenv('TMPDIR')) { + return $var; + } + return realpath('/tmp'); + } + + /** + * The "which" command (show the full path of a command) + * + * @param string $program The command to search for + * @param mixed $fallback Value to return if $program is not found + * + * @return mixed A string with the full path or false if not found + * @static + * @author Stig Bakken + */ + function which($program, $fallback = false) + { + // enforce API + if (!is_string($program) || '' == $program) { + return $fallback; + } + + // full path given + if (basename($program) != $program) { + $path_elements[] = dirname($program); + $program = basename($program); + } else { + // Honor safe mode + if (!ini_get('safe_mode') || !$path = ini_get('safe_mode_exec_dir')) { + $path = getenv('PATH'); + if (!$path) { + $path = getenv('Path'); // some OSes are just stupid enough to do this + } + } + $path_elements = explode(PATH_SEPARATOR, $path); + } + + if (OS_WINDOWS) { + $exe_suffixes = getenv('PATHEXT') + ? explode(PATH_SEPARATOR, getenv('PATHEXT')) + : array('.exe','.bat','.cmd','.com'); + // allow passing a command.exe param + if (strpos($program, '.') !== false) { + array_unshift($exe_suffixes, ''); + } + // is_executable() is not available on windows for PHP4 + $pear_is_executable = (function_exists('is_executable')) ? 'is_executable' : 'is_file'; + } else { + $exe_suffixes = array(''); + $pear_is_executable = 'is_executable'; + } + + foreach ($exe_suffixes as $suff) { + foreach ($path_elements as $dir) { + $file = $dir . DIRECTORY_SEPARATOR . $program . $suff; + if (@$pear_is_executable($file)) { + return $file; + } + } + } + return $fallback; + } + + /** + * The "find" command + * + * Usage: + * + * System::find($dir); + * System::find("$dir -type d"); + * System::find("$dir -type f"); + * System::find("$dir -name *.php"); + * System::find("$dir -name *.php -name *.htm*"); + * System::find("$dir -maxdepth 1"); + * + * Params implmented: + * $dir -> Start the search at this directory + * -type d -> return only directories + * -type f -> return only files + * -maxdepth -> max depth of recursion + * -name -> search pattern (bash style). Multiple -name param allowed + * + * @param mixed Either array or string with the command line + * @return array Array of found files + * @static + * + */ + function find($args) + { + if (!is_array($args)) { + $args = preg_split('/\s+/', $args, -1, PREG_SPLIT_NO_EMPTY); + } + $dir = realpath(array_shift($args)); + if (!$dir) { + return array(); + } + $patterns = array(); + $depth = 0; + $do_files = $do_dirs = true; + $args_count = count($args); + for ($i = 0; $i < $args_count; $i++) { + switch ($args[$i]) { + case '-type': + if (in_array($args[$i+1], array('d', 'f'))) { + if ($args[$i+1] == 'd') { + $do_files = false; + } else { + $do_dirs = false; + } + } + $i++; + break; + case '-name': + $name = preg_quote($args[$i+1], '#'); + // our magic characters ? and * have just been escaped, + // so now we change the escaped versions to PCRE operators + $name = strtr($name, array('\?' => '.', '\*' => '.*')); + $patterns[] = '('.$name.')'; + $i++; + break; + case '-maxdepth': + $depth = $args[$i+1]; + break; + } + } + $path = System::_dirToStruct($dir, $depth, 0, true); + if ($do_files && $do_dirs) { + $files = array_merge($path['files'], $path['dirs']); + } elseif ($do_dirs) { + $files = $path['dirs']; + } else { + $files = $path['files']; + } + if (count($patterns)) { + $dsq = preg_quote(DIRECTORY_SEPARATOR, '#'); + $pattern = '#(^|'.$dsq.')'.implode('|', $patterns).'($|'.$dsq.')#'; + $ret = array(); + $files_count = count($files); + for ($i = 0; $i < $files_count; $i++) { + // only search in the part of the file below the current directory + $filepart = basename($files[$i]); + if (preg_match($pattern, $filepart)) { + $ret[] = $files[$i]; + } + } + return $ret; + } + return $files; + } +} \ No newline at end of file diff --git a/library/pear/VERSIONS.txt b/library/pear/VERSIONS.txt new file mode 100644 index 000000000..175e400e2 --- /dev/null +++ b/library/pear/VERSIONS.txt @@ -0,0 +1,20 @@ +Last updated: + - 2010-09-13: PHPUnit added + - 2010-08-17: Updated everything to latest version + - 2007-02-13: Update DB from 1.7.6 to 1.7.9 + +* Archive_Tar-1.3.7 +* Calendar-0.5.5(beta) +* Console_Getopt-1.2.3 +* DB-1.7.13 +* File-1.3.0 +* File_Find-1.3.0 +* HTML_Common-1.2.5 +* HTML_Quickform-3.2.11 +* PEAR-1.9.1 +* XML_Beautifier-1.2.0 +* XML_Parser-1.3.2 +* XML_RPC-1.5.4 +* XML_Serializer-0.20.0(beta) +* XML_Util-1.2.1 +* PHPUnit 1.3.2 diff --git a/library/pear/XML/Beautifier.php b/library/pear/XML/Beautifier.php new file mode 100644 index 000000000..86e48b0c1 --- /dev/null +++ b/library/pear/XML/Beautifier.php @@ -0,0 +1,397 @@ + + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * @category XML + * @package XML_Beautifier + * @author Stephan Schmidt + * @copyright 2003-2008 Stephan Schmidt + * @license http://opensource.org/licenses/bsd-license New BSD License + * @version CVS: $Id: Beautifier.php,v 1.15 2008/08/24 19:44:14 ashnazg Exp $ + * @link http://pear.php.net/package/XML_Beautifier + */ + + +/** + * define include path constant + */ +if (!defined('XML_BEAUTIFIER_INCLUDE_PATH')) { + define('XML_BEAUTIFIER_INCLUDE_PATH', 'XML/Beautifier'); +} + +/** + * element is empty + */ +define('XML_BEAUTIFIER_EMPTY', 0); + +/** + * CData + */ +define('XML_BEAUTIFIER_CDATA', 1); + +/** + * XML element + */ +define('XML_BEAUTIFIER_ELEMENT', 2); + +/** + * processing instruction + */ +define('XML_BEAUTIFIER_PI', 4); + +/** + * entity + */ +define('XML_BEAUTIFIER_ENTITY', 8); + +/** + * comment + */ +define('XML_BEAUTIFIER_COMMENT', 16); + +/** + * XML declaration + */ +define('XML_BEAUTIFIER_XML_DECLARATION', 32); + +/** + * doctype declaration + */ +define('XML_BEAUTIFIER_DT_DECLARATION', 64); + +/** + * cdata section + */ +define('XML_BEAUTIFIER_CDATA_SECTION', 128); + +/** + * default + */ +define('XML_BEAUTIFIER_DEFAULT', 256); + +/** + * overwrite the original file + */ +define('XML_BEAUTIFIER_OVERWRITE', -1); + +/** + * could not write to output file + */ +define('XML_BEAUTIFIER_ERROR_NO_OUTPUT_FILE', 151); + +/** + * could not load renderer + */ +define('XML_BEAUTIFIER_ERROR_UNKNOWN_RENDERER', 152); + +/** + * XML_Beautifier is a class that adds linebreaks and + * indentation to your XML files. It can be used on XML + * that looks ugly (e.g. any generated XML) to transform it + * to a nicely looking XML that can be read by humans. + * + * It removes unnecessary whitespace and adds indentation + * depending on the nesting level. + * + * It is able to treat tags, data, processing instructions + * comments, external entities and the XML prologue. + * + * XML_Beautifier is using XML_Beautifier_Tokenizer to parse an XML + * document with a SAX based parser and builds tokens of tags, comments, + * entities, data, etc. + * These tokens will be serialized and indented by a renderer + * with your indent string. + * + * Example 1: Formatting a file + * + * require_once 'XML/Beautifier.php'; + * $fmt = new XML_Beautifier(); + * $result = $fmt->formatFile('oldFile.xml', 'newFile.xml'); + * + * + * Example 2: Formatting a string + * + * require_once 'XML/Beautifier.php'; + * $xml = ''; + * $fmt = new XML_Beautifier(); + * $result = $fmt->formatString($xml); + * + * + * @category XML + * @package XML_Beautifier + * @author Stephan Schmidt + * @copyright 2003-2008 Stephan Schmidt + * @license http://opensource.org/licenses/bsd-license New BSD License + * @version Release: 1.2.0 + * @link http://pear.php.net/package/XML_Beautifier + */ +class XML_Beautifier +{ + /** + * default options for the output format + * @var array + * @access private + */ + var $_defaultOptions = array( + "removeLineBreaks" => true, + "removeLeadingSpace" => true, // not implemented, yet + "indent" => " ", + "linebreak" => "\n", + "caseFolding" => false, + "caseFoldingTo" => "uppercase", + "normalizeComments" => false, + "maxCommentLine" => -1, + "multilineTags" => false + ); + + /** + * options for the output format + * @var array + * @access private + */ + var $_options = array(); + + /** + * Constructor + * + * This is only used to specify the options of the + * beautifying process. + * + * @param array $options options that override default options + * + * @access public + */ + function XML_Beautifier($options = array()) + { + $this->_options = array_merge($this->_defaultOptions, $options); + $this->folding = false; + } + + /** + * reset all options to default options + * + * @return void + * @access public + * @see setOption(), XML_Beautifier(), setOptions() + */ + function resetOptions() + { + $this->_options = $this->_defaultOptions; + } + + /** + * set an option + * + * You can use this method if you do not want + * to set all options in the constructor + * + * @param string $name option name + * @param mixed $value option value + * + * @return void + * @access public + * @see resetOptions(), XML_Beautifier(), setOptions() + */ + function setOption($name, $value) + { + $this->_options[$name] = $value; + } + + /** + * set several options at once + * + * You can use this method if you do not want + * to set all options in the constructor + * + * @param array $options an options array + * + * @return void + * @access public + * @see resetOptions(), XML_Beautifier() + */ + function setOptions($options) + { + $this->_options = array_merge($this->_options, $options); + } + + /** + * format a file or URL + * + * @param string $file filename + * @param mixed $newFile filename for beautified XML file + * (if none is given, the XML string + * will be returned). + * if you want overwrite the original file, + * use XML_BEAUTIFIER_OVERWRITE + * @param string $renderer Renderer to use, + * default is the plain xml renderer + * + * @return mixed XML string of no file should be written, + * true if file could be written + * @access public + * @throws PEAR_Error + * @uses _loadRenderer() to load the desired renderer + * @todo PEAR CS - should require_once be include_once? + */ + function formatFile($file, $newFile = null, $renderer = "Plain") + { + if ($newFile == XML_BEAUTIFIER_OVERWRITE) { + $newFile = $file; + } + + /** + * Split the document into tokens + * using the XML_Tokenizer + */ + require_once XML_BEAUTIFIER_INCLUDE_PATH . '/Tokenizer.php'; + $tokenizer = new XML_Beautifier_Tokenizer(); + + $tokens = $tokenizer->tokenize($file, true); + + if (PEAR::isError($tokens)) { + return $tokens; + } + + $renderer = $this->_loadRenderer($renderer, $this->_options); + + if (PEAR::isError($renderer)) { + return $renderer; + } + + $xml = $renderer->serialize($tokens); + + if ($newFile == null) { + return $xml; + } + + $fp = @fopen($newFile, "w"); + if (!$fp) { + return PEAR::raiseError("Could not write to output file", + XML_BEAUTIFIER_ERROR_NO_OUTPUT_FILE); + } + + flock($fp, LOCK_EX); + fwrite($fp, $xml); + flock($fp, LOCK_UN); + fclose($fp); + return true; + } + + /** + * format an XML string + * + * @param string $string XML + * @param string $renderer the renderer type + * + * @return string formatted XML string + * @access public + * @throws PEAR_Error + * @todo PEAR CS - should require_once be include_once? + */ + function formatString($string, $renderer = "Plain") + { + /** + * Split the document into tokens + * using the XML_Tokenizer + */ + require_once XML_BEAUTIFIER_INCLUDE_PATH . '/Tokenizer.php'; + $tokenizer = new XML_Beautifier_Tokenizer(); + + $tokens = $tokenizer->tokenize($string, false); + + if (PEAR::isError($tokens)) { + return $tokens; + } + + $renderer = $this->_loadRenderer($renderer, $this->_options); + + if (PEAR::isError($renderer)) { + return $renderer; + } + + $xml = $renderer->serialize($tokens); + + return $xml; + } + + /** + * load a renderer + * + * Renderers are used to serialize the XML tokens back + * to an XML string. + * + * Renderers are located in the XML/Beautifier/Renderer directory. + * + * NOTE: the "@" error suppression is used in this method + * + * @param string $name name of the renderer + * @param array $options options for the renderer + * + * @return object renderer + * @access private + * @throws PEAR_Error + */ + function &_loadRenderer($name, $options = array()) + { + $file = XML_BEAUTIFIER_INCLUDE_PATH . "/Renderer/$name.php"; + $class = "XML_Beautifier_Renderer_$name"; + + @include_once $file; + if (!class_exists($class)) { + return PEAR::raiseError("Could not load renderer.", + XML_BEAUTIFIER_ERROR_UNKNOWN_RENDERER); + } + + $renderer = &new $class($options); + + return $renderer; + } + + /** + * return API version + * + * @access public + * @static + * @return string $version API version + */ + function apiVersion() + { + return "1.0"; + } +} +?> diff --git a/library/pear/XML/Beautifier/Renderer.php b/library/pear/XML/Beautifier/Renderer.php new file mode 100644 index 000000000..213748524 --- /dev/null +++ b/library/pear/XML/Beautifier/Renderer.php @@ -0,0 +1,226 @@ + + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * @category XML + * @package XML_Beautifier + * @author Stephan Schmidt + * @copyright 2003-2008 Stephan Schmidt + * @license http://opensource.org/licenses/bsd-license New BSD License + * @version CVS: $Id: Renderer.php,v 1.6 2008/08/24 19:44:14 ashnazg Exp $ + * @link http://pear.php.net/package/XML_Beautifier + */ + +/** + * Renderer base class for XML_Beautifier + * + * @category XML + * @package XML_Beautifier + * @author Stephan Schmidt + * @copyright 2003-2008 Stephan Schmidt + * @license http://opensource.org/licenses/bsd-license New BSD License + * @version Release: 1.2.0 + * @link http://pear.php.net/package/XML_Beautifier + */ +class XML_Beautifier_Renderer +{ + /** + * options + * @var array + */ + var $_options = array(); + + /** + * create a new renderer + * + * @param array $options for the serialization + * + * @access public + */ + function XML_Beautifier_Renderer($options = array()) + { + $this->_options = $options; + } + + /** + * Serialize the XML tokens + * + * @param array $tokens XML tokens + * + * @return string XML document + * @access public + * @abstract + */ + function serialize($tokens) + { + return ''; + } + + /** + * normalize the XML tree + * + * When normalizing an XML tree, adjacent data sections + * are combined to one data section. + * + * @param array $tokens XML tree as returned by the tokenizer + * + * @return array XML tree + * @access public + */ + function normalize($tokens) + { + $tmp = array(); + foreach ($tokens as $token) { + array_push($tmp, $this->_normalizeToken($token)); + } + return $tmp; + } + + /** + * normalize one element in the XML tree + * + * This method will combine all data sections of an element. + * + * @param array $token token array + * + * @return array $struct + * @access private + */ + function _normalizeToken($token) + { + if ((isset($token["children"])) + && !is_array($token["children"]) + || empty($token["children"]) + ) { + return $token; + } + + $children = $token["children"]; + $token["children"] = array(); + $cnt = count($children); + $currentMode = 0; + for ($i = 0; $i < $cnt; $i++ ) { + // no data section + if ($children[$i]["type"] != XML_BEAUTIFIER_CDATA + && $children[$i]["type"] != XML_BEAUTIFIER_CDATA_SECTION + ) { + $children[$i] = $this->_normalizeToken($children[$i]); + + $currentMode = 0; + array_push($token["children"], $children[$i]); + continue; + } + + /* + * remove whitespace + */ + if ($this->_options['removeLineBreaks'] == true) { + $children[$i]['data'] = trim($children[$i]['data']); + if ($children[$i]['data'] == '') { + continue; + } + } + + if ($currentMode == $children[$i]["type"]) { + $tmp = array_pop($token["children"]); + + if ($children[$i]['data'] != '') { + if ($tmp['data'] != '' + && $this->_options['removeLineBreaks'] == true + ) { + $tmp['data'] .= ' '; + } + $tmp["data"] .= $children[$i]["data"]; + } + array_push($token["children"], $tmp); + } else { + array_push($token["children"], $children[$i]); + } + + $currentMode = $children[$i]["type"]; + } + return $token; + } + + /** + * indent a text block consisting of several lines + * + * @param string $text textblock + * @param integer $depth depth to indent + * @param boolean $trim trim the lines + * + * @return string indented text block + * @access private + */ + function _indentTextBlock($text, $depth, $trim = false) + { + $indent = $this->_getIndentString($depth); + $tmp = explode("\n", $text); + $cnt = count($tmp); + $xml = ''; + for ($i = 0; $i < $cnt; $i++ ) { + if ($trim) { + $tmp[$i] = trim($tmp[$i]); + } + if ($tmp[$i] == '') { + continue; + } + $xml .= $indent.$tmp[$i].$this->_options["linebreak"]; + } + return $xml; + } + + /** + * get the string that is used for indentation in a specific depth + * + * This depends on the option 'indent'. + * + * @param integer $depth nesting level + * + * @return string indent string + * @access private + */ + function _getIndentString($depth) + { + if ($depth > 0) { + return str_repeat($this->_options["indent"], $depth); + } + return ""; + } +} +?> diff --git a/library/pear/XML/Beautifier/Renderer/Plain.php b/library/pear/XML/Beautifier/Renderer/Plain.php new file mode 100644 index 000000000..11c1931a9 --- /dev/null +++ b/library/pear/XML/Beautifier/Renderer/Plain.php @@ -0,0 +1,313 @@ + + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * @category XML + * @package XML_Beautifier + * @author Stephan Schmidt + * @copyright 2003-2008 Stephan Schmidt + * @license http://opensource.org/licenses/bsd-license New BSD License + * @version CVS: $Id: Plain.php,v 1.7 2008/08/24 19:44:14 ashnazg Exp $ + * @link http://pear.php.net/package/XML_Beautifier + */ + +/** + * XML_Util is needed to create the tags + */ +require_once 'XML/Util.php'; + +/** + * Renderer base class + */ +require_once XML_BEAUTIFIER_INCLUDE_PATH . '/Renderer.php'; + +/** + * Basic XML Renderer for XML Beautifier + * + * @category XML + * @package XML_Beautifier + * @author Stephan Schmidt + * @copyright 2003-2008 Stephan Schmidt + * @license http://opensource.org/licenses/bsd-license New BSD License + * @version Release: 1.2.0 + * @link http://pear.php.net/package/XML_Beautifier + * @todo option to specify inline tags + * @todo option to specify treatment of whitespace in data sections + */ +class XML_Beautifier_Renderer_Plain extends XML_Beautifier_Renderer +{ + /** + * Serialize the XML tokens + * + * @param array $tokens XML tokens + * + * @return string XML document + * @access public + */ + function serialize($tokens) + { + $tokens = $this->normalize($tokens); + + $xml = ''; + $cnt = count($tokens); + for ($i = 0; $i < $cnt; $i++) { + $xml .= $this->_serializeToken($tokens[$i]); + } + return $xml; + } + + /** + * serialize a token + * + * This method does the actual beautifying. + * + * @param array $token structure that should be serialized + * + * @return mixed + * @access private + * @todo split this method into smaller methods + */ + function _serializeToken($token) + { + switch ($token["type"]) { + + /* + * serialize XML Element + */ + case XML_BEAUTIFIER_ELEMENT: + $indent = $this->_getIndentString($token["depth"]); + + // adjust tag case + if ($this->_options["caseFolding"] === true) { + switch ($this->_options["caseFoldingTo"]) { + case "uppercase": + $token["tagname"] = strtoupper($token["tagname"]); + $token["attribs"] = + array_change_key_case($token["attribs"], CASE_UPPER); + break; + case "lowercase": + $token["tagname"] = strtolower($token["tagname"]); + $token["attribs"] = + array_change_key_case($token["attribs"], CASE_LOWER); + break; + } + } + + if ($this->_options["multilineTags"] == true) { + $attIndent = $indent . str_repeat(" ", + (2+strlen($token["tagname"]))); + } else { + $attIndent = null; + } + + // check for children + switch ($token["contains"]) { + + // contains only CData or is empty + case XML_BEAUTIFIER_CDATA: + case XML_BEAUTIFIER_EMPTY: + if (sizeof($token["children"]) >= 1) { + $data = $token["children"][0]["data"]; + } else { + $data = ''; + } + + if (strstr($data, "\n")) { + $data = "\n" + . $this->_indentTextBlock($data, $token['depth']+1, true); + } + + $xml = $indent + . XML_Util::createTag($token["tagname"], + $token["attribs"], $data, null, XML_UTIL_REPLACE_ENTITIES, + $this->_options["multilineTags"], $attIndent) + . $this->_options["linebreak"]; + break; + // contains mixed content + default: + $xml = $indent . XML_Util::createStartElement($token["tagname"], + $token["attribs"], null, $this->_options["multilineTags"], + $attIndent) . $this->_options["linebreak"]; + + $cnt = count($token["children"]); + for ($i = 0; $i < $cnt; $i++) { + $xml .= $this->_serializeToken($token["children"][$i]); + } + $xml .= $indent . XML_Util::createEndElement($token["tagname"]) + . $this->_options["linebreak"]; + break; + break; + } + break; + + /* + * serialize CData + */ + case XML_BEAUTIFIER_CDATA: + if ($token["depth"] > 0) { + $xml = str_repeat($this->_options["indent"], $token["depth"]); + } else { + $xml = ""; + } + + $xml .= XML_Util::replaceEntities($token["data"]) + . $this->_options["linebreak"]; + break; + + /* + * serialize CData section + */ + case XML_BEAUTIFIER_CDATA_SECTION: + if ($token["depth"] > 0) { + $xml = str_repeat($this->_options["indent"], $token["depth"]); + } else { + $xml = ""; + } + + $xml .= '' . $this->_options["linebreak"]; + break; + + /* + * serialize entity + */ + case XML_BEAUTIFIER_ENTITY: + if ($token["depth"] > 0) { + $xml = str_repeat($this->_options["indent"], $token["depth"]); + } else { + $xml = ""; + } + $xml .= "&".$token["name"].";".$this->_options["linebreak"]; + break; + + + /* + * serialize Processing instruction + */ + case XML_BEAUTIFIER_PI: + $indent = $this->_getIndentString($token["depth"]); + + $xml = $indent."_options["linebreak"] + . $this->_indentTextBlock(rtrim($token["data"]), $token["depth"]) + . $indent."?>".$this->_options["linebreak"]; + break; + + /* + * comments + */ + case XML_BEAUTIFIER_COMMENT: + $lines = count(explode("\n", $token["data"])); + + /* + * normalize comment, i.e. combine it to one + * line and remove whitespace + */ + if ($this->_options["normalizeComments"] && $lines > 1) { + $comment = preg_replace("/\s\s+/s", " ", + str_replace("\n", " ", $token["data"])); + $lines = 1; + } else { + $comment = $token["data"]; + } + + /* + * check for the maximum length of one line + */ + if ($this->_options["maxCommentLine"] > 0) { + if ($lines > 1) { + $commentLines = explode("\n", $comment); + } else { + $commentLines = array($comment); + } + + $comment = ""; + for ($i = 0; $i < $lines; $i++) { + if (strlen($commentLines[$i]) + <= $this->_options["maxCommentLine"] + ) { + $comment .= $commentLines[$i]; + continue; + } + $comment .= wordwrap($commentLines[$i], + $this->_options["maxCommentLine"]); + if ($i != ($lines-1)) { + $comment .= "\n"; + } + } + $lines = count(explode("\n", $comment)); + } + + $indent = $this->_getIndentString($token["depth"]); + + if ($lines > 1) { + $xml = $indent . "" . $this->_options["linebreak"]; + } else { + $xml = $indent . sprintf("", trim($comment)) + . $this->_options["linebreak"]; + } + break; + + /* + * xml declaration + */ + case XML_BEAUTIFIER_XML_DECLARATION: + $indent = $this->_getIndentString($token["depth"]); + $xml = $indent . XML_Util::getXMLDeclaration($token["version"], + $token["encoding"], $token["standalone"]); + break; + + /* + * xml declaration + */ + case XML_BEAUTIFIER_DT_DECLARATION: + $xml = $token["data"]; + break; + + /* + * all other elements + */ + case XML_BEAUTIFIER_DEFAULT: + default: + $xml = XML_Util::replaceEntities($token["data"]); + break; + } + return $xml; + } +} +?> diff --git a/library/pear/XML/Beautifier/Tokenizer.php b/library/pear/XML/Beautifier/Tokenizer.php new file mode 100644 index 000000000..6e7ce2730 --- /dev/null +++ b/library/pear/XML/Beautifier/Tokenizer.php @@ -0,0 +1,456 @@ + + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * @category XML + * @package XML_Beautifier + * @author Stephan Schmidt + * @copyright 2003-2008 Stephan Schmidt + * @license http://opensource.org/licenses/bsd-license New BSD License + * @version CVS: $Id: Tokenizer.php,v 1.10 2008/08/24 19:44:14 ashnazg Exp $ + * @link http://pear.php.net/package/XML_Beautifier + */ + +/** + * XML_Parser is needed to parse the document + */ +require_once 'XML/Parser.php'; + +/** + * Tokenizer for XML_Beautifier + * + * This class breaks an XML document in seperate tokens + * that will be rendered by an XML_Beautifier renderer. + * + * @category XML + * @package XML_Beautifier + * @author Stephan Schmidt + * @copyright 2003-2008 Stephan Schmidt + * @license http://opensource.org/licenses/bsd-license New BSD License + * @version Release: 1.2.0 + * @link http://pear.php.net/package/XML_Beautifier + * @todo tokenize DTD + * @todo check for xml:space attribute + */ +class XML_Beautifier_Tokenizer extends XML_Parser +{ + /** + * current depth + * @var integer + * @access private + */ + var $_depth = 0; + + /** + * stack for all found elements + * @var array + * @access private + */ + var $_struct = array(); + + /** + * current parsing mode + * @var string + * @access private + */ + var $_mode = "xml"; + + /** + * indicates, whether parser is in cdata section + * @var boolean + * @access private + */ + var $_inCDataSection = false; + + /** + * Tokenize a document + * + * @param string $document filename or XML document + * @param boolean $isFile flag to indicate whether + * the first parameter is a file + * + * @return mixed + */ + function tokenize($document, $isFile = true) + { + $this->folding = false; + $this->XML_Parser(); + $this->_resetVars(); + + if ($isFile === true) { + $this->setInputFile($document); + $result = $this->parse(); + } else { + $result = $this->parseString($document); + } + + if ($this->isError($result)) { + return $result; + } + + return $this->_struct; + } + + /** + * Start element handler for XML parser + * + * @param object $parser XML parser object + * @param string $element XML element + * @param array $attribs attributes of XML tag + * + * @return void + * @access protected + */ + function startHandler($parser, $element, $attribs) + { + $struct = array( + "type" => XML_BEAUTIFIER_ELEMENT, + "tagname" => $element, + "attribs" => $attribs, + "contains" => XML_BEAUTIFIER_EMPTY, + "depth" => $this->_depth++, + "children" => array() + ); + + array_push($this->_struct, $struct); + } + + /** + * End element handler for XML parser + * + * @param object $parser XML parser object + * @param string $element element + * + * @return void + * @access protected + */ + function endHandler($parser, $element) + { + $struct = array_pop($this->_struct); + if ($struct["depth"] > 0) { + $parent = array_pop($this->_struct); + array_push($parent["children"], $struct); + $parent["contains"] = $parent["contains"] | XML_BEAUTIFIER_ELEMENT; + array_push($this->_struct, $parent); + } else { + array_push($this->_struct, $struct); + } + $this->_depth--; + } + + /** + * Handler for character data + * + * @param object $parser XML parser object + * @param string $cdata CDATA + * + * @return void + * @access protected + */ + function cdataHandler($parser, $cdata) + { + if ((string)$cdata === '') { + return true; + } + + if ($this->_inCDataSection === true) { + $type = XML_BEAUTIFIER_CDATA_SECTION; + } else { + $type = XML_BEAUTIFIER_CDATA; + } + + $struct = array( + "type" => $type, + "data" => $cdata, + "depth" => $this->_depth + ); + + $this->_appendToParent($struct); + } + + /** + * Handler for processing instructions + * + * @param object $parser XML parser object + * @param string $target target + * @param string $data data + * + * @return void + * @access protected + */ + function piHandler($parser, $target, $data) + { + $struct = array( + "type" => XML_BEAUTIFIER_PI, + "target" => $target, + "data" => $data, + "depth" => $this->_depth + ); + + $this->_appendToParent($struct); + } + + /** + * Handler for external entities + * + * @param object $parser XML parser object + * @param string $open_entity_names entity name + * @param string $base ?? (unused?) + * @param string $system_id ?? (unused?) + * @param string $public_id ?? (unused?) + * + * @return bool + * @access protected + * @todo revisit parameter signature... doesn't seem to be correct + * @todo PEAR CS - need to shorten arg list for 85-char rule + */ + function entityrefHandler($parser, $open_entity_names, $base, $system_id, $public_id) + { + $struct = array( + "type" => XML_BEAUTIFIER_ENTITY, + "name" => $open_entity_names, + "depth" => $this->_depth + ); + + $this->_appendToParent($struct); + return true; + } + + /** + * Handler for all other stuff + * + * @param object $parser XML parser object + * @param string $data data + * + * @return void + * @access protected + */ + function defaultHandler($parser, $data) + { + switch ($this->_mode) { + case "xml": + $this->_handleXMLDefault($data); + break; + case "doctype": + $this->_handleDoctype($data); + break; + } + } + + /** + * handler for all data inside the doctype declaration + * + * @param string $data data + * + * @return void + * @access private + * @todo improve doctype parsing to split the declaration into seperate tokens + */ + function _handleDoctype($data) + { + if (eregi(">", $data)) { + $last = $this->_getLastToken(); + if ($last["data"] == "]" ) { + $this->_mode = "xml"; + } + } + + $struct = array( + "type" => XML_BEAUTIFIER_DT_DECLARATION, + "data" => $data, + "depth" => $this->_depth + ); + $this->_appendToParent($struct); + } + + /** + * handler for all default XML data + * + * @param string $data data + * + * @return bool + * @access private + */ + function _handleXMLDefault($data) + { + if (strncmp("", $data, $regs); + $comment = trim($regs[1]); + + $struct = array( + "type" => XML_BEAUTIFIER_COMMENT, + "data" => $comment, + "depth" => $this->_depth + ); + + } elseif ($data == "_inCDataSection = true; + $struct = null; + + } elseif ($data == "]]>") { + /* + * handle end of cdata section + */ + $this->_inCDataSection = false; + $struct = null; + + } elseif (strncmp(" XML_BEAUTIFIER_XML_DECLARATION, + "version" => $attribs["version"], + "encoding" => $attribs["encoding"], + "standalone" => $attribs["standalone"], + "depth" => $this->_depth + ); + + } elseif (eregi("^_mode = "doctype"; + $struct = array( + "type" => XML_BEAUTIFIER_DT_DECLARATION, + "data" => $data, + "depth" => $this->_depth + ); + + } else { + /* + * handle all other data + */ + $struct = array( + "type" => XML_BEAUTIFIER_DEFAULT, + "data" => $data, + "depth" => $this->_depth + ); + } + + if (!is_null($struct)) { + $this->_appendToParent($struct); + } + return true; + } + + /** + * append a struct to the last struct on the stack + * + * @param array $struct structure to append + * + * @return bool + * @access private + */ + function _appendToParent($struct) + { + if ($this->_depth > 0) { + $parent = array_pop($this->_struct); + array_push($parent["children"], $struct); + $parent["contains"] = $parent["contains"] | $struct["type"]; + array_push($this->_struct, $parent); + return true; + } + array_push($this->_struct, $struct); + } + + /** + * get the last token + * + * @access private + * @return array + */ + function _getLastToken() + { + $parent = array_pop($this->_struct); + if (isset($parent["children"]) && is_array($parent["children"])) { + $last = array_pop($parent["children"]); + array_push($parent["children"], $last); + } else { + $last = $parent; + } + array_push($this->_struct, $parent); + + return $last; + } + + /** + * reset all used object properties + * + * This method is called before parsing a new document + * + * @return void + * @access private + */ + function _resetVars() + { + $this->_depth = 0; + $this->_struct = array(); + $this->_mode = "xml"; + $this->_inCDataSection = false; + } +} +?> diff --git a/library/pear/XML/Parser.php b/library/pear/XML/Parser.php new file mode 100644 index 000000000..ec145ba5a --- /dev/null +++ b/library/pear/XML/Parser.php @@ -0,0 +1,768 @@ + + * @author Tomas V.V.Cox + * @author Stephan Schmidt + * @copyright 2002-2008 The PHP Group + * @license http://opensource.org/licenses/bsd-license New BSD License + * @version CVS: $Id: Parser.php,v 1.30 2008/09/16 16:06:22 ashnazg Exp $ + * @link http://pear.php.net/package/XML_Parser + */ + +/** + * uses PEAR's error handling + */ +require_once 'PEAR.php'; + +/** + * resource could not be created + */ +define('XML_PARSER_ERROR_NO_RESOURCE', 200); + +/** + * unsupported mode + */ +define('XML_PARSER_ERROR_UNSUPPORTED_MODE', 201); + +/** + * invalid encoding was given + */ +define('XML_PARSER_ERROR_INVALID_ENCODING', 202); + +/** + * specified file could not be read + */ +define('XML_PARSER_ERROR_FILE_NOT_READABLE', 203); + +/** + * invalid input + */ +define('XML_PARSER_ERROR_INVALID_INPUT', 204); + +/** + * remote file cannot be retrieved in safe mode + */ +define('XML_PARSER_ERROR_REMOTE', 205); + +/** + * XML Parser class. + * + * This is an XML parser based on PHP's "xml" extension, + * based on the bundled expat library. + * + * Notes: + * - It requires PHP 4.0.4pl1 or greater + * - From revision 1.17, the function names used by the 'func' mode + * are in the format "xmltag_$elem", for example: use "xmltag_name" + * to handle the tags of your xml file. + * - different parsing modes + * + * @category XML + * @package XML_Parser + * @author Stig Bakken + * @author Tomas V.V.Cox + * @author Stephan Schmidt + * @copyright 2002-2008 The PHP Group + * @license http://opensource.org/licenses/bsd-license New BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/package/XML_Parser + * @todo create XML_Parser_Namespace to parse documents with namespaces + * @todo create XML_Parser_Pull + * @todo Tests that need to be made: + * - mixing character encodings + * - a test using all expat handlers + * - options (folding, output charset) + */ +class XML_Parser extends PEAR +{ + // {{{ properties + + /** + * XML parser handle + * + * @var resource + * @see xml_parser_create() + */ + var $parser; + + /** + * File handle if parsing from a file + * + * @var resource + */ + var $fp; + + /** + * Whether to do case folding + * + * If set to true, all tag and attribute names will + * be converted to UPPER CASE. + * + * @var boolean + */ + var $folding = true; + + /** + * Mode of operation, one of "event" or "func" + * + * @var string + */ + var $mode; + + /** + * Mapping from expat handler function to class method. + * + * @var array + */ + var $handler = array( + 'character_data_handler' => 'cdataHandler', + 'default_handler' => 'defaultHandler', + 'processing_instruction_handler' => 'piHandler', + 'unparsed_entity_decl_handler' => 'unparsedHandler', + 'notation_decl_handler' => 'notationHandler', + 'external_entity_ref_handler' => 'entityrefHandler' + ); + + /** + * source encoding + * + * @var string + */ + var $srcenc; + + /** + * target encoding + * + * @var string + */ + var $tgtenc; + + /** + * handler object + * + * @var object + */ + var $_handlerObj; + + /** + * valid encodings + * + * @var array + */ + var $_validEncodings = array('ISO-8859-1', 'UTF-8', 'US-ASCII'); + + // }}} + // {{{ php4 constructor + + /** + * Creates an XML parser. + * + * This is needed for PHP4 compatibility, it will + * call the constructor, when a new instance is created. + * + * @param string $srcenc source charset encoding, use NULL (default) to use + * whatever the document specifies + * @param string $mode how this parser object should work, "event" for + * startelement/endelement-type events, "func" + * to have it call functions named after elements + * @param string $tgtenc a valid target encoding + */ + function XML_Parser($srcenc = null, $mode = 'event', $tgtenc = null) + { + XML_Parser::__construct($srcenc, $mode, $tgtenc); + } + // }}} + // {{{ php5 constructor + + /** + * PHP5 constructor + * + * @param string $srcenc source charset encoding, use NULL (default) to use + * whatever the document specifies + * @param string $mode how this parser object should work, "event" for + * startelement/endelement-type events, "func" + * to have it call functions named after elements + * @param string $tgtenc a valid target encoding + */ + function __construct($srcenc = null, $mode = 'event', $tgtenc = null) + { + $this->PEAR('XML_Parser_Error'); + + $this->mode = $mode; + $this->srcenc = $srcenc; + $this->tgtenc = $tgtenc; + } + // }}} + + /** + * Sets the mode of the parser. + * + * Possible modes are: + * - func + * - event + * + * You can set the mode using the second parameter + * in the constructor. + * + * This method is only needed, when switching to a new + * mode at a later point. + * + * @param string $mode mode, either 'func' or 'event' + * + * @return boolean|object true on success, PEAR_Error otherwise + * @access public + */ + function setMode($mode) + { + if ($mode != 'func' && $mode != 'event') { + $this->raiseError('Unsupported mode given', + XML_PARSER_ERROR_UNSUPPORTED_MODE); + } + + $this->mode = $mode; + return true; + } + + /** + * Sets the object, that will handle the XML events + * + * This allows you to create a handler object independent of the + * parser object that you are using and easily switch the underlying + * parser. + * + * If no object will be set, XML_Parser assumes that you + * extend this class and handle the events in $this. + * + * @param object &$obj object to handle the events + * + * @return boolean will always return true + * @access public + * @since v1.2.0beta3 + */ + function setHandlerObj(&$obj) + { + $this->_handlerObj = &$obj; + return true; + } + + /** + * Init the element handlers + * + * @return mixed + * @access private + */ + function _initHandlers() + { + if (!is_resource($this->parser)) { + return false; + } + + if (!is_object($this->_handlerObj)) { + $this->_handlerObj = &$this; + } + switch ($this->mode) { + + case 'func': + xml_set_object($this->parser, $this->_handlerObj); + xml_set_element_handler($this->parser, + array(&$this, 'funcStartHandler'), array(&$this, 'funcEndHandler')); + break; + + case 'event': + xml_set_object($this->parser, $this->_handlerObj); + xml_set_element_handler($this->parser, 'startHandler', 'endHandler'); + break; + default: + return $this->raiseError('Unsupported mode given', + XML_PARSER_ERROR_UNSUPPORTED_MODE); + break; + } + + /** + * set additional handlers for character data, entities, etc. + */ + foreach ($this->handler as $xml_func => $method) { + if (method_exists($this->_handlerObj, $method)) { + $xml_func = 'xml_set_' . $xml_func; + $xml_func($this->parser, $method); + } + } + } + + // {{{ _create() + + /** + * create the XML parser resource + * + * Has been moved from the constructor to avoid + * problems with object references. + * + * Furthermore it allows us returning an error + * if something fails. + * + * NOTE: uses '@' error suppresion in this method + * + * @return bool|PEAR_Error true on success, PEAR_Error otherwise + * @access private + * @see xml_parser_create + */ + function _create() + { + if ($this->srcenc === null) { + $xp = @xml_parser_create(); + } else { + $xp = @xml_parser_create($this->srcenc); + } + if (is_resource($xp)) { + if ($this->tgtenc !== null) { + if (!@xml_parser_set_option($xp, XML_OPTION_TARGET_ENCODING, + $this->tgtenc) + ) { + return $this->raiseError('invalid target encoding', + XML_PARSER_ERROR_INVALID_ENCODING); + } + } + $this->parser = $xp; + $result = $this->_initHandlers($this->mode); + if ($this->isError($result)) { + return $result; + } + xml_parser_set_option($xp, XML_OPTION_CASE_FOLDING, $this->folding); + return true; + } + if (!in_array(strtoupper($this->srcenc), $this->_validEncodings)) { + return $this->raiseError('invalid source encoding', + XML_PARSER_ERROR_INVALID_ENCODING); + } + return $this->raiseError('Unable to create XML parser resource.', + XML_PARSER_ERROR_NO_RESOURCE); + } + + // }}} + // {{{ reset() + + /** + * Reset the parser. + * + * This allows you to use one parser instance + * to parse multiple XML documents. + * + * @access public + * @return boolean|object true on success, PEAR_Error otherwise + */ + function reset() + { + $result = $this->_create(); + if ($this->isError($result)) { + return $result; + } + return true; + } + + // }}} + // {{{ setInputFile() + + /** + * Sets the input xml file to be parsed + * + * @param string $file Filename (full path) + * + * @return resource fopen handle of the given file + * @access public + * @throws XML_Parser_Error + * @see setInput(), setInputString(), parse() + */ + function setInputFile($file) + { + /** + * check, if file is a remote file + */ + if (eregi('^(http|ftp)://', substr($file, 0, 10))) { + if (!ini_get('allow_url_fopen')) { + return $this-> + raiseError('Remote files cannot be parsed, as safe mode is enabled.', + XML_PARSER_ERROR_REMOTE); + } + } + + $fp = @fopen($file, 'rb'); + if (is_resource($fp)) { + $this->fp = $fp; + return $fp; + } + return $this->raiseError('File could not be opened.', + XML_PARSER_ERROR_FILE_NOT_READABLE); + } + + // }}} + // {{{ setInputString() + + /** + * XML_Parser::setInputString() + * + * Sets the xml input from a string + * + * @param string $data a string containing the XML document + * + * @return null + */ + function setInputString($data) + { + $this->fp = $data; + return null; + } + + // }}} + // {{{ setInput() + + /** + * Sets the file handle to use with parse(). + * + * You should use setInputFile() or setInputString() if you + * pass a string + * + * @param mixed $fp Can be either a resource returned from fopen(), + * a URL, a local filename or a string. + * + * @return mixed + * @access public + * @see parse() + * @uses setInputString(), setInputFile() + */ + function setInput($fp) + { + if (is_resource($fp)) { + $this->fp = $fp; + return true; + } elseif (eregi('^[a-z]+://', substr($fp, 0, 10))) { + // see if it's an absolute URL (has a scheme at the beginning) + return $this->setInputFile($fp); + } elseif (file_exists($fp)) { + // see if it's a local file + return $this->setInputFile($fp); + } else { + // it must be a string + $this->fp = $fp; + return true; + } + + return $this->raiseError('Illegal input format', + XML_PARSER_ERROR_INVALID_INPUT); + } + + // }}} + // {{{ parse() + + /** + * Central parsing function. + * + * @return bool|PEAR_Error returns true on success, or a PEAR_Error otherwise + * @access public + */ + function parse() + { + /** + * reset the parser + */ + $result = $this->reset(); + if ($this->isError($result)) { + return $result; + } + // if $this->fp was fopened previously + if (is_resource($this->fp)) { + + while ($data = fread($this->fp, 4096)) { + if (!$this->_parseString($data, feof($this->fp))) { + $error = &$this->raiseError(); + $this->free(); + return $error; + } + } + } else { + // otherwise, $this->fp must be a string + if (!$this->_parseString($this->fp, true)) { + $error = &$this->raiseError(); + $this->free(); + return $error; + } + } + $this->free(); + + return true; + } + + /** + * XML_Parser::_parseString() + * + * @param string $data data + * @param bool $eof end-of-file flag + * + * @return bool + * @access private + * @see parseString() + **/ + function _parseString($data, $eof = false) + { + return xml_parse($this->parser, $data, $eof); + } + + // }}} + // {{{ parseString() + + /** + * XML_Parser::parseString() + * + * Parses a string. + * + * @param string $data XML data + * @param boolean $eof If set and TRUE, data is the last piece + * of data sent in this parser + * + * @return bool|PEAR_Error true on success or a PEAR Error + * @throws XML_Parser_Error + * @see _parseString() + */ + function parseString($data, $eof = false) + { + if (!isset($this->parser) || !is_resource($this->parser)) { + $this->reset(); + } + + if (!$this->_parseString($data, $eof)) { + $error = &$this->raiseError(); + $this->free(); + return $error; + } + + if ($eof === true) { + $this->free(); + } + return true; + } + + /** + * XML_Parser::free() + * + * Free the internal resources associated with the parser + * + * @return null + **/ + function free() + { + if (isset($this->parser) && is_resource($this->parser)) { + xml_parser_free($this->parser); + unset( $this->parser ); + } + if (isset($this->fp) && is_resource($this->fp)) { + fclose($this->fp); + } + unset($this->fp); + return null; + } + + /** + * XML_Parser::raiseError() + * + * Throws a XML_Parser_Error + * + * @param string $msg the error message + * @param integer $ecode the error message code + * + * @return XML_Parser_Error reference to the error object + **/ + function &raiseError($msg = null, $ecode = 0) + { + $msg = !is_null($msg) ? $msg : $this->parser; + $err = &new XML_Parser_Error($msg, $ecode); + return parent::raiseError($err); + } + + // }}} + // {{{ funcStartHandler() + + /** + * derives and calls the Start Handler function + * + * @param mixed $xp ?? + * @param mixed $elem ?? + * @param mixed $attribs ?? + * + * @return void + */ + function funcStartHandler($xp, $elem, $attribs) + { + $func = 'xmltag_' . $elem; + $func = str_replace(array('.', '-', ':'), '_', $func); + if (method_exists($this->_handlerObj, $func)) { + call_user_func(array(&$this->_handlerObj, $func), $xp, $elem, $attribs); + } elseif (method_exists($this->_handlerObj, 'xmltag')) { + call_user_func(array(&$this->_handlerObj, 'xmltag'), + $xp, $elem, $attribs); + } + } + + // }}} + // {{{ funcEndHandler() + + /** + * derives and calls the End Handler function + * + * @param mixed $xp ?? + * @param mixed $elem ?? + * + * @return void + */ + function funcEndHandler($xp, $elem) + { + $func = 'xmltag_' . $elem . '_'; + $func = str_replace(array('.', '-', ':'), '_', $func); + if (method_exists($this->_handlerObj, $func)) { + call_user_func(array(&$this->_handlerObj, $func), $xp, $elem); + } elseif (method_exists($this->_handlerObj, 'xmltag_')) { + call_user_func(array(&$this->_handlerObj, 'xmltag_'), $xp, $elem); + } + } + + // }}} + // {{{ startHandler() + + /** + * abstract method signature for Start Handler + * + * @param mixed $xp ?? + * @param mixed $elem ?? + * @param mixed &$attribs ?? + * + * @return null + * @abstract + */ + function startHandler($xp, $elem, &$attribs) + { + return null; + } + + // }}} + // {{{ endHandler() + + /** + * abstract method signature for End Handler + * + * @param mixed $xp ?? + * @param mixed $elem ?? + * + * @return null + * @abstract + */ + function endHandler($xp, $elem) + { + return null; + } + + + // }}}me +} + +/** + * error class, replaces PEAR_Error + * + * An instance of this class will be returned + * if an error occurs inside XML_Parser. + * + * There are three advantages over using the standard PEAR_Error: + * - All messages will be prefixed + * - check for XML_Parser error, using is_a( $error, 'XML_Parser_Error' ) + * - messages can be generated from the xml_parser resource + * + * @category XML + * @package XML_Parser + * @author Stig Bakken + * @author Tomas V.V.Cox + * @author Stephan Schmidt + * @copyright 2002-2008 The PHP Group + * @license http://opensource.org/licenses/bsd-license New BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/package/XML_Parser + * @see PEAR_Error + */ +class XML_Parser_Error extends PEAR_Error +{ + // {{{ properties + + /** + * prefix for all messages + * + * @var string + */ + var $error_message_prefix = 'XML_Parser: '; + + // }}} + // {{{ constructor() + /** + * construct a new error instance + * + * You may either pass a message or an xml_parser resource as first + * parameter. If a resource has been passed, the last error that + * happened will be retrieved and returned. + * + * @param string|resource $msgorparser message or parser resource + * @param integer $code error code + * @param integer $mode error handling + * @param integer $level error level + * + * @access public + * @todo PEAR CS - can't meet 85char line limit without arg refactoring + */ + function XML_Parser_Error($msgorparser = 'unknown error', $code = 0, $mode = PEAR_ERROR_RETURN, $level = E_USER_NOTICE) + { + if (is_resource($msgorparser)) { + $code = xml_get_error_code($msgorparser); + $msgorparser = sprintf('%s at XML input line %d:%d', + xml_error_string($code), + xml_get_current_line_number($msgorparser), + xml_get_current_column_number($msgorparser)); + } + $this->PEAR_Error($msgorparser, $code, $mode, $level); + } + // }}} +} +?> diff --git a/library/pear/XML/Parser/Simple.php b/library/pear/XML/Parser/Simple.php new file mode 100644 index 000000000..2ba9fb513 --- /dev/null +++ b/library/pear/XML/Parser/Simple.php @@ -0,0 +1,326 @@ + + * @copyright 2004-2008 Stephan Schmidt + * @license http://opensource.org/licenses/bsd-license New BSD License + * @version CVS: $Id: Simple.php,v 1.7 2008/08/24 21:48:21 ashnazg Exp $ + * @link http://pear.php.net/package/XML_Parser + */ + +/** + * built on XML_Parser + */ +require_once 'XML/Parser.php'; + +/** + * Simple XML parser class. + * + * This class is a simplified version of XML_Parser. + * In most XML applications the real action is executed, + * when a closing tag is found. + * + * XML_Parser_Simple allows you to just implement one callback + * for each tag that will receive the tag with its attributes + * and CData. + * + * + * require_once '../Parser/Simple.php'; + * + * class myParser extends XML_Parser_Simple + * { + * function myParser() + * { + * $this->XML_Parser_Simple(); + * } + * + * function handleElement($name, $attribs, $data) + * { + * printf('handle %s
    ', $name); + * } + * } + * + * $p = &new myParser(); + * + * $result = $p->setInputFile('myDoc.xml'); + * $result = $p->parse(); + *
    + * + * @category XML + * @package XML_Parser + * @author Stephan Schmidt + * @copyright 2004-2008 The PHP Group + * @license http://opensource.org/licenses/bsd-license New BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/package/XML_Parser + */ +class XML_Parser_Simple extends XML_Parser +{ + /** + * element stack + * + * @access private + * @var array + */ + var $_elStack = array(); + + /** + * all character data + * + * @access private + * @var array + */ + var $_data = array(); + + /** + * element depth + * + * @access private + * @var integer + */ + var $_depth = 0; + + /** + * Mapping from expat handler function to class method. + * + * @var array + */ + var $handler = array( + 'default_handler' => 'defaultHandler', + 'processing_instruction_handler' => 'piHandler', + 'unparsed_entity_decl_handler' => 'unparsedHandler', + 'notation_decl_handler' => 'notationHandler', + 'external_entity_ref_handler' => 'entityrefHandler' + ); + + /** + * Creates an XML parser. + * + * This is needed for PHP4 compatibility, it will + * call the constructor, when a new instance is created. + * + * @param string $srcenc source charset encoding, use NULL (default) to use + * whatever the document specifies + * @param string $mode how this parser object should work, "event" for + * handleElement(), "func" to have it call functions + * named after elements (handleElement_$name()) + * @param string $tgtenc a valid target encoding + */ + function XML_Parser_Simple($srcenc = null, $mode = 'event', $tgtenc = null) + { + $this->XML_Parser($srcenc, $mode, $tgtenc); + } + + /** + * inits the handlers + * + * @return mixed + * @access private + */ + function _initHandlers() + { + if (!is_object($this->_handlerObj)) { + $this->_handlerObj = &$this; + } + + if ($this->mode != 'func' && $this->mode != 'event') { + return $this->raiseError('Unsupported mode given', + XML_PARSER_ERROR_UNSUPPORTED_MODE); + } + xml_set_object($this->parser, $this->_handlerObj); + + xml_set_element_handler($this->parser, array(&$this, 'startHandler'), + array(&$this, 'endHandler')); + xml_set_character_data_handler($this->parser, array(&$this, 'cdataHandler')); + + /** + * set additional handlers for character data, entities, etc. + */ + foreach ($this->handler as $xml_func => $method) { + if (method_exists($this->_handlerObj, $method)) { + $xml_func = 'xml_set_' . $xml_func; + $xml_func($this->parser, $method); + } + } + } + + /** + * Reset the parser. + * + * This allows you to use one parser instance + * to parse multiple XML documents. + * + * @access public + * @return boolean|object true on success, PEAR_Error otherwise + */ + function reset() + { + $this->_elStack = array(); + $this->_data = array(); + $this->_depth = 0; + + $result = $this->_create(); + if ($this->isError($result)) { + return $result; + } + return true; + } + + /** + * start handler + * + * Pushes attributes and tagname onto a stack + * + * @param resource $xp xml parser resource + * @param string $elem element name + * @param array &$attribs attributes + * + * @return mixed + * @access private + * @final + */ + function startHandler($xp, $elem, &$attribs) + { + array_push($this->_elStack, array( + 'name' => $elem, + 'attribs' => $attribs + )); + $this->_depth++; + $this->_data[$this->_depth] = ''; + } + + /** + * end handler + * + * Pulls attributes and tagname from a stack + * + * @param resource $xp xml parser resource + * @param string $elem element name + * + * @return mixed + * @access private + * @final + */ + function endHandler($xp, $elem) + { + $el = array_pop($this->_elStack); + $data = $this->_data[$this->_depth]; + $this->_depth--; + + switch ($this->mode) { + case 'event': + $this->_handlerObj->handleElement($el['name'], $el['attribs'], $data); + break; + case 'func': + $func = 'handleElement_' . $elem; + if (strchr($func, '.')) { + $func = str_replace('.', '_', $func); + } + if (method_exists($this->_handlerObj, $func)) { + call_user_func(array(&$this->_handlerObj, $func), + $el['name'], $el['attribs'], $data); + } + break; + } + } + + /** + * handle character data + * + * @param resource $xp xml parser resource + * @param string $data data + * + * @return void + * @access private + * @final + */ + function cdataHandler($xp, $data) + { + $this->_data[$this->_depth] .= $data; + } + + /** + * handle a tag + * + * Implement this in your parser + * + * @param string $name element name + * @param array $attribs attributes + * @param string $data character data + * + * @return void + * @access public + * @abstract + */ + function handleElement($name, $attribs, $data) + { + } + + /** + * get the current tag depth + * + * The root tag is in depth 0. + * + * @access public + * @return integer + */ + function getCurrentDepth() + { + return $this->_depth; + } + + /** + * add some string to the current ddata. + * + * This is commonly needed, when a document is parsed recursively. + * + * @param string $data data to add + * + * @return void + * @access public + */ + function addToData($data) + { + $this->_data[$this->_depth] .= $data; + } +} +?> diff --git a/library/pear/XML/RPC.php b/library/pear/XML/RPC.php new file mode 100644 index 000000000..463d947fb --- /dev/null +++ b/library/pear/XML/RPC.php @@ -0,0 +1,2053 @@ + + * @author Stig Bakken + * @author Martin Jansen + * @author Daniel Convissor + * @copyright 1999-2001 Edd Dumbill, 2001-2010 The PHP Group + * @license http://www.php.net/license/3_01.txt PHP License + * @version SVN: $Id: RPC.php 300961 2010-07-03 02:17:34Z danielc $ + * @link http://pear.php.net/package/XML_RPC + */ + + +if (!function_exists('xml_parser_create')) { + include_once 'PEAR.php'; + PEAR::loadExtension('xml'); +} + +/**#@+ + * Error constants + */ +/** + * Parameter values don't match parameter types + */ +define('XML_RPC_ERROR_INVALID_TYPE', 101); +/** + * Parameter declared to be numeric but the values are not + */ +define('XML_RPC_ERROR_NON_NUMERIC_FOUND', 102); +/** + * Communication error + */ +define('XML_RPC_ERROR_CONNECTION_FAILED', 103); +/** + * The array or struct has already been started + */ +define('XML_RPC_ERROR_ALREADY_INITIALIZED', 104); +/** + * Incorrect parameters submitted + */ +define('XML_RPC_ERROR_INCORRECT_PARAMS', 105); +/** + * Programming error by developer + */ +define('XML_RPC_ERROR_PROGRAMMING', 106); +/**#@-*/ + + +/** + * Data types + * @global string $GLOBALS['XML_RPC_I4'] + */ +$GLOBALS['XML_RPC_I4'] = 'i4'; + +/** + * Data types + * @global string $GLOBALS['XML_RPC_Int'] + */ +$GLOBALS['XML_RPC_Int'] = 'int'; + +/** + * Data types + * @global string $GLOBALS['XML_RPC_Boolean'] + */ +$GLOBALS['XML_RPC_Boolean'] = 'boolean'; + +/** + * Data types + * @global string $GLOBALS['XML_RPC_Double'] + */ +$GLOBALS['XML_RPC_Double'] = 'double'; + +/** + * Data types + * @global string $GLOBALS['XML_RPC_String'] + */ +$GLOBALS['XML_RPC_String'] = 'string'; + +/** + * Data types + * @global string $GLOBALS['XML_RPC_DateTime'] + */ +$GLOBALS['XML_RPC_DateTime'] = 'dateTime.iso8601'; + +/** + * Data types + * @global string $GLOBALS['XML_RPC_Base64'] + */ +$GLOBALS['XML_RPC_Base64'] = 'base64'; + +/** + * Data types + * @global string $GLOBALS['XML_RPC_Array'] + */ +$GLOBALS['XML_RPC_Array'] = 'array'; + +/** + * Data types + * @global string $GLOBALS['XML_RPC_Struct'] + */ +$GLOBALS['XML_RPC_Struct'] = 'struct'; + + +/** + * Data type meta-types + * @global array $GLOBALS['XML_RPC_Types'] + */ +$GLOBALS['XML_RPC_Types'] = array( + $GLOBALS['XML_RPC_I4'] => 1, + $GLOBALS['XML_RPC_Int'] => 1, + $GLOBALS['XML_RPC_Boolean'] => 1, + $GLOBALS['XML_RPC_String'] => 1, + $GLOBALS['XML_RPC_Double'] => 1, + $GLOBALS['XML_RPC_DateTime'] => 1, + $GLOBALS['XML_RPC_Base64'] => 1, + $GLOBALS['XML_RPC_Array'] => 2, + $GLOBALS['XML_RPC_Struct'] => 3, +); + + +/** + * Error message numbers + * @global array $GLOBALS['XML_RPC_err'] + */ +$GLOBALS['XML_RPC_err'] = array( + 'unknown_method' => 1, + 'invalid_return' => 2, + 'incorrect_params' => 3, + 'introspect_unknown' => 4, + 'http_error' => 5, + 'not_response_object' => 6, + 'invalid_request' => 7, +); + +/** + * Error message strings + * @global array $GLOBALS['XML_RPC_str'] + */ +$GLOBALS['XML_RPC_str'] = array( + 'unknown_method' => 'Unknown method', + 'invalid_return' => 'Invalid return payload: enable debugging to examine incoming payload', + 'incorrect_params' => 'Incorrect parameters passed to method', + 'introspect_unknown' => 'Can\'t introspect: method unknown', + 'http_error' => 'Didn\'t receive 200 OK from remote server.', + 'not_response_object' => 'The requested method didn\'t return an XML_RPC_Response object.', + 'invalid_request' => 'Invalid request payload', +); + + +/** + * Default XML encoding (ISO-8859-1, UTF-8 or US-ASCII) + * @global string $GLOBALS['XML_RPC_defencoding'] + */ +$GLOBALS['XML_RPC_defencoding'] = 'UTF-8'; + +/** + * User error codes start at 800 + * @global int $GLOBALS['XML_RPC_erruser'] + */ +$GLOBALS['XML_RPC_erruser'] = 800; + +/** + * XML parse error codes start at 100 + * @global int $GLOBALS['XML_RPC_errxml'] + */ +$GLOBALS['XML_RPC_errxml'] = 100; + + +/** + * Compose backslashes for escaping regexp + * @global string $GLOBALS['XML_RPC_backslash'] + */ +$GLOBALS['XML_RPC_backslash'] = chr(92) . chr(92); + + +/** + * Should we automatically base64 encode strings that contain characters + * which can cause PHP's SAX-based XML parser to break? + * @global boolean $GLOBALS['XML_RPC_auto_base64'] + */ +$GLOBALS['XML_RPC_auto_base64'] = false; + + +/** + * Valid parents of XML elements + * @global array $GLOBALS['XML_RPC_valid_parents'] + */ +$GLOBALS['XML_RPC_valid_parents'] = array( + 'BOOLEAN' => array('VALUE'), + 'I4' => array('VALUE'), + 'INT' => array('VALUE'), + 'STRING' => array('VALUE'), + 'DOUBLE' => array('VALUE'), + 'DATETIME.ISO8601' => array('VALUE'), + 'BASE64' => array('VALUE'), + 'ARRAY' => array('VALUE'), + 'STRUCT' => array('VALUE'), + 'PARAM' => array('PARAMS'), + 'METHODNAME' => array('METHODCALL'), + 'PARAMS' => array('METHODCALL', 'METHODRESPONSE'), + 'MEMBER' => array('STRUCT'), + 'NAME' => array('MEMBER'), + 'DATA' => array('ARRAY'), + 'FAULT' => array('METHODRESPONSE'), + 'VALUE' => array('MEMBER', 'DATA', 'PARAM', 'FAULT'), +); + + +/** + * Stores state during parsing + * + * quick explanation of components: + * + ac = accumulates values + * + qt = decides if quotes are needed for evaluation + * + cm = denotes struct or array (comma needed) + * + isf = indicates a fault + * + lv = indicates "looking for a value": implements the logic + * to allow values with no types to be strings + * + params = stores parameters in method calls + * + method = stores method name + * + * @global array $GLOBALS['XML_RPC_xh'] + */ +$GLOBALS['XML_RPC_xh'] = array(); + + +/** + * Start element handler for the XML parser + * + * @return void + */ +function XML_RPC_se($parser_resource, $name, $attrs) +{ + global $XML_RPC_xh, $XML_RPC_valid_parents; + + $parser = (int) $parser_resource; + + // if invalid xmlrpc already detected, skip all processing + if ($XML_RPC_xh[$parser]['isf'] >= 2) { + return; + } + + // check for correct element nesting + // top level element can only be of 2 types + if (count($XML_RPC_xh[$parser]['stack']) == 0) { + if ($name != 'METHODRESPONSE' && $name != 'METHODCALL') { + $XML_RPC_xh[$parser]['isf'] = 2; + $XML_RPC_xh[$parser]['isf_reason'] = 'missing top level xmlrpc element'; + return; + } + } else { + // not top level element: see if parent is OK + if (!in_array($XML_RPC_xh[$parser]['stack'][0], $XML_RPC_valid_parents[$name])) { + $name = preg_replace('@[^a-zA-Z0-9._-]@', '', $name); + $XML_RPC_xh[$parser]['isf'] = 2; + $XML_RPC_xh[$parser]['isf_reason'] = "xmlrpc element $name cannot be child of {$XML_RPC_xh[$parser]['stack'][0]}"; + return; + } + } + + switch ($name) { + case 'STRUCT': + $XML_RPC_xh[$parser]['cm']++; + + // turn quoting off + $XML_RPC_xh[$parser]['qt'] = 0; + + $cur_val = array(); + $cur_val['value'] = array(); + $cur_val['members'] = 1; + array_unshift($XML_RPC_xh[$parser]['valuestack'], $cur_val); + break; + + case 'ARRAY': + $XML_RPC_xh[$parser]['cm']++; + + // turn quoting off + $XML_RPC_xh[$parser]['qt'] = 0; + + $cur_val = array(); + $cur_val['value'] = array(); + $cur_val['members'] = 0; + array_unshift($XML_RPC_xh[$parser]['valuestack'], $cur_val); + break; + + case 'NAME': + $XML_RPC_xh[$parser]['ac'] = ''; + break; + + case 'FAULT': + $XML_RPC_xh[$parser]['isf'] = 1; + break; + + case 'PARAM': + $XML_RPC_xh[$parser]['valuestack'] = array(); + break; + + case 'VALUE': + $XML_RPC_xh[$parser]['lv'] = 1; + $XML_RPC_xh[$parser]['vt'] = $GLOBALS['XML_RPC_String']; + $XML_RPC_xh[$parser]['ac'] = ''; + $XML_RPC_xh[$parser]['qt'] = 0; + // look for a value: if this is still 1 by the + // time we reach the first data segment then the type is string + // by implication and we need to add in a quote + break; + + case 'I4': + case 'INT': + case 'STRING': + case 'BOOLEAN': + case 'DOUBLE': + case 'DATETIME.ISO8601': + case 'BASE64': + $XML_RPC_xh[$parser]['ac'] = ''; // reset the accumulator + + if ($name == 'DATETIME.ISO8601' || $name == 'STRING') { + $XML_RPC_xh[$parser]['qt'] = 1; + + if ($name == 'DATETIME.ISO8601') { + $XML_RPC_xh[$parser]['vt'] = $GLOBALS['XML_RPC_DateTime']; + } + + } elseif ($name == 'BASE64') { + $XML_RPC_xh[$parser]['qt'] = 2; + } else { + // No quoting is required here -- but + // at the end of the element we must check + // for data format errors. + $XML_RPC_xh[$parser]['qt'] = 0; + } + break; + + case 'MEMBER': + $XML_RPC_xh[$parser]['ac'] = ''; + break; + + case 'DATA': + case 'METHODCALL': + case 'METHODNAME': + case 'METHODRESPONSE': + case 'PARAMS': + // valid elements that add little to processing + break; + } + + + // Save current element to stack + array_unshift($XML_RPC_xh[$parser]['stack'], $name); + + if ($name != 'VALUE') { + $XML_RPC_xh[$parser]['lv'] = 0; + } +} + +/** + * End element handler for the XML parser + * + * @return void + */ +function XML_RPC_ee($parser_resource, $name) +{ + global $XML_RPC_xh; + + $parser = (int) $parser_resource; + + if ($XML_RPC_xh[$parser]['isf'] >= 2) { + return; + } + + // push this element from stack + // NB: if XML validates, correct opening/closing is guaranteed and + // we do not have to check for $name == $curr_elem. + // we also checked for proper nesting at start of elements... + $curr_elem = array_shift($XML_RPC_xh[$parser]['stack']); + + switch ($name) { + case 'STRUCT': + case 'ARRAY': + $cur_val = array_shift($XML_RPC_xh[$parser]['valuestack']); + $XML_RPC_xh[$parser]['value'] = $cur_val['value']; + $XML_RPC_xh[$parser]['vt'] = strtolower($name); + $XML_RPC_xh[$parser]['cm']--; + break; + + case 'NAME': + $XML_RPC_xh[$parser]['valuestack'][0]['name'] = $XML_RPC_xh[$parser]['ac']; + break; + + case 'BOOLEAN': + // special case here: we translate boolean 1 or 0 into PHP + // constants true or false + if ($XML_RPC_xh[$parser]['ac'] == '1') { + $XML_RPC_xh[$parser]['ac'] = 'true'; + } else { + $XML_RPC_xh[$parser]['ac'] = 'false'; + } + + $XML_RPC_xh[$parser]['vt'] = strtolower($name); + // Drop through intentionally. + + case 'I4': + case 'INT': + case 'STRING': + case 'DOUBLE': + case 'DATETIME.ISO8601': + case 'BASE64': + if ($XML_RPC_xh[$parser]['qt'] == 1) { + // we use double quotes rather than single so backslashification works OK + $XML_RPC_xh[$parser]['value'] = $XML_RPC_xh[$parser]['ac']; + } elseif ($XML_RPC_xh[$parser]['qt'] == 2) { + $XML_RPC_xh[$parser]['value'] = base64_decode($XML_RPC_xh[$parser]['ac']); + } elseif ($name == 'BOOLEAN') { + $XML_RPC_xh[$parser]['value'] = $XML_RPC_xh[$parser]['ac']; + } else { + // we have an I4, INT or a DOUBLE + // we must check that only 0123456789-. are characters here + if (!preg_match("@^[+-]?[0123456789 \t\.]+$@", $XML_RPC_xh[$parser]['ac'])) { + XML_RPC_Base::raiseError('Non-numeric value received in INT or DOUBLE', + XML_RPC_ERROR_NON_NUMERIC_FOUND); + $XML_RPC_xh[$parser]['value'] = XML_RPC_ERROR_NON_NUMERIC_FOUND; + } else { + // it's ok, add it on + $XML_RPC_xh[$parser]['value'] = $XML_RPC_xh[$parser]['ac']; + } + } + + $XML_RPC_xh[$parser]['ac'] = ''; + $XML_RPC_xh[$parser]['qt'] = 0; + $XML_RPC_xh[$parser]['lv'] = 3; // indicate we've found a value + break; + + case 'VALUE': + if ($XML_RPC_xh[$parser]['vt'] == $GLOBALS['XML_RPC_String']) { + if (strlen($XML_RPC_xh[$parser]['ac']) > 0) { + $XML_RPC_xh[$parser]['value'] = $XML_RPC_xh[$parser]['ac']; + } elseif ($XML_RPC_xh[$parser]['lv'] == 1) { + // The element was empty. + $XML_RPC_xh[$parser]['value'] = ''; + } + } + + $temp = new XML_RPC_Value($XML_RPC_xh[$parser]['value'], $XML_RPC_xh[$parser]['vt']); + + $cur_val = array_shift($XML_RPC_xh[$parser]['valuestack']); + if (is_array($cur_val)) { + if ($cur_val['members']==0) { + $cur_val['value'][] = $temp; + } else { + $XML_RPC_xh[$parser]['value'] = $temp; + } + array_unshift($XML_RPC_xh[$parser]['valuestack'], $cur_val); + } else { + $XML_RPC_xh[$parser]['value'] = $temp; + } + break; + + case 'MEMBER': + $XML_RPC_xh[$parser]['ac'] = ''; + $XML_RPC_xh[$parser]['qt'] = 0; + + $cur_val = array_shift($XML_RPC_xh[$parser]['valuestack']); + if (is_array($cur_val)) { + if ($cur_val['members']==1) { + $cur_val['value'][$cur_val['name']] = $XML_RPC_xh[$parser]['value']; + } + array_unshift($XML_RPC_xh[$parser]['valuestack'], $cur_val); + } + break; + + case 'DATA': + $XML_RPC_xh[$parser]['ac'] = ''; + $XML_RPC_xh[$parser]['qt'] = 0; + break; + + case 'PARAM': + $XML_RPC_xh[$parser]['params'][] = $XML_RPC_xh[$parser]['value']; + break; + + case 'METHODNAME': + case 'RPCMETHODNAME': + $XML_RPC_xh[$parser]['method'] = preg_replace("@^[\n\r\t ]+@", '', + $XML_RPC_xh[$parser]['ac']); + break; + } + + // if it's a valid type name, set the type + if (isset($GLOBALS['XML_RPC_Types'][strtolower($name)])) { + $XML_RPC_xh[$parser]['vt'] = strtolower($name); + } +} + +/** + * Character data handler for the XML parser + * + * @return void + */ +function XML_RPC_cd($parser_resource, $data) +{ + global $XML_RPC_xh, $XML_RPC_backslash; + + $parser = (int) $parser_resource; + + if ($XML_RPC_xh[$parser]['lv'] != 3) { + // "lookforvalue==3" means that we've found an entire value + // and should discard any further character data + + if ($XML_RPC_xh[$parser]['lv'] == 1) { + // if we've found text and we're just in a then + // turn quoting on, as this will be a string + $XML_RPC_xh[$parser]['qt'] = 1; + // and say we've found a value + $XML_RPC_xh[$parser]['lv'] = 2; + } + + // replace characters that eval would + // do special things with + if (!isset($XML_RPC_xh[$parser]['ac'])) { + $XML_RPC_xh[$parser]['ac'] = ''; + } + $XML_RPC_xh[$parser]['ac'] .= $data; + } +} + +/** + * The common methods and properties for all of the XML_RPC classes + * + * @category Web Services + * @package XML_RPC + * @author Edd Dumbill + * @author Stig Bakken + * @author Martin Jansen + * @author Daniel Convissor + * @copyright 1999-2001 Edd Dumbill, 2001-2010 The PHP Group + * @license http://www.php.net/license/3_01.txt PHP License + * @version Release: @package_version@ + * @link http://pear.php.net/package/XML_RPC + */ +class XML_RPC_Base { + + /** + * PEAR Error handling + * + * @return object PEAR_Error object + */ + function raiseError($msg, $code) + { + include_once 'PEAR.php'; + if (is_object(@$this)) { + return PEAR::raiseError(get_class($this) . ': ' . $msg, $code); + } else { + return PEAR::raiseError('XML_RPC: ' . $msg, $code); + } + } + + /** + * Tell whether something is a PEAR_Error object + * + * @param mixed $value the item to check + * + * @return bool whether $value is a PEAR_Error object or not + * + * @access public + */ + function isError($value) + { + return is_a($value, 'PEAR_Error'); + } +} + +/** + * The methods and properties for submitting XML RPC requests + * + * @category Web Services + * @package XML_RPC + * @author Edd Dumbill + * @author Stig Bakken + * @author Martin Jansen + * @author Daniel Convissor + * @copyright 1999-2001 Edd Dumbill, 2001-2010 The PHP Group + * @license http://www.php.net/license/3_01.txt PHP License + * @version Release: @package_version@ + * @link http://pear.php.net/package/XML_RPC + */ +class XML_RPC_Client extends XML_RPC_Base { + + /** + * The path and name of the RPC server script you want the request to go to + * @var string + */ + var $path = ''; + + /** + * The name of the remote server to connect to + * @var string + */ + var $server = ''; + + /** + * The protocol to use in contacting the remote server + * @var string + */ + var $protocol = 'http://'; + + /** + * The port for connecting to the remote server + * + * The default is 80 for http:// connections + * and 443 for https:// and ssl:// connections. + * + * @var integer + */ + var $port = 80; + + /** + * A user name for accessing the RPC server + * @var string + * @see XML_RPC_Client::setCredentials() + */ + var $username = ''; + + /** + * A password for accessing the RPC server + * @var string + * @see XML_RPC_Client::setCredentials() + */ + var $password = ''; + + /** + * The name of the proxy server to use, if any + * @var string + */ + var $proxy = ''; + + /** + * The protocol to use in contacting the proxy server, if any + * @var string + */ + var $proxy_protocol = 'http://'; + + /** + * The port for connecting to the proxy server + * + * The default is 8080 for http:// connections + * and 443 for https:// and ssl:// connections. + * + * @var integer + */ + var $proxy_port = 8080; + + /** + * A user name for accessing the proxy server + * @var string + */ + var $proxy_user = ''; + + /** + * A password for accessing the proxy server + * @var string + */ + var $proxy_pass = ''; + + /** + * The error number, if any + * @var integer + */ + var $errno = 0; + + /** + * The error message, if any + * @var string + */ + var $errstr = ''; + + /** + * The current debug mode (1 = on, 0 = off) + * @var integer + */ + var $debug = 0; + + /** + * The HTTP headers for the current request. + * @var string + */ + var $headers = ''; + + + /** + * Sets the object's properties + * + * @param string $path the path and name of the RPC server script + * you want the request to go to + * @param string $server the URL of the remote server to connect to. + * If this parameter doesn't specify a + * protocol and $port is 443, ssl:// is + * assumed. + * @param integer $port a port for connecting to the remote server. + * Defaults to 80 for http:// connections and + * 443 for https:// and ssl:// connections. + * @param string $proxy the URL of the proxy server to use, if any. + * If this parameter doesn't specify a + * protocol and $port is 443, ssl:// is + * assumed. + * @param integer $proxy_port a port for connecting to the remote server. + * Defaults to 8080 for http:// connections and + * 443 for https:// and ssl:// connections. + * @param string $proxy_user a user name for accessing the proxy server + * @param string $proxy_pass a password for accessing the proxy server + * + * @return void + */ + function XML_RPC_Client($path, $server, $port = 0, + $proxy = '', $proxy_port = 0, + $proxy_user = '', $proxy_pass = '') + { + $this->path = $path; + $this->proxy_user = $proxy_user; + $this->proxy_pass = $proxy_pass; + + preg_match('@^(http://|https://|ssl://)?(.*)$@', $server, $match); + if ($match[1] == '') { + if ($port == 443) { + $this->server = $match[2]; + $this->protocol = 'ssl://'; + $this->port = 443; + } else { + $this->server = $match[2]; + if ($port) { + $this->port = $port; + } + } + } elseif ($match[1] == 'http://') { + $this->server = $match[2]; + if ($port) { + $this->port = $port; + } + } else { + $this->server = $match[2]; + $this->protocol = 'ssl://'; + if ($port) { + $this->port = $port; + } else { + $this->port = 443; + } + } + + if ($proxy) { + preg_match('@^(http://|https://|ssl://)?(.*)$@', $proxy, $match); + if ($match[1] == '') { + if ($proxy_port == 443) { + $this->proxy = $match[2]; + $this->proxy_protocol = 'ssl://'; + $this->proxy_port = 443; + } else { + $this->proxy = $match[2]; + if ($proxy_port) { + $this->proxy_port = $proxy_port; + } + } + } elseif ($match[1] == 'http://') { + $this->proxy = $match[2]; + if ($proxy_port) { + $this->proxy_port = $proxy_port; + } + } else { + $this->proxy = $match[2]; + $this->proxy_protocol = 'ssl://'; + if ($proxy_port) { + $this->proxy_port = $proxy_port; + } else { + $this->proxy_port = 443; + } + } + } + } + + /** + * Change the current debug mode + * + * @param int $in where 1 = on, 0 = off + * + * @return void + */ + function setDebug($in) + { + if ($in) { + $this->debug = 1; + } else { + $this->debug = 0; + } + } + + /** + * Sets whether strings that contain characters which may cause PHP's + * SAX-based XML parser to break should be automatically base64 encoded + * + * This is is a workaround for systems that don't have PHP's mbstring + * extension available. + * + * @param int $in where 1 = on, 0 = off + * + * @return void + */ + function setAutoBase64($in) + { + if ($in) { + $GLOBALS['XML_RPC_auto_base64'] = true; + } else { + $GLOBALS['XML_RPC_auto_base64'] = false; + } + } + + /** + * Set username and password properties for connecting to the RPC server + * + * @param string $u the user name + * @param string $p the password + * + * @return void + * + * @see XML_RPC_Client::$username, XML_RPC_Client::$password + */ + function setCredentials($u, $p) + { + $this->username = $u; + $this->password = $p; + } + + /** + * Transmit the RPC request via HTTP 1.0 protocol + * + * @param object $msg the XML_RPC_Message object + * @param int $timeout how many seconds to wait for the request + * + * @return object an XML_RPC_Response object. 0 is returned if any + * problems happen. + * + * @see XML_RPC_Message, XML_RPC_Client::XML_RPC_Client(), + * XML_RPC_Client::setCredentials() + */ + function send($msg, $timeout = 0) + { + if (!is_a($msg, 'XML_RPC_Message')) { + $this->errstr = 'send()\'s $msg parameter must be an' + . ' XML_RPC_Message object.'; + $this->raiseError($this->errstr, XML_RPC_ERROR_PROGRAMMING); + return 0; + } + $msg->debug = $this->debug; + return $this->sendPayloadHTTP10($msg, $this->server, $this->port, + $timeout, $this->username, + $this->password); + } + + /** + * Transmit the RPC request via HTTP 1.0 protocol + * + * Requests should be sent using XML_RPC_Client send() rather than + * calling this method directly. + * + * @param object $msg the XML_RPC_Message object + * @param string $server the server to send the request to + * @param int $port the server port send the request to + * @param int $timeout how many seconds to wait for the request + * before giving up + * @param string $username a user name for accessing the RPC server + * @param string $password a password for accessing the RPC server + * + * @return object an XML_RPC_Response object. 0 is returned if any + * problems happen. + * + * @access protected + * @see XML_RPC_Client::send() + */ + function sendPayloadHTTP10($msg, $server, $port, $timeout = 0, + $username = '', $password = '') + { + // Pre-emptive BC hacks for fools calling sendPayloadHTTP10() directly + if ($username != $this->username) { + $this->setCredentials($username, $password); + } + + // Only create the payload if it was not created previously + if (empty($msg->payload)) { + $msg->createPayload(); + } + $this->createHeaders($msg); + + $op = $this->headers . "\r\n\r\n"; + $op .= $msg->payload; + + if ($this->debug) { + print "\n
    ---SENT---\n";
    +            print $op;
    +            print "\n---END---
    \n"; + } + + /* + * If we're using a proxy open a socket to the proxy server + * instead to the xml-rpc server + */ + if ($this->proxy) { + if ($this->proxy_protocol == 'http://') { + $protocol = ''; + } else { + $protocol = $this->proxy_protocol; + } + if ($timeout > 0) { + $fp = @fsockopen($protocol . $this->proxy, $this->proxy_port, + $this->errno, $this->errstr, $timeout); + } else { + $fp = @fsockopen($protocol . $this->proxy, $this->proxy_port, + $this->errno, $this->errstr); + } + } else { + if ($this->protocol == 'http://') { + $protocol = ''; + } else { + $protocol = $this->protocol; + } + if ($timeout > 0) { + $fp = @fsockopen($protocol . $server, $port, + $this->errno, $this->errstr, $timeout); + } else { + $fp = @fsockopen($protocol . $server, $port, + $this->errno, $this->errstr); + } + } + + /* + * Just raising the error without returning it is strange, + * but keep it here for backwards compatibility. + */ + if (!$fp && $this->proxy) { + $this->raiseError('Connection to proxy server ' + . $this->proxy . ':' . $this->proxy_port + . ' failed. ' . $this->errstr, + XML_RPC_ERROR_CONNECTION_FAILED); + return 0; + } elseif (!$fp) { + $this->raiseError('Connection to RPC server ' + . $server . ':' . $port + . ' failed. ' . $this->errstr, + XML_RPC_ERROR_CONNECTION_FAILED); + return 0; + } + + if ($timeout) { + /* + * Using socket_set_timeout() because stream_set_timeout() + * was introduced in 4.3.0, but we need to support 4.2.0. + */ + socket_set_timeout($fp, $timeout); + } + + if (!fputs($fp, $op, strlen($op))) { + $this->errstr = 'Write error'; + return 0; + } + $resp = $msg->parseResponseFile($fp); + + $meta = socket_get_status($fp); + if ($meta['timed_out']) { + fclose($fp); + $this->errstr = 'RPC server did not send response before timeout.'; + $this->raiseError($this->errstr, XML_RPC_ERROR_CONNECTION_FAILED); + return 0; + } + + fclose($fp); + return $resp; + } + + /** + * Determines the HTTP headers and puts it in the $headers property + * + * @param object $msg the XML_RPC_Message object + * + * @return boolean TRUE if okay, FALSE if the message payload isn't set. + * + * @access protected + */ + function createHeaders($msg) + { + if (empty($msg->payload)) { + return false; + } + if ($this->proxy) { + $this->headers = 'POST ' . $this->protocol . $this->server; + if ($this->proxy_port) { + $this->headers .= ':' . $this->port; + } + } else { + $this->headers = 'POST '; + } + $this->headers .= $this->path. " HTTP/1.0\r\n"; + + $this->headers .= "User-Agent: PEAR XML_RPC\r\n"; + $this->headers .= 'Host: ' . $this->server . "\r\n"; + + if ($this->proxy && $this->proxy_user) { + $this->headers .= 'Proxy-Authorization: Basic ' + . base64_encode("$this->proxy_user:$this->proxy_pass") + . "\r\n"; + } + + // thanks to Grant Rauscher for this + if ($this->username) { + $this->headers .= 'Authorization: Basic ' + . base64_encode("$this->username:$this->password") + . "\r\n"; + } + + $this->headers .= "Content-Type: text/xml\r\n"; + $this->headers .= 'Content-Length: ' . strlen($msg->payload); + return true; + } +} + +/** + * The methods and properties for interpreting responses to XML RPC requests + * + * @category Web Services + * @package XML_RPC + * @author Edd Dumbill + * @author Stig Bakken + * @author Martin Jansen + * @author Daniel Convissor + * @copyright 1999-2001 Edd Dumbill, 2001-2010 The PHP Group + * @license http://www.php.net/license/3_01.txt PHP License + * @version Release: @package_version@ + * @link http://pear.php.net/package/XML_RPC + */ +class XML_RPC_Response extends XML_RPC_Base +{ + var $xv; + var $fn; + var $fs; + var $hdrs; + + /** + * @return void + */ + function XML_RPC_Response($val, $fcode = 0, $fstr = '') + { + if ($fcode != 0) { + $this->fn = $fcode; + $this->fs = htmlspecialchars($fstr); + } else { + $this->xv = $val; + } + } + + /** + * @return int the error code + */ + function faultCode() + { + if (isset($this->fn)) { + return $this->fn; + } else { + return 0; + } + } + + /** + * @return string the error string + */ + function faultString() + { + return $this->fs; + } + + /** + * @return mixed the value + */ + function value() + { + return $this->xv; + } + + /** + * @return string the error message in XML format + */ + function serialize() + { + $rs = "\n"; + if ($this->fn) { + $rs .= " + + + + faultCode + " . $this->fn . " + + + faultString + " . $this->fs . " + + + +"; + } else { + $rs .= "\n\n" . $this->xv->serialize() . + "\n"; + } + $rs .= "\n"; + return $rs; + } +} + +/** + * The methods and properties for composing XML RPC messages + * + * @category Web Services + * @package XML_RPC + * @author Edd Dumbill + * @author Stig Bakken + * @author Martin Jansen + * @author Daniel Convissor + * @copyright 1999-2001 Edd Dumbill, 2001-2010 The PHP Group + * @license http://www.php.net/license/3_01.txt PHP License + * @version Release: @package_version@ + * @link http://pear.php.net/package/XML_RPC + */ +class XML_RPC_Message extends XML_RPC_Base +{ + /** + * Should the payload's content be passed through mb_convert_encoding()? + * + * @see XML_RPC_Message::setConvertPayloadEncoding() + * @since Property available since Release 1.5.1 + * @var boolean + */ + var $convert_payload_encoding = false; + + /** + * The current debug mode (1 = on, 0 = off) + * @var integer + */ + var $debug = 0; + + /** + * The encoding to be used for outgoing messages + * + * Defaults to the value of $GLOBALS['XML_RPC_defencoding'] + * + * @var string + * @see XML_RPC_Message::setSendEncoding(), + * $GLOBALS['XML_RPC_defencoding'], XML_RPC_Message::xml_header() + */ + var $send_encoding = ''; + + /** + * The method presently being evaluated + * @var string + */ + var $methodname = ''; + + /** + * @var array + */ + var $params = array(); + + /** + * The XML message being generated + * @var string + */ + var $payload = ''; + + /** + * Should extra line breaks be removed from the payload? + * @since Property available since Release 1.4.6 + * @var boolean + */ + var $remove_extra_lines = true; + + /** + * The XML response from the remote server + * @since Property available since Release 1.4.6 + * @var string + */ + var $response_payload = ''; + + + /** + * @return void + */ + function XML_RPC_Message($meth, $pars = 0) + { + $this->methodname = $meth; + if (is_array($pars) && sizeof($pars) > 0) { + for ($i = 0; $i < sizeof($pars); $i++) { + $this->addParam($pars[$i]); + } + } + } + + /** + * Produces the XML declaration including the encoding attribute + * + * The encoding is determined by this class' $send_encoding + * property. If the $send_encoding property is not set, use + * $GLOBALS['XML_RPC_defencoding']. + * + * @return string the XML declaration and element + * + * @see XML_RPC_Message::setSendEncoding(), + * XML_RPC_Message::$send_encoding, $GLOBALS['XML_RPC_defencoding'] + */ + function xml_header() + { + global $XML_RPC_defencoding; + + if (!$this->send_encoding) { + $this->send_encoding = $XML_RPC_defencoding; + } + return 'send_encoding . '"?>' + . "\n\n"; + } + + /** + * @return string the closing tag + */ + function xml_footer() + { + return "\n"; + } + + /** + * Fills the XML_RPC_Message::$payload property + * + * Part of the process makes sure all line endings are in DOS format + * (CRLF), which is probably required by specifications. + * + * If XML_RPC_Message::setConvertPayloadEncoding() was set to true, + * the payload gets passed through mb_convert_encoding() + * to ensure the payload matches the encoding set in the + * XML declaration. The encoding type can be manually set via + * XML_RPC_Message::setSendEncoding(). + * + * @return void + * + * @uses XML_RPC_Message::xml_header(), XML_RPC_Message::xml_footer() + * @see XML_RPC_Message::setSendEncoding(), $GLOBALS['XML_RPC_defencoding'], + * XML_RPC_Message::setConvertPayloadEncoding() + */ + function createPayload() + { + $this->payload = $this->xml_header(); + $this->payload .= '' . $this->methodname . "\n"; + $this->payload .= "\n"; + for ($i = 0; $i < sizeof($this->params); $i++) { + $p = $this->params[$i]; + $this->payload .= "\n" . $p->serialize() . "\n"; + } + $this->payload .= "\n"; + $this->payload .= $this->xml_footer(); + if ($this->remove_extra_lines) { + $this->payload = preg_replace("@[\r\n]+@", "\r\n", $this->payload); + } else { + $this->payload = preg_replace("@\r\n|\n|\r|\n\r@", "\r\n", $this->payload); + } + if ($this->convert_payload_encoding) { + $this->payload = mb_convert_encoding($this->payload, $this->send_encoding); + } + } + + /** + * @return string the name of the method + */ + function method($meth = '') + { + if ($meth != '') { + $this->methodname = $meth; + } + return $this->methodname; + } + + /** + * @return string the payload + */ + function serialize() + { + $this->createPayload(); + return $this->payload; + } + + /** + * @return void + */ + function addParam($par) + { + $this->params[] = $par; + } + + /** + * Obtains an XML_RPC_Value object for the given parameter + * + * @param int $i the index number of the parameter to obtain + * + * @return object the XML_RPC_Value object. + * If the parameter doesn't exist, an XML_RPC_Response object. + * + * @since Returns XML_RPC_Response object on error since Release 1.3.0 + */ + function getParam($i) + { + global $XML_RPC_err, $XML_RPC_str; + + if (isset($this->params[$i])) { + return $this->params[$i]; + } else { + $this->raiseError('The submitted request did not contain this parameter', + XML_RPC_ERROR_INCORRECT_PARAMS); + return new XML_RPC_Response(0, $XML_RPC_err['incorrect_params'], + $XML_RPC_str['incorrect_params']); + } + } + + /** + * @return int the number of parameters + */ + function getNumParams() + { + return sizeof($this->params); + } + + /** + * Sets whether the payload's content gets passed through + * mb_convert_encoding() + * + * Returns PEAR_ERROR object if mb_convert_encoding() isn't available. + * + * @param int $in where 1 = on, 0 = off + * + * @return void + * + * @see XML_RPC_Message::setSendEncoding() + * @since Method available since Release 1.5.1 + */ + function setConvertPayloadEncoding($in) + { + if ($in && !function_exists('mb_convert_encoding')) { + return $this->raiseError('mb_convert_encoding() is not available', + XML_RPC_ERROR_PROGRAMMING); + } + $this->convert_payload_encoding = $in; + } + + /** + * Sets the XML declaration's encoding attribute + * + * @param string $type the encoding type (ISO-8859-1, UTF-8 or US-ASCII) + * + * @return void + * + * @see XML_RPC_Message::setConvertPayloadEncoding(), XML_RPC_Message::xml_header() + * @since Method available since Release 1.2.0 + */ + function setSendEncoding($type) + { + $this->send_encoding = $type; + } + + /** + * Determine the XML's encoding via the encoding attribute + * in the XML declaration + * + * If the encoding parameter is not set or is not ISO-8859-1, UTF-8 + * or US-ASCII, $XML_RPC_defencoding will be returned. + * + * @param string $data the XML that will be parsed + * + * @return string the encoding to be used + * + * @link http://php.net/xml_parser_create + * @since Method available since Release 1.2.0 + */ + function getEncoding($data) + { + global $XML_RPC_defencoding; + + if (preg_match('@<\?xml[^>]*\s*encoding\s*=\s*[\'"]([^"\']*)[\'"]@', + $data, $match)) + { + $match[1] = trim(strtoupper($match[1])); + switch ($match[1]) { + case 'ISO-8859-1': + case 'UTF-8': + case 'US-ASCII': + return $match[1]; + break; + + default: + return $XML_RPC_defencoding; + } + } else { + return $XML_RPC_defencoding; + } + } + + /** + * @return object a new XML_RPC_Response object + */ + function parseResponseFile($fp) + { + $ipd = ''; + while ($data = @fread($fp, 8192)) { + $ipd .= $data; + } + return $this->parseResponse($ipd); + } + + /** + * @return object a new XML_RPC_Response object + */ + function parseResponse($data = '') + { + global $XML_RPC_xh, $XML_RPC_err, $XML_RPC_str, $XML_RPC_defencoding; + + $encoding = $this->getEncoding($data); + $parser_resource = xml_parser_create($encoding); + $parser = (int) $parser_resource; + + $XML_RPC_xh = array(); + $XML_RPC_xh[$parser] = array(); + + $XML_RPC_xh[$parser]['cm'] = 0; + $XML_RPC_xh[$parser]['isf'] = 0; + $XML_RPC_xh[$parser]['ac'] = ''; + $XML_RPC_xh[$parser]['qt'] = ''; + $XML_RPC_xh[$parser]['stack'] = array(); + $XML_RPC_xh[$parser]['valuestack'] = array(); + + xml_parser_set_option($parser_resource, XML_OPTION_CASE_FOLDING, true); + xml_set_element_handler($parser_resource, 'XML_RPC_se', 'XML_RPC_ee'); + xml_set_character_data_handler($parser_resource, 'XML_RPC_cd'); + + $hdrfnd = 0; + if ($this->debug) { + print "\n
    ---GOT---\n";
    +            print isset($_SERVER['SERVER_PROTOCOL']) ? htmlspecialchars($data) : $data;
    +            print "\n---END---
    \n"; + } + + // See if response is a 200 or a 100 then a 200, else raise error. + // But only do this if we're using the HTTP protocol. + if (preg_match('@^HTTP@', $data) && + !preg_match('@^HTTP/[0-9\.]+ 200 @', $data) && + !preg_match('@^HTTP/[0-9\.]+ 10[0-9]([A-Z ]+)?[\r\n]+HTTP/[0-9\.]+ 200@', $data)) + { + $errstr = substr($data, 0, strpos($data, "\n") - 1); + error_log('HTTP error, got response: ' . $errstr); + $r = new XML_RPC_Response(0, $XML_RPC_err['http_error'], + $XML_RPC_str['http_error'] . ' (' . + $errstr . ')'); + xml_parser_free($parser_resource); + return $r; + } + + // gotta get rid of headers here + if (!$hdrfnd && ($brpos = strpos($data,"\r\n\r\n"))) { + $XML_RPC_xh[$parser]['ha'] = substr($data, 0, $brpos); + $data = substr($data, $brpos + 4); + $hdrfnd = 1; + } + + /* + * be tolerant of junk after methodResponse + * (e.g. javascript automatically inserted by free hosts) + * thanks to Luca Mariano + */ + $data = substr($data, 0, strpos($data, "") + 17); + $this->response_payload = $data; + + if (!xml_parse($parser_resource, $data, sizeof($data))) { + // thanks to Peter Kocks + if (xml_get_current_line_number($parser_resource) == 1) { + $errstr = 'XML error at line 1, check URL'; + } else { + $errstr = sprintf('XML error: %s at line %d', + xml_error_string(xml_get_error_code($parser_resource)), + xml_get_current_line_number($parser_resource)); + } + error_log($errstr); + $r = new XML_RPC_Response(0, $XML_RPC_err['invalid_return'], + $XML_RPC_str['invalid_return']); + xml_parser_free($parser_resource); + return $r; + } + + xml_parser_free($parser_resource); + + if ($this->debug) { + print "\n
    ---PARSED---\n";
    +            var_dump($XML_RPC_xh[$parser]['value']);
    +            print "---END---
    \n"; + } + + if ($XML_RPC_xh[$parser]['isf'] > 1) { + $r = new XML_RPC_Response(0, $XML_RPC_err['invalid_return'], + $XML_RPC_str['invalid_return'].' '.$XML_RPC_xh[$parser]['isf_reason']); + } elseif (!is_object($XML_RPC_xh[$parser]['value'])) { + // then something odd has happened + // and it's time to generate a client side error + // indicating something odd went on + $r = new XML_RPC_Response(0, $XML_RPC_err['invalid_return'], + $XML_RPC_str['invalid_return']); + } else { + $v = $XML_RPC_xh[$parser]['value']; + if ($XML_RPC_xh[$parser]['isf']) { + $f = $v->structmem('faultCode'); + $fs = $v->structmem('faultString'); + $r = new XML_RPC_Response($v, $f->scalarval(), + $fs->scalarval()); + } else { + $r = new XML_RPC_Response($v); + } + } + $r->hdrs = preg_split("@\r?\n@", $XML_RPC_xh[$parser]['ha'][1]); + return $r; + } +} + +/** + * The methods and properties that represent data in XML RPC format + * + * @category Web Services + * @package XML_RPC + * @author Edd Dumbill + * @author Stig Bakken + * @author Martin Jansen + * @author Daniel Convissor + * @copyright 1999-2001 Edd Dumbill, 2001-2010 The PHP Group + * @license http://www.php.net/license/3_01.txt PHP License + * @version Release: @package_version@ + * @link http://pear.php.net/package/XML_RPC + */ +class XML_RPC_Value extends XML_RPC_Base +{ + var $me = array(); + var $mytype = 0; + + /** + * @return void + */ + function XML_RPC_Value($val = -1, $type = '') + { + $this->me = array(); + $this->mytype = 0; + if ($val != -1 || $type != '') { + if ($type == '') { + $type = 'string'; + } + if (!array_key_exists($type, $GLOBALS['XML_RPC_Types'])) { + // XXX + // need some way to report this error + } elseif ($GLOBALS['XML_RPC_Types'][$type] == 1) { + $this->addScalar($val, $type); + } elseif ($GLOBALS['XML_RPC_Types'][$type] == 2) { + $this->addArray($val); + } elseif ($GLOBALS['XML_RPC_Types'][$type] == 3) { + $this->addStruct($val); + } + } + } + + /** + * @return int returns 1 if successful or 0 if there are problems + */ + function addScalar($val, $type = 'string') + { + if ($this->mytype == 1) { + $this->raiseError('Scalar can have only one value', + XML_RPC_ERROR_INVALID_TYPE); + return 0; + } + $typeof = $GLOBALS['XML_RPC_Types'][$type]; + if ($typeof != 1) { + $this->raiseError("Not a scalar type (${typeof})", + XML_RPC_ERROR_INVALID_TYPE); + return 0; + } + + if ($type == $GLOBALS['XML_RPC_Boolean']) { + if (strcasecmp($val, 'true') == 0 + || $val == 1 + || ($val == true && strcasecmp($val, 'false'))) + { + $val = 1; + } else { + $val = 0; + } + } + + if ($this->mytype == 2) { + // we're adding to an array here + $ar = $this->me['array']; + $ar[] = new XML_RPC_Value($val, $type); + $this->me['array'] = $ar; + } else { + // a scalar, so set the value and remember we're scalar + $this->me[$type] = $val; + $this->mytype = $typeof; + } + return 1; + } + + /** + * @return int returns 1 if successful or 0 if there are problems + */ + function addArray($vals) + { + if ($this->mytype != 0) { + $this->raiseError( + 'Already initialized as a [' . $this->kindOf() . ']', + XML_RPC_ERROR_ALREADY_INITIALIZED); + return 0; + } + $this->mytype = $GLOBALS['XML_RPC_Types']['array']; + $this->me['array'] = $vals; + return 1; + } + + /** + * @return int returns 1 if successful or 0 if there are problems + */ + function addStruct($vals) + { + if ($this->mytype != 0) { + $this->raiseError( + 'Already initialized as a [' . $this->kindOf() . ']', + XML_RPC_ERROR_ALREADY_INITIALIZED); + return 0; + } + $this->mytype = $GLOBALS['XML_RPC_Types']['struct']; + $this->me['struct'] = $vals; + return 1; + } + + /** + * @return void + */ + function dump($ar) + { + reset($ar); + foreach ($ar as $key => $val) { + echo "$key => $val
    "; + if ($key == 'array') { + foreach ($val as $key2 => $val2) { + echo "-- $key2 => $val2
    "; + } + } + } + } + + /** + * @return string the data type of the current value + */ + function kindOf() + { + switch ($this->mytype) { + case 3: + return 'struct'; + + case 2: + return 'array'; + + case 1: + return 'scalar'; + + default: + return 'undef'; + } + } + + /** + * @return string the data in XML format + */ + function serializedata($typ, $val) + { + $rs = ''; + if (!array_key_exists($typ, $GLOBALS['XML_RPC_Types'])) { + // XXX + // need some way to report this error + return; + } + switch ($GLOBALS['XML_RPC_Types'][$typ]) { + case 3: + // struct + $rs .= "\n"; + reset($val); + foreach ($val as $key2 => $val2) { + $rs .= "" . htmlspecialchars($key2) . "\n"; + $rs .= $this->serializeval($val2); + $rs .= "\n"; + } + $rs .= ''; + break; + + case 2: + // array + $rs .= "\n\n"; + foreach ($val as $value) { + $rs .= $this->serializeval($value); + } + $rs .= "\n"; + break; + + case 1: + switch ($typ) { + case $GLOBALS['XML_RPC_Base64']: + $rs .= "<${typ}>" . base64_encode($val) . ""; + break; + case $GLOBALS['XML_RPC_Boolean']: + $rs .= "<${typ}>" . ($val ? '1' : '0') . ""; + break; + case $GLOBALS['XML_RPC_String']: + $rs .= "<${typ}>" . htmlspecialchars($val). ""; + break; + default: + $rs .= "<${typ}>${val}"; + } + } + return $rs; + } + + /** + * @return string the data in XML format + */ + function serialize() + { + return $this->serializeval($this); + } + + /** + * @return string the data in XML format + */ + function serializeval($o) + { + if (!is_object($o) || empty($o->me) || !is_array($o->me)) { + return ''; + } + $ar = $o->me; + reset($ar); + list($typ, $val) = each($ar); + return '' . $this->serializedata($typ, $val) . "\n"; + } + + /** + * @return mixed the contents of the element requested + */ + function structmem($m) + { + return $this->me['struct'][$m]; + } + + /** + * @return void + */ + function structreset() + { + reset($this->me['struct']); + } + + /** + * @return the key/value pair of the struct's current element + */ + function structeach() + { + return each($this->me['struct']); + } + + /** + * @return mixed the current value + */ + function getval() + { + // UNSTABLE + + reset($this->me); + $b = current($this->me); + + // contributed by I Sofer, 2001-03-24 + // add support for nested arrays to scalarval + // i've created a new method here, so as to + // preserve back compatibility + + if (is_array($b)) { + foreach ($b as $id => $cont) { + $b[$id] = $cont->scalarval(); + } + } + + // add support for structures directly encoding php objects + if (is_object($b)) { + $t = get_object_vars($b); + foreach ($t as $id => $cont) { + $t[$id] = $cont->scalarval(); + } + foreach ($t as $id => $cont) { + $b->$id = $cont; + } + } + + // end contrib + return $b; + } + + /** + * @return mixed the current element's scalar value. If the value is + * not scalar, FALSE is returned. + */ + function scalarval() + { + reset($this->me); + $v = current($this->me); + if (!is_scalar($v)) { + $v = false; + } + return $v; + } + + /** + * @return string + */ + function scalartyp() + { + reset($this->me); + $a = key($this->me); + if ($a == $GLOBALS['XML_RPC_I4']) { + $a = $GLOBALS['XML_RPC_Int']; + } + return $a; + } + + /** + * @return mixed the struct's current element + */ + function arraymem($m) + { + return $this->me['array'][$m]; + } + + /** + * @return int the number of elements in the array + */ + function arraysize() + { + reset($this->me); + list($a, $b) = each($this->me); + return sizeof($b); + } + + /** + * Determines if the item submitted is an XML_RPC_Value object + * + * @param mixed $val the variable to be evaluated + * + * @return bool TRUE if the item is an XML_RPC_Value object + * + * @static + * @since Method available since Release 1.3.0 + */ + function isValue($val) + { + return (strtolower(get_class($val)) == 'xml_rpc_value'); + } +} + +/** + * Return an ISO8601 encoded string + * + * While timezones ought to be supported, the XML-RPC spec says: + * + * "Don't assume a timezone. It should be specified by the server in its + * documentation what assumptions it makes about timezones." + * + * This routine always assumes localtime unless $utc is set to 1, in which + * case UTC is assumed and an adjustment for locale is made when encoding. + * + * @return string the formatted date + */ +function XML_RPC_iso8601_encode($timet, $utc = 0) +{ + if (!$utc) { + $t = strftime('%Y%m%dT%H:%M:%S', $timet); + } else { + if (function_exists('gmstrftime')) { + // gmstrftime doesn't exist in some versions + // of PHP + $t = gmstrftime('%Y%m%dT%H:%M:%S', $timet); + } else { + $t = strftime('%Y%m%dT%H:%M:%S', $timet - date('Z')); + } + } + return $t; +} + +/** + * Convert a datetime string into a Unix timestamp + * + * While timezones ought to be supported, the XML-RPC spec says: + * + * "Don't assume a timezone. It should be specified by the server in its + * documentation what assumptions it makes about timezones." + * + * This routine always assumes localtime unless $utc is set to 1, in which + * case UTC is assumed and an adjustment for locale is made when encoding. + * + * @return int the unix timestamp of the date submitted + */ +function XML_RPC_iso8601_decode($idate, $utc = 0) +{ + $t = 0; + if (preg_match('@([0-9]{4})([0-9]{2})([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2})@', $idate, $regs)) { + if ($utc) { + $t = gmmktime($regs[4], $regs[5], $regs[6], $regs[2], $regs[3], $regs[1]); + } else { + $t = mktime($regs[4], $regs[5], $regs[6], $regs[2], $regs[3], $regs[1]); + } + } + return $t; +} + +/** + * Converts an XML_RPC_Value object into native PHP types + * + * @param object $XML_RPC_val the XML_RPC_Value object to decode + * + * @return mixed the PHP values + */ +function XML_RPC_decode($XML_RPC_val) +{ + $kind = $XML_RPC_val->kindOf(); + + if ($kind == 'scalar') { + return $XML_RPC_val->scalarval(); + + } elseif ($kind == 'array') { + $size = $XML_RPC_val->arraysize(); + $arr = array(); + for ($i = 0; $i < $size; $i++) { + $arr[] = XML_RPC_decode($XML_RPC_val->arraymem($i)); + } + return $arr; + + } elseif ($kind == 'struct') { + $XML_RPC_val->structreset(); + $arr = array(); + while (list($key, $value) = $XML_RPC_val->structeach()) { + $arr[$key] = XML_RPC_decode($value); + } + return $arr; + } +} + +/** + * Converts native PHP types into an XML_RPC_Value object + * + * @param mixed $php_val the PHP value or variable you want encoded + * + * @return object the XML_RPC_Value object + */ +function XML_RPC_encode($php_val) +{ + $type = gettype($php_val); + $XML_RPC_val = new XML_RPC_Value; + + switch ($type) { + case 'array': + if (empty($php_val)) { + $XML_RPC_val->addArray($php_val); + break; + } + $tmp = array_diff(array_keys($php_val), range(0, count($php_val)-1)); + if (empty($tmp)) { + $arr = array(); + foreach ($php_val as $k => $v) { + $arr[$k] = XML_RPC_encode($v); + } + $XML_RPC_val->addArray($arr); + break; + } + // fall though if it's not an enumerated array + + case 'object': + $arr = array(); + foreach ($php_val as $k => $v) { + $arr[$k] = XML_RPC_encode($v); + } + $XML_RPC_val->addStruct($arr); + break; + + case 'integer': + $XML_RPC_val->addScalar($php_val, $GLOBALS['XML_RPC_Int']); + break; + + case 'double': + $XML_RPC_val->addScalar($php_val, $GLOBALS['XML_RPC_Double']); + break; + + case 'string': + case 'NULL': + if (preg_match('@^[0-9]{8}\T{1}[0-9]{2}\:[0-9]{2}\:[0-9]{2}$@', $php_val)) { + $XML_RPC_val->addScalar($php_val, $GLOBALS['XML_RPC_DateTime']); + } elseif ($GLOBALS['XML_RPC_auto_base64'] + && preg_match("@[^ -~\t\r\n]@", $php_val)) + { + // Characters other than alpha-numeric, punctuation, SP, TAB, + // LF and CR break the XML parser, encode value via Base 64. + $XML_RPC_val->addScalar($php_val, $GLOBALS['XML_RPC_Base64']); + } else { + $XML_RPC_val->addScalar($php_val, $GLOBALS['XML_RPC_String']); + } + break; + + case 'boolean': + // Add support for encoding/decoding of booleans, since they + // are supported in PHP + // by + $XML_RPC_val->addScalar($php_val, $GLOBALS['XML_RPC_Boolean']); + break; + + case 'unknown type': + default: + $XML_RPC_val = false; + } + return $XML_RPC_val; +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * c-hanging-comment-ender-p: nil + * End: + */ + +?> diff --git a/library/pear/XML/RPC/Dump.php b/library/pear/XML/RPC/Dump.php new file mode 100644 index 000000000..9de00eb76 --- /dev/null +++ b/library/pear/XML/RPC/Dump.php @@ -0,0 +1,189 @@ + + * @license http://www.php.net/license/3_01.txt PHP License + * @version SVN: $Id: Dump.php 300962 2010-07-03 02:24:24Z danielc $ + * @link http://pear.php.net/package/XML_RPC + */ + + +/** + * Pull in the XML_RPC class + */ +require_once 'XML/RPC.php'; + + +/** + * Generates the dump of the XML_RPC_Value and echoes it + * + * @param object $value the XML_RPC_Value object to dump + * + * @return void + */ +function XML_RPC_Dump($value) +{ + $dumper = new XML_RPC_Dump(); + echo $dumper->generateDump($value); +} + + +/** + * Class which generates a dump of a XML_RPC_Value object + * + * @category Web Services + * @package XML_RPC + * @author Christian Weiske + * @license http://www.php.net/license/3_01.txt PHP License + * @version Release: @package_version@ + * @link http://pear.php.net/package/XML_RPC + */ +class XML_RPC_Dump +{ + /** + * The indentation array cache + * @var array + */ + var $arIndent = array(); + + /** + * The spaces used for indenting the XML + * @var string + */ + var $strBaseIndent = ' '; + + /** + * Returns the dump in XML format without printing it out + * + * @param object $value the XML_RPC_Value object to dump + * @param int $nLevel the level of indentation + * + * @return string the dump + */ + function generateDump($value, $nLevel = 0) + { + if (!is_object($value) || strtolower(get_class($value)) != 'xml_rpc_value') { + require_once 'PEAR.php'; + PEAR::raiseError('Tried to dump non-XML_RPC_Value variable' . "\r\n", + 0, PEAR_ERROR_PRINT); + if (is_object($value)) { + $strType = get_class($value); + } else { + $strType = gettype($value); + } + return $this->getIndent($nLevel) . 'NOT A XML_RPC_Value: ' + . $strType . "\r\n"; + } + + switch ($value->kindOf()) { + case 'struct': + $ret = $this->genStruct($value, $nLevel); + break; + case 'array': + $ret = $this->genArray($value, $nLevel); + break; + case 'scalar': + $ret = $this->genScalar($value->scalarval(), $nLevel); + break; + default: + require_once 'PEAR.php'; + PEAR::raiseError('Illegal type "' . $value->kindOf() + . '" in XML_RPC_Value' . "\r\n", 0, + PEAR_ERROR_PRINT); + } + + return $ret; + } + + /** + * Returns the scalar value dump + * + * @param object $value the scalar XML_RPC_Value object to dump + * @param int $nLevel the level of indentation + * + * @return string Dumped version of the scalar value + */ + function genScalar($value, $nLevel) + { + if (gettype($value) == 'object') { + $strClass = ' ' . get_class($value); + } else { + $strClass = ''; + } + return $this->getIndent($nLevel) . gettype($value) . $strClass + . ' ' . $value . "\r\n"; + } + + /** + * Returns the dump of a struct + * + * @param object $value the struct XML_RPC_Value object to dump + * @param int $nLevel the level of indentation + * + * @return string Dumped version of the scalar value + */ + function genStruct($value, $nLevel) + { + $value->structreset(); + $strOutput = $this->getIndent($nLevel) . 'struct' . "\r\n"; + while (list($key, $keyval) = $value->structeach()) { + $strOutput .= $this->getIndent($nLevel + 1) . $key . "\r\n"; + $strOutput .= $this->generateDump($keyval, $nLevel + 2); + } + return $strOutput; + } + + /** + * Returns the dump of an array + * + * @param object $value the array XML_RPC_Value object to dump + * @param int $nLevel the level of indentation + * + * @return string Dumped version of the scalar value + */ + function genArray($value, $nLevel) + { + $nSize = $value->arraysize(); + $strOutput = $this->getIndent($nLevel) . 'array' . "\r\n"; + for($nA = 0; $nA < $nSize; $nA++) { + $strOutput .= $this->getIndent($nLevel + 1) . $nA . "\r\n"; + $strOutput .= $this->generateDump($value->arraymem($nA), + $nLevel + 2); + } + return $strOutput; + } + + /** + * Returns the indent for a specific level and caches it for faster use + * + * @param int $nLevel the level + * + * @return string the indented string + */ + function getIndent($nLevel) + { + if (!isset($this->arIndent[$nLevel])) { + $this->arIndent[$nLevel] = str_repeat($this->strBaseIndent, $nLevel); + } + return $this->arIndent[$nLevel]; + } +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * c-hanging-comment-ender-p: nil + * End: + */ + +?> diff --git a/library/pear/XML/RPC/Server.php b/library/pear/XML/RPC/Server.php new file mode 100644 index 000000000..22673b399 --- /dev/null +++ b/library/pear/XML/RPC/Server.php @@ -0,0 +1,672 @@ + + * @author Stig Bakken + * @author Martin Jansen + * @author Daniel Convissor + * @copyright 1999-2001 Edd Dumbill, 2001-2010 The PHP Group + * @license http://www.php.net/license/3_01.txt PHP License + * @version SVN: $Id: Server.php 300961 2010-07-03 02:17:34Z danielc $ + * @link http://pear.php.net/package/XML_RPC + */ + + +/** + * Pull in the XML_RPC class + */ +require_once 'XML/RPC.php'; + + +/** + * signature for system.listMethods: return = array, + * parameters = a string or nothing + * @global array $GLOBALS['XML_RPC_Server_listMethods_sig'] + */ +$GLOBALS['XML_RPC_Server_listMethods_sig'] = array( + array($GLOBALS['XML_RPC_Array'], + $GLOBALS['XML_RPC_String'] + ), + array($GLOBALS['XML_RPC_Array']) +); + +/** + * docstring for system.listMethods + * @global string $GLOBALS['XML_RPC_Server_listMethods_doc'] + */ +$GLOBALS['XML_RPC_Server_listMethods_doc'] = 'This method lists all the' + . ' methods that the XML-RPC server knows how to dispatch'; + +/** + * signature for system.methodSignature: return = array, + * parameters = string + * @global array $GLOBALS['XML_RPC_Server_methodSignature_sig'] + */ +$GLOBALS['XML_RPC_Server_methodSignature_sig'] = array( + array($GLOBALS['XML_RPC_Array'], + $GLOBALS['XML_RPC_String'] + ) +); + +/** + * docstring for system.methodSignature + * @global string $GLOBALS['XML_RPC_Server_methodSignature_doc'] + */ +$GLOBALS['XML_RPC_Server_methodSignature_doc'] = 'Returns an array of known' + . ' signatures (an array of arrays) for the method name passed. If' + . ' no signatures are known, returns a none-array (test for type !=' + . ' array to detect missing signature)'; + +/** + * signature for system.methodHelp: return = string, + * parameters = string + * @global array $GLOBALS['XML_RPC_Server_methodHelp_sig'] + */ +$GLOBALS['XML_RPC_Server_methodHelp_sig'] = array( + array($GLOBALS['XML_RPC_String'], + $GLOBALS['XML_RPC_String'] + ) +); + +/** + * docstring for methodHelp + * @global string $GLOBALS['XML_RPC_Server_methodHelp_doc'] + */ +$GLOBALS['XML_RPC_Server_methodHelp_doc'] = 'Returns help text if defined' + . ' for the method passed, otherwise returns an empty string'; + +/** + * dispatch map for the automatically declared XML-RPC methods. + * @global array $GLOBALS['XML_RPC_Server_dmap'] + */ +$GLOBALS['XML_RPC_Server_dmap'] = array( + 'system.listMethods' => array( + 'function' => 'XML_RPC_Server_listMethods', + 'signature' => $GLOBALS['XML_RPC_Server_listMethods_sig'], + 'docstring' => $GLOBALS['XML_RPC_Server_listMethods_doc'] + ), + 'system.methodHelp' => array( + 'function' => 'XML_RPC_Server_methodHelp', + 'signature' => $GLOBALS['XML_RPC_Server_methodHelp_sig'], + 'docstring' => $GLOBALS['XML_RPC_Server_methodHelp_doc'] + ), + 'system.methodSignature' => array( + 'function' => 'XML_RPC_Server_methodSignature', + 'signature' => $GLOBALS['XML_RPC_Server_methodSignature_sig'], + 'docstring' => $GLOBALS['XML_RPC_Server_methodSignature_doc'] + ) +); + +/** + * @global string $GLOBALS['XML_RPC_Server_debuginfo'] + */ +$GLOBALS['XML_RPC_Server_debuginfo'] = ''; + + +/** + * Lists all the methods that the XML-RPC server knows how to dispatch + * + * @return object a new XML_RPC_Response object + */ +function XML_RPC_Server_listMethods($server, $m) +{ + global $XML_RPC_err, $XML_RPC_str, $XML_RPC_Server_dmap; + + $v = new XML_RPC_Value(); + $outAr = array(); + foreach ($server->dmap as $key => $val) { + $outAr[] = new XML_RPC_Value($key, 'string'); + } + foreach ($XML_RPC_Server_dmap as $key => $val) { + $outAr[] = new XML_RPC_Value($key, 'string'); + } + $v->addArray($outAr); + return new XML_RPC_Response($v); +} + +/** + * Returns an array of known signatures (an array of arrays) + * for the given method + * + * If no signatures are known, returns a none-array + * (test for type != array to detect missing signature) + * + * @return object a new XML_RPC_Response object + */ +function XML_RPC_Server_methodSignature($server, $m) +{ + global $XML_RPC_err, $XML_RPC_str, $XML_RPC_Server_dmap; + + $methName = $m->getParam(0); + $methName = $methName->scalarval(); + if (strpos($methName, 'system.') === 0) { + $dmap = $XML_RPC_Server_dmap; + $sysCall = 1; + } else { + $dmap = $server->dmap; + $sysCall = 0; + } + // print "\n"; + if (isset($dmap[$methName])) { + if ($dmap[$methName]['signature']) { + $sigs = array(); + $thesigs = $dmap[$methName]['signature']; + for ($i = 0; $i < sizeof($thesigs); $i++) { + $cursig = array(); + $inSig = $thesigs[$i]; + for ($j = 0; $j < sizeof($inSig); $j++) { + $cursig[] = new XML_RPC_Value($inSig[$j], 'string'); + } + $sigs[] = new XML_RPC_Value($cursig, 'array'); + } + $r = new XML_RPC_Response(new XML_RPC_Value($sigs, 'array')); + } else { + $r = new XML_RPC_Response(new XML_RPC_Value('undef', 'string')); + } + } else { + $r = new XML_RPC_Response(0, $XML_RPC_err['introspect_unknown'], + $XML_RPC_str['introspect_unknown']); + } + return $r; +} + +/** + * Returns help text if defined for the method passed, otherwise returns + * an empty string + * + * @return object a new XML_RPC_Response object + */ +function XML_RPC_Server_methodHelp($server, $m) +{ + global $XML_RPC_err, $XML_RPC_str, $XML_RPC_Server_dmap; + + $methName = $m->getParam(0); + $methName = $methName->scalarval(); + if (strpos($methName, 'system.') === 0) { + $dmap = $XML_RPC_Server_dmap; + $sysCall = 1; + } else { + $dmap = $server->dmap; + $sysCall = 0; + } + + if (isset($dmap[$methName])) { + if ($dmap[$methName]['docstring']) { + $r = new XML_RPC_Response(new XML_RPC_Value($dmap[$methName]['docstring']), + 'string'); + } else { + $r = new XML_RPC_Response(new XML_RPC_Value('', 'string')); + } + } else { + $r = new XML_RPC_Response(0, $XML_RPC_err['introspect_unknown'], + $XML_RPC_str['introspect_unknown']); + } + return $r; +} + +/** + * @return void + */ +function XML_RPC_Server_debugmsg($m) +{ + global $XML_RPC_Server_debuginfo; + $XML_RPC_Server_debuginfo = $XML_RPC_Server_debuginfo . $m . "\n"; +} + + +/** + * A server for receiving and replying to XML RPC requests + * + * + * $server = new XML_RPC_Server( + * array( + * 'isan8' => + * array( + * 'function' => 'is_8', + * 'signature' => + * array( + * array('boolean', 'int'), + * array('boolean', 'int', 'boolean'), + * array('boolean', 'string'), + * array('boolean', 'string', 'boolean'), + * ), + * 'docstring' => 'Is the value an 8?' + * ), + * ), + * 1, + * 0 + * ); + * + * + * @category Web Services + * @package XML_RPC + * @author Edd Dumbill + * @author Stig Bakken + * @author Martin Jansen + * @author Daniel Convissor + * @copyright 1999-2001 Edd Dumbill, 2001-2010 The PHP Group + * @license http://www.php.net/license/3_01.txt PHP License + * @version Release: @package_version@ + * @link http://pear.php.net/package/XML_RPC + */ +class XML_RPC_Server +{ + /** + * Should the payload's content be passed through mb_convert_encoding()? + * + * @see XML_RPC_Server::setConvertPayloadEncoding() + * @since Property available since Release 1.5.1 + * @var boolean + */ + var $convert_payload_encoding = false; + + /** + * The dispatch map, listing the methods this server provides. + * @var array + */ + var $dmap = array(); + + /** + * The present response's encoding + * @var string + * @see XML_RPC_Message::getEncoding() + */ + var $encoding = ''; + + /** + * Debug mode (0 = off, 1 = on) + * @var integer + */ + var $debug = 0; + + /** + * The response's HTTP headers + * @var string + */ + var $server_headers = ''; + + /** + * The response's XML payload + * @var string + */ + var $server_payload = ''; + + + /** + * Constructor for the XML_RPC_Server class + * + * @param array $dispMap the dispatch map. An associative array + * explaining each function. The keys of the main + * array are the procedure names used by the + * clients. The value is another associative array + * that contains up to three elements: + * + The 'function' element's value is the name + * of the function or method that gets called. + * To define a class' method: 'class::method'. + * + The 'signature' element (optional) is an + * array describing the return values and + * parameters + * + The 'docstring' element (optional) is a + * string describing what the method does + * @param int $serviceNow should the HTTP response be sent now? + * (1 = yes, 0 = no) + * @param int $debug should debug output be displayed? + * (1 = yes, 0 = no) + * + * @return void + */ + function XML_RPC_Server($dispMap, $serviceNow = 1, $debug = 0) + { + global $HTTP_RAW_POST_DATA; + + if ($debug) { + $this->debug = 1; + } else { + $this->debug = 0; + } + + $this->dmap = $dispMap; + + if ($serviceNow) { + $this->service(); + } else { + $this->createServerPayload(); + $this->createServerHeaders(); + } + } + + /** + * @return string the debug information if debug debug mode is on + */ + function serializeDebug() + { + global $XML_RPC_Server_debuginfo, $HTTP_RAW_POST_DATA; + + if ($this->debug) { + XML_RPC_Server_debugmsg('vvv POST DATA RECEIVED BY SERVER vvv' . "\n" + . $HTTP_RAW_POST_DATA + . "\n" . '^^^ END POST DATA ^^^'); + } + + if ($XML_RPC_Server_debuginfo != '') { + return "\n"; + } else { + return ''; + } + } + + /** + * Sets whether the payload's content gets passed through + * mb_convert_encoding() + * + * Returns PEAR_ERROR object if mb_convert_encoding() isn't available. + * + * @param int $in where 1 = on, 0 = off + * + * @return void + * + * @see XML_RPC_Message::getEncoding() + * @since Method available since Release 1.5.1 + */ + function setConvertPayloadEncoding($in) + { + if ($in && !function_exists('mb_convert_encoding')) { + return $this->raiseError('mb_convert_encoding() is not available', + XML_RPC_ERROR_PROGRAMMING); + } + $this->convert_payload_encoding = $in; + } + + /** + * Sends the response + * + * The encoding and content-type are determined by + * XML_RPC_Message::getEncoding() + * + * @return void + * + * @uses XML_RPC_Server::createServerPayload(), + * XML_RPC_Server::createServerHeaders() + */ + function service() + { + if (!$this->server_payload) { + $this->createServerPayload(); + } + if (!$this->server_headers) { + $this->createServerHeaders(); + } + + /* + * $server_headers needs to remain a string for compatibility with + * old scripts using this package, but PHP 4.4.2 no longer allows + * line breaks in header() calls. So, we split each header into + * an individual call. The initial replace handles the off chance + * that someone composed a single header with multiple lines, which + * the RFCs allow. + */ + $this->server_headers = preg_replace("@[\r\n]+[ \t]+@", + ' ', trim($this->server_headers)); + $headers = preg_split("@[\r\n]+@", $this->server_headers); + foreach ($headers as $header) + { + header($header); + } + + print $this->server_payload; + } + + /** + * Generates the payload and puts it in the $server_payload property + * + * If XML_RPC_Server::setConvertPayloadEncoding() was set to true, + * the payload gets passed through mb_convert_encoding() + * to ensure the payload matches the encoding set in the + * XML declaration. The encoding type can be manually set via + * XML_RPC_Message::setSendEncoding(). + * + * @return void + * + * @uses XML_RPC_Server::parseRequest(), XML_RPC_Server::$encoding, + * XML_RPC_Response::serialize(), XML_RPC_Server::serializeDebug() + * @see XML_RPC_Server::setConvertPayloadEncoding() + */ + function createServerPayload() + { + $r = $this->parseRequest(); + $this->server_payload = 'encoding . '"?>' . "\n" + . $this->serializeDebug() + . $r->serialize(); + if ($this->convert_payload_encoding) { + $this->server_payload = mb_convert_encoding($this->server_payload, + $this->encoding); + } + } + + /** + * Determines the HTTP headers and puts them in the $server_headers + * property + * + * @return boolean TRUE if okay, FALSE if $server_payload isn't set. + * + * @uses XML_RPC_Server::createServerPayload(), + * XML_RPC_Server::$server_headers + */ + function createServerHeaders() + { + if (!$this->server_payload) { + return false; + } + $this->server_headers = 'Content-Length: ' + . strlen($this->server_payload) . "\r\n" + . 'Content-Type: text/xml;' + . ' charset=' . $this->encoding; + return true; + } + + /** + * @return array + */ + function verifySignature($in, $sig) + { + for ($i = 0; $i < sizeof($sig); $i++) { + // check each possible signature in turn + $cursig = $sig[$i]; + if (sizeof($cursig) == $in->getNumParams() + 1) { + $itsOK = 1; + for ($n = 0; $n < $in->getNumParams(); $n++) { + $p = $in->getParam($n); + // print "\n"; + if ($p->kindOf() == 'scalar') { + $pt = $p->scalartyp(); + } else { + $pt = $p->kindOf(); + } + // $n+1 as first type of sig is return type + if ($pt != $cursig[$n+1]) { + $itsOK = 0; + $pno = $n+1; + $wanted = $cursig[$n+1]; + $got = $pt; + break; + } + } + if ($itsOK) { + return array(1); + } + } + } + if (isset($wanted)) { + return array(0, "Wanted ${wanted}, got ${got} at param ${pno}"); + } else { + $allowed = array(); + foreach ($sig as $val) { + end($val); + $allowed[] = key($val); + } + $allowed = array_unique($allowed); + $last = count($allowed) - 1; + if ($last > 0) { + $allowed[$last] = 'or ' . $allowed[$last]; + } + return array(0, + 'Signature permits ' . implode(', ', $allowed) + . ' parameters but the request had ' + . $in->getNumParams()); + } + } + + /** + * @return object a new XML_RPC_Response object + * + * @uses XML_RPC_Message::getEncoding(), XML_RPC_Server::$encoding + */ + function parseRequest($data = '') + { + global $XML_RPC_xh, $HTTP_RAW_POST_DATA, + $XML_RPC_err, $XML_RPC_str, $XML_RPC_errxml, + $XML_RPC_defencoding, $XML_RPC_Server_dmap; + + if ($data == '') { + $data = $HTTP_RAW_POST_DATA; + } + + $this->encoding = XML_RPC_Message::getEncoding($data); + $parser_resource = xml_parser_create($this->encoding); + $parser = (int) $parser_resource; + + $XML_RPC_xh[$parser] = array(); + $XML_RPC_xh[$parser]['cm'] = 0; + $XML_RPC_xh[$parser]['isf'] = 0; + $XML_RPC_xh[$parser]['params'] = array(); + $XML_RPC_xh[$parser]['method'] = ''; + $XML_RPC_xh[$parser]['stack'] = array(); + $XML_RPC_xh[$parser]['valuestack'] = array(); + + $plist = ''; + + // decompose incoming XML into request structure + + xml_parser_set_option($parser_resource, XML_OPTION_CASE_FOLDING, true); + xml_set_element_handler($parser_resource, 'XML_RPC_se', 'XML_RPC_ee'); + xml_set_character_data_handler($parser_resource, 'XML_RPC_cd'); + if (!xml_parse($parser_resource, $data, 1)) { + // return XML error as a faultCode + $r = new XML_RPC_Response(0, + $XML_RPC_errxml+xml_get_error_code($parser_resource), + sprintf('XML error: %s at line %d', + xml_error_string(xml_get_error_code($parser_resource)), + xml_get_current_line_number($parser_resource))); + xml_parser_free($parser_resource); + } elseif ($XML_RPC_xh[$parser]['isf']>1) { + $r = new XML_RPC_Response(0, + $XML_RPC_err['invalid_request'], + $XML_RPC_str['invalid_request'] + . ': ' + . $XML_RPC_xh[$parser]['isf_reason']); + xml_parser_free($parser_resource); + } else { + xml_parser_free($parser_resource); + $m = new XML_RPC_Message($XML_RPC_xh[$parser]['method']); + // now add parameters in + for ($i = 0; $i < sizeof($XML_RPC_xh[$parser]['params']); $i++) { + // print '\n"; + $plist .= "$i - " . var_export($XML_RPC_xh[$parser]['params'][$i], true) . " \n"; + $m->addParam($XML_RPC_xh[$parser]['params'][$i]); + } + + if ($this->debug) { + XML_RPC_Server_debugmsg($plist); + } + + // now to deal with the method + $methName = $XML_RPC_xh[$parser]['method']; + if (strpos($methName, 'system.') === 0) { + $dmap = $XML_RPC_Server_dmap; + $sysCall = 1; + } else { + $dmap = $this->dmap; + $sysCall = 0; + } + + if (isset($dmap[$methName]['function']) + && is_string($dmap[$methName]['function']) + && strpos($dmap[$methName]['function'], '::') !== false) + { + $dmap[$methName]['function'] = + explode('::', $dmap[$methName]['function']); + } + + if (isset($dmap[$methName]['function']) + && is_callable($dmap[$methName]['function'])) + { + // dispatch if exists + if (isset($dmap[$methName]['signature'])) { + $sr = $this->verifySignature($m, + $dmap[$methName]['signature'] ); + } + if (!isset($dmap[$methName]['signature']) || $sr[0]) { + // if no signature or correct signature + if ($sysCall) { + $r = call_user_func($dmap[$methName]['function'], $this, $m); + } else { + $r = call_user_func($dmap[$methName]['function'], $m); + } + if (!is_a($r, 'XML_RPC_Response')) { + $r = new XML_RPC_Response(0, $XML_RPC_err['not_response_object'], + $XML_RPC_str['not_response_object']); + } + } else { + $r = new XML_RPC_Response(0, $XML_RPC_err['incorrect_params'], + $XML_RPC_str['incorrect_params'] + . ': ' . $sr[1]); + } + } else { + // else prepare error response + $r = new XML_RPC_Response(0, $XML_RPC_err['unknown_method'], + $XML_RPC_str['unknown_method']); + } + } + return $r; + } + + /** + * Echos back the input packet as a string value + * + * @return void + * + * Useful for debugging. + */ + function echoInput() + { + global $HTTP_RAW_POST_DATA; + + $r = new XML_RPC_Response(0); + $r->xv = new XML_RPC_Value("'Aha said I: '" . $HTTP_RAW_POST_DATA, 'string'); + print $r->serialize(); + } +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * c-hanging-comment-ender-p: nil + * End: + */ + +?> diff --git a/library/pear/XML/Serializer.php b/library/pear/XML/Serializer.php new file mode 100644 index 000000000..30442c7fb --- /dev/null +++ b/library/pear/XML/Serializer.php @@ -0,0 +1,1255 @@ + + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * @category XML + * @package XML_Serializer + * @author Stephan Schmidt + * @copyright 2003-2008 Stephan Schmidt + * @license http://opensource.org/licenses/bsd-license New BSD License + * @version CVS: $Id: Serializer.php,v 1.61 2009/06/12 02:11:44 ashnazg Exp $ + * @link http://pear.php.net/package/XML_Serializer + * @see XML_Unserializer + */ + +/** + * uses PEAR error management + */ +require_once 'PEAR.php'; + +/** + * uses XML_Util to create XML tags + */ +require_once 'XML/Util.php'; + +/** + * option: string used for indentation + * + * Possible values: + * - any string (default is any string) + */ +define('XML_SERIALIZER_OPTION_INDENT', 'indent'); + +/** + * option: string used for linebreaks + * + * Possible values: + * - any string (default is \n) + */ +define('XML_SERIALIZER_OPTION_LINEBREAKS', 'linebreak'); + +/** + * option: enable type hints + * + * Possible values: + * - true + * - false + */ +define('XML_SERIALIZER_OPTION_TYPEHINTS', 'typeHints'); + +/** + * option: add an XML declaration + * + * Possible values: + * - true + * - false + */ +define('XML_SERIALIZER_OPTION_XML_DECL_ENABLED', 'addDecl'); + +/** + * option: encoding of the document + * + * Possible values: + * - any valid encoding + * - null (default) + */ +define('XML_SERIALIZER_OPTION_XML_ENCODING', 'encoding'); + +/** + * option: default name for tags + * + * Possible values: + * - any string (XML_Serializer_Tag is default) + */ +define('XML_SERIALIZER_OPTION_DEFAULT_TAG', 'defaultTagName'); + +/** + * option: use classname for objects in indexed arrays + * + * Possible values: + * - true + * - false (default) + */ +define('XML_SERIALIZER_OPTION_CLASSNAME_AS_TAGNAME', 'classAsTagName'); + +/** + * option: attribute where original key is stored + * + * Possible values: + * - any string (default is _originalKey) + */ +define('XML_SERIALIZER_OPTION_ATTRIBUTE_KEY', 'keyAttribute'); + +/** + * option: attribute for type (only if typeHints => true) + * + * Possible values: + * - any string (default is _type) + */ +define('XML_SERIALIZER_OPTION_ATTRIBUTE_TYPE', 'typeAttribute'); + +/** + * option: attribute for class (only if typeHints => true) + * + * Possible values: + * - any string (default is _class) + */ +define('XML_SERIALIZER_OPTION_ATTRIBUTE_CLASS', 'classAttribute'); + +/** + * option: scalar values (strings, ints,..) will be serialized as attribute + * + * Possible values: + * - true + * - false (default) + * - array which sets this option on a per-tag basis + */ +define('XML_SERIALIZER_OPTION_SCALAR_AS_ATTRIBUTES', 'scalarAsAttributes'); + +/** + * option: prepend string for attributes + * + * Possible values: + * - any string (default is any string) + */ +define('XML_SERIALIZER_OPTION_PREPEND_ATTRIBUTES', 'prependAttributes'); + +/** + * option: indent the attributes, if set to '_auto', + * it will indent attributes so they all start at the same column + * + * Possible values: + * - true + * - false (default) + * - '_auto' + */ +define('XML_SERIALIZER_OPTION_INDENT_ATTRIBUTES', 'indentAttributes'); + +/** + * option: use 'simplexml' to use parent name as tagname + * if transforming an indexed array + * + * Possible values: + * - XML_SERIALIZER_MODE_DEFAULT (default) + * - XML_SERIALIZER_MODE_SIMPLEXML + */ +define('XML_SERIALIZER_OPTION_MODE', 'mode'); + +/** + * option: add a doctype declaration + * + * Possible values: + * - true + * - false (default) + */ +define('XML_SERIALIZER_OPTION_DOCTYPE_ENABLED', 'addDoctype'); + +/** + * option: supply a string or an array with id and uri + * ({@see XML_Util::getDoctypeDeclaration()} + * + * Possible values: + * - string + * - array + */ +define('XML_SERIALIZER_OPTION_DOCTYPE', 'doctype'); + +/** + * option: name of the root tag + * + * Possible values: + * - string + * - null (default) + */ +define('XML_SERIALIZER_OPTION_ROOT_NAME', 'rootName'); + +/** + * option: attributes of the root tag + * + * Possible values: + * - array + */ +define('XML_SERIALIZER_OPTION_ROOT_ATTRIBS', 'rootAttributes'); + +/** + * option: all values in this key will be treated as attributes + * + * Possible values: + * - string + */ +define('XML_SERIALIZER_OPTION_ATTRIBUTES_KEY', 'attributesArray'); + +/** + * option: this value will be used directly as content, + * instead of creating a new tag, may only be used + * in conjuction with attributesArray + * + * Possible values: + * - string + * - null (default) + */ +define('XML_SERIALIZER_OPTION_CONTENT_KEY', 'contentName'); + +/** + * option: this value will be used in a comment, instead of creating a new tag + * + * Possible values: + * - string + * - null (default) + */ +define('XML_SERIALIZER_OPTION_COMMENT_KEY', 'commentName'); + +/** + * option: tag names that will be changed + * + * Possible values: + * - array + */ +define('XML_SERIALIZER_OPTION_TAGMAP', 'tagMap'); + +/** + * option: function that will be applied before serializing + * + * Possible values: + * - any valid PHP callback + */ +define('XML_SERIALIZER_OPTION_ENCODE_FUNC', 'encodeFunction'); + +/** + * option: namespace to use for the document + * + * Possible values: + * - string + * - null (default) + */ +define('XML_SERIALIZER_OPTION_NAMESPACE', 'namespace'); + +/** + * option: type of entities to replace + * + * Possible values: + * - XML_SERIALIZER_ENTITIES_NONE + * - XML_SERIALIZER_ENTITIES_XML (default) + * - XML_SERIALIZER_ENTITIES_XML_REQUIRED + * - XML_SERIALIZER_ENTITIES_HTML + */ +define('XML_SERIALIZER_OPTION_ENTITIES', 'replaceEntities'); + +/** + * option: whether to return the result of the serialization from serialize() + * + * Possible values: + * - true + * - false (default) + */ +define('XML_SERIALIZER_OPTION_RETURN_RESULT', 'returnResult'); + +/** + * option: whether to ignore properties that are set to null + * + * Possible values: + * - true + * - false (default) + */ +define('XML_SERIALIZER_OPTION_IGNORE_NULL', 'ignoreNull'); + +/** + * option: whether to use cdata sections for character data + * + * Possible values: + * - true + * - false (default) + */ +define('XML_SERIALIZER_OPTION_CDATA_SECTIONS', 'cdata'); + +/** + * option: whether a boolean FALSE value should become a string + * + * Possible values: + * - true + * - false (default) + * + * @since 0.20.0 + */ +define('XML_SERIALIZER_OPTION_FALSE_AS_STRING', 'falseAsString'); + +/** + * default mode + */ +define('XML_SERIALIZER_MODE_DEFAULT', 'default'); + +/** + * SimpleXML mode + * + * When serializing indexed arrays, the key of the parent value is used as a tagname. + */ +define('XML_SERIALIZER_MODE_SIMPLEXML', 'simplexml'); + +/** + * error code for no serialization done + */ +define('XML_SERIALIZER_ERROR_NO_SERIALIZATION', 51); + +/** + * do not replace entitites + */ +define('XML_SERIALIZER_ENTITIES_NONE', XML_UTIL_ENTITIES_NONE); + +/** + * replace all XML entitites + * This setting will replace <, >, ", ' and & + */ +define('XML_SERIALIZER_ENTITIES_XML', XML_UTIL_ENTITIES_XML); + +/** + * replace only required XML entitites + * This setting will replace <, " and & + */ +define('XML_SERIALIZER_ENTITIES_XML_REQUIRED', XML_UTIL_ENTITIES_XML_REQUIRED); + +/** + * replace HTML entitites + * @link http://www.php.net/htmlentities + */ +define('XML_SERIALIZER_ENTITIES_HTML', XML_UTIL_ENTITIES_HTML); + +/** + * Creates XML documents from PHP data structures like arrays, objects or scalars. + * + * This class can be used in two modes: + * + * 1. Create an XML document from an array or object that is processed by other + * applications. That means you can create an RDF document from an array in the + * following format: + * + * $data = array( + * 'channel' => array( + * 'title' => 'Example RDF channel', + * 'link' => 'http://www.php-tools.de', + * 'image' => array( + * 'title' => 'Example image', + * 'url' => 'http://www.php-tools.de/image.gif', + * 'link' => 'http://www.php-tools.de' + * ), + * array( + * 'title' => 'Example item', + * 'link' => 'http://example.com' + * ), + * array( + * 'title' => 'Another Example item', + * 'link' => 'http://example.org' + * ) + * ) + * ); + * + * + * To create an RDF document from this array, do the following: + * + * + * require_once 'XML/Serializer.php'; + * $options = array( + * XML_SERIALIZER_OPTION_INDENT => "\t", // indent with tabs + * XML_SERIALIZER_OPTION_LINEBREAKS => "\n", // use UNIX line breaks + * XML_SERIALIZER_OPTION_ROOT_NAME => 'rdf:RDF',// root tag + * XML_SERIALIZER_OPTION_DEFAULT_TAG => 'item' // tag for values + * // with numeric keys + * ); + * $serializer = new XML_Serializer($options); + * $rdf = $serializer->serialize($data); + * + * + * You will get a complete XML document that can be processed like any RDF document. + * + * 2. This class can be used to serialize any data structure in a way that it can + * later be unserialized again. XML_Serializer will store the type of the value + * and additional meta information in attributes of the surrounding tag. This + * meta information can later be used to restore the original data structure + * in PHP. If you want XML_Serializer to add meta information to the tags, add + * + * XML_SERIALIZER_OPTION_TYPEHINTS => true + * + * to the options array in the constructor. + * + * @category XML + * @package XML_Serializer + * @author Stephan Schmidt + * @copyright 2003-2008 Stephan Schmidt + * @license http://opensource.org/licenses/bsd-license New BSD License + * @version Release: 0.20.0 + * @link http://pear.php.net/package/XML_Serializer + * @see XML_Unserializer + * @todo replace extending PEAR class with instead using a PEAR_Error object + */ +class XML_Serializer extends PEAR +{ + /** + * List of all available options + * + * @access private + * @var array + */ + var $_knownOptions = array( + XML_SERIALIZER_OPTION_INDENT, + XML_SERIALIZER_OPTION_LINEBREAKS, + XML_SERIALIZER_OPTION_TYPEHINTS, + XML_SERIALIZER_OPTION_XML_DECL_ENABLED, + XML_SERIALIZER_OPTION_XML_ENCODING, + XML_SERIALIZER_OPTION_DEFAULT_TAG, + XML_SERIALIZER_OPTION_CLASSNAME_AS_TAGNAME, + XML_SERIALIZER_OPTION_ATTRIBUTE_KEY, + XML_SERIALIZER_OPTION_ATTRIBUTE_TYPE, + XML_SERIALIZER_OPTION_ATTRIBUTE_CLASS, + XML_SERIALIZER_OPTION_SCALAR_AS_ATTRIBUTES, + XML_SERIALIZER_OPTION_PREPEND_ATTRIBUTES, + XML_SERIALIZER_OPTION_INDENT_ATTRIBUTES, + XML_SERIALIZER_OPTION_MODE, + XML_SERIALIZER_OPTION_DOCTYPE_ENABLED, + XML_SERIALIZER_OPTION_DOCTYPE, + XML_SERIALIZER_OPTION_ROOT_NAME, + XML_SERIALIZER_OPTION_ROOT_ATTRIBS, + XML_SERIALIZER_OPTION_ATTRIBUTES_KEY, + XML_SERIALIZER_OPTION_CONTENT_KEY, + XML_SERIALIZER_OPTION_COMMENT_KEY, + XML_SERIALIZER_OPTION_TAGMAP, + XML_SERIALIZER_OPTION_ENCODE_FUNC, + XML_SERIALIZER_OPTION_NAMESPACE, + XML_SERIALIZER_OPTION_ENTITIES, + XML_SERIALIZER_OPTION_RETURN_RESULT, + XML_SERIALIZER_OPTION_IGNORE_NULL, + XML_SERIALIZER_OPTION_CDATA_SECTIONS, + ); + + /** + * Default options for the serialization + * + * @access private + * @var array + */ + var $_defaultOptions = array( + + // string used for indentation + XML_SERIALIZER_OPTION_INDENT => '', + + // string used for newlines + XML_SERIALIZER_OPTION_LINEBREAKS => "\n", + + // automatically add type hin attributes + XML_SERIALIZER_OPTION_TYPEHINTS => false, + + // add an XML declaration + XML_SERIALIZER_OPTION_XML_DECL_ENABLED => false, + + // encoding specified in the XML declaration + XML_SERIALIZER_OPTION_XML_ENCODING => null, + + // tag used for indexed arrays or invalid names + XML_SERIALIZER_OPTION_DEFAULT_TAG => 'XML_Serializer_Tag', + + // use classname for objects in indexed arrays + XML_SERIALIZER_OPTION_CLASSNAME_AS_TAGNAME => false, + + // attribute where original key is stored + XML_SERIALIZER_OPTION_ATTRIBUTE_KEY => '_originalKey', + + // attribute for type (only if typeHints => true) + XML_SERIALIZER_OPTION_ATTRIBUTE_TYPE => '_type', + + // attribute for class of objects (only if typeHints => true) + XML_SERIALIZER_OPTION_ATTRIBUTE_CLASS => '_class', + + // scalar values (strings, ints,..) will be serialized as attribute + XML_SERIALIZER_OPTION_SCALAR_AS_ATTRIBUTES => false, + + // prepend string for attributes + XML_SERIALIZER_OPTION_PREPEND_ATTRIBUTES => '', + + // indent the attributes, if set to '_auto', + // it will indent attributes so they all start at the same column + XML_SERIALIZER_OPTION_INDENT_ATTRIBUTES => false, + + // use XML_SERIALIZER_MODE_SIMPLEXML to use parent name as tagname + // if transforming an indexed array + XML_SERIALIZER_OPTION_MODE => XML_SERIALIZER_MODE_DEFAULT, + + // add a doctype declaration + XML_SERIALIZER_OPTION_DOCTYPE_ENABLED => false, + + // supply a string or an array with id and uri + // ({@see XML_Util::getDoctypeDeclaration()} + XML_SERIALIZER_OPTION_DOCTYPE => null, + + // name of the root tag + XML_SERIALIZER_OPTION_ROOT_NAME => null, + + // attributes of the root tag + XML_SERIALIZER_OPTION_ROOT_ATTRIBS => array(), + + // all values in this key will be treated as attributes + XML_SERIALIZER_OPTION_ATTRIBUTES_KEY => null, + + // this value will be used directly as content, + // instead of creating a new tag, may only be used + // in conjuction with attributesArray + XML_SERIALIZER_OPTION_CONTENT_KEY => null, + + // this value will be used directly as comment, + // instead of creating a new tag, may only be used + // in conjuction with attributesArray + XML_SERIALIZER_OPTION_COMMENT_KEY => null, + + // tag names that will be changed + XML_SERIALIZER_OPTION_TAGMAP => array(), + + // function that will be applied before serializing + XML_SERIALIZER_OPTION_ENCODE_FUNC => null, + + // namespace to use + XML_SERIALIZER_OPTION_NAMESPACE => null, + + // type of entities to replace, + XML_SERIALIZER_OPTION_ENTITIES => XML_SERIALIZER_ENTITIES_XML, + + // serialize() returns the result of the serialization instead of true + XML_SERIALIZER_OPTION_RETURN_RESULT => false, + + // ignore properties that are set to null + XML_SERIALIZER_OPTION_IGNORE_NULL => false, + + // Whether to use cdata sections for plain character data + XML_SERIALIZER_OPTION_CDATA_SECTIONS => false, + + // Whether to convert a boolean FALSE into a string + XML_SERIALIZER_OPTION_FALSE_AS_STRING => false, + ); + + /** + * Options for the serialization + * + * @access public + * @var array + */ + var $options = array(); + + /** + * Current tag depth + * + * @access private + * @var integer + */ + var $_tagDepth = 0; + + /** + * Serialized representation of the data + * + * @access private + * @var string + */ + var $_serializedData = null; + + /** + * Constructor + * + * @param mixed $options array containing options for the serialization + * + * @access public + */ + function XML_Serializer( $options = null ) + { + $this->PEAR(); + if (is_array($options)) { + $this->options = array_merge($this->_defaultOptions, $options); + } else { + $this->options = $this->_defaultOptions; + } + } + + /** + * Return the package version number + * + * @access public + * @static + * @return string the version number of XML_Serializer + */ + function apiVersion() + { + return '0.20.0'; + } + + /** + * Reset all options to default options + * + * @return void + * @access public + */ + function resetOptions() + { + $this->options = $this->_defaultOptions; + } + + /** + * Set an option + * + * You can use this method if you do not want + * to set all options in the constructor. + * + * @param string $name option name + * @param mixed $value option value + * + * @return void + * @access public + */ + function setOption($name, $value) + { + $this->options[$name] = $value; + } + + /** + * Sets several options at once + * + * You can use this method if you do not want + * to set all options in the constructor. + * + * @param array $options options array + * + * @return void + * @access public + */ + function setOptions($options) + { + $this->options = array_merge($this->options, $options); + } + + /** + * serialize data + * + * @param mixed $data data to serialize + * @param array $options options array + * + * @return boolean true on success, pear error on failure + * @access public + * @uses XML_Util::getDoctypeDeclaration() + * @uses XML_Util::getXMLDeclaration() + * @internal uses error suppression "@settype()" + */ + function serialize($data, $options = null) + { + // if options have been specified, use them instead + // of the previously defined ones + if (is_array($options)) { + $optionsBak = $this->options; + if (isset($options['overrideOptions']) + && $options['overrideOptions'] == true + ) { + $this->options = array_merge($this->_defaultOptions, $options); + } else { + $this->options = array_merge($this->options, $options); + } + } else { + $optionsBak = null; + } + + // start depth is zero + $this->_tagDepth = 0; + + $rootAttributes = $this->options[XML_SERIALIZER_OPTION_ROOT_ATTRIBS]; + if (isset($this->options[XML_SERIALIZER_OPTION_NAMESPACE]) + && is_array($this->options[XML_SERIALIZER_OPTION_NAMESPACE]) + ) { + $rootAttributes['xmlns:' + . $this->options[XML_SERIALIZER_OPTION_NAMESPACE][0]] = + $this->options[XML_SERIALIZER_OPTION_NAMESPACE][1]; + } + + $this->_serializedData = ''; + // serialize an array + if (is_array($data)) { + if (isset($this->options[XML_SERIALIZER_OPTION_ROOT_NAME])) { + $tagName = $this->options[XML_SERIALIZER_OPTION_ROOT_NAME]; + } else { + $tagName = 'array'; + } + + $this->_serializedData .= + $this->_serializeArray($data, $tagName, $rootAttributes); + } elseif (is_object($data)) { + // serialize an object + if (isset($this->options[XML_SERIALIZER_OPTION_ROOT_NAME])) { + $tagName = $this->options[XML_SERIALIZER_OPTION_ROOT_NAME]; + } else { + $tagName = get_class($data); + } + $this->_serializedData .= + $this->_serializeObject($data, $tagName, $rootAttributes); + } else { + $tag = array(); + if (isset($this->options[XML_SERIALIZER_OPTION_ROOT_NAME])) { + $tag['qname'] = $this->options[XML_SERIALIZER_OPTION_ROOT_NAME]; + } else { + $tag['qname'] = gettype($data); + } + $tagName = $tag['qname']; + if ($this->options[XML_SERIALIZER_OPTION_TYPEHINTS] === true) { + $rootAttributes[$this-> + options[XML_SERIALIZER_OPTION_ATTRIBUTE_TYPE]] = gettype($data); + } + + if (!is_bool($data)) { + $tag['content'] = $data; + } elseif ($data === false) { + if ($this->options[XML_SERIALIZER_OPTION_FALSE_AS_STRING] === true) { + $tag['content'] = '0'; + } else { + $tag['content'] = ''; + } + } else { + $tag['content'] = $data; + } + + @settype($data, 'string'); + $tag['attributes'] = $rootAttributes; + $this->_serializedData = $this->_createXMLTag($tag); + } + + // add doctype declaration + if ($this->options[XML_SERIALIZER_OPTION_DOCTYPE_ENABLED] === true) { + $this->_serializedData = + XML_Util::getDoctypeDeclaration($tagName, + $this->options[XML_SERIALIZER_OPTION_DOCTYPE]) + . $this->options[XML_SERIALIZER_OPTION_LINEBREAKS] + . $this->_serializedData; + } + + // build xml declaration + if ($this->options[XML_SERIALIZER_OPTION_XML_DECL_ENABLED]) { + $atts = array(); + $this->_serializedData = XML_Util::getXMLDeclaration('1.0', + $this->options[XML_SERIALIZER_OPTION_XML_ENCODING]) + . $this->options[XML_SERIALIZER_OPTION_LINEBREAKS] + . $this->_serializedData; + } + + if ($this->options[XML_SERIALIZER_OPTION_RETURN_RESULT] === true) { + $result = $this->_serializedData; + } else { + $result = true; + } + + if ($optionsBak !== null) { + $this->options = $optionsBak; + } + + return $result; + } + + /** + * get the result of the serialization + * + * @access public + * @return string serialized XML + */ + function getSerializedData() + { + if ($this->_serializedData == null) { + return $this->raiseError('No serialized data available. ' + . 'Use XML_Serializer::serialize() first.', + XML_SERIALIZER_ERROR_NO_SERIALIZATION); + } + return $this->_serializedData; + } + + /** + * serialize any value + * + * This method checks for the type of the value and calls the appropriate method + * + * @param mixed $value tag value + * @param string $tagName tag name + * @param array $attributes attributes + * + * @return string + * @access private + */ + function _serializeValue($value, $tagName = null, $attributes = array()) + { + if (is_array($value)) { + $xml = $this->_serializeArray($value, $tagName, $attributes); + } elseif (is_object($value)) { + $xml = $this->_serializeObject($value, $tagName); + } else { + $tag = array( + 'qname' => $tagName, + 'attributes' => $attributes, + 'content' => $value + ); + $xml = $this->_createXMLTag($tag); + } + return $xml; + } + + /** + * serialize an array + * + * @param array &$array array to serialize + * @param string $tagName name of the root tag + * @param array $attributes attributes for the root tag + * + * @return string $string serialized data + * @access private + * @uses XML_Util::isValidName() to check, whether key has to be substituted + * @uses XML_Util::replaceEntities() + * @uses XML_Util::createComment() + * @uses PEAR::popExpect() + * @uses PEAR::expectError() + */ + function _serializeArray(&$array, $tagName = null, $attributes = array()) + { + $_content = null; + $_comment = null; + + // check for comment + if ($this->options[XML_SERIALIZER_OPTION_COMMENT_KEY] !== null) { + if (isset($array[$this->options[XML_SERIALIZER_OPTION_COMMENT_KEY]]) + ) { + $_comment = + $array[$this->options[XML_SERIALIZER_OPTION_COMMENT_KEY]]; + unset($array[$this->options[XML_SERIALIZER_OPTION_COMMENT_KEY]]); + } + } + + /** + * check for special attributes + */ + if ($this->options[XML_SERIALIZER_OPTION_ATTRIBUTES_KEY] !== null) { + if (isset($array[$this->options[XML_SERIALIZER_OPTION_ATTRIBUTES_KEY]]) + ) { + $attributes = + $array[$this->options[XML_SERIALIZER_OPTION_ATTRIBUTES_KEY]]; + unset($array[$this->options[XML_SERIALIZER_OPTION_ATTRIBUTES_KEY]]); + } + /** + * check for special content + */ + if ($this->options[XML_SERIALIZER_OPTION_CONTENT_KEY] !== null) { + if (isset($array[$this->options[XML_SERIALIZER_OPTION_CONTENT_KEY]]) + ) { + $_content = + XML_Util::replaceEntities($array + [$this->options[XML_SERIALIZER_OPTION_CONTENT_KEY]]); + unset($array[$this->options[XML_SERIALIZER_OPTION_CONTENT_KEY]]); + } + } + } + + if ($this->options[XML_SERIALIZER_OPTION_IGNORE_NULL] === true) { + foreach (array_keys($array) as $key) { + if (is_null($array[$key])) { + unset($array[$key]); + } + } + } + + /* + * if mode is set to simpleXML, check whether + * the array is associative or indexed + */ + if (is_array($array) && !empty($array) + && $this->options[XML_SERIALIZER_OPTION_MODE] + == XML_SERIALIZER_MODE_SIMPLEXML + ) { + $indexed = true; + foreach ($array as $key => $val) { + if (!is_int($key)) { + $indexed = false; + break; + } + } + + if ($indexed + && $this->options[XML_SERIALIZER_OPTION_MODE] + == XML_SERIALIZER_MODE_SIMPLEXML + ) { + $string = ''; + foreach ($array as $key => $val) { + $string .= $this->_serializeValue($val, $tagName, $attributes); + + $string .= $this->options[XML_SERIALIZER_OPTION_LINEBREAKS]; + // do indentation + if ($this->options[XML_SERIALIZER_OPTION_INDENT]!==null + && $this->_tagDepth>0 + ) { + $string .= + str_repeat($this->options[XML_SERIALIZER_OPTION_INDENT], + $this->_tagDepth); + } + } + return rtrim($string); + } + } + + $scalarAsAttributes = false; + if (is_array($this->options[XML_SERIALIZER_OPTION_SCALAR_AS_ATTRIBUTES]) + && isset($this->options[XML_SERIALIZER_OPTION_SCALAR_AS_ATTRIBUTES] + [$tagName]) + ) { + $scalarAsAttributes = + $this->options[XML_SERIALIZER_OPTION_SCALAR_AS_ATTRIBUTES][$tagName]; + } elseif ($this->options[XML_SERIALIZER_OPTION_SCALAR_AS_ATTRIBUTES] === true + ) { + $scalarAsAttributes = true; + } + + if ($scalarAsAttributes === true) { + $this->expectError('*'); + foreach ($array as $key => $value) { + if (is_scalar($value) && (XML_Util::isValidName($key) === true)) { + unset($array[$key]); + $attributes[$this->options + [XML_SERIALIZER_OPTION_PREPEND_ATTRIBUTES].$key] = $value; + } + } + $this->popExpect(); + } elseif (is_array($scalarAsAttributes)) { + $this->expectError('*'); + foreach ($scalarAsAttributes as $key) { + if (!isset($array[$key])) { + continue; + } + $value = $array[$key]; + if (is_scalar($value) && (XML_Util::isValidName($key) === true)) { + unset($array[$key]); + $attributes[$this->options + [XML_SERIALIZER_OPTION_PREPEND_ATTRIBUTES].$key] = $value; + } + } + $this->popExpect(); + } + + // check for empty array => create empty tag + if (empty($array)) { + $tag = array( + 'qname' => $tagName, + 'content' => $_content, + 'attributes' => $attributes + ); + } else { + $this->_tagDepth++; + $tmp = $_content . $this->options[XML_SERIALIZER_OPTION_LINEBREAKS]; + foreach ($array as $key => $value) { + // do indentation + if ($this->options[XML_SERIALIZER_OPTION_INDENT]!==null + && $this->_tagDepth>0 + ) { + $tmp .= str_repeat($this->options[XML_SERIALIZER_OPTION_INDENT], + $this->_tagDepth); + } + + // copy key + $origKey = $key; + $this->expectError('*'); + // key cannot be used as tagname => use default tag + $valid = XML_Util::isValidName($key); + $this->popExpect(); + if (PEAR::isError($valid)) { + if ($this->options[XML_SERIALIZER_OPTION_CLASSNAME_AS_TAGNAME] + && is_object($value) + ) { + $key = get_class($value); + } else { + $key = $this->_getDefaultTagname($tagName); + } + } + + // once we've established the true $key, is there a tagmap for it? + if (isset($this->options[XML_SERIALIZER_OPTION_TAGMAP][$key])) { + $key = $this->options[XML_SERIALIZER_OPTION_TAGMAP][$key]; + } + + $atts = array(); + if ($this->options[XML_SERIALIZER_OPTION_TYPEHINTS] === true) { + $atts[$this->options[XML_SERIALIZER_OPTION_ATTRIBUTE_TYPE]] = + gettype($value); + if ($key !== $origKey) { + $atts[$this->options[XML_SERIALIZER_OPTION_ATTRIBUTE_KEY]] = + (string)$origKey; + } + } + + $tmp .= $this->_createXMLTag(array( + 'qname' => $key, + 'attributes' => $atts, + 'content' => $value + )); + $tmp .= $this->options[XML_SERIALIZER_OPTION_LINEBREAKS]; + } + + $this->_tagDepth--; + if ($this->options[XML_SERIALIZER_OPTION_INDENT]!==null + && $this->_tagDepth>0 + ) { + $tmp .= str_repeat($this->options[XML_SERIALIZER_OPTION_INDENT], + $this->_tagDepth); + } + + if (trim($tmp) === '') { + $tmp = null; + } + + $tag = array( + 'qname' => $tagName, + 'content' => $tmp, + 'attributes' => $attributes + ); + } + if ($this->options[XML_SERIALIZER_OPTION_TYPEHINTS] === true) { + if (!isset($tag['attributes'] + [$this->options[XML_SERIALIZER_OPTION_ATTRIBUTE_TYPE]]) + ) { + $tag['attributes'] + [$this->options[XML_SERIALIZER_OPTION_ATTRIBUTE_TYPE]] = 'array'; + } + } + + $string = ''; + if (!is_null($_comment)) { + $string .= XML_Util::createComment($_comment); + $string .= $this->options[XML_SERIALIZER_OPTION_LINEBREAKS]; + if ($this->options[XML_SERIALIZER_OPTION_INDENT]!==null + && $this->_tagDepth>0 + ) { + $string .= str_repeat($this->options[XML_SERIALIZER_OPTION_INDENT], + $this->_tagDepth); + } + } + $string .= $this->_createXMLTag($tag, false); + return $string; + } + + /** + * get the name of the default tag. + * + * The name of the parent tag needs to be passed as the + * default name can depend on the context. + * + * @param string $parent name of the parent tag + * + * @return string default tag name + */ + function _getDefaultTagname($parent) + { + if (is_string($this->options[XML_SERIALIZER_OPTION_DEFAULT_TAG])) { + return $this->options[XML_SERIALIZER_OPTION_DEFAULT_TAG]; + } + if (isset($this->options[XML_SERIALIZER_OPTION_DEFAULT_TAG][$parent])) { + return $this->options[XML_SERIALIZER_OPTION_DEFAULT_TAG][$parent]; + } elseif (isset($this->options[XML_SERIALIZER_OPTION_DEFAULT_TAG] + ['#default']) + ) { + return $this->options[XML_SERIALIZER_OPTION_DEFAULT_TAG]['#default']; + } elseif (isset($this->options[XML_SERIALIZER_OPTION_DEFAULT_TAG] + ['__default']) + ) { + // keep this for BC + return $this->options[XML_SERIALIZER_OPTION_DEFAULT_TAG]['__default']; + } + return 'XML_Serializer_Tag'; + } + + /** + * serialize an object + * + * @param object &$object object to serialize + * @param string $tagName tag name + * @param array $attributes attributes + * + * @return string $string serialized data + * @access private + */ + function _serializeObject(&$object, $tagName = null, $attributes = array()) + { + // check for magic function + if (method_exists($object, '__sleep')) { + $propNames = $object->__sleep(); + if (is_array($propNames)) { + $properties = array(); + foreach ($propNames as $propName) { + $properties[$propName] = $object->$propName; + } + } else { + $properties = get_object_vars($object); + } + } else { + $properties = get_object_vars($object); + } + + if (empty($tagName)) { + $tagName = get_class($object); + } + + // typehints activated? + if ($this->options[XML_SERIALIZER_OPTION_TYPEHINTS] === true) { + $attributes[$this->options[XML_SERIALIZER_OPTION_ATTRIBUTE_TYPE]] = + 'object'; + $attributes[$this->options[XML_SERIALIZER_OPTION_ATTRIBUTE_CLASS]] = + get_class($object); + } + $string = $this->_serializeArray($properties, $tagName, $attributes); + return $string; + } + + /** + * create a tag from an array + * this method awaits an array in the following format + * array( + * 'qname' => $tagName, + * 'attributes' => array(), + * 'content' => $content, // optional + * 'namespace' => $namespace // optional + * 'namespaceUri' => $namespaceUri // optional + * ) + * + * @param array $tag tag definition + * @param boolean $firstCall whether or not this is the first call + * + * @return string $string XML tag + * @access private + */ + function _createXMLTag($tag, $firstCall = true) + { + // build fully qualified tag name + if ($this->options[XML_SERIALIZER_OPTION_NAMESPACE] !== null) { + if (is_array($this->options[XML_SERIALIZER_OPTION_NAMESPACE])) { + $tag['qname'] = $this->options[XML_SERIALIZER_OPTION_NAMESPACE][0] + . ':' . $tag['qname']; + } else { + $tag['qname'] = $this->options[XML_SERIALIZER_OPTION_NAMESPACE] + . ':' . $tag['qname']; + } + } + + // attribute indentation + if ($this->options[XML_SERIALIZER_OPTION_INDENT_ATTRIBUTES] !== false) { + $multiline = true; + $indent = str_repeat($this->options[XML_SERIALIZER_OPTION_INDENT], + $this->_tagDepth); + + if ($this->options[XML_SERIALIZER_OPTION_INDENT_ATTRIBUTES] == '_auto') { + $indent .= str_repeat(' ', (strlen($tag['qname'])+2)); + + } else { + $indent .= $this->options[XML_SERIALIZER_OPTION_INDENT_ATTRIBUTES]; + } + } else { + $multiline = false; + $indent = false; + } + + if (is_array($tag['content'])) { + if (empty($tag['content'])) { + $tag['content'] = ''; + } +} elseif (XML_SERIALIZER_OPTION_FALSE_AS_STRING && $tag['content'] === false) { +$tag['content'] = '0'; + } elseif (is_scalar($tag['content']) && (string)$tag['content'] == '') { + $tag['content'] = ''; + } + + // replace XML entities + if ($firstCall === true) { + if ($this->options[XML_SERIALIZER_OPTION_CDATA_SECTIONS] === true) { + $replaceEntities = XML_UTIL_CDATA_SECTION; + } else { + $replaceEntities = $this->options[XML_SERIALIZER_OPTION_ENTITIES]; + } + } else { + // this is a nested call, so value is already encoded + // and must not be encoded again + $replaceEntities = XML_SERIALIZER_ENTITIES_NONE; + // but attributes need to be encoded anyways + // (done here because the rest of the code assumes the same encoding + // can be used both for attributes and content) + foreach ($tag['attributes'] as $k => $v) { + $v = XML_Util::replaceEntities($v, + $this->options[XML_SERIALIZER_OPTION_ENTITIES]); + + $tag['attributes'][$k] = $v; + } + } + if (is_scalar($tag['content']) || is_null($tag['content'])) { + if ($this->options[XML_SERIALIZER_OPTION_ENCODE_FUNC]) { + if ($firstCall === true) { + $tag['content'] = call_user_func($this-> + options[XML_SERIALIZER_OPTION_ENCODE_FUNC], $tag['content']); + } + $tag['attributes'] = array_map($this-> + options[XML_SERIALIZER_OPTION_ENCODE_FUNC], $tag['attributes']); + } + $tag = XML_Util::createTagFromArray($tag, $replaceEntities, $multiline, + $indent, $this->options[XML_SERIALIZER_OPTION_LINEBREAKS]); + } elseif (is_array($tag['content'])) { + $tag = $this->_serializeArray($tag['content'], $tag['qname'], + $tag['attributes']); + } elseif (is_object($tag['content'])) { + $tag = $this->_serializeObject($tag['content'], $tag['qname'], + $tag['attributes']); + } elseif (is_resource($tag['content'])) { + settype($tag['content'], 'string'); + if ($this->options[XML_SERIALIZER_OPTION_ENCODE_FUNC]) { + if ($replaceEntities === true) { + $tag['content'] = call_user_func($this-> + options[XML_SERIALIZER_OPTION_ENCODE_FUNC], $tag['content']); + } + $tag['attributes'] = array_map($this-> + options[XML_SERIALIZER_OPTION_ENCODE_FUNC], + $tag['attributes']); + } + $tag = XML_Util::createTagFromArray($tag, $replaceEntities); + } + return $tag; + } +} +?> diff --git a/library/pear/XML/Unserializer.php b/library/pear/XML/Unserializer.php new file mode 100644 index 000000000..5f9fb3ff0 --- /dev/null +++ b/library/pear/XML/Unserializer.php @@ -0,0 +1,983 @@ + + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * @category XML + * @package XML_Serializer + * @author Stephan Schmidt + * @copyright 2003-2008 Stephan Schmidt + * @license http://opensource.org/licenses/bsd-license New BSD License + * @version CVS: $Id: Unserializer.php,v 1.42 2009/02/09 14:49:52 ashnazg Exp $ + * @link http://pear.php.net/package/XML_Serializer + * @see XML_Unserializer + */ + +/** + * uses PEAR error managemt + */ +require_once 'PEAR.php'; + +/** + * uses XML_Parser to unserialize document + */ +require_once 'XML/Parser.php'; + +/** + * option: Convert nested tags to array or object + * + * Possible values: + * - array + * - object + * - associative array to define this option per tag name + */ +define('XML_UNSERIALIZER_OPTION_COMPLEXTYPE', 'complexType'); + +/** + * option: Name of the attribute that stores the original key + * + * Possible values: + * - any string + */ +define('XML_UNSERIALIZER_OPTION_ATTRIBUTE_KEY', 'keyAttribute'); + +/** + * option: Name of the attribute that stores the type + * + * Possible values: + * - any string + */ +define('XML_UNSERIALIZER_OPTION_ATTRIBUTE_TYPE', 'typeAttribute'); + +/** + * option: Name of the attribute that stores the class name + * + * Possible values: + * - any string + */ +define('XML_UNSERIALIZER_OPTION_ATTRIBUTE_CLASS', 'classAttribute'); + +/** + * option: Whether to use the tag name as a class name + * + * Possible values: + * - true or false + */ +define('XML_UNSERIALIZER_OPTION_TAG_AS_CLASSNAME', 'tagAsClass'); + +/** + * option: Name of the default class + * + * Possible values: + * - any string + */ +define('XML_UNSERIALIZER_OPTION_DEFAULT_CLASS', 'defaultClass'); + +/** + * option: Whether to parse attributes + * + * Possible values: + * - true or false + */ +define('XML_UNSERIALIZER_OPTION_ATTRIBUTES_PARSE', 'parseAttributes'); + +/** + * option: Key of the array to store attributes (if any) + * + * Possible values: + * - any string + * - false (disabled) + */ +define('XML_UNSERIALIZER_OPTION_ATTRIBUTES_ARRAYKEY', 'attributesArray'); + +/** + * option: string to prepend attribute name (if any) + * + * Possible values: + * - any string + * - false (disabled) + */ +define('XML_UNSERIALIZER_OPTION_ATTRIBUTES_PREPEND', 'prependAttributes'); + +/** + * option: key to store the content, + * if XML_UNSERIALIZER_OPTION_ATTRIBUTES_PARSE is used + * + * Possible values: + * - any string + */ +define('XML_UNSERIALIZER_OPTION_CONTENT_KEY', 'contentName'); + +/** + * option: map tag names + * + * Possible values: + * - associative array + */ +define('XML_UNSERIALIZER_OPTION_TAG_MAP', 'tagMap'); + +/** + * option: list of tags that will always be enumerated + * + * Possible values: + * - indexed array + */ +define('XML_UNSERIALIZER_OPTION_FORCE_ENUM', 'forceEnum'); + +/** + * option: Encoding of the XML document + * + * Possible values: + * - UTF-8 + * - ISO-8859-1 + */ +define('XML_UNSERIALIZER_OPTION_ENCODING_SOURCE', 'encoding'); + +/** + * option: Desired target encoding of the data + * + * Possible values: + * - UTF-8 + * - ISO-8859-1 + */ +define('XML_UNSERIALIZER_OPTION_ENCODING_TARGET', 'targetEncoding'); + +/** + * option: Callback that will be applied to textual data + * + * Possible values: + * - any valid PHP callback + */ +define('XML_UNSERIALIZER_OPTION_DECODE_FUNC', 'decodeFunction'); + +/** + * option: whether to return the result of the unserialization from unserialize() + * + * Possible values: + * - true + * - false (default) + */ +define('XML_UNSERIALIZER_OPTION_RETURN_RESULT', 'returnResult'); + +/** + * option: set the whitespace behaviour + * + * Possible values: + * - XML_UNSERIALIZER_WHITESPACE_KEEP + * - XML_UNSERIALIZER_WHITESPACE_TRIM + * - XML_UNSERIALIZER_WHITESPACE_NORMALIZE + */ +define('XML_UNSERIALIZER_OPTION_WHITESPACE', 'whitespace'); + +/** + * Keep all whitespace + */ +define('XML_UNSERIALIZER_WHITESPACE_KEEP', 'keep'); + +/** + * remove whitespace from start and end of the data + */ +define('XML_UNSERIALIZER_WHITESPACE_TRIM', 'trim'); + +/** + * normalize whitespace + */ +define('XML_UNSERIALIZER_WHITESPACE_NORMALIZE', 'normalize'); + +/** + * option: whether to ovverride all options that have been set before + * + * Possible values: + * - true + * - false (default) + */ +define('XML_UNSERIALIZER_OPTION_OVERRIDE_OPTIONS', 'overrideOptions'); + +/** + * option: list of tags, that will not be used as keys + */ +define('XML_UNSERIALIZER_OPTION_IGNORE_KEYS', 'ignoreKeys'); + +/** + * option: whether to use type guessing for scalar values + */ +define('XML_UNSERIALIZER_OPTION_GUESS_TYPES', 'guessTypes'); + +/** + * error code for no serialization done + */ +define('XML_UNSERIALIZER_ERROR_NO_UNSERIALIZATION', 151); + +/** + * XML_Unserializer + * + * class to unserialize XML documents that have been created with + * XML_Serializer. To unserialize an XML document you have to add + * type hints to the XML_Serializer options. + * + * If no type hints are available, XML_Unserializer will guess how + * the tags should be treated, that means complex structures will be + * arrays and tags with only CData in them will be strings. + * + * + * require_once 'XML/Unserializer.php'; + * + * // be careful to always use the ampersand in front of the new operator + * $unserializer = &new XML_Unserializer(); + * + * $unserializer->unserialize($xml); + * + * $data = $unserializer->getUnserializedData(); + * + * + * @category XML + * @package XML_Serializer + * @author Stephan Schmidt + * @copyright 2003-2008 Stephan Schmidt + * @license http://opensource.org/licenses/bsd-license New BSD License + * @version Release: 0.20.0 + * @link http://pear.php.net/package/XML_Serializer + * @see XML_Serializer + */ +class XML_Unserializer extends PEAR +{ + /** + * list of all available options + * + * @access private + * @var array + */ + var $_knownOptions = array( + XML_UNSERIALIZER_OPTION_COMPLEXTYPE, + XML_UNSERIALIZER_OPTION_ATTRIBUTE_KEY, + XML_UNSERIALIZER_OPTION_ATTRIBUTE_TYPE, + XML_UNSERIALIZER_OPTION_ATTRIBUTE_CLASS, + XML_UNSERIALIZER_OPTION_TAG_AS_CLASSNAME, + XML_UNSERIALIZER_OPTION_DEFAULT_CLASS, + XML_UNSERIALIZER_OPTION_ATTRIBUTES_PARSE, + XML_UNSERIALIZER_OPTION_ATTRIBUTES_ARRAYKEY, + XML_UNSERIALIZER_OPTION_ATTRIBUTES_PREPEND, + XML_UNSERIALIZER_OPTION_CONTENT_KEY, + XML_UNSERIALIZER_OPTION_TAG_MAP, + XML_UNSERIALIZER_OPTION_FORCE_ENUM, + XML_UNSERIALIZER_OPTION_ENCODING_SOURCE, + XML_UNSERIALIZER_OPTION_ENCODING_TARGET, + XML_UNSERIALIZER_OPTION_DECODE_FUNC, + XML_UNSERIALIZER_OPTION_RETURN_RESULT, + XML_UNSERIALIZER_OPTION_WHITESPACE, + XML_UNSERIALIZER_OPTION_IGNORE_KEYS, + XML_UNSERIALIZER_OPTION_GUESS_TYPES + ); + /** + * default options for the serialization + * + * @access private + * @var array + */ + var $_defaultOptions = array( + // complex types will be converted to arrays, if no type hint is given + XML_UNSERIALIZER_OPTION_COMPLEXTYPE => 'array', + + // get array key/property name from this attribute + XML_UNSERIALIZER_OPTION_ATTRIBUTE_KEY => '_originalKey', + + // get type from this attribute + XML_UNSERIALIZER_OPTION_ATTRIBUTE_TYPE => '_type', + + // get class from this attribute (if not given, use tag name) + XML_UNSERIALIZER_OPTION_ATTRIBUTE_CLASS => '_class', + + // use the tagname as the classname + XML_UNSERIALIZER_OPTION_TAG_AS_CLASSNAME => true, + + // name of the class that is used to create objects + XML_UNSERIALIZER_OPTION_DEFAULT_CLASS => 'stdClass', + + // parse the attributes of the tag into an array + XML_UNSERIALIZER_OPTION_ATTRIBUTES_PARSE => false, + + // parse them into sperate array (specify name of array here) + XML_UNSERIALIZER_OPTION_ATTRIBUTES_ARRAYKEY => false, + + // prepend attribute names with this string + XML_UNSERIALIZER_OPTION_ATTRIBUTES_PREPEND => '', + + // put cdata found in a tag that has been converted + // to a complex type in this key + XML_UNSERIALIZER_OPTION_CONTENT_KEY => '_content', + + // use this to map tagnames + XML_UNSERIALIZER_OPTION_TAG_MAP => array(), + + // these tags will always be an indexed array + XML_UNSERIALIZER_OPTION_FORCE_ENUM => array(), + + // specify the encoding character of the document to parse + XML_UNSERIALIZER_OPTION_ENCODING_SOURCE => null, + + // specify the target encoding + XML_UNSERIALIZER_OPTION_ENCODING_TARGET => null, + + // function used to decode data + XML_UNSERIALIZER_OPTION_DECODE_FUNC => null, + + // unserialize() returns the result of the unserialization instead of true + XML_UNSERIALIZER_OPTION_RETURN_RESULT => false, + + // remove whitespace around data + XML_UNSERIALIZER_OPTION_WHITESPACE => XML_UNSERIALIZER_WHITESPACE_TRIM, + + // List of tags that will automatically be added to the parent, + // instead of adding a new key + XML_UNSERIALIZER_OPTION_IGNORE_KEYS => array(), + + // Whether to use type guessing + XML_UNSERIALIZER_OPTION_GUESS_TYPES => false + ); + + /** + * current options for the serialization + * + * @access public + * @var array + */ + var $options = array(); + + /** + * unserialized data + * + * @access private + * @var string + */ + var $_unserializedData = null; + + /** + * name of the root tag + * + * @access private + * @var string + */ + var $_root = null; + + /** + * stack for all data that is found + * + * @access private + * @var array + */ + var $_dataStack = array(); + + /** + * stack for all values that are generated + * + * @access private + * @var array + */ + var $_valStack = array(); + + /** + * current tag depth + * + * @access private + * @var int + */ + var $_depth = 0; + + /** + * XML_Parser instance + * + * @access private + * @var object XML_Parser + */ + var $_parser = null; + + /** + * constructor + * + * @param mixed $options array containing options for the unserialization + * + * @access public + */ + function XML_Unserializer($options = null) + { + if (is_array($options)) { + $this->options = array_merge($this->_defaultOptions, $options); + } else { + $this->options = $this->_defaultOptions; + } + } + + /** + * return API version + * + * @access public + * @return string $version API version + * @static + */ + function apiVersion() + { + return '0.20.0'; + } + + /** + * reset all options to default options + * + * @return void + * @access public + * @see setOption(), XML_Unserializer(), setOptions() + */ + function resetOptions() + { + $this->options = $this->_defaultOptions; + } + + /** + * set an option + * + * You can use this method if you do not want + * to set all options in the constructor + * + * @param string $name name of option + * @param mixed $value value of option + * + * @return void + * @access public + * @see resetOption(), XML_Unserializer(), setOptions() + */ + function setOption($name, $value) + { + $this->options[$name] = $value; + } + + /** + * sets several options at once + * + * You can use this method if you do not want + * to set all options in the constructor + * + * @param array $options options array + * + * @return void + * @access public + * @see resetOption(), XML_Unserializer(), setOption() + */ + function setOptions($options) + { + $this->options = array_merge($this->options, $options); + } + + /** + * unserialize data + * + * @param mixed $data data to unserialize (string, filename or resource) + * @param boolean $isFile data should be treated as a file + * @param array $options options that will override + * the global options for this call + * + * @return boolean $success + * @access public + */ + function unserialize($data, $isFile = false, $options = null) + { + $this->_unserializedData = null; + $this->_root = null; + + // if options have been specified, use them instead + // of the previously defined ones + if (is_array($options)) { + $optionsBak = $this->options; + if (isset($options[XML_UNSERIALIZER_OPTION_OVERRIDE_OPTIONS]) + && $options[XML_UNSERIALIZER_OPTION_OVERRIDE_OPTIONS] == true + ) { + $this->options = array_merge($this->_defaultOptions, $options); + } else { + $this->options = array_merge($this->options, $options); + } + } else { + $optionsBak = null; + } + + $this->_valStack = array(); + $this->_dataStack = array(); + $this->_depth = 0; + + $this->_createParser(); + + if (is_string($data)) { + if ($isFile) { + $result = $this->_parser->setInputFile($data); + if (PEAR::isError($result)) { + return $result; + } + $result = $this->_parser->parse(); + } else { + $result = $this->_parser->parseString($data, true); + } + } else { + $this->_parser->setInput($data); + $result = $this->_parser->parse(); + } + + if ($this->options[XML_UNSERIALIZER_OPTION_RETURN_RESULT] === true) { + $return = $this->_unserializedData; + } else { + $return = true; + } + + if ($optionsBak !== null) { + $this->options = $optionsBak; + } + + if (PEAR::isError($result)) { + return $result; + } + + return $return; + } + + /** + * get the result of the serialization + * + * @access public + * @return string $serializedData + */ + function getUnserializedData() + { + if ($this->_root === null) { + return $this->raiseError('No unserialized data available. ' + . 'Use XML_Unserializer::unserialize() first.', + XML_UNSERIALIZER_ERROR_NO_UNSERIALIZATION); + } + return $this->_unserializedData; + } + + /** + * get the name of the root tag + * + * @access public + * @return string $rootName + */ + function getRootName() + { + if ($this->_root === null) { + return $this->raiseError('No unserialized data available. ' + . 'Use XML_Unserializer::unserialize() first.', + XML_UNSERIALIZER_ERROR_NO_UNSERIALIZATION); + } + return $this->_root; + } + + /** + * Start element handler for XML parser + * + * @param object $parser XML parser object + * @param string $element XML element + * @param array $attribs attributes of XML tag + * + * @return void + * @access private + */ + function startHandler($parser, $element, $attribs) + { + if (isset($attribs[$this->options[XML_UNSERIALIZER_OPTION_ATTRIBUTE_TYPE]]) + ) { + $type = $attribs[$this->options[XML_UNSERIALIZER_OPTION_ATTRIBUTE_TYPE]]; + + $guessType = false; + } else { + $type = 'string'; + if ($this->options[XML_UNSERIALIZER_OPTION_GUESS_TYPES] === true) { + $guessType = true; + } else { + $guessType = false; + } + } + + if ($this->options[XML_UNSERIALIZER_OPTION_DECODE_FUNC] !== null) { + $attribs = array_map($this->options[XML_UNSERIALIZER_OPTION_DECODE_FUNC], + $attribs); + } + + $this->_depth++; + $this->_dataStack[$this->_depth] = null; + + if (is_array($this->options[XML_UNSERIALIZER_OPTION_TAG_MAP]) + && isset($this->options[XML_UNSERIALIZER_OPTION_TAG_MAP][$element]) + ) { + $element = $this->options[XML_UNSERIALIZER_OPTION_TAG_MAP][$element]; + } + + $val = array( + 'name' => $element, + 'value' => null, + 'type' => $type, + 'guessType' => $guessType, + 'childrenKeys' => array(), + 'aggregKeys' => array() + ); + + if ($this->options[XML_UNSERIALIZER_OPTION_ATTRIBUTES_PARSE] == true + && (count($attribs) > 0) + ) { + $val['children'] = array(); + $val['type'] = $this->_getComplexType($element); + $val['class'] = $element; + + if ($this->options[XML_UNSERIALIZER_OPTION_GUESS_TYPES] === true) { + $attribs = $this->_guessAndSetTypes($attribs); + } + if ($this->options[XML_UNSERIALIZER_OPTION_ATTRIBUTES_ARRAYKEY] != false + ) { + $val['children'][$this-> + options[XML_UNSERIALIZER_OPTION_ATTRIBUTES_ARRAYKEY]] = $attribs; + } else { + foreach ($attribs as $attrib => $value) { + $val['children'][$this-> + options[XML_UNSERIALIZER_OPTION_ATTRIBUTES_PREPEND] + . $attrib] = $value; + } + } + } + + $keyAttr = false; + + if (is_string($this->options[XML_UNSERIALIZER_OPTION_ATTRIBUTE_KEY])) { + $keyAttr = $this->options[XML_UNSERIALIZER_OPTION_ATTRIBUTE_KEY]; + } elseif (is_array($this->options[XML_UNSERIALIZER_OPTION_ATTRIBUTE_KEY])) { + if (isset($this->options[XML_UNSERIALIZER_OPTION_ATTRIBUTE_KEY] + [$element]) + ) { + $keyAttr = + $this->options[XML_UNSERIALIZER_OPTION_ATTRIBUTE_KEY][$element]; + } elseif (isset($this->options[XML_UNSERIALIZER_OPTION_ATTRIBUTE_KEY] + ['#default']) + ) { + $keyAttr = $this->options[XML_UNSERIALIZER_OPTION_ATTRIBUTE_KEY] + ['#default']; + } elseif (isset($this->options[XML_UNSERIALIZER_OPTION_ATTRIBUTE_KEY] + ['__default']) + ) { + // keep this for BC + $keyAttr = + $this->options[XML_UNSERIALIZER_OPTION_ATTRIBUTE_KEY] + ['__default']; + } + } + + if ($keyAttr !== false && isset($attribs[$keyAttr])) { + $val['name'] = $attribs[$keyAttr]; + } + + if (isset($attribs[$this-> + options[XML_UNSERIALIZER_OPTION_ATTRIBUTE_CLASS]]) + ) { + $val['class'] = + $attribs[$this->options[XML_UNSERIALIZER_OPTION_ATTRIBUTE_CLASS]]; + } + + array_push($this->_valStack, $val); + } + + /** + * Try to guess the type of several values and + * set them accordingly + * + * @param array $array array containing the values + * + * @return array array, containing the values with their correct types + * @access private + */ + function _guessAndSetTypes($array) + { + foreach ($array as $key => $value) { + $array[$key] = $this->_guessAndSetType($value); + } + return $array; + } + + /** + * Try to guess the type of a value and + * set it accordingly + * + * @param string $value character data + * + * @return mixed value with the best matching type + * @access private + */ + function _guessAndSetType($value) + { + if ($value === 'true') { + return true; + } + if ($value === 'false') { + return false; + } + if ($value === 'NULL') { + return null; + } + if (preg_match('/^[-+]?[0-9]{1,}\\z/', $value)) { + return intval($value); + } + if (preg_match('/^[-+]?[0-9]{1,}\.[0-9]{1,}\\z/', $value)) { + return doubleval($value); + } + return (string)$value; + } + + /** + * End element handler for XML parser + * + * @param object $parser XML parser object + * @param string $element element + * + * @return void + * @access private + */ + function endHandler($parser, $element) + { + $value = array_pop($this->_valStack); + switch ($this->options[XML_UNSERIALIZER_OPTION_WHITESPACE]) { + case XML_UNSERIALIZER_WHITESPACE_KEEP: + $data = $this->_dataStack[$this->_depth]; + break; + case XML_UNSERIALIZER_WHITESPACE_NORMALIZE: + $data = trim(preg_replace('/\s\s+/m', ' ', + $this->_dataStack[$this->_depth])); + break; + case XML_UNSERIALIZER_WHITESPACE_TRIM: + default: + $data = trim($this->_dataStack[$this->_depth]); + break; + } + + // adjust type of the value + switch(strtolower($value['type'])) { + + // unserialize an object + case 'object': + if (isset($value['class'])) { + $classname = $value['class']; + } else { + $classname = ''; + } + // instantiate the class + if ($this->options[XML_UNSERIALIZER_OPTION_TAG_AS_CLASSNAME] === true + && class_exists($classname) + ) { + $value['value'] = &new $classname; + } else { + $value['value'] = + &new $this->options[XML_UNSERIALIZER_OPTION_DEFAULT_CLASS]; + } + if (trim($data) !== '') { + if ($value['guessType'] === true) { + $data = $this->_guessAndSetType($data); + } + $value['children'][$this-> + options[XML_UNSERIALIZER_OPTION_CONTENT_KEY]] = $data; + } + + // set properties + foreach ($value['children'] as $prop => $propVal) { + // check whether there is a special method to set this property + $setMethod = 'set'.$prop; + if (method_exists($value['value'], $setMethod)) { + call_user_func(array(&$value['value'], $setMethod), $propVal); + } else { + $value['value']->$prop = $propVal; + } + } + // check for magic function + if (method_exists($value['value'], '__wakeup')) { + $value['value']->__wakeup(); + } + break; + + // unserialize an array + case 'array': + if (trim($data) !== '') { + if ($value['guessType'] === true) { + $data = $this->_guessAndSetType($data); + } + $value['children'][$this-> + options[XML_UNSERIALIZER_OPTION_CONTENT_KEY]] = $data; + } + if (isset($value['children'])) { + $value['value'] = $value['children']; + } else { + $value['value'] = array(); + } + break; + + // unserialize a null value + case 'null': + $data = null; + break; + + // unserialize a resource => this is not possible :-( + case 'resource': + $value['value'] = $data; + break; + + // unserialize any scalar value + default: + if ($value['guessType'] === true) { + $data = $this->_guessAndSetType($data); + } else { + settype($data, $value['type']); + } + + $value['value'] = $data; + break; + } + $parent = array_pop($this->_valStack); + if ($parent === null) { + $this->_unserializedData = &$value['value']; + $this->_root = &$value['name']; + return true; + } else { + // parent has to be an array + if (!isset($parent['children']) || !is_array($parent['children'])) { + $parent['children'] = array(); + if (!in_array($parent['type'], array('array', 'object'))) { + $parent['type'] = $this->_getComplexType($parent['name']); + if ($parent['type'] == 'object') { + $parent['class'] = $parent['name']; + } + } + } + + if (in_array($element, + $this->options[XML_UNSERIALIZER_OPTION_IGNORE_KEYS]) + ) { + $ignoreKey = true; + } else { + $ignoreKey = false; + } + + if (!empty($value['name']) && $ignoreKey === false) { + // there already has been a tag with this name + if (in_array($value['name'], $parent['childrenKeys']) + || in_array($value['name'], + $this->options[XML_UNSERIALIZER_OPTION_FORCE_ENUM]) + ) { + // no aggregate has been created for this tag + if (!in_array($value['name'], $parent['aggregKeys'])) { + if (isset($parent['children'][$value['name']])) { + $parent['children'][$value['name']] = + array($parent['children'][$value['name']]); + } else { + $parent['children'][$value['name']] = array(); + } + array_push($parent['aggregKeys'], $value['name']); + } + array_push($parent['children'][$value['name']], $value['value']); + } else { + $parent['children'][$value['name']] = &$value['value']; + array_push($parent['childrenKeys'], $value['name']); + } + } else { + array_push($parent['children'], $value['value']); + } + array_push($this->_valStack, $parent); + } + + $this->_depth--; + } + + /** + * Handler for character data + * + * @param object $parser XML parser object + * @param string $cdata CDATA + * + * @return void + * @access private + */ + function cdataHandler($parser, $cdata) + { + if ($this->options[XML_UNSERIALIZER_OPTION_DECODE_FUNC] !== null) { + $cdata = call_user_func($this-> + options[XML_UNSERIALIZER_OPTION_DECODE_FUNC], $cdata); + } + $this->_dataStack[$this->_depth] .= $cdata; + } + + /** + * get the complex type, that should be used for a specified tag + * + * @param string $tagname name of the tag + * + * @return string complex type ('array' or 'object') + * @access private + */ + function _getComplexType($tagname) + { + if (is_string($this->options[XML_UNSERIALIZER_OPTION_COMPLEXTYPE])) { + return $this->options[XML_UNSERIALIZER_OPTION_COMPLEXTYPE]; + } + if (isset($this->options[XML_UNSERIALIZER_OPTION_COMPLEXTYPE][$tagname])) { + return $this->options[XML_UNSERIALIZER_OPTION_COMPLEXTYPE][$tagname]; + } + if (isset($this->options[XML_UNSERIALIZER_OPTION_COMPLEXTYPE]['#default'])) { + return $this->options[XML_UNSERIALIZER_OPTION_COMPLEXTYPE]['#default']; + } + return 'array'; + } + + /** + * create the XML_Parser instance + * + * @return boolean + * @access private + */ + function _createParser() + { + if (is_object($this->_parser)) { + $this->_parser->free(); + unset($this->_parser); + } + $this->_parser = &new XML_Parser($this-> + options[XML_UNSERIALIZER_OPTION_ENCODING_SOURCE], + 'event', $this->options[XML_UNSERIALIZER_OPTION_ENCODING_TARGET]); + + $this->_parser->folding = false; + $this->_parser->setHandlerObj($this); + return true; + } +} +?> diff --git a/library/pear/XML/Util.php b/library/pear/XML/Util.php new file mode 100644 index 000000000..f5927b16c --- /dev/null +++ b/library/pear/XML/Util.php @@ -0,0 +1,911 @@ + + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * @category XML + * @package XML_Util + * @author Stephan Schmidt + * @copyright 2003-2008 Stephan Schmidt + * @license http://opensource.org/licenses/bsd-license New BSD License + * @version CVS: $Id: Util.php,v 1.38 2008/11/13 00:03:38 ashnazg Exp $ + * @link http://pear.php.net/package/XML_Util + */ + +/** + * error code for invalid chars in XML name + */ +define('XML_UTIL_ERROR_INVALID_CHARS', 51); + +/** + * error code for invalid chars in XML name + */ +define('XML_UTIL_ERROR_INVALID_START', 52); + +/** + * error code for non-scalar tag content + */ +define('XML_UTIL_ERROR_NON_SCALAR_CONTENT', 60); + +/** + * error code for missing tag name + */ +define('XML_UTIL_ERROR_NO_TAG_NAME', 61); + +/** + * replace XML entities + */ +define('XML_UTIL_REPLACE_ENTITIES', 1); + +/** + * embedd content in a CData Section + */ +define('XML_UTIL_CDATA_SECTION', 5); + +/** + * do not replace entitites + */ +define('XML_UTIL_ENTITIES_NONE', 0); + +/** + * replace all XML entitites + * This setting will replace <, >, ", ' and & + */ +define('XML_UTIL_ENTITIES_XML', 1); + +/** + * replace only required XML entitites + * This setting will replace <, " and & + */ +define('XML_UTIL_ENTITIES_XML_REQUIRED', 2); + +/** + * replace HTML entitites + * @link http://www.php.net/htmlentities + */ +define('XML_UTIL_ENTITIES_HTML', 3); + +/** + * Collapse all empty tags. + */ +define('XML_UTIL_COLLAPSE_ALL', 1); + +/** + * Collapse only empty XHTML tags that have no end tag. + */ +define('XML_UTIL_COLLAPSE_XHTML_ONLY', 2); + +/** + * utility class for working with XML documents + * + + * @category XML + * @package XML_Util + * @author Stephan Schmidt + * @copyright 2003-2008 Stephan Schmidt + * @license http://opensource.org/licenses/bsd-license New BSD License + * @version Release: 1.2.1 + * @link http://pear.php.net/package/XML_Util + */ +class XML_Util +{ + /** + * return API version + * + * @return string $version API version + * @access public + * @static + */ + function apiVersion() + { + return '1.1'; + } + + /** + * replace XML entities + * + * With the optional second parameter, you may select, which + * entities should be replaced. + * + * + * require_once 'XML/Util.php'; + * + * // replace XML entites: + * $string = XML_Util::replaceEntities('This string contains < & >.'); + * + * + * With the optional third parameter, you may pass the character encoding + * + * require_once 'XML/Util.php'; + * + * // replace XML entites in UTF-8: + * $string = XML_Util::replaceEntities( + * 'This string contains < & > as well as ä, ö, ß, à and ê', + * XML_UTIL_ENTITIES_HTML, + * 'UTF-8' + * ); + * + * + * @param string $string string where XML special chars + * should be replaced + * @param int $replaceEntities setting for entities in attribute values + * (one of XML_UTIL_ENTITIES_XML, + * XML_UTIL_ENTITIES_XML_REQUIRED, + * XML_UTIL_ENTITIES_HTML) + * @param string $encoding encoding value (if any)... + * must be a valid encoding as determined + * by the htmlentities() function + * + * @return string string with replaced chars + * @access public + * @static + * @see reverseEntities() + */ + function replaceEntities($string, $replaceEntities = XML_UTIL_ENTITIES_XML, + $encoding = 'ISO-8859-1') + { + switch ($replaceEntities) { + case XML_UTIL_ENTITIES_XML: + return strtr($string, array( + '&' => '&', + '>' => '>', + '<' => '<', + '"' => '"', + '\'' => ''' )); + break; + case XML_UTIL_ENTITIES_XML_REQUIRED: + return strtr($string, array( + '&' => '&', + '<' => '<', + '"' => '"' )); + break; + case XML_UTIL_ENTITIES_HTML: + return htmlentities($string, ENT_COMPAT, $encoding); + break; + } + return $string; + } + + /** + * reverse XML entities + * + * With the optional second parameter, you may select, which + * entities should be reversed. + * + * + * require_once 'XML/Util.php'; + * + * // reverse XML entites: + * $string = XML_Util::reverseEntities('This string contains < & >.'); + * + * + * With the optional third parameter, you may pass the character encoding + * + * require_once 'XML/Util.php'; + * + * // reverse XML entites in UTF-8: + * $string = XML_Util::reverseEntities( + * 'This string contains < & > as well as' + * . ' ä, ö, ß, à and ê', + * XML_UTIL_ENTITIES_HTML, + * 'UTF-8' + * ); + * + * + * @param string $string string where XML special chars + * should be replaced + * @param int $replaceEntities setting for entities in attribute values + * (one of XML_UTIL_ENTITIES_XML, + * XML_UTIL_ENTITIES_XML_REQUIRED, + * XML_UTIL_ENTITIES_HTML) + * @param string $encoding encoding value (if any)... + * must be a valid encoding as determined + * by the html_entity_decode() function + * + * @return string string with replaced chars + * @access public + * @static + * @see replaceEntities() + */ + function reverseEntities($string, $replaceEntities = XML_UTIL_ENTITIES_XML, + $encoding = 'ISO-8859-1') + { + switch ($replaceEntities) { + case XML_UTIL_ENTITIES_XML: + return strtr($string, array( + '&' => '&', + '>' => '>', + '<' => '<', + '"' => '"', + ''' => '\'' )); + break; + case XML_UTIL_ENTITIES_XML_REQUIRED: + return strtr($string, array( + '&' => '&', + '<' => '<', + '"' => '"' )); + break; + case XML_UTIL_ENTITIES_HTML: + return html_entity_decode($string, ENT_COMPAT, $encoding); + break; + } + return $string; + } + + /** + * build an xml declaration + * + * + * require_once 'XML/Util.php'; + * + * // get an XML declaration: + * $xmlDecl = XML_Util::getXMLDeclaration('1.0', 'UTF-8', true); + * + * + * @param string $version xml version + * @param string $encoding character encoding + * @param bool $standalone document is standalone (or not) + * + * @return string xml declaration + * @access public + * @static + * @uses attributesToString() to serialize the attributes of the XML declaration + */ + function getXMLDeclaration($version = '1.0', $encoding = null, + $standalone = null) + { + $attributes = array( + 'version' => $version, + ); + // add encoding + if ($encoding !== null) { + $attributes['encoding'] = $encoding; + } + // add standalone, if specified + if ($standalone !== null) { + $attributes['standalone'] = $standalone ? 'yes' : 'no'; + } + + return sprintf('', + XML_Util::attributesToString($attributes, false)); + } + + /** + * build a document type declaration + * + * + * require_once 'XML/Util.php'; + * + * // get a doctype declaration: + * $xmlDecl = XML_Util::getDocTypeDeclaration('rootTag','myDocType.dtd'); + * + * + * @param string $root name of the root tag + * @param string $uri uri of the doctype definition + * (or array with uri and public id) + * @param string $internalDtd internal dtd entries + * + * @return string doctype declaration + * @access public + * @static + * @since 0.2 + */ + function getDocTypeDeclaration($root, $uri = null, $internalDtd = null) + { + if (is_array($uri)) { + $ref = sprintf(' PUBLIC "%s" "%s"', $uri['id'], $uri['uri']); + } elseif (!empty($uri)) { + $ref = sprintf(' SYSTEM "%s"', $uri); + } else { + $ref = ''; + } + + if (empty($internalDtd)) { + return sprintf('', $root, $ref); + } else { + return sprintf("", $root, $ref, $internalDtd); + } + } + + /** + * create string representation of an attribute list + * + * + * require_once 'XML/Util.php'; + * + * // build an attribute string + * $att = array( + * 'foo' => 'bar', + * 'argh' => 'tomato' + * ); + * + * $attList = XML_Util::attributesToString($att); + * + * + * @param array $attributes attribute array + * @param bool|array $sort sort attribute list alphabetically, + * may also be an assoc array containing + * the keys 'sort', 'multiline', 'indent', + * 'linebreak' and 'entities' + * @param bool $multiline use linebreaks, if more than + * one attribute is given + * @param string $indent string used for indentation of + * multiline attributes + * @param string $linebreak string used for linebreaks of + * multiline attributes + * @param int $entities setting for entities in attribute values + * (one of XML_UTIL_ENTITIES_NONE, + * XML_UTIL_ENTITIES_XML, + * XML_UTIL_ENTITIES_XML_REQUIRED, + * XML_UTIL_ENTITIES_HTML) + * + * @return string string representation of the attributes + * @access public + * @static + * @uses replaceEntities() to replace XML entities in attribute values + * @todo allow sort also to be an options array + */ + function attributesToString($attributes, $sort = true, $multiline = false, + $indent = ' ', $linebreak = "\n", $entities = XML_UTIL_ENTITIES_XML) + { + /* + * second parameter may be an array + */ + if (is_array($sort)) { + if (isset($sort['multiline'])) { + $multiline = $sort['multiline']; + } + if (isset($sort['indent'])) { + $indent = $sort['indent']; + } + if (isset($sort['linebreak'])) { + $multiline = $sort['linebreak']; + } + if (isset($sort['entities'])) { + $entities = $sort['entities']; + } + if (isset($sort['sort'])) { + $sort = $sort['sort']; + } else { + $sort = true; + } + } + $string = ''; + if (is_array($attributes) && !empty($attributes)) { + if ($sort) { + ksort($attributes); + } + if ( !$multiline || count($attributes) == 1) { + foreach ($attributes as $key => $value) { + if ($entities != XML_UTIL_ENTITIES_NONE) { + if ($entities === XML_UTIL_CDATA_SECTION) { + $entities = XML_UTIL_ENTITIES_XML; + } + $value = XML_Util::replaceEntities($value, $entities); + } + $string .= ' ' . $key . '="' . $value . '"'; + } + } else { + $first = true; + foreach ($attributes as $key => $value) { + if ($entities != XML_UTIL_ENTITIES_NONE) { + $value = XML_Util::replaceEntities($value, $entities); + } + if ($first) { + $string .= ' ' . $key . '="' . $value . '"'; + $first = false; + } else { + $string .= $linebreak . $indent . $key . '="' . $value . '"'; + } + } + } + } + return $string; + } + + /** + * Collapses empty tags. + * + * @param string $xml XML + * @param int $mode Whether to collapse all empty tags (XML_UTIL_COLLAPSE_ALL) + * or only XHTML (XML_UTIL_COLLAPSE_XHTML_ONLY) ones. + * + * @return string XML + * @access public + * @static + * @todo PEAR CS - unable to avoid "space after open parens" error + * in the IF branch + */ + function collapseEmptyTags($xml, $mode = XML_UTIL_COLLAPSE_ALL) + { + if ($mode == XML_UTIL_COLLAPSE_XHTML_ONLY) { + return preg_replace( + '/<(area|base(?:font)?|br|col|frame|hr|img|input|isindex|link|meta|' + . 'param)([^>]*)><\/\\1>/s', + '<\\1\\2 />', + $xml); + } else { + return preg_replace('/<(\w+)([^>]*)><\/\\1>/s', '<\\1\\2 />', $xml); + } + } + + /** + * create a tag + * + * This method will call XML_Util::createTagFromArray(), which + * is more flexible. + * + * + * require_once 'XML/Util.php'; + * + * // create an XML tag: + * $tag = XML_Util::createTag('myNs:myTag', + * array('foo' => 'bar'), + * 'This is inside the tag', + * 'http://www.w3c.org/myNs#'); + * + * + * @param string $qname qualified tagname (including namespace) + * @param array $attributes array containg attributes + * @param mixed $content the content + * @param string $namespaceUri URI of the namespace + * @param int $replaceEntities whether to replace XML special chars in + * content, embedd it in a CData section + * or none of both + * @param bool $multiline whether to create a multiline tag where + * each attribute gets written to a single line + * @param string $indent string used to indent attributes + * (_auto indents attributes so they start + * at the same column) + * @param string $linebreak string used for linebreaks + * @param bool $sortAttributes Whether to sort the attributes or not + * + * @return string XML tag + * @access public + * @static + * @see createTagFromArray() + * @uses createTagFromArray() to create the tag + */ + function createTag($qname, $attributes = array(), $content = null, + $namespaceUri = null, $replaceEntities = XML_UTIL_REPLACE_ENTITIES, + $multiline = false, $indent = '_auto', $linebreak = "\n", + $sortAttributes = true) + { + $tag = array( + 'qname' => $qname, + 'attributes' => $attributes + ); + + // add tag content + if ($content !== null) { + $tag['content'] = $content; + } + + // add namespace Uri + if ($namespaceUri !== null) { + $tag['namespaceUri'] = $namespaceUri; + } + + return XML_Util::createTagFromArray($tag, $replaceEntities, $multiline, + $indent, $linebreak, $sortAttributes); + } + + /** + * create a tag from an array + * this method awaits an array in the following format + *
    +     * array(
    +     *     // qualified name of the tag
    +     *     'qname' => $qname        
    +     *
    +     *     // namespace prefix (optional, if qname is specified or no namespace)
    +     *     'namespace' => $namespace    
    +     *
    +     *     // local part of the tagname (optional, if qname is specified)
    +     *     'localpart' => $localpart,   
    +     *
    +     *     // array containing all attributes (optional)
    +     *     'attributes' => array(),      
    +     *
    +     *     // tag content (optional)
    +     *     'content' => $content,     
    +     *
    +     *     // namespaceUri for the given namespace (optional)
    +     *     'namespaceUri' => $namespaceUri 
    +     * )
    +     * 
    + * + * + * require_once 'XML/Util.php'; + * + * $tag = array( + * 'qname' => 'foo:bar', + * 'namespaceUri' => 'http://foo.com', + * 'attributes' => array('key' => 'value', 'argh' => 'fruit&vegetable'), + * 'content' => 'I\'m inside the tag', + * ); + * // creating a tag with qualified name and namespaceUri + * $string = XML_Util::createTagFromArray($tag); + * + * + * @param array $tag tag definition + * @param int $replaceEntities whether to replace XML special chars in + * content, embedd it in a CData section + * or none of both + * @param bool $multiline whether to create a multiline tag where each + * attribute gets written to a single line + * @param string $indent string used to indent attributes + * (_auto indents attributes so they start + * at the same column) + * @param string $linebreak string used for linebreaks + * @param bool $sortAttributes Whether to sort the attributes or not + * + * @return string XML tag + * @access public + * @static + * @see createTag() + * @uses attributesToString() to serialize the attributes of the tag + * @uses splitQualifiedName() to get local part and namespace of a qualified name + * @uses createCDataSection() + * @uses raiseError() + */ + function createTagFromArray($tag, $replaceEntities = XML_UTIL_REPLACE_ENTITIES, + $multiline = false, $indent = '_auto', $linebreak = "\n", + $sortAttributes = true) + { + if (isset($tag['content']) && !is_scalar($tag['content'])) { + return XML_Util::raiseError('Supplied non-scalar value as tag content', + XML_UTIL_ERROR_NON_SCALAR_CONTENT); + } + + if (!isset($tag['qname']) && !isset($tag['localPart'])) { + return XML_Util::raiseError('You must either supply a qualified name ' + . '(qname) or local tag name (localPart).', + XML_UTIL_ERROR_NO_TAG_NAME); + } + + // if no attributes hav been set, use empty attributes + if (!isset($tag['attributes']) || !is_array($tag['attributes'])) { + $tag['attributes'] = array(); + } + + if (isset($tag['namespaces'])) { + foreach ($tag['namespaces'] as $ns => $uri) { + $tag['attributes']['xmlns:' . $ns] = $uri; + } + } + + if (!isset($tag['qname'])) { + // qualified name is not given + + // check for namespace + if (isset($tag['namespace']) && !empty($tag['namespace'])) { + $tag['qname'] = $tag['namespace'] . ':' . $tag['localPart']; + } else { + $tag['qname'] = $tag['localPart']; + } + } elseif (isset($tag['namespaceUri']) && !isset($tag['namespace'])) { + // namespace URI is set, but no namespace + + $parts = XML_Util::splitQualifiedName($tag['qname']); + + $tag['localPart'] = $parts['localPart']; + if (isset($parts['namespace'])) { + $tag['namespace'] = $parts['namespace']; + } + } + + if (isset($tag['namespaceUri']) && !empty($tag['namespaceUri'])) { + // is a namespace given + if (isset($tag['namespace']) && !empty($tag['namespace'])) { + $tag['attributes']['xmlns:' . $tag['namespace']] = + $tag['namespaceUri']; + } else { + // define this Uri as the default namespace + $tag['attributes']['xmlns'] = $tag['namespaceUri']; + } + } + + // check for multiline attributes + if ($multiline === true) { + if ($indent === '_auto') { + $indent = str_repeat(' ', (strlen($tag['qname'])+2)); + } + } + + // create attribute list + $attList = XML_Util::attributesToString($tag['attributes'], + $sortAttributes, $multiline, $indent, $linebreak, $replaceEntities); + if (!isset($tag['content']) || (string)$tag['content'] == '') { + $tag = sprintf('<%s%s />', $tag['qname'], $attList); + } else { + switch ($replaceEntities) { + case XML_UTIL_ENTITIES_NONE: + break; + case XML_UTIL_CDATA_SECTION: + $tag['content'] = XML_Util::createCDataSection($tag['content']); + break; + default: + $tag['content'] = XML_Util::replaceEntities($tag['content'], + $replaceEntities); + break; + } + $tag = sprintf('<%s%s>%s', $tag['qname'], $attList, $tag['content'], + $tag['qname']); + } + return $tag; + } + + /** + * create a start element + * + * + * require_once 'XML/Util.php'; + * + * // create an XML start element: + * $tag = XML_Util::createStartElement('myNs:myTag', + * array('foo' => 'bar') ,'http://www.w3c.org/myNs#'); + * + * + * @param string $qname qualified tagname (including namespace) + * @param array $attributes array containg attributes + * @param string $namespaceUri URI of the namespace + * @param bool $multiline whether to create a multiline tag where each + * attribute gets written to a single line + * @param string $indent string used to indent attributes (_auto indents + * attributes so they start at the same column) + * @param string $linebreak string used for linebreaks + * @param bool $sortAttributes Whether to sort the attributes or not + * + * @return string XML start element + * @access public + * @static + * @see createEndElement(), createTag() + */ + function createStartElement($qname, $attributes = array(), $namespaceUri = null, + $multiline = false, $indent = '_auto', $linebreak = "\n", + $sortAttributes = true) + { + // if no attributes hav been set, use empty attributes + if (!isset($attributes) || !is_array($attributes)) { + $attributes = array(); + } + + if ($namespaceUri != null) { + $parts = XML_Util::splitQualifiedName($qname); + } + + // check for multiline attributes + if ($multiline === true) { + if ($indent === '_auto') { + $indent = str_repeat(' ', (strlen($qname)+2)); + } + } + + if ($namespaceUri != null) { + // is a namespace given + if (isset($parts['namespace']) && !empty($parts['namespace'])) { + $attributes['xmlns:' . $parts['namespace']] = $namespaceUri; + } else { + // define this Uri as the default namespace + $attributes['xmlns'] = $namespaceUri; + } + } + + // create attribute list + $attList = XML_Util::attributesToString($attributes, $sortAttributes, + $multiline, $indent, $linebreak); + $element = sprintf('<%s%s>', $qname, $attList); + return $element; + } + + /** + * create an end element + * + * + * require_once 'XML/Util.php'; + * + * // create an XML start element: + * $tag = XML_Util::createEndElement('myNs:myTag'); + * + * + * @param string $qname qualified tagname (including namespace) + * + * @return string XML end element + * @access public + * @static + * @see createStartElement(), createTag() + */ + function createEndElement($qname) + { + $element = sprintf('', $qname); + return $element; + } + + /** + * create an XML comment + * + * + * require_once 'XML/Util.php'; + * + * // create an XML start element: + * $tag = XML_Util::createComment('I am a comment'); + * + * + * @param string $content content of the comment + * + * @return string XML comment + * @access public + * @static + */ + function createComment($content) + { + $comment = sprintf('', $content); + return $comment; + } + + /** + * create a CData section + * + * + * require_once 'XML/Util.php'; + * + * // create a CData section + * $tag = XML_Util::createCDataSection('I am content.'); + * + * + * @param string $data data of the CData section + * + * @return string CData section with content + * @access public + * @static + */ + function createCDataSection($data) + { + return sprintf('', + preg_replace('/\]\]>/', ']]]]>', strval($data))); + + } + + /** + * split qualified name and return namespace and local part + * + * + * require_once 'XML/Util.php'; + * + * // split qualified tag + * $parts = XML_Util::splitQualifiedName('xslt:stylesheet'); + * + * the returned array will contain two elements: + *
    +     * array(
    +     *     'namespace' => 'xslt',
    +     *     'localPart' => 'stylesheet'
    +     * );
    +     * 
    + * + * @param string $qname qualified tag name + * @param string $defaultNs default namespace (optional) + * + * @return array array containing namespace and local part + * @access public + * @static + */ + function splitQualifiedName($qname, $defaultNs = null) + { + if (strstr($qname, ':')) { + $tmp = explode(':', $qname); + return array( + 'namespace' => $tmp[0], + 'localPart' => $tmp[1] + ); + } + return array( + 'namespace' => $defaultNs, + 'localPart' => $qname + ); + } + + /** + * check, whether string is valid XML name + * + *

    XML names are used for tagname, attribute names and various + * other, lesser known entities.

    + *

    An XML name may only consist of alphanumeric characters, + * dashes, undescores and periods, and has to start with a letter + * or an underscore.

    + * + * + * require_once 'XML/Util.php'; + * + * // verify tag name + * $result = XML_Util::isValidName('invalidTag?'); + * if (is_a($result, 'PEAR_Error')) { + * print 'Invalid XML name: ' . $result->getMessage(); + * } + * + * + * @param string $string string that should be checked + * + * @return mixed true, if string is a valid XML name, PEAR error otherwise + * @access public + * @static + * @todo support for other charsets + * @todo PEAR CS - unable to avoid 85-char limit on second preg_match + */ + function isValidName($string) + { + // check for invalid chars + if (!preg_match('/^[[:alpha:]_]$/', $string{0})) { + return XML_Util::raiseError('XML names may only start with letter ' + . 'or underscore', XML_UTIL_ERROR_INVALID_START); + } + + // check for invalid chars + if (!preg_match('/^([[:alpha:]_]([[:alnum:]\-\.]*)?:)?[[:alpha:]_]([[:alnum:]\_\-\.]+)?$/', + $string) + ) { + return XML_Util::raiseError('XML names may only contain alphanumeric ' + . 'chars, period, hyphen, colon and underscores', + XML_UTIL_ERROR_INVALID_CHARS); + } + // XML name is valid + return true; + } + + /** + * replacement for XML_Util::raiseError + * + * Avoids the necessity to always require + * PEAR.php + * + * @param string $msg error message + * @param int $code error code + * + * @return PEAR_Error + * @access public + * @static + * @todo PEAR CS - should this use include_once instead? + */ + function raiseError($msg, $code) + { + require_once 'PEAR.php'; + return PEAR::raiseError($msg, $code); + } +} +?> diff --git a/library/phing/BuildEvent.php b/library/phing/BuildEvent.php new file mode 100644 index 000000000..3370d8490 --- /dev/null +++ b/library/phing/BuildEvent.php @@ -0,0 +1,198 @@ +. + */ + +require_once 'phing/system/lang/EventObject.php'; + +/** + * Encapsulates a build specific event. + * + *

    We have three sources of events all handled by this class: + * + *

      + *
    • Project level events
    • + *
    • Target level events
    • + *
    • Task level events
    • + *
    + * + *

    Events are all fired from the project class by creating an event object + * using this class and passing it to the listeners. + * + * @author Andreas Aderhold + * @author Hans Lellelid + * @version $Revision: 905 $ + * @package phing + */ +class BuildEvent extends EventObject { + + /** + * A reference to the project + * @var Project + */ + protected $project; + + /** + * A reference to the target + * @var Target + */ + protected $target; + + /** + * A reference to the task + * + * @var Task + */ + protected $task; + + /** + * The message of this event, if the event is a message + * @var string + */ + protected $message = null; + + /** + * The priority of the message + * + * @var string + * @see $message + */ + protected $priority = Project::MSG_VERBOSE; + + /** + * The execption that caused the event, if any + * + * @var object + */ + protected $exception = null; + + /** + * Construct a BuildEvent for a project, task or target source event + * + * @param object project the project that emitted the event. + */ + public function __construct($source) { + parent::__construct($source); + if ($source instanceof Project) { + $this->project = $source; + $this->target = null; + $this->task = null; + } elseif ($source instanceof Target) { + $this->project = $source->getProject(); + $this->target = $source; + $this->task = null; + } elseif ($source instanceof Task) { + $this->project = $source->getProject(); + $this->target = $source->getOwningTarget(); + $this->task = $source; + } else { + throw new Exception("Can not construct BuildEvent, unknown source given."); + } + } + + /** + * Sets the message with details and the message priority for this event. + * + * @param string The string message of the event + * @param integer The priority this message should have + */ + public function setMessage($message, $priority) { + $this->message = (string) $message; + $this->priority = (int) $priority; + } + + /** + * Set the exception that was the cause of this event. + * + * @param Exception The exception that caused the event + */ + public function setException($exception) { + $this->exception = $exception; + } + + /** + * Returns the project instance that fired this event. + * + * The reference to the project instance is set by the constructor if this + * event was fired from the project class. + * + * @return Project The project instance that fired this event + */ + public function getProject() { + return $this->project; + } + + /** + * Returns the target instance that fired this event. + * + * The reference to the target instance is set by the constructor if this + * event was fired from the target class. + * + * @return Target The target that fired this event + */ + public function getTarget() { + return $this->target; + } + + /** + * Returns the target instance that fired this event. + * + * The reference to the task instance is set by the constructor if this + * event was fired within a task. + * + * @return Task The task that fired this event + */ + public function getTask() { + return $this->task; + } + + /** + * Returns the logging message. This field will only be set for + * "messageLogged" events. + * + * @return string The log message + */ + function getMessage() { + return $this->message; + } + + /** + * Returns the priority of the logging message. This field will only + * be set for "messageLogged" events. + * + * @return integer The message priority + */ + function getPriority() { + return $this->priority; + } + + /** + * Returns the exception that was thrown, if any. + * This field will only be set for "taskFinished", "targetFinished", and + * "buildFinished" events. + * + * @see BuildListener::taskFinished() + * @see BuildListener::targetFinished() + * @see BuildListener::buildFinished() + * @return Exception + */ + public function getException() { + return $this->exception; + } +} diff --git a/library/phing/BuildException.php b/library/phing/BuildException.php new file mode 100644 index 000000000..2febbfd14 --- /dev/null +++ b/library/phing/BuildException.php @@ -0,0 +1,121 @@ +. + */ + +/** + * BuildException is for when things go wrong in a build execution. + * + * @author Andreas Aderhold + * @version $Revision: 905 $ + * @package phing + */ +class BuildException extends Exception { + + /** + * Location in the xml file. + * @var Location + */ + protected $location; + + /** + * The nested "cause" exception. + * @var Exception + */ + protected $cause; + + /** + * Construct a BuildException. + * Supported signatures: + * throw new BuildException($causeExc); + * throw new BuildException($msg); + * throw new Buildexception($causeExc, $loc); + * throw new BuildException($msg, $causeExc); + * throw new BuildException($msg, $loc); + * throw new BuildException($msg, $causeExc, $loc); + */ + function __construct($p1, $p2 = null, $p3 = null) { + + $cause = null; + $loc = null; + $msg = ""; + + if ($p3 !== null) { + $cause = $p2; + $loc = $p3; + $msg = $p1; + } elseif ($p2 !== null) { + if ($p2 instanceof Exception) { + $cause = $p2; + $msg = $p1; + } elseif ($p2 instanceof Location) { + $loc = $p2; + if ($p1 instanceof Exception) { + $cause = $p1; + } else { + $msg = $p1; + } + } + } elseif ($p1 instanceof Exception) { + $cause = $p1; + } else { + $msg = $p1; + } + + parent::__construct($msg); + + if ($cause !== null) { + $this->cause = $cause; + $this->message .= " [wrapped: " . $cause->getMessage() ."]"; + } + + if ($loc !== null) { + $this->setLocation($loc); + } + } + + /** + * Gets the cause exception. + * + * @return Exception + */ + public function getCause() { + return $this->cause; + } + + /** + * Gets the location of error in XML file. + * + * @return Location + */ + public function getLocation() { + return $this->location; + } + + /** + * Sets the location of error in XML file. + * + * @param Locaiton $loc + */ + public function setLocation(Location $loc) { + $this->location = $loc; + $this->message = $loc->toString() . ': ' . $this->message; + } + +} diff --git a/library/phing/BuildListener.php b/library/phing/BuildListener.php new file mode 100644 index 000000000..f8ddc44b8 --- /dev/null +++ b/library/phing/BuildListener.php @@ -0,0 +1,91 @@ +. + */ + +/** + * Interface for build listeners. + * + * Classes that implement a listener must extend this class and (faux)implement + * all methods that are decleard as dummies below. + * + * @author Andreas Aderhold + * @author Hans Lellelid + * @version $Revision: 905 $ + * @see BuildEvent + * @see Project::addBuildListener() + * @package phing + */ +interface BuildListener { + + /** + * Fired before any targets are started. + * + * @param BuildEvent The BuildEvent + */ + function buildStarted(BuildEvent $event); + + /** + * Fired after the last target has finished. + * + * @param BuildEvent The BuildEvent + * @see BuildEvent::getException() + */ + function buildFinished(BuildEvent $event); + + /** + * Fired when a target is started. + * + * @param BuildEvent The BuildEvent + * @see BuildEvent::getTarget() + */ + function targetStarted(BuildEvent $event); + + /** + * Fired when a target has finished. + * + * @param BuildEvent The BuildEvent + * @see BuildEvent#getException() + */ + function targetFinished(BuildEvent $event); + + /** + * Fired when a task is started. + * + * @param BuildEvent The BuildEvent + * @see BuildEvent::getTask() + */ + function taskStarted(BuildEvent $event); + + /** + * Fired when a task has finished. + * + * @param BuildEvent The BuildEvent + * @see BuildEvent::getException() + */ + function taskFinished(BuildEvent $event); + + /** + * Fired whenever a message is logged. + * + * @param BuildEvent The BuildEvent + * @see BuildEvent::getMessage() + */ + function messageLogged(BuildEvent $event); +} diff --git a/library/phing/BuildLogger.php b/library/phing/BuildLogger.php new file mode 100644 index 000000000..b738d6814 --- /dev/null +++ b/library/phing/BuildLogger.php @@ -0,0 +1,70 @@ +. + */ + +require_once 'phing/BuildListener.php'; + +/** + * Interface for build loggers. + * + * Build loggers are build listeners but with some additional functionality: + * - They can be configured with a log level (below which they will ignore messages) + * - They have error and output streams + * + * Classes that implement a listener must implement this interface. + * + * @author Hans Lellelid + * @version $Revision: 905 $ + * @see BuildEvent + * @see Project::addBuildListener() + * @package phing + */ +interface BuildLogger extends BuildListener { + + /** + * Sets the min log level that this logger should respect. + * + * Messages below this level are ignored. + * + * Constants for the message levels are in Project.php. The order of + * the levels, from least to most verbose, is: + * - Project::MSG_ERR + * - Project::MSG_WARN + * - Project::MSG_INFO + * - Project::MSG_VERBOSE + * - Project::MSG_DEBUG + * + * @param int $level The log level integer (e.g. Project::MSG_VERBOSE, etc.). + */ + public function setMessageOutputLevel($level); + + /** + * Sets the standard output stream to use. + * @param OutputStream $output Configured output stream (e.g. STDOUT) for standard output. + */ + public function setOutputStream(OutputStream $output); + + /** + * Sets the output stream to use for errors. + * @param OutputStream $err Configured output stream (e.g. STDERR) for errors. + */ + public function setErrorStream(OutputStream $err); + +} diff --git a/library/phing/ConfigurationException.php b/library/phing/ConfigurationException.php new file mode 100644 index 000000000..94a98ef03 --- /dev/null +++ b/library/phing/ConfigurationException.php @@ -0,0 +1,83 @@ +. + */ + +/** + * ConfigurationException is thrown by Phing during the configuration and setup phase of the project. + * + * @author Hans Lellelid + * @version $Revision: 905 $ + * @package phing + */ +class ConfigurationException extends Exception { + + /** + * Location in the xml file. + * @var Location + */ + protected $location; + + /** + * The nested "cause" exception. + * @var Exception + */ + protected $cause; + + /** + * Construct a BuildException. + * Supported signatures: + * throw new BuildException($causeExc); + * throw new BuildException($msg); + * throw new BuildException($msg, $causeExc); + */ + function __construct($p1, $p2 = null, $p3 = null) { + + $cause = null; + $msg = ""; + + if ($p2 !== null) { + if ($p2 instanceof Exception) { + $cause = $p2; + $msg = $p1; + } + } elseif ($p1 instanceof Exception) { + $cause = $p1; + } else { + $msg = $p1; + } + + parent::__construct($msg); + + if ($cause !== null) { + $this->cause = $cause; + $this->message .= " [wrapped: " . $cause->getMessage() ."]"; + } + } + + /** + * Gets the cause exception. + * + * @return Exception + */ + public function getCause() { + return $this->cause; + } + +} diff --git a/library/phing/IntrospectionHelper.php b/library/phing/IntrospectionHelper.php new file mode 100644 index 000000000..cefde0af8 --- /dev/null +++ b/library/phing/IntrospectionHelper.php @@ -0,0 +1,538 @@ +. + */ + +include_once 'phing/types/Reference.php'; +include_once 'phing/types/Path.php'; +include_once 'phing/util/StringHelper.php'; + +/** + * Helper class that collects the methods that a task or nested element + * holds to set attributes, create nested elements or hold PCDATA + * elements. + * + *

      + *
    • SMART-UP INLINE DOCS
    • + *
    • POLISH-UP THIS CLASS
    • + *
    + * + * @author Andreas Aderhold + * @author Hans Lellelid + * @copyright © 2001,2002 THYRELL. All rights reserved + * @version $Revision: 905 $ + * @package phing + */ +class IntrospectionHelper { + + + + /** + * Holds the attribute setter methods. + * + * @var array string[] + */ + private $attributeSetters = array(); + + /** + * Holds methods to create nested elements. + * + * @var array string[] + */ + private $nestedCreators = array(); + + /** + * Holds methods to store configured nested elements. + * + * @var array string[] + */ + private $nestedStorers = array(); + + /** + * Map from attribute names to nested types. + */ + private $nestedTypes = array(); + + /** + * New idea in phing: any class can register certain + * keys -- e.g. "task.current_file" -- which can be used in + * task attributes, if supported. In the build XML these + * are referred to like this: + * + * In the type/task a listener method must be defined: + * function setListeningReplace($slot) {} + * @var array string[] + */ + private $slotListeners = array(); + + /** + * The method to add PCDATA stuff. + * + * @var string Method name of the addText (redundant?) method, if class supports it :) + */ + private $methodAddText = null; + + /** + * The Class that's been introspected. + * + * @var object + * @access private + */ + private $bean; + + /** + * The cache of IntrospectionHelper classes instantiated by getHelper(). + * @var array IntrospectionHelpers[] + */ + private static $helpers = array(); + + /** + * Factory method for helper objects. + * + * @param string $class The class to create a Helper for + */ + public static function getHelper($class) { + if (!isset(self::$helpers[$class])) { + self::$helpers[$class] = new IntrospectionHelper($class); + } + return self::$helpers[$class]; + } + + /** + * This function constructs a new introspection helper for a specific class. + * + * This method loads all methods for the specified class and categorizes them + * as setters, creators, slot listeners, etc. This way, the setAttribue() doesn't + * need to perform any introspection -- either the requested attribute setter/creator + * exists or it does not & a BuildException is thrown. + * + * @param string $bean The classname for this IH. + */ + function __construct($class) { + + $this->bean = new ReflectionClass($class); + + //$methods = get_class_methods($bean); + foreach($this->bean->getMethods() as $method) { + + if ($method->isPublic()) { + + // We're going to keep case-insensitive method names + // for as long as we're allowed :) It makes it much + // easier to map XML attributes to PHP class method names. + $name = strtolower($method->getName()); + + // There are a few "reserved" names that might look like attribute setters + // but should actually just be skipped. (Note: this means you can't ever + // have an attribute named "location" or "tasktype" or a nested element named "task".) + if ($name === "setlocation" || $name === "settasktype" || $name === "addtask") { + continue; + } + + if ($name === "addtext") { + + $this->methodAddText = $method; + + } elseif (strpos($name, "setlistening") === 0) { + + // Phing supports something unique called "RegisterSlots" + // These are dynamic values that use a basic slot system so that + // classes can register to listen to specific slots, and the value + // will always be grabbed from the slot (and never set in the project + // component). This is useful for things like tracking the current + // file being processed by a filter (e.g. AppendTask sets an append.current_file + // slot, which can be ready by the XSLTParam type.) + + if (count($method->getParameters()) !== 1) { + throw new BuildException($method->getDeclaringClass()->getName()."::".$method->getName()."() must take exactly one parameter."); + } + + $this->slotListeners[$name] = $method; + + } elseif (strpos($name, "set") === 0) { + + // A standard attribute setter. + + if (count($method->getParameters()) !== 1) { + throw new BuildException($method->getDeclaringClass()->getName()."::".$method->getName()."() must take exactly one parameter."); + } + + $this->attributeSetters[$name] = $method; + + } elseif (strpos($name, "create") === 0) { + + if (count($method->getParameters()) > 0) { + throw new BuildException($method->getDeclaringClass()->getName()."::".$method->getName()."() may not take any parameters."); + } + + // Because PHP doesn't support return types, we are going to do + // two things here to guess return type: + // 1) parse comments for an explicit value + // 2) if that fails, assume that the part of the method after "create" + // is the name of the return type (in many cases it is not) + + // This isn't super important -- i.e. we're not instantaiting classes + // based on this information. It's more just so that IntrospectionHelper + // can keep track of all the nested types -- and provide more helpful + // exception messages, etc. + + preg_match('/@return[\s]+([\w]+)/', $method->getDocComment(), $matches); + if (!empty($matches[1]) && class_exists($matches[1], false)) { + $this->nestedTypes[$name] = $matches[1]; + } else { + // assume that method createEquals() creates object of type "Equals" + // (that example would be false, of course) + $this->nestedTypes[$name] = $this->getPropertyName($name, "create"); + } + + $this->nestedCreators[$name] = $method; + + } elseif (strpos($name, "addconfigured") === 0) { + + // *must* use class hints if using addConfigured ... + + // 1 param only + $params = $method->getParameters(); + + if (count($params) < 1) { + throw new BuildException($method->getDeclaringClass()->getName()."::".$method->getName()."() must take at least one parameter."); + } + + if (count($params) > 1) { + $this->warn($method->getDeclaringClass()->getName()."::".$method->getName()."() takes more than one parameter. (IH only uses the first)"); + } + + $classname = null; + + if (($hint = $params[0]->getClass()) !== null) { + $classname = $hint->getName(); + } + + if ($classname === null) { + throw new BuildException($method->getDeclaringClass()->getName()."::".$method->getName()."() method MUST use a class hint to indicate the class type of parameter."); + } + + $this->nestedTypes[$name] = $classname; + + $this->nestedStorers[$name] = $method; + + } elseif (strpos($name, "add") === 0) { + + // *must* use class hints if using add ... + + // 1 param only + $params = $method->getParameters(); + if (count($params) < 1) { + throw new BuildException($method->getDeclaringClass()->getName()."::".$method->getName()."() must take at least one parameter."); + } + + if (count($params) > 1) { + $this->warn($method->getDeclaringClass()->getName()."::".$method->getName()."() takes more than one parameter. (IH only uses the first)"); + } + + $classname = null; + + if (($hint = $params[0]->getClass()) !== null) { + $classname = $hint->getName(); + } + + // we don't use the classname here, but we need to make sure it exists before + // we later try to instantiate a non-existant class + if ($classname === null) { + throw new BuildException($method->getDeclaringClass()->getName()."::".$method->getName()."() method MUST use a class hint to indicate the class type of parameter."); + } + + $this->nestedCreators[$name] = $method; + } + } // if $method->isPublic() + } // foreach + } + + + /** Sets the named attribute. */ + function setAttribute(Project $project, $element, $attributeName, &$value) { + + // we want to check whether the value we are setting looks like + // a slot-listener variable: %{task.current_file} + // + // slot-listener variables are not like properties, in that they cannot be mixed with + // other text values. The reason for this disparity is that properties are only + // set when first constructing objects from XML, whereas slot-listeners are always dynamic. + // + // This is made possible by PHP5 (objects automatically passed by reference) and PHP's loose + // typing. + + if (StringHelper::isSlotVar($value)) { + + $as = "setlistening" . strtolower($attributeName); + + if (!isset($this->slotListeners[$as])) { + $msg = $this->getElementName($project, $element) . " doesn't support a slot-listening '$attributeName' attribute."; + throw new BuildException($msg); + } + + $method = $this->slotListeners[$as]; + + $key = StringHelper::slotVar($value); + $value = Register::getSlot($key); // returns a RegisterSlot object which will hold current value of that register (accessible using getValue()) + + } else { + + // Traditional value options + + $as = "set".strtolower($attributeName); + + if (!isset($this->attributeSetters[$as])) { + $msg = $this->getElementName($project, $element) . " doesn't support the '$attributeName' attribute."; + throw new BuildException($msg); + } + + $method = $this->attributeSetters[$as]; + + if ($as == "setrefid") { + $value = new Reference($value); + } else { + // value is a string representation of a boolean type, + // convert it to primitive + if (StringHelper::isBoolean($value)) { + + $value = StringHelper::booleanValue($value); + } + + // does method expect a PhingFile object? if so, then + // pass a project-relative file. + $params = $method->getParameters(); + + $classname = null; + + if (($hint = $params[0]->getClass()) !== null) { + $classname = $hint->getName(); + } + + // there should only be one param; we'll just assume .... + if ($classname !== null) { + switch(strtolower($classname)) { + case "phingfile": + $value = $project->resolveFile($value); + break; + case "path": + $value = new Path($project, $value); + break; + case "reference": + $value = new Reference($value); + break; + // any other object params we want to support should go here ... + } + + } // if hint !== null + + } // if not setrefid + + } // if is slot-listener + + try { + $project->log(" -calling setter ".$method->getDeclaringClass()->getName()."::".$method->getName()."()", Project::MSG_DEBUG); + $method->invoke($element, $value); + } catch(Exception $exc) { + throw new BuildException($exc); + } + + } + + /** Adds PCDATA areas.*/ + function addText(Project $project, $element, $text) { + if ($this->methodAddText === null) { + $msg = $this->getElementName($project, $element)." doesn't support nested text data."; + throw new BuildException($msg); + } + try { + $method = $this->methodAddText; + $method->invoke($element, $text); + } catch (Exception $exc) { + throw new BuildException($exc); + } + } + + /** + * Creates a named nested element. + * + * Valid creators can be in the form createFoo() or addFoo(Bar). + * @return object Returns the nested element. + * @throws BuildException + */ + function createElement(Project $project, $element, $elementName) { + + $addMethod = "add".strtolower($elementName); + $createMethod = "create".strtolower($elementName); + $nestedElement = null; + + if (isset($this->nestedCreators[$createMethod])) { + + $method = $this->nestedCreators[$createMethod]; + try { // try to invoke the creator method on object + $project->log(" -calling creator ".$method->getDeclaringClass()->getName()."::".$method->getName()."()", Project::MSG_DEBUG); + $nestedElement = $method->invoke($element); + } catch (Exception $exc) { + throw new BuildException($exc); + } + + } elseif (isset($this->nestedCreators[$addMethod])) { + + $method = $this->nestedCreators[$addMethod]; + + // project components must use class hints to support the add methods + + try { // try to invoke the adder method on object + + $project->log(" -calling adder ".$method->getDeclaringClass()->getName()."::".$method->getName()."()", Project::MSG_DEBUG); + // we've already assured that correct num of params + // exist and that method is using class hints + $params = $method->getParameters(); + + $classname = null; + + if (($hint = $params[0]->getClass()) !== null) { + $classname = $hint->getName(); + } + + // create a new instance of the object and add it via $addMethod + $nestedElement = new $classname(); + + $method->invoke($element, $nestedElement); + + } catch (Exception $exc) { + throw new BuildException($exc); + } + } else { + $msg = $this->getElementName($project, $element) . " doesn't support the '$elementName' creator/adder."; + throw new BuildException($msg); + } + + if ($nestedElement instanceof ProjectComponent) { + $nestedElement->setProject($project); + } + + return $nestedElement; + } + + /** + * Creates a named nested element. + * @return void + * @throws BuildException + */ + function storeElement($project, $element, $child, $elementName = null) { + + if ($elementName === null) { + return; + } + + $storer = "addconfigured".strtolower($elementName); + + if (isset($this->nestedStorers[$storer])) { + + $method = $this->nestedStorers[$storer]; + + try { + $project->log(" -calling storer ".$method->getDeclaringClass()->getName()."::".$method->getName()."()", Project::MSG_DEBUG); + $method->invoke($element, $child); + } catch (Exception $exc) { + throw new BuildException($exc); + } + } + + } + + /** Does the introspected class support PCDATA? */ + function supportsCharacters() { + return ($this->methodAddText !== null); + } + + /** Return all attribues supported by the introspected class. */ + function getAttributes() { + $attribs = array(); + foreach (array_keys($this->attributeSetters) as $setter) { + $attribs[] =$this->getPropertyName($setter, "set"); + } + return $attribs; + } + + /** Return all nested elements supported by the introspected class. */ + function getNestedElements() { + return $this->nestedTypes; + } + + /** + * Get the the name for an element. + * When possible the full classnam (phing.tasks.system.PropertyTask) will + * be returned. If not available (loaded in taskdefs or typedefs) then the + * XML element name will be returned. + * + * @param Project $project + * @param object $element The Task or type element. + * @return string Fully qualified class name of element when possible. + */ + function getElementName(Project $project, $element) { + + $taskdefs = $project->getTaskDefinitions(); + $typedefs = $project->getDataTypeDefinitions(); + + // check if class of element is registered with project (tasks & types) + // most element types don't have a getTag() method + $elClass = get_class($element); + + if (!in_array('getTag', get_class_methods($elClass))) { + // loop through taskdefs and typesdefs and see if the class name + // matches (case-insensitive) any of the classes in there + foreach(array_merge($taskdefs, $typedefs) as $elName => $class) { + if (0 === strcasecmp($elClass, StringHelper::unqualify($class))) { + return $class; + } + } + return "$elClass (unknown)"; + } else { + // ->getTag() method does exist, so use it + $elName = $element->getTag(); + if (isset($taskdefs[$elName])) { + return $taskdefs[$elName]; + } elseif (isset($typedefs[$elName])) { + + return $typedefs[$elName]; + } else { + return "$elName (unknown)"; + } + } + } + + /** extract the name of a property from a method name - subtracting a given prefix. */ + function getPropertyName($methodName, $prefix) { + $start = strlen($prefix); + return strtolower(substr($methodName, $start)); + } + + /** + * Prints warning message to screen if -debug was used. + */ + function warn($msg) { + if (Phing::getMsgOutputLevel() === Project::MSG_DEBUG) { + print("[IntrospectionHelper] " . $msg . "\n"); + } + } + +} diff --git a/library/phing/Phing.php b/library/phing/Phing.php new file mode 100644 index 000000000..db21107fc --- /dev/null +++ b/library/phing/Phing.php @@ -0,0 +1,1371 @@ +. + */ + +require_once 'phing/Project.php'; +require_once 'phing/ProjectComponent.php'; +require_once 'phing/Target.php'; +require_once 'phing/Task.php'; + +include_once 'phing/BuildException.php'; +include_once 'phing/ConfigurationException.php'; +include_once 'phing/BuildEvent.php'; + +include_once 'phing/parser/Location.php'; +include_once 'phing/parser/ExpatParser.php'; +include_once 'phing/parser/AbstractHandler.php'; +include_once 'phing/parser/ProjectConfigurator.php'; +include_once 'phing/parser/RootHandler.php'; +include_once 'phing/parser/ProjectHandler.php'; +include_once 'phing/parser/TaskHandler.php'; +include_once 'phing/parser/TargetHandler.php'; +include_once 'phing/parser/DataTypeHandler.php'; +include_once 'phing/parser/NestedElementHandler.php'; + +include_once 'phing/system/util/Properties.php'; +include_once 'phing/util/StringHelper.php'; +include_once 'phing/system/io/PhingFile.php'; +include_once 'phing/system/io/OutputStream.php'; +include_once 'phing/system/io/FileOutputStream.php'; +include_once 'phing/system/io/FileReader.php'; +include_once 'phing/system/util/Register.php'; + +/** + * Entry point into Phing. This class handles the full lifecycle of a build -- from + * parsing & handling commandline arguments to assembling the project to shutting down + * and cleaning up in the end. + * + * If you are invoking Phing from an external application, this is still + * the class to use. Your applicaiton can invoke the start() method, passing + * any commandline arguments or additional properties. + * + * @author Andreas Aderhold + * @author Hans Lellelid + * @version $Revision: 905 $ + * @package phing + */ +class Phing { + + /** The default build file name */ + const DEFAULT_BUILD_FILENAME = "build.xml"; + + /** Our current message output status. Follows Project::MSG_XXX */ + private static $msgOutputLevel = Project::MSG_INFO; + + /** PhingFile that we are using for configuration */ + private $buildFile = null; + + /** The build targets */ + private $targets = array(); + + /** + * Set of properties that are passed in from commandline or invoking code. + * @var Properties + */ + private static $definedProps; + + /** Names of classes to add as listeners to project */ + private $listeners = array(); + + private $loggerClassname = null; + + /** The class to handle input (can be only one). */ + private $inputHandlerClassname; + + /** Indicates if this phing should be run */ + private $readyToRun = false; + + /** Indicates we should only parse and display the project help information */ + private $projectHelp = false; + + /** Used by utility function getResourcePath() */ + private static $importPaths; + + /** System-wide static properties (moved from System) */ + private static $properties = array(); + + /** Static system timer. */ + private static $timer; + + /** The current Project */ + private static $currentProject; + + /** Whether to capture PHP errors to buffer. */ + private static $phpErrorCapture = false; + + /** Array of captured PHP errors */ + private static $capturedPhpErrors = array(); + + /** + * @var OUtputStream Stream for standard output. + */ + private static $out; + + /** + * @var OutputStream Stream for error output. + */ + private static $err; + + /** + * @var boolean Whether we are using a logfile. + */ + private static $isLogFileUsed = false; + + /** + * Array to hold original ini settings that Phing changes (and needs + * to restore in restoreIni() method). + * + * @var array Struct of array(setting-name => setting-value) + * @see restoreIni() + */ + private static $origIniSettings = array(); + + /** + * Entry point allowing for more options from other front ends. + * + * This method encapsulates the complete build lifecycle. + * + * @param array $args The commandline args passed to phing shell script. + * @param array $additionalUserProperties Any additional properties to be passed to Phing (alternative front-end might implement this). + * These additional properties will be available using the getDefinedProperty() method and will + * be added to the project's "user" properties + * @see execute() + * @see runBuild() + * @throws Exception - if there is an error during build + */ + public static function start($args, array $additionalUserProperties = null) { + + try { + $m = new Phing(); + $m->execute($args); + } catch (Exception $exc) { + self::handleLogfile(); + throw $exc; + } + + if ($additionalUserProperties !== null) { + foreach($additionalUserProperties as $key => $value) { + $m->setDefinedProperty($key, $value); + } + } + + try { + $m->runBuild(); + } catch(Exception $exc) { + self::handleLogfile(); + throw $exc; + } + + // everything fine, shutdown + self::handleLogfile(); + } + + /** + * Prints the message of the Exception if it's not null. + * @param Exception $t + */ + public static function printMessage(Exception $t) { + if (self::$err === null) { // Make sure our error output is initialized + self::initializeOutputStreams(); + } + if (self::getMsgOutputLevel() >= Project::MSG_VERBOSE) { + self::$err->write($t->__toString() . PHP_EOL); + } else { + self::$err->write($t->getMessage() . PHP_EOL); + } + } + + /** + * Sets the stdout and stderr streams if they are not already set. + */ + private static function initializeOutputStreams() { + if (self::$out === null) { + self::$out = new OutputStream(fopen("php://stdout", "w")); + } + if (self::$err === null) { + self::$err = new OutputStream(fopen("php://stderr", "w")); + } + } + + /** + * Sets the stream to use for standard (non-error) output. + * @param OutputStream $stream The stream to use for standard output. + */ + public static function setOutputStream(OutputStream $stream) { + self::$out = $stream; + } + + /** + * Gets the stream to use for standard (non-error) output. + * @return OutputStream + */ + public static function getOutputStream() { + return self::$out; + } + + /** + * Sets the stream to use for error output. + * @param OutputStream $stream The stream to use for error output. + */ + public static function setErrorStream(OutputStream $stream) { + self::$err = $stream; + } + + /** + * Gets the stream to use for error output. + * @return OutputStream + */ + public static function getErrorStream() { + return self::$err; + } + + /** + * Close logfiles, if we have been writing to them. + * + * @since Phing 2.3.0 + */ + private static function handleLogfile() { + if (self::$isLogFileUsed) { + self::$err->close(); + self::$out->close(); + } + } + + /** + * Making output level a static property so that this property + * can be accessed by other parts of the system, enabling + * us to display more information -- e.g. backtraces -- for "debug" level. + * @return int + */ + public static function getMsgOutputLevel() { + return self::$msgOutputLevel; + } + + /** + * Command line entry point. This method kicks off the building + * of a project object and executes a build using either a given + * target or the default target. + * + * @param array $args Command line args. + * @return void + */ + public static function fire($args) { + self::start($args, null); + } + + /** + * Setup/initialize Phing environment from commandline args. + * @param array $args commandline args passed to phing shell. + * @return void + */ + public function execute($args) { + + self::$definedProps = new Properties(); + $this->searchForThis = null; + + // 1) First handle any options which should always + // Note: The order in which these are executed is important (if multiple of these options are specified) + + if (in_array('-help', $args) || in_array('-h', $args)) { + $this->printUsage(); + return; + } + + if (in_array('-version', $args) || in_array('-v', $args)) { + $this->printVersion(); + return; + } + + // 2) Next pull out stand-alone args. + // Note: The order in which these are executed is important (if multiple of these options are specified) + + if (false !== ($key = array_search('-quiet', $args, true))) { + self::$msgOutputLevel = Project::MSG_WARN; + unset($args[$key]); + } + + if (false !== ($key = array_search('-verbose', $args, true))) { + self::$msgOutputLevel = Project::MSG_VERBOSE; + unset($args[$key]); + } + + if (false !== ($key = array_search('-debug', $args, true))) { + self::$msgOutputLevel = Project::MSG_DEBUG; + unset($args[$key]); + } + + // 3) Finally, cycle through to parse remaining args + // + $keys = array_keys($args); // Use keys and iterate to max(keys) since there may be some gaps + $max = $keys ? max($keys) : -1; + for($i=0; $i <= $max; $i++) { + + if (!array_key_exists($i, $args)) { + // skip this argument, since it must have been removed above. + continue; + } + + $arg = $args[$i]; + + if ($arg == "-logfile") { + try { + // see: http://phing.info/trac/ticket/65 + if (!isset($args[$i+1])) { + $msg = "You must specify a log file when using the -logfile argument\n"; + throw new ConfigurationException($msg); + } else { + $logFile = new PhingFile($args[++$i]); + $out = new FileOutputStream($logFile); // overwrite + self::setOutputStream($out); + self::setErrorStream($out); + self::$isLogFileUsed = true; + } + } catch (IOException $ioe) { + $msg = "Cannot write on the specified log file. Make sure the path exists and you have write permissions."; + throw new ConfigurationException($msg, $ioe); + } + } elseif ($arg == "-buildfile" || $arg == "-file" || $arg == "-f") { + if (!isset($args[$i+1])) { + $msg = "You must specify a buildfile when using the -buildfile argument."; + throw new ConfigurationException($msg); + } else { + $this->buildFile = new PhingFile($args[++$i]); + } + } elseif ($arg == "-listener") { + if (!isset($args[$i+1])) { + $msg = "You must specify a listener class when using the -listener argument"; + throw new ConfigurationException($msg); + } else { + $this->listeners[] = $args[++$i]; + } + } elseif (StringHelper::startsWith("-D", $arg)) { + $name = substr($arg, 2); + $value = null; + $posEq = strpos($name, "="); + if ($posEq !== false) { + $value = substr($name, $posEq+1); + $name = substr($name, 0, $posEq); + } elseif ($i < count($args)-1 && !StringHelper::startsWith("-D", $arg)) { + $value = $args[++$i]; + } + self::$definedProps->setProperty($name, $value); + } elseif ($arg == "-logger") { + if (!isset($args[$i+1])) { + $msg = "You must specify a classname when using the -logger argument"; + throw new ConfigurationException($msg); + } else { + $this->loggerClassname = $args[++$i]; + } + } elseif ($arg == "-inputhandler") { + if ($this->inputHandlerClassname !== null) { + throw new ConfigurationException("Only one input handler class may be specified."); + } + if (!isset($args[$i+1])) { + $msg = "You must specify a classname when using the -inputhandler argument"; + throw new ConfigurationException($msg); + } else { + $this->inputHandlerClassname = $args[++$i]; + } + } elseif ($arg == "-longtargets") { + self::$definedProps->setProperty('phing.showlongtargets', 1); + } elseif ($arg == "-projecthelp" || $arg == "-targets" || $arg == "-list" || $arg == "-l" || $arg == "-p") { + // set the flag to display the targets and quit + $this->projectHelp = true; + } elseif ($arg == "-find") { + // eat up next arg if present, default to build.xml + if ($i < count($args)-1) { + $this->searchForThis = $args[++$i]; + } else { + $this->searchForThis = self::DEFAULT_BUILD_FILENAME; + } + } elseif (substr($arg,0,1) == "-") { + // we don't have any more args + self::$err->write("Unknown argument: $arg" . PHP_EOL); + self::printUsage(); + return; + } else { + // if it's no other arg, it may be the target + array_push($this->targets, $arg); + } + } + + // if buildFile was not specified on the command line, + if ($this->buildFile === null) { + // but -find then search for it + if ($this->searchForThis !== null) { + $this->buildFile = $this->_findBuildFile(self::getProperty("user.dir"), $this->searchForThis); + } else { + $this->buildFile = new PhingFile(self::DEFAULT_BUILD_FILENAME); + } + } + // make sure buildfile exists + if (!$this->buildFile->exists()) { + throw new ConfigurationException("Buildfile: " . $this->buildFile->__toString() . " does not exist!"); + } + + // make sure it's not a directory + if ($this->buildFile->isDirectory()) { + throw new ConfigurationException("Buildfile: " . $this->buildFile->__toString() . " is a dir!"); + } + + $this->readyToRun = true; + } + + /** + * Helper to get the parent file for a given file. + * + * @param PhingFile $file + * @return PhingFile Parent file or null if none + */ + private function _getParentFile(PhingFile $file) { + $filename = $file->getAbsolutePath(); + $file = new PhingFile($filename); + $filename = $file->getParent(); + return ($filename === null) ? null : new PhingFile($filename); + } + + /** + * Search parent directories for the build file. + * + * Takes the given target as a suffix to append to each + * parent directory in search of a build file. Once the + * root of the file-system has been reached an exception + * is thrown. + * + * @param string $start Start file path. + * @param string $suffix Suffix filename to look for in parents. + * @return PhingFile A handle to the build file + * + * @throws BuildException Failed to locate a build file + */ + private function _findBuildFile($start, $suffix) { + $startf = new PhingFile($start); + $parent = new PhingFile($startf->getAbsolutePath()); + $file = new PhingFile($parent, $suffix); + + // check if the target file exists in the current directory + while (!$file->exists()) { + // change to parent directory + $parent = $this->_getParentFile($parent); + + // if parent is null, then we are at the root of the fs, + // complain that we can't find the build file. + if ($parent === null) { + throw new ConfigurationException("Could not locate a build file!"); + } + // refresh our file handle + $file = new PhingFile($parent, $suffix); + } + return $file; + } + + /** + * Executes the build. + * @return void + */ + function runBuild() { + + if (!$this->readyToRun) { + return; + } + + $project = new Project(); + + self::setCurrentProject($project); + set_error_handler(array('Phing', 'handlePhpError')); + + $error = null; + + $this->addBuildListeners($project); + $this->addInputHandler($project); + + // set this right away, so that it can be used in logging. + $project->setUserProperty("phing.file", $this->buildFile->getAbsolutePath()); + + try { + $project->fireBuildStarted(); + $project->init(); + } catch (Exception $exc) { + $project->fireBuildFinished($exc); + throw $exc; + } + + $project->setUserProperty("phing.version", $this->getPhingVersion()); + + $e = self::$definedProps->keys(); + while (count($e)) { + $arg = (string) array_shift($e); + $value = (string) self::$definedProps->getProperty($arg); + $project->setUserProperty($arg, $value); + } + unset($e); + + $project->setUserProperty("phing.file", $this->buildFile->getAbsolutePath()); + + // first use the Configurator to create the project object + // from the given build file. + + try { + ProjectConfigurator::configureProject($project, $this->buildFile); + } catch (Exception $exc) { + $project->fireBuildFinished($exc); + restore_error_handler(); + self::unsetCurrentProject(); + throw $exc; + } + + // make sure that we have a target to execute + if (count($this->targets) === 0) { + $this->targets[] = $project->getDefaultTarget(); + } + + // make sure that minimum required phing version is satisfied + try { + $this->comparePhingVersion($project->getPhingVersion()); + } catch(Exception $exc) { + $project->fireBuildFinished($exc); + restore_error_handler(); + self::unsetCurrentProject(); + throw $exc; + } + + // execute targets if help param was not given + if (!$this->projectHelp) { + + try { + $project->executeTargets($this->targets); + } catch (Exception $exc) { + $project->fireBuildFinished($exc); + restore_error_handler(); + self::unsetCurrentProject(); + throw $exc; + } + } + // if help is requested print it + if ($this->projectHelp) { + try { + $this->printDescription($project); + $this->printTargets($project); + } catch (Exception $exc) { + $project->fireBuildFinished($exc); + restore_error_handler(); + self::unsetCurrentProject(); + throw $exc; + } + } + + // finally { + if (!$this->projectHelp) { + $project->fireBuildFinished(null); + } + + restore_error_handler(); + self::unsetCurrentProject(); + } + + private function comparePhingVersion($version) { + $current = strtolower(self::getPhingVersion()); + $current = trim(str_replace('phing', '', $current)); + + // make sure that version checks are not applied to trunk + if('dev' === $current) { + return 1; + } + + if(-1 == version_compare($current, $version)) { + throw new BuildException( + sprintf('Incompatible Phing version (%s). Version "%s" required.', $current, $version)); + } + } + + /** + * Bind any registered build listeners to this project. + * + * This means adding the logger and any build listeners that were specified + * with -listener arg. + * + * @param Project $project + * @return void + */ + private function addBuildListeners(Project $project) { + // Add the default listener + $project->addBuildListener($this->createLogger()); + + foreach($this->listeners as $listenerClassname) { + try { + $clz = Phing::import($listenerClassname); + } catch (Exception $x) { + $msg = "Unable to instantiate specified listener " + . "class " . $listenerClassname . " : " + . $e->getMessage(); + throw new ConfigurationException($msg); + } + + $listener = new $clz(); + + if ($listener instanceof StreamRequiredBuildLogger) { + throw new ConfigurationException("Unable to add " . $listenerClassname . " as a listener, since it requires explicit error/output streams. (You can specify it as a -logger.)"); + } + $project->addBuildListener($listener); + } + } + + /** + * Creates the InputHandler and adds it to the project. + * + * @param Project $project the project instance. + * + * @throws BuildException if a specified InputHandler + * class could not be loaded. + */ + private function addInputHandler(Project $project) { + if ($this->inputHandlerClassname === null) { + $handler = new DefaultInputHandler(); + } else { + try { + $clz = Phing::import($this->inputHandlerClassname); + $handler = new $clz(); + if ($project !== null && method_exists($handler, 'setProject')) { + $handler->setProject($project); + } + } catch (Exception $e) { + $msg = "Unable to instantiate specified input handler " + . "class " . $this->inputHandlerClassname . " : " + . $e->getMessage(); + throw new ConfigurationException($msg); + } + } + $project->setInputHandler($handler); + } + + /** + * Creates the default build logger for sending build events to the log. + * @return BuildLogger The created Logger + */ + private function createLogger() { + if ($this->loggerClassname !== null) { + self::import($this->loggerClassname); + // get class name part + $classname = self::import($this->loggerClassname); + $logger = new $classname; + if (!($logger instanceof BuildLogger)) { + throw new BuildException($classname . ' does not implement the BuildLogger interface.'); + } + } else { + require_once 'phing/listener/DefaultLogger.php'; + $logger = new DefaultLogger(); + } + $logger->setMessageOutputLevel(self::$msgOutputLevel); + $logger->setOutputStream(self::$out); + $logger->setErrorStream(self::$err); + return $logger; + } + + /** + * Sets the current Project + * @param Project $p + */ + public static function setCurrentProject($p) { + self::$currentProject = $p; + } + + /** + * Unsets the current Project + */ + public static function unsetCurrentProject() { + self::$currentProject = null; + } + + /** + * Gets the current Project. + * @return Project Current Project or NULL if none is set yet/still. + */ + public static function getCurrentProject() { + return self::$currentProject; + } + + /** + * A static convenience method to send a log to the current (last-setup) Project. + * If there is no currently-configured Project, then this will do nothing. + * @param string $message + * @param int $priority Project::MSG_INFO, etc. + */ + public static function log($message, $priority = Project::MSG_INFO) { + $p = self::getCurrentProject(); + if ($p) { + $p->log($message, $priority); + } + } + + /** + * Error handler for PHP errors encountered during the build. + * This uses the logging for the currently configured project. + */ + public static function handlePhpError($level, $message, $file, $line) { + + // don't want to print supressed errors + if (error_reporting() > 0) { + + if (self::$phpErrorCapture) { + + self::$capturedPhpErrors[] = array('message' => $message, 'level' => $level, 'line' => $line, 'file' => $file); + + } else { + + $message = '[PHP Error] ' . $message; + $message .= ' [line ' . $line . ' of ' . $file . ']'; + + switch ($level) { + case 16384: // E_USER_DEPRECATED + case 8192: // E_DEPRECATED + case E_STRICT: + case E_NOTICE: + case E_USER_NOTICE: + self::log($message, Project::MSG_VERBOSE); + break; + case E_WARNING: + case E_USER_WARNING: + self::log($message, Project::MSG_WARN); + break; + case E_ERROR: + case E_USER_ERROR: + default: + self::log($message, Project::MSG_ERR); + + } // switch + + } // if phpErrorCapture + + } // if not @ + + } + + /** + * Begins capturing PHP errors to a buffer. + * While errors are being captured, they are not logged. + */ + public static function startPhpErrorCapture() { + self::$phpErrorCapture = true; + self::$capturedPhpErrors = array(); + } + + /** + * Stops capturing PHP errors to a buffer. + * The errors will once again be logged after calling this method. + */ + public static function stopPhpErrorCapture() { + self::$phpErrorCapture = false; + } + + /** + * Clears the captured errors without affecting the starting/stopping of the capture. + */ + public static function clearCapturedPhpErrors() { + self::$capturedPhpErrors = array(); + } + + /** + * Gets any PHP errors that were captured to buffer. + * @return array array('message' => message, 'line' => line number, 'file' => file name, 'level' => error level) + */ + public static function getCapturedPhpErrors() { + return self::$capturedPhpErrors; + } + + /** Prints the usage of how to use this class */ + public static function printUsage() { + + $msg = ""; + $msg .= "phing [options] [target [target2 [target3] ...]]" . PHP_EOL; + $msg .= "Options: " . PHP_EOL; + $msg .= " -h -help print this message" . PHP_EOL; + $msg .= " -l -list list available targets in this project" . PHP_EOL; + $msg .= " -v -version print the version information and exit" . PHP_EOL; + $msg .= " -q -quiet be extra quiet" . PHP_EOL; + $msg .= " -verbose be extra verbose" . PHP_EOL; + $msg .= " -debug print debugging information" . PHP_EOL; + $msg .= " -longtargets show target descriptions during build" . PHP_EOL; + $msg .= " -logfile use given file for log" . PHP_EOL; + $msg .= " -logger the class which is to perform logging" . PHP_EOL; + $msg .= " -f -buildfile use given buildfile" . PHP_EOL; + $msg .= " -D= use value for given property" . PHP_EOL; + $msg .= " -find search for buildfile towards the root of the" . PHP_EOL; + $msg .= " filesystem and use it" . PHP_EOL; + $msg .= " -inputhandler the class to use to handle user input" . PHP_EOL; + //$msg .= " -recursive search for buildfile downwards and use it" . PHP_EOL; + $msg .= PHP_EOL; + $msg .= "Report bugs to ".PHP_EOL; + self::$err->write($msg); + } + + /** + * Prints the current Phing version. + */ + public static function printVersion() { + self::$out->write(self::getPhingVersion().PHP_EOL); + } + + /** + * Gets the current Phing version based on VERSION.TXT file. + * @return string + * @throws BuildException - if unable to find version file. + */ + public static function getPhingVersion() { + $versionPath = self::getResourcePath("phing/etc/VERSION.TXT"); + if ($versionPath === null) { + $versionPath = self::getResourcePath("etc/VERSION.TXT"); + } + if ($versionPath === null) { + throw new ConfigurationException("No VERSION.TXT file found; try setting phing.home environment variable."); + } + try { // try to read file + $buffer = null; + $file = new PhingFile($versionPath); + $reader = new FileReader($file); + $reader->readInto($buffer); + $buffer = trim($buffer); + //$buffer = "PHING version 1.0, Released 2002-??-??"; + $phingVersion = $buffer; + } catch (IOException $iox) { + throw new ConfigurationException("Can't read version information file"); + } + return $phingVersion; + } + + /** + * Print the project description, if any + */ + public static function printDescription(Project $project) { + if ($project->getDescription() !== null) { + self::$out->write($project->getDescription() . PHP_EOL); + } + } + + /** Print out a list of all targets in the current buildfile */ + function printTargets($project) { + // find the target with the longest name + $maxLength = 0; + $targets = $project->getTargets(); + $targetNames = array_keys($targets); + $targetName = null; + $targetDescription = null; + $currentTarget = null; + + // split the targets in top-level and sub-targets depending + // on the presence of a description + + $subNames = array(); + $topNameDescMap = array(); + + foreach($targets as $currentTarget) { + $targetName = $currentTarget->getName(); + $targetDescription = $currentTarget->getDescription(); + + // subtargets are targets w/o descriptions + if ($targetDescription === null) { + $subNames[] = $targetName; + } else { + // topNames and topDescriptions are handled later + // here we store in hash map (for sorting purposes) + $topNameDescMap[$targetName] = $targetDescription; + if (strlen($targetName) > $maxLength) { + $maxLength = strlen($targetName); + } + } + } + + // Sort the arrays + sort($subNames); // sort array values, resetting keys (which are numeric) + ksort($topNameDescMap); // sort the keys (targetName) keeping key=>val associations + + $topNames = array_keys($topNameDescMap); + $topDescriptions = array_values($topNameDescMap); + + $defaultTarget = $project->getDefaultTarget(); + + if ($defaultTarget !== null && $defaultTarget !== "") { + $defaultName = array(); + $defaultDesc = array(); + $defaultName[] = $defaultTarget; + + $indexOfDefDesc = array_search($defaultTarget, $topNames, true); + if ($indexOfDefDesc !== false && $indexOfDefDesc >= 0) { + $defaultDesc = array(); + $defaultDesc[] = $topDescriptions[$indexOfDefDesc]; + } + + $this->_printTargets($defaultName, $defaultDesc, "Default target:", $maxLength); + + } + $this->_printTargets($topNames, $topDescriptions, "Main targets:", $maxLength); + $this->_printTargets($subNames, null, "Subtargets:", 0); + } + + /** + * Writes a formatted list of target names with an optional description. + * + * @param array $names The names to be printed. + * Must not be null. + * @param array $descriptions The associated target descriptions. + * May be null, in which case + * no descriptions are displayed. + * If non-null, this should have + * as many elements as names. + * @param string $heading The heading to display. + * Should not be null. + * @param int $maxlen The maximum length of the names of the targets. + * If descriptions are given, they are padded to this + * position so they line up (so long as the names really + * are shorter than this). + */ + private function _printTargets($names, $descriptions, $heading, $maxlen) { + + $spaces = ' '; + while (strlen($spaces) < $maxlen) { + $spaces .= $spaces; + } + $msg = ""; + $msg .= $heading . PHP_EOL; + $msg .= str_repeat("-",79) . PHP_EOL; + + $total = count($names); + for($i=0; $i < $total; $i++) { + $msg .= " "; + $msg .= $names[$i]; + if (!empty($descriptions)) { + $msg .= substr($spaces, 0, $maxlen - strlen($names[$i]) + 2); + $msg .= $descriptions[$i]; + } + $msg .= PHP_EOL; + } + if ($total > 0) { + self::$out->write($msg . PHP_EOL); + } + } + + /** + * Import a dot-path notation class path. + * @param string $dotPath + * @param mixed $classpath String or object supporting __toString() + * @return string The unqualified classname (which can be instantiated). + * @throws BuildException - if cannot find the specified file + */ + public static function import($dotPath, $classpath = null) { + + /// check if this is a PEAR-style path (@see http://pear.php.net/manual/en/standards.naming.php) + if (strpos($dotPath, '.') === false && strpos($dotPath, '_') !== false) { + $classname = $dotPath; + $dotPath = str_replace('_', '.', $dotPath); + } else { + $classname = StringHelper::unqualify($dotPath); + } + + // first check to see that the class specified hasn't already been included. + // (this also handles case where this method is called w/ a classname rather than dotpath) + if (class_exists($classname, false)) { + return $classname; + } + + $dotClassname = basename($dotPath); + $dotClassnamePos = strlen($dotPath) - strlen($dotClassname); + + // 1- temporarily replace escaped '.' with another illegal char (#) + $tmp = str_replace('\.', '##', $dotClassname); + // 2- swap out the remaining '.' with DIR_SEP + $tmp = strtr($tmp, '.', DIRECTORY_SEPARATOR); + // 3- swap back the escaped '.' + $tmp = str_replace('##', '.', $tmp); + + $classFile = $tmp . ".php"; + + $path = substr_replace($dotPath, $classFile, $dotClassnamePos); + + Phing::__import($path, $classpath); + + return $classname; + } + + /** + * Import a PHP file + * @param string $path Path to the PHP file + * @param mixed $classpath String or object supporting __toString() + * @throws BuildException - if cannot find the specified file + */ + public static function __import($path, $classpath = null) { + + if ($classpath) { + + // Apparently casting to (string) no longer invokes __toString() automatically. + if (is_object($classpath)) { + $classpath = $classpath->__toString(); + } + + // classpaths are currently additive, but we also don't want to just + // indiscriminantly prepand/append stuff to the include_path. This means + // we need to parse current incldue_path, and prepend any + // specified classpath locations that are not already in the include_path. + // + // NOTE: the reason why we do it this way instead of just changing include_path + // and then changing it back, is that in many cases applications (e.g. Propel) will + // include/require class files from within method calls. This means that not all + // necessary files will be included in this import() call, and hence we can't + // change the include_path back without breaking those apps. While this method could + // be more expensive than switching & switching back (not sure, but maybe), it makes it + // possible to write far less expensive run-time applications (e.g. using Propel), which is + // really where speed matters more. + + $curr_parts = explode(PATH_SEPARATOR, get_include_path()); + $add_parts = explode(PATH_SEPARATOR, $classpath); + $new_parts = array_diff($add_parts, $curr_parts); + if ($new_parts) { + set_include_path(implode(PATH_SEPARATOR, array_merge($new_parts, $curr_parts))); + } + } + + $ret = include_once($path); + + if ($ret === false) { + $msg = "Error importing $path"; + if (self::getMsgOutputLevel() >= Project::MSG_DEBUG) { + $x = new Exception("for-path-trace-only"); + $msg .= $x->getTraceAsString(); + } + throw new ConfigurationException($msg); + } + } + + /** + * Looks on include path for specified file. + * @return string File found (null if no file found). + */ + public static function getResourcePath($path) { + + if (self::$importPaths === null) { + $paths = get_include_path(); + self::$importPaths = explode(PATH_SEPARATOR, ini_get("include_path")); + } + + $path = str_replace('\\', DIRECTORY_SEPARATOR, $path); + $path = str_replace('/', DIRECTORY_SEPARATOR, $path); + + foreach (self::$importPaths as $prefix) { + $testPath = $prefix . DIRECTORY_SEPARATOR . $path; + if (file_exists($testPath)) { + return $testPath; + } + } + + // Check for the property phing.home + $homeDir = self::getProperty('phing.home'); + if ($homeDir) { + $testPath = $homeDir . DIRECTORY_SEPARATOR . $path; + if (file_exists($testPath)) { + return $testPath; + } + } + + // If we are using this via PEAR then check for the file in the data dir + // This is a bit of a hack, but works better than previous solution of assuming + // data_dir is on the include_path. + $dataDir = '/usr/share/php/data'; + if ($dataDir{0} != '@') { // if we're using PEAR then the @ DATA-DIR @ token will have been substituted. + $testPath = $dataDir . DIRECTORY_SEPARATOR . $path; + if (file_exists($testPath)) { + return $testPath; + } + } else { + // We're not using PEAR, so do one additional check based on path of + // current file (Phing.php) + $maybeHomeDir = realpath(dirname(__FILE__) . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..'); + $testPath = $maybeHomeDir . DIRECTORY_SEPARATOR . $path; + if (file_exists($testPath)) { + return $testPath; + } + } + + return null; + } + + // ------------------------------------------------------------------------------------------- + // System-wide methods (moved from System class, which had namespace conflicts w/ PEAR System) + // ------------------------------------------------------------------------------------------- + + /** + * Set System constants which can be retrieved by calling Phing::getProperty($propName). + * @return void + */ + private static function setSystemConstants() { + + /* + * PHP_OS returns on + * WindowsNT4.0sp6 => WINNT + * Windows2000 => WINNT + * Windows ME => WIN32 + * Windows 98SE => WIN32 + * FreeBSD 4.5p7 => FreeBSD + * Redhat Linux => Linux + * Mac OS X => Darwin + */ + self::setProperty('host.os', PHP_OS); + + // this is used by some tasks too + self::setProperty('os.name', PHP_OS); + + // it's still possible this won't be defined, + // e.g. if Phing is being included in another app w/o + // using the phing.php script. + if (!defined('PHP_CLASSPATH')) { + define('PHP_CLASSPATH', get_include_path()); + } + + self::setProperty('php.classpath', PHP_CLASSPATH); + + // try to determine the host filesystem and set system property + // used by Fileself::getFileSystem to instantiate the correct + // abstraction layer + + switch (strtoupper(PHP_OS)) { + case 'WINNT': + self::setProperty('host.fstype', 'WINNT'); + self::setProperty('php.interpreter', getenv('PHP_COMMAND')); + break; + case 'WIN32': + self::setProperty('host.fstype', 'WIN32'); + break; + default: + self::setProperty('host.fstype', 'UNIX'); + break; + } + + self::setProperty('line.separator', PHP_EOL); + self::setProperty('php.version', PHP_VERSION); + self::setProperty('user.home', getenv('HOME')); + self::setProperty('application.startdir', getcwd()); + self::setProperty('phing.startTime', gmdate('D, d M Y H:i:s', time()) . ' GMT'); + + // try to detect machine dependent information + $sysInfo = array(); + if (strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN' && function_exists("posix_uname")) { + $sysInfo = posix_uname(); + } else { + $sysInfo['nodename'] = php_uname('n'); + $sysInfo['machine']= php_uname('m') ; + //this is a not so ideal substition, but maybe better than nothing + $sysInfo['domain'] = isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : "unknown"; + $sysInfo['release'] = php_uname('r'); + $sysInfo['version'] = php_uname('v'); + } + + + self::setProperty("host.name", isset($sysInfo['nodename']) ? $sysInfo['nodename'] : "unknown"); + self::setProperty("host.arch", isset($sysInfo['machine']) ? $sysInfo['machine'] : "unknown"); + self::setProperty("host.domain",isset($sysInfo['domain']) ? $sysInfo['domain'] : "unknown"); + self::setProperty("host.os.release", isset($sysInfo['release']) ? $sysInfo['release'] : "unknown"); + self::setProperty("host.os.version", isset($sysInfo['version']) ? $sysInfo['version'] : "unknown"); + unset($sysInfo); + } + + /** + * This gets a property that was set via command line or otherwise passed into Phing. + * "Defined" in this case means "externally defined". The reason this method exists is to + * provide a public means of accessing commandline properties for (e.g.) logger or listener + * scripts. E.g. to specify which logfile to use, PearLogger needs to be able to access + * the pear.log.name property. + * + * @param string $name + * @return string value of found property (or null, if none found). + */ + public static function getDefinedProperty($name) { + return self::$definedProps->getProperty($name); + } + + /** + * This sets a property that was set via command line or otherwise passed into Phing. + * + * @param string $name + * @return string value of found property (or null, if none found). + */ + public static function setDefinedProperty($name, $value) { + return self::$definedProps->setProperty($name, $value); + } + + /** + * Returns property value for a System property. + * System properties are "global" properties like application.startdir, + * and user.dir. Many of these correspond to similar properties in Java + * or Ant. + * + * @param string $paramName + * @return string Value of found property (or null, if none found). + */ + public static function getProperty($propName) { + + // some properties are detemined on each access + // some are cached, see below + + // default is the cached value: + $val = isset(self::$properties[$propName]) ? self::$properties[$propName] : null; + + // special exceptions + switch($propName) { + case 'user.dir': + $val = getcwd(); + break; + } + + return $val; + } + + /** Retuns reference to all properties*/ + public static function &getProperties() { + return self::$properties; + } + + public static function setProperty($propName, $propValue) { + $propName = (string) $propName; + $oldValue = self::getProperty($propName); + self::$properties[$propName] = $propValue; + return $oldValue; + } + + public static function currentTimeMillis() { + list($usec, $sec) = explode(" ",microtime()); + return ((float)$usec + (float)$sec); + } + + /** + * Sets the include path to PHP_CLASSPATH constant (if this has been defined). + * @return void + * @throws ConfigurationException - if the include_path could not be set (for some bizarre reason) + */ + private static function setIncludePaths() { + if (defined('PHP_CLASSPATH')) { + $result = set_include_path(PHP_CLASSPATH); + if ($result === false) { + throw new ConfigurationException("Could not set PHP include_path."); + } + self::$origIniSettings['include_path'] = $result; // save original value for setting back later + } + } + + /** + * Sets PHP INI values that Phing needs. + * @return void + */ + private static function setIni() { + + self::$origIniSettings['error_reporting'] = error_reporting(E_ALL); + + // We won't bother storing original max_execution_time, since 1) the value in + // php.ini may be wrong (and there's no way to get the current value) and + // 2) it would mean something very strange to set it to a value less than time script + // has already been running, which would be the likely change. + + set_time_limit(0); + + self::$origIniSettings['magic_quotes_gpc'] = ini_set('magic_quotes_gpc', 'off'); + self::$origIniSettings['short_open_tag'] = ini_set('short_open_tag', 'off'); + self::$origIniSettings['default_charset'] = ini_set('default_charset', 'iso-8859-1'); + self::$origIniSettings['register_globals'] = ini_set('register_globals', 'off'); + self::$origIniSettings['allow_call_time_pass_reference'] = ini_set('allow_call_time_pass_reference', 'on'); + self::$origIniSettings['track_errors'] = ini_set('track_errors', 1); + + // should return memory limit in MB + $mem_limit = (int) ini_get('memory_limit'); + if ($mem_limit < 32 && $mem_limit > -1) { + // We do *not* need to save the original value here, since we don't plan to restore + // this after shutdown (we don't trust the effectiveness of PHP's garbage collection). + ini_set('memory_limit', '32M'); // nore: this may need to be higher for many projects + } + } + + /** + * Restores [most] PHP INI values to their pre-Phing state. + * + * Currently the following settings are not restored: + * - max_execution_time (because getting current time limit is not possible) + * - memory_limit (which may have been increased by Phing) + * + * @return void + */ + private static function restoreIni() + { + foreach(self::$origIniSettings as $settingName => $settingValue) { + switch($settingName) { + case 'error_reporting': + error_reporting($settingValue); + break; + default: + ini_set($settingName, $settingValue); + } + } + } + + /** + * Returns reference to Timer object. + * @return Timer + */ + public static function getTimer() { + if (self::$timer === null) { + include_once 'phing/system/util/Timer.php'; + self::$timer= new Timer(); + } + return self::$timer; + } + + /** + * Start up Phing. + * Sets up the Phing environment but does not initiate the build process. + * @return void + * @throws Exception - If the Phing environment cannot be initialized. + */ + public static function startup() { + + // setup STDOUT and STDERR defaults + self::initializeOutputStreams(); + + // some init stuff + self::getTimer()->start(); + + self::setSystemConstants(); + self::setIncludePaths(); + self::setIni(); + } + + /** + * Halts the system. + * @deprecated This method is deprecated and is no longer called by Phing internally. Any + * normal shutdown routines are handled by the shutdown() method. + * @see shutdown() + */ + public static function halt() { + self::shutdown(); + } + + /** + * Performs any shutdown routines, such as stopping timers. + * @return void + */ + public static function shutdown() { + self::restoreIni(); + self::getTimer()->stop(); + } + +} diff --git a/library/phing/Project.php b/library/phing/Project.php new file mode 100644 index 000000000..468a3025b --- /dev/null +++ b/library/phing/Project.php @@ -0,0 +1,999 @@ +. + */ + +include_once 'phing/system/io/PhingFile.php'; +include_once 'phing/util/FileUtils.php'; +include_once 'phing/TaskAdapter.php'; +include_once 'phing/util/StringHelper.php'; +include_once 'phing/BuildEvent.php'; +include_once 'phing/input/DefaultInputHandler.php'; + +/** + * The Phing project class. Represents a completely configured Phing project. + * The class defines the project and all tasks/targets. It also contains + * methods to start a build as well as some properties and FileSystem + * abstraction. + * + * @author Andreas Aderhold + * @author Hans Lellelid + * @version $Revision: 905 $ + * @package phing + */ +class Project { + + // Logging level constants. + const MSG_DEBUG = 4; + const MSG_VERBOSE = 3; + const MSG_INFO = 2; + const MSG_WARN = 1; + const MSG_ERR = 0; + + /** contains the targets */ + private $targets = array(); + /** global filterset (future use) */ + private $globalFilterSet = array(); + /** all globals filters (future use) */ + private $globalFilters = array(); + + /** Project properties map (usually String to String). */ + private $properties = array(); + + /** + * Map of "user" properties (as created in the Ant task, for example). + * Note that these key/value pairs are also always put into the + * project properties, so only the project properties need to be queried. + * Mapping is String to String. + */ + private $userProperties = array(); + + /** + * Map of inherited "user" properties - that are those "user" + * properties that have been created by tasks and not been set + * from the command line or a GUI tool. + * Mapping is String to String. + */ + private $inheritedProperties = array(); + + /** task definitions for this project*/ + private $taskdefs = array(); + + /** type definitions for this project */ + private $typedefs = array(); + + /** holds ref names and a reference to the referred object*/ + private $references = array(); + + /** The InputHandler being used by this project. */ + private $inputHandler; + + /* -- properties that come in via xml attributes -- */ + + /** basedir (PhingFile object) */ + private $basedir; + + /** the default target name */ + private $defaultTarget = 'all'; + + /** project name (required) */ + private $name; + + /** project description */ + private $description; + + /** require phing version */ + private $phingVersion; + + /** a FileUtils object */ + private $fileUtils; + + /** Build listeneers */ + private $listeners = array(); + + /** + * Constructor, sets any default vars. + */ + function __construct() { + $this->fileUtils = new FileUtils(); + $this->inputHandler = new DefaultInputHandler(); + } + + /** + * Sets the input handler + */ + public function setInputHandler(InputHandler $handler) { + $this->inputHandler = $handler; + } + + /** + * Retrieves the current input handler. + */ + public function getInputHandler() { + return $this->inputHandler; + } + + /** inits the project, called from main app */ + function init() { + // set builtin properties + $this->setSystemProperties(); + + // load default tasks + $taskdefs = Phing::getResourcePath("phing/tasks/defaults.properties"); + + try { // try to load taskdefs + $props = new Properties(); + $in = new PhingFile((string)$taskdefs); + + if ($in === null) { + throw new BuildException("Can't load default task list"); + } + $props->load($in); + + $enum = $props->propertyNames(); + foreach($enum as $key) { + $value = $props->getProperty($key); + $this->addTaskDefinition($key, $value); + } + } catch (IOException $ioe) { + throw new BuildException("Can't load default task list"); + } + + // load default tasks + $typedefs = Phing::getResourcePath("phing/types/defaults.properties"); + + try { // try to load typedefs + $props = new Properties(); + $in = new PhingFile((string)$typedefs); + if ($in === null) { + throw new BuildException("Can't load default datatype list"); + } + $props->load($in); + + $enum = $props->propertyNames(); + foreach($enum as $key) { + $value = $props->getProperty($key); + $this->addDataTypeDefinition($key, $value); + } + } catch(IOException $ioe) { + throw new BuildException("Can't load default datatype list"); + } + } + + /** returns the global filterset (future use) */ + function getGlobalFilterSet() { + return $this->globalFilterSet; + } + + // --------------------------------------------------------- + // Property methods + // --------------------------------------------------------- + + /** + * Sets a property. Any existing property of the same name + * is overwritten, unless it is a user property. + * @param string $name The name of property to set. + * Must not be null. + * @param string $value The new value of the property. + * Must not be null. + * @return void + */ + public function setProperty($name, $value) { + + // command line properties take precedence + if (isset($this->userProperties[$name])) { + $this->log("Override ignored for user property " . $name, Project::MSG_VERBOSE); + return; + } + + if (isset($this->properties[$name])) { + $this->log("Overriding previous definition of property " . $name, Project::MSG_VERBOSE); + } + + $this->log("Setting project property: " . $name . " -> " . $value, Project::MSG_DEBUG); + $this->properties[$name] = $value; + } + + /** + * Sets a property if no value currently exists. If the property + * exists already, a message is logged and the method returns with + * no other effect. + * + * @param string $name The name of property to set. + * Must not be null. + * @param string $value The new value of the property. + * Must not be null. + * @since 2.0 + */ + public function setNewProperty($name, $value) { + if (isset($this->properties[$name])) { + $this->log("Override ignored for property " . $name, Project::MSG_DEBUG); + return; + } + $this->log("Setting project property: " . $name . " -> " . $value, Project::MSG_DEBUG); + $this->properties[$name] = $value; + } + + /** + * Sets a user property, which cannot be overwritten by + * set/unset property calls. Any previous value is overwritten. + * @param string $name The name of property to set. + * Must not be null. + * @param string $value The new value of the property. + * Must not be null. + * @see #setProperty() + */ + public function setUserProperty($name, $value) { + $this->log("Setting ro project property: " . $name . " -> " . $value, Project::MSG_DEBUG); + $this->userProperties[$name] = $value; + $this->properties[$name] = $value; + } + + /** + * Sets a user property, which cannot be overwritten by set/unset + * property calls. Any previous value is overwritten. Also marks + * these properties as properties that have not come from the + * command line. + * + * @param string $name The name of property to set. + * Must not be null. + * @param string $value The new value of the property. + * Must not be null. + * @see #setProperty() + */ + public function setInheritedProperty($name, $value) { + $this->inheritedProperties[$name] = $value; + $this->setUserProperty($name, $value); + } + + /** + * Sets a property unless it is already defined as a user property + * (in which case the method returns silently). + * + * @param name The name of the property. + * Must not be null. + * @param value The property value. Must not be null. + */ + private function setPropertyInternal($name, $value) { + if (isset($this->userProperties[$name])) { + $this->log("Override ignored for user property " . $name, Project::MSG_VERBOSE); + return; + } + $this->properties[$name] = $value; + } + + /** + * Returns the value of a property, if it is set. + * + * @param string $name The name of the property. + * May be null, in which case + * the return value is also null. + * @return string The property value, or null for no match + * or if a null name is provided. + */ + public function getProperty($name) { + if (!isset($this->properties[$name])) { + return null; + } + $found = $this->properties[$name]; + // check to see if there are unresolved property references + if (false !== strpos($found, '${')) { + // attempt to resolve properties + $found = $this->replaceProperties($found); + // save resolved value + $this->properties[$name] = $found; + } + return $found; + } + + /** + * Replaces ${} style constructions in the given value with the + * string value of the corresponding data types. + * + * @param value The string to be scanned for property references. + * May be null. + * + * @return the given string with embedded property names replaced + * by values, or null if the given string is + * null. + * + * @exception BuildException if the given value has an unclosed + * property name, e.g. ${xxx + */ + public function replaceProperties($value) { + return ProjectConfigurator::replaceProperties($this, $value, $this->properties); + } + + /** + * Returns the value of a user property, if it is set. + * + * @param string $name The name of the property. + * May be null, in which case + * the return value is also null. + * @return string The property value, or null for no match + * or if a null name is provided. + */ + public function getUserProperty($name) { + if (!isset($this->userProperties[$name])) { + return null; + } + return $this->userProperties[$name]; + } + + /** + * Returns a copy of the properties table. + * @return array A hashtable containing all properties + * (including user properties). + */ + public function getProperties() { + return $this->properties; + } + + /** + * Returns a copy of the user property hashtable + * @return a hashtable containing just the user properties + */ + public function getUserProperties() { + return $this->userProperties; + } + + /** + * Copies all user properties that have been set on the command + * line or a GUI tool from this instance to the Project instance + * given as the argument. + * + *

    To copy all "user" properties, you will also have to call + * {@link #copyInheritedProperties copyInheritedProperties}.

    + * + * @param Project $other the project to copy the properties to. Must not be null. + * @return void + * @since phing 2.0 + */ + public function copyUserProperties(Project $other) { + foreach($this->userProperties as $arg => $value) { + if (isset($this->inheritedProperties[$arg])) { + continue; + } + $other->setUserProperty($arg, $value); + } + } + + /** + * Copies all user properties that have not been set on the + * command line or a GUI tool from this instance to the Project + * instance given as the argument. + * + *

    To copy all "user" properties, you will also have to call + * {@link #copyUserProperties copyUserProperties}.

    + * + * @param other the project to copy the properties to. Must not be null. + * + * @since phing 2.0 + */ + public function copyInheritedProperties(Project $other) { + foreach($this->userProperties as $arg => $value) { + if ($other->getUserProperty($arg) !== null) { + continue; + } + $other->setInheritedProperty($arg, $value); + } + } + + // --------------------------------------------------------- + // END Properties methods + // --------------------------------------------------------- + + + function setDefaultTarget($targetName) { + $this->defaultTarget = (string) trim($targetName); + } + + function getDefaultTarget() { + return (string) $this->defaultTarget; + } + + /** + * Sets the name of the current project + * + * @param string name of project + * @return void + * @access public + * @author Andreas Aderhold, andi@binarycloud.com + */ + + function setName($name) { + $this->name = (string) trim($name); + $this->setProperty("phing.project.name", $this->name); + } + + /** + * Returns the name of this project + * + * @returns string projectname + * @access public + * @author Andreas Aderhold, andi@binarycloud.com + */ + function getName() { + return (string) $this->name; + } + + /** Set the projects description */ + function setDescription($description) { + $this->description = (string) trim($description); + } + + /** return the description, null otherwise */ + function getDescription() { + return $this->description; + } + + /** Set the minimum required phing version **/ + function setPhingVersion($version) { + $version = str_replace('phing', '', strtolower($version)); + $this->phingVersion = (string)trim($version); + } + + /** Get the minimum required phing version **/ + function getPhingVersion() { + if($this->phingVersion === null) { + $this->setPhingVersion(Phing::getPhingVersion()); + } + return $this->phingVersion; + } + + /** Set basedir object from xml*/ + function setBasedir($dir) { + if ($dir instanceof PhingFile) { + $dir = $dir->getAbsolutePath(); + } + + $dir = $this->fileUtils->normalize($dir); + + $dir = new PhingFile((string) $dir); + if (!$dir->exists()) { + throw new BuildException("Basedir ".$dir->getAbsolutePath()." does not exist"); + } + if (!$dir->isDirectory()) { + throw new BuildException("Basedir ".$dir->getAbsolutePath()." is not a directory"); + } + $this->basedir = $dir; + $this->setPropertyInternal("project.basedir", $this->basedir->getAbsolutePath()); + $this->log("Project base dir set to: " . $this->basedir->getPath(), Project::MSG_VERBOSE); + + // [HL] added this so that ./ files resolve correctly. This may be a mistake ... or may be in wrong place. + chdir($dir->getAbsolutePath()); + } + + /** + * Returns the basedir of this project + * + * @returns PhingFile Basedir PhingFile object + * @access public + * @throws BuildException + * @author Andreas Aderhold, andi@binarycloud.com + */ + function getBasedir() { + if ($this->basedir === null) { + try { // try to set it + $this->setBasedir("."); + } catch (BuildException $exc) { + throw new BuildException("Can not set default basedir. ".$exc->getMessage()); + } + } + return $this->basedir; + } + + /** + * Sets system properties and the environment variables for this project. + * + * @return void + */ + function setSystemProperties() { + + // first get system properties + $systemP = array_merge( self::getProperties(), Phing::getProperties() ); + foreach($systemP as $name => $value) { + $this->setPropertyInternal($name, $value); + } + + // and now the env vars + foreach($_SERVER as $name => $value) { + // skip arrays + if (is_array($value)) { + continue; + } + $this->setPropertyInternal('env.' . $name, $value); + } + return true; + } + + + /** + * Adds a task definition. + * @param string $name Name of tag. + * @param string $class The class path to use. + * @param string $classpath The classpat to use. + */ + function addTaskDefinition($name, $class, $classpath = null) { + $name = $name; + $class = $class; + if ($class === "") { + $this->log("Task $name has no class defined.", Project::MSG_ERR); + } elseif (!isset($this->taskdefs[$name])) { + Phing::import($class, $classpath); + $this->taskdefs[$name] = $class; + $this->log(" +Task definiton: $name ($class)", Project::MSG_DEBUG); + } else { + $this->log("Task $name ($class) already registerd, skipping", Project::MSG_VERBOSE); + } + } + + function &getTaskDefinitions() { + return $this->taskdefs; + } + + /** + * Adds a data type definition. + * @param string $name Name of tag. + * @param string $class The class path to use. + * @param string $classpath The classpat to use. + */ + function addDataTypeDefinition($typeName, $typeClass, $classpath = null) { + if (!isset($this->typedefs[$typeName])) { + Phing::import($typeClass, $classpath); + $this->typedefs[$typeName] = $typeClass; + $this->log(" +User datatype: $typeName ($typeClass)", Project::MSG_DEBUG); + } else { + $this->log("Type $typeName ($typeClass) already registerd, skipping", Project::MSG_VERBOSE); + } + } + + function getDataTypeDefinitions() { + return $this->typedefs; + } + + /** add a new target to the project */ + function addTarget($targetName, &$target) { + if (isset($this->targets[$targetName])) { + throw new BuildException("Duplicate target: $targetName"); + } + $this->addOrReplaceTarget($targetName, $target); + } + + function addOrReplaceTarget($targetName, &$target) { + $this->log(" +Target: $targetName", Project::MSG_DEBUG); + $target->setProject($this); + $this->targets[$targetName] = $target; + + $ctx = $this->getReference("phing.parsing.context"); + $current = $ctx->getConfigurator()->getCurrentTargets(); + $current[$targetName] = $target; + } + + function getTargets() { + return $this->targets; + } + + /** + * Create a new task instance and return reference to it. This method is + * sorta factory like. A _local_ instance is created and a reference returned to + * that instance. Usually PHP destroys local variables when the function call + * ends. But not if you return a reference to that variable. + * This is kinda error prone, because if no reference exists to the variable + * it is destroyed just like leaving the local scope with primitive vars. There's no + * central place where the instance is stored as in other OOP like languages. + * + * [HL] Well, ZE2 is here now, and this is still working. We'll leave this alone + * unless there's any good reason not to. + * + * @param string $taskType Task name + * @returns Task A task object + * @throws BuildException + * Exception + */ + function createTask($taskType) { + try { + $classname = ""; + $tasklwr = strtolower($taskType); + foreach ($this->taskdefs as $name => $class) { + if (strtolower($name) === $tasklwr) { + $classname = $class; + break; + } + } + + if ($classname === "") { + return null; + } + + $cls = Phing::import($classname); + + if (!class_exists($cls)) { + throw new BuildException("Could not instantiate class $cls, even though a class was specified. (Make sure that the specified class file contains a class with the correct name.)"); + } + + $o = new $cls(); + + if ($o instanceof Task) { + $task = $o; + } else { + $this->log (" (Using TaskAdapter for: $taskType)", Project::MSG_DEBUG); + // not a real task, try adapter + $taskA = new TaskAdapter(); + $taskA->setProxy($o); + $task = $taskA; + } + $task->setProject($this); + $task->setTaskType($taskType); + // set default value, can be changed by the user + $task->setTaskName($taskType); + $this->log (" +Task: " . $taskType, Project::MSG_DEBUG); + } catch (Exception $t) { + throw new BuildException("Could not create task of type: " . $taskType, $t); + } + // everything fine return reference + return $task; + } + + /** + * Create a datatype instance and return reference to it + * See createTask() for explanation how this works + * + * @param string Type name + * @returns object A datatype object + * @throws BuildException + * Exception + */ + function createDataType($typeName) { + try { + $cls = ""; + $typelwr = strtolower($typeName); + foreach ($this->typedefs as $name => $class) { + if (strtolower($name) === $typelwr) { + $cls = StringHelper::unqualify($class); + break; + } + } + + if ($cls === "") { + return null; + } + + if (!class_exists($cls)) { + throw new BuildException("Could not instantiate class $cls, even though a class was specified. (Make sure that the specified class file contains a class with the correct name.)"); + } + + $type = new $cls(); + $this->log(" +Type: $typeName", Project::MSG_DEBUG); + if (!($type instanceof DataType)) { + throw new Exception("$class is not an instance of phing.types.DataType"); + } + if ($type instanceof ProjectComponent) { + $type->setProject($this); + } + } catch (Exception $t) { + throw new BuildException("Could not create type: $typeName", $t); + } + // everything fine return reference + return $type; + } + + /** + * Executes a list of targets + * + * @param array List of target names to execute + * @returns void + * @throws BuildException + */ + function executeTargets($targetNames) { + foreach($targetNames as $tname) { + $this->executeTarget($tname); + } + } + + /** + * Executes a target + * + * @param string Name of Target to execute + * @returns void + * @throws BuildException + */ + function executeTarget($targetName) { + + // complain about executing void + if ($targetName === null) { + throw new BuildException("No target specified"); + } + + // invoke topological sort of the target tree and run all targets + // until targetName occurs. + $sortedTargets = $this->_topoSort($targetName, $this->targets); + + $curIndex = (int) 0; + $curTarget = null; + do { + try { + $curTarget = $sortedTargets[$curIndex++]; + $curTarget->performTasks(); + } catch (BuildException $exc) { + $this->log("Execution of target \"".$curTarget->getName()."\" failed for the following reason: ".$exc->getMessage(), Project::MSG_ERR); + throw $exc; + } + } while ($curTarget->getName() !== $targetName); + } + + + function resolveFile($fileName, $rootDir = null) { + if ($rootDir === null) { + return $this->fileUtils->resolveFile($this->basedir, $fileName); + } else { + return $this->fileUtils->resolveFile($rootDir, $fileName); + } + } + + /** + * Topologically sort a set of Targets. + * @param $root is the (String) name of the root Target. The sort is + * created in such a way that the sequence of Targets until the root + * target is the minimum possible such sequence. + * @param $targets is a array representing a "name to Target" mapping + * @return An array of Strings with the names of the targets in + * sorted order. + */ + function _topoSort($root, &$targets) { + + $root = (string) $root; + $ret = array(); + $state = array(); + $visiting = array(); + + // We first run a DFS based sort using the root as the starting node. + // This creates the minimum sequence of Targets to the root node. + // We then do a sort on any remaining unVISITED targets. + // This is unnecessary for doing our build, but it catches + // circular dependencies or missing Targets on the entire + // dependency tree, not just on the Targets that depend on the + // build Target. + + $this->_tsort($root, $targets, $state, $visiting, $ret); + + $retHuman = ""; + for ($i=0, $_i=count($ret); $i < $_i; $i++) { + $retHuman .= $ret[$i]->toString()." "; + } + $this->log("Build sequence for target '$root' is: $retHuman", Project::MSG_VERBOSE); + + $keys = array_keys($targets); + while($keys) { + $curTargetName = (string) array_shift($keys); + if (!isset($state[$curTargetName])) { + $st = null; + } else { + $st = (string) $state[$curTargetName]; + } + + if ($st === null) { + $this->_tsort($curTargetName, $targets, $state, $visiting, $ret); + } elseif ($st === "VISITING") { + throw new Exception("Unexpected node in visiting state: $curTargetName"); + } + } + + $retHuman = ""; + for ($i=0,$_i=count($ret); $i < $_i; $i++) { + $retHuman .= $ret[$i]->toString()." "; + } + $this->log("Complete build sequence is: $retHuman", Project::MSG_VERBOSE); + + return $ret; + } + + // one step in a recursive DFS traversal of the target dependency tree. + // - The array "state" contains the state (VISITED or VISITING or null) + // of all the target names. + // - The stack "visiting" contains a stack of target names that are + // currently on the DFS stack. (NB: the target names in "visiting" are + // exactly the target names in "state" that are in the VISITING state.) + // 1. Set the current target to the VISITING state, and push it onto + // the "visiting" stack. + // 2. Throw a BuildException if any child of the current node is + // in the VISITING state (implies there is a cycle.) It uses the + // "visiting" Stack to construct the cycle. + // 3. If any children have not been VISITED, tsort() the child. + // 4. Add the current target to the Vector "ret" after the children + // have been visited. Move the current target to the VISITED state. + // "ret" now contains the sorted sequence of Targets upto the current + // Target. + + function _tsort($root, &$targets, &$state, &$visiting, &$ret) { + $state[$root] = "VISITING"; + $visiting[] = $root; + + if (!isset($targets[$root]) || !($targets[$root] instanceof Target)) { + $target = null; + } else { + $target = $targets[$root]; + } + + // make sure we exist + if ($target === null) { + $sb = "Target '$root' does not exist in this project."; + array_pop($visiting); + if (!empty($visiting)) { + $parent = (string) $visiting[count($visiting)-1]; + $sb .= "It is used from target '$parent'."; + } + throw new BuildException($sb); + } + + $deps = $target->getDependencies(); + + while($deps) { + $cur = (string) array_shift($deps); + if (!isset($state[$cur])) { + $m = null; + } else { + $m = (string) $state[$cur]; + } + if ($m === null) { + // not been visited + $this->_tsort($cur, $targets, $state, $visiting, $ret); + } elseif ($m == "VISITING") { + // currently visiting this node, so have a cycle + throw $this->_makeCircularException($cur, $visiting); + } + } + + $p = (string) array_pop($visiting); + if ($root !== $p) { + throw new Exception("Unexpected internal error: expected to pop $root but got $p"); + } + + $state[$root] = "VISITED"; + $ret[] = $target; + } + + function _makeCircularException($end, $stk) { + $sb = "Circular dependency: $end"; + do { + $c = (string) array_pop($stk); + $sb .= " <- ".$c; + } while($c != $end); + return new BuildException($sb); + } + + /** + * Adds a reference to an object. This method is called when the parser + * detects a id="foo" attribute. It passes the id as $name and a reference + * to the object assigned to this id as $value + */ + function addReference($name, $object) { + if (isset($this->references[$name])) { + $this->log("Overriding previous definition of reference to $name", Project::MSG_WARN); + } + $this->log("Adding reference: $name -> ".get_class($object), Project::MSG_DEBUG); + $this->references[$name] = $object; + } + + /** + * Returns the references array. + * @return array + */ + function getReferences() { + return $this->references; + } + + /** + * Returns a specific reference. + * @param string $key The reference id/key. + * @return Reference or null if not defined + */ + function getReference($key) + { + if (isset($this->references[$key])) { + return $this->references[$key]; + } + return null; // just to be explicit + } + + /** + * Abstracting and simplifyling Logger calls for project messages + */ + function log($msg, $level = Project::MSG_INFO) { + $this->logObject($this, $msg, $level); + } + + function logObject($obj, $msg, $level) { + $this->fireMessageLogged($obj, $msg, $level); + } + + function addBuildListener(BuildListener $listener) { + $this->listeners[] = $listener; + } + + function removeBuildListener(BuildListener $listener) { + $newarray = array(); + for ($i=0, $size=count($this->listeners); $i < $size; $i++) { + if ($this->listeners[$i] !== $listener) { + $newarray[] = $this->listeners[$i]; + } + } + $this->listeners = $newarray; + } + + function getBuildListeners() { + return $this->listeners; + } + + function fireBuildStarted() { + $event = new BuildEvent($this); + foreach($this->listeners as $listener) { + $listener->buildStarted($event); + } + } + + function fireBuildFinished($exception) { + $event = new BuildEvent($this); + $event->setException($exception); + foreach($this->listeners as $listener) { + $listener->buildFinished($event); + } + } + + function fireTargetStarted($target) { + $event = new BuildEvent($target); + foreach($this->listeners as $listener) { + $listener->targetStarted($event); + } + } + + function fireTargetFinished($target, $exception) { + $event = new BuildEvent($target); + $event->setException($exception); + foreach($this->listeners as $listener) { + $listener->targetFinished($event); + } + } + + function fireTaskStarted($task) { + $event = new BuildEvent($task); + foreach($this->listeners as $listener) { + $listener->taskStarted($event); + } + } + + function fireTaskFinished($task, $exception) { + $event = new BuildEvent($task); + $event->setException($exception); + foreach($this->listeners as $listener) { + $listener->taskFinished($event); + } + } + + function fireMessageLoggedEvent($event, $message, $priority) { + $event->setMessage($message, $priority); + foreach($this->listeners as $listener) { + $listener->messageLogged($event); + } + } + + function fireMessageLogged($object, $message, $priority) { + $this->fireMessageLoggedEvent(new BuildEvent($object), $message, $priority); + } +} diff --git a/library/phing/ProjectComponent.php b/library/phing/ProjectComponent.php new file mode 100644 index 000000000..a1e270883 --- /dev/null +++ b/library/phing/ProjectComponent.php @@ -0,0 +1,70 @@ +. + */ + +/** + * Abstract class providing properties and methods common to all + * the project components + * + * @author Andreas Aderhold + * @author Hans Lellelid + * @version $Revision: 905 $ + * @package phing + */ +abstract class ProjectComponent { + + /** + * Holds a reference to the project that a project component + * (a task, a target, etc.) belongs to + * + * @var Project A reference to the current project instance + */ + protected $project = null; + + /** + * References the project to the current component. + * + * @param Project $project The reference to the current project + */ + public function setProject($project) { + $this->project = $project; + } + + /** + * Returns a reference to current project + * + * @return Project Reference to current porject object + */ + public function getProject() { + return $this->project; + } + + /** + * Logs a message with the given priority. + * + * @param string $msg The message to be logged. + * @param integer $level The message's priority at this message should have + */ + public function log($msg, $level = Project::MSG_INFO) { + if ($this->project !== null) { + $this->project->log($msg, $level); + } + } +} diff --git a/library/phing/RuntimeConfigurable.php b/library/phing/RuntimeConfigurable.php new file mode 100644 index 000000000..e2f7ccc6e --- /dev/null +++ b/library/phing/RuntimeConfigurable.php @@ -0,0 +1,118 @@ +. + */ + +/** + * Wrapper class that holds the attributes of a Task (or elements + * nested below that level) and takes care of configuring that element + * at runtime. + * + * SMART-UP INLINE DOCS + * + * @author Andreas Aderhold + * @author Hans Lellelid + * @version $Revision: 905 $ + * @package phing + */ +class RuntimeConfigurable { + + private $elementTag = null; + private $children = array(); + private $wrappedObject = null; + private $attributes = array(); + private $characters = ""; + + + /** @param proxy The element to wrap. */ + function __construct($proxy, $elementTag) { + $this->wrappedObject = $proxy; + $this->elementTag = $elementTag; + } + + function setProxy($proxy) { + $this->wrappedObject = $proxy; + } + + /** Set's the attributes for the wrapped element. */ + function setAttributes($attributes) { + $this->attributes = $attributes; + } + + /** Returns the AttributeList of the wrapped element. */ + function getAttributes() { + return $this->attributes; + } + + /** Adds child elements to the wrapped element. */ + function addChild(RuntimeConfigurable $child) { + $this->children[] = $child; + } + + /** Returns the child with index */ + function getChild($index) { + return $this->children[(int)$index]; + } + + /** Add characters from #PCDATA areas to the wrapped element. */ + function addText($data) { + $this->characters .= (string) $data; + } + + function getElementTag() { + return $this->elementTag; + } + + + /** Configure the wrapped element and all children. */ + function maybeConfigure(Project $project) { + $id = null; + + // DataType configured in ProjectConfigurator + // if ( is_a($this->wrappedObject, "DataType") ) + // return; + + if ($this->attributes || $this->characters) { + ProjectConfigurator::configure($this->wrappedObject, $this->attributes, $project); + + if (isset($this->attributes["id"])) { + $id = $this->attributes["id"]; + } + + $this->attributes = null; + + if ($this->characters) { + ProjectConfigurator::addText($project, $this->wrappedObject, (string) $this->characters); + $this->characters=""; + } + if ($id !== null) { + $project->addReference($id, $this->wrappedObject); + } + } + + if ( is_array($this->children) && !empty($this->children) ) { + // Configure all child of this object ... + foreach($this->children as $child) { + $child->maybeConfigure($project); + ProjectConfigurator::storeChild($project, $this->wrappedObject, $child->wrappedObject, strtolower($child->getElementTag())); + } + } + } +} + diff --git a/library/phing/Target.php b/library/phing/Target.php new file mode 100644 index 000000000..bcb78cf41 --- /dev/null +++ b/library/phing/Target.php @@ -0,0 +1,317 @@ +. + */ + +include_once 'phing/TaskContainer.php'; + +/** + * The Target component. Carries all required target data. Implements the + * abstract class {@link TaskContainer} + * + * @author Andreas Aderhold + * @copyright � 2001,2002 THYRELL. All rights reserved + * @version $Revision: 905 $ $Date: 2010-10-05 18:28:03 +0200 (Tue, 05 Oct 2010) $ + * @access public + * @see TaskContainer + * @package phing + */ + +class Target implements TaskContainer { + + /** name of target */ + private $name; + + /** dependencies */ + private $dependencies = array(); + + /** holds objects of children of this target */ + private $children = array(); + + /** the if cond. from xml */ + private $ifCondition = ""; + + /** the unless cond. from xml */ + private $unlessCondition = ""; + + /** description of this target */ + private $description; + + /** reference to project */ + private $project; + + /** + * References the project to the current component. + * + * @param Project The reference to the current project + */ + public function setProject(Project $project) { + $this->project = $project; + } + + /** + * Returns reference to current project + * + * @return Project Reference to current porject object + */ + public function getProject() { + return $this->project; + } + + /** + * Sets the target dependencies from xml + * + * @param string $depends Comma separated list of targetnames that depend on + * this target + * @throws BuildException + */ + public function setDepends($depends) { + // explode should be faster than strtok + $deps = explode(',', $depends); + for ($i=0, $size=count($deps); $i < $size; $i++) { + $trimmed = trim($deps[$i]); + if ($trimmed === "") { + throw new BuildException("Syntax Error: Depend attribute for target ".$this->getName()." is malformed."); + } + $this->addDependency($trimmed); + } + } + + /** + * Adds a singular dependent target name to the list + * + * @param string The dependency target to add + * @access public + */ + public function addDependency($dependency) { + $this->dependencies[] = (string) $dependency; + } + + /** + * Returns reference to indexed array of the dependencies this target has. + * + * @return array Referece to target dependencoes + */ + public function getDependencies() { + return $this->dependencies; + } + + /** + * Sets the name of the target + * + * @param string Name of this target + */ + public function setName($name) { + $this->name = (string) $name; + } + + /** + * Returns name of this target. + * + * @return string The name of the target + * @access public + */ + function getName() { + return (string) $this->name; + } + + /** + * Adds a task element to the list of this targets child elements + * + * @param object The task object to add + * @access public + */ + function addTask(Task $task) { + $this->children[] = $task; + } + + /** + * Adds a runtime configurable element to the list of this targets child + * elements. + * + * @param object The RuntimeConfigurabel object + * @access public + */ + function addDataType($rtc) { + $this->children[] = $rtc; + } + + /** + * Returns an array of all tasks this target has as childrens. + * + * The task objects are copied here. Don't use this method to modify + * task objects. + * + * @return array Task[] + */ + public function getTasks() { + $tasks = array(); + for ($i=0,$size=count($this->children); $i < $size; $i++) { + $tsk = $this->children[$i]; + if ($tsk instanceof Task) { + // note: we're copying objects here! + $tasks[] = clone $tsk; + } + } + return $tasks; + } + + /** + * Set the if-condition from the XML tag, if any. The property name given + * as parameter must be present so the if condition evaluates to true + * + * @param string The property name that has to be present + * @access public + */ + public function setIf($property) { + $this->ifCondition = ($property === null) ? "" : $property; + } + + /** + * Set the unless-condition from the XML tag, if any. The property name + * given as parameter must be present so the unless condition evaluates + * to true + * + * @param string The property name that has to be present + * @access public + */ + public function setUnless($property) { + $this->unlessCondition = ($property === null) ? "" : $property; + } + + /** + * Sets a textual description of this target. + * + * @param string The description text + */ + public function setDescription($description) { + if ($description !== null && strcmp($description, "") !== 0) { + $this->description = (string) $description; + } else { + $this->description = null; + } + } + + /** + * Returns the description of this target. + * + * @return string The description text of this target + */ + public function getDescription() { + return $this->description; + } + + /** + * Returns a string representation of this target. In our case it + * simply returns the target name field + * + * @return string The string representation of this target + */ + function toString() { + return (string) $this->name; + } + + /** + * The entry point for this class. Does some checking, then processes and + * performs the tasks for this target. + * + */ + public function main() { + if ($this->testIfCondition() && $this->testUnlessCondition()) { + foreach($this->children as $o) { + if ($o instanceof Task) { + // child is a task + $o->perform(); + } else { + // child is a RuntimeConfigurable + $o->maybeConfigure($this->project); + } + } + } elseif (!$this->testIfCondition()) { + $this->project->log("Skipped target '".$this->name."' because property '".$this->ifCondition."' not set.", Project::MSG_VERBOSE); + } else { + $this->project->log("Skipped target '".$this->name."' because property '".$this->unlessCondition."' set.", Project::MSG_VERBOSE); + } + } + + /** + * Performs the tasks by calling the main method of this target that + * actually executes the tasks. + * + * This method is for ZE2 and used for proper exception handling of + * task exceptions. + */ + public function performTasks() { + try {// try to execute this target + $this->project->fireTargetStarted($this); + $this->main(); + $this->project->fireTargetFinished($this, $null=null); + } catch (BuildException $exc) { + // log here and rethrow + $this->project->fireTargetFinished($this, $exc); + throw $exc; + } + } + + /** + * Tests if the property set in ifConfiditon exists. + * + * @return boolean true if the property specified + * in $this->ifCondition exists; + * false otherwise + */ + private function testIfCondition() { + if ($this->ifCondition === "") { + return true; + } + + $properties = explode(",", $this->ifCondition); + + $result = true; + foreach ($properties as $property) { + $test = ProjectConfigurator::replaceProperties($this->getProject(), $property, $this->project->getProperties()); + $result = $result && ($this->project->getProperty($test) !== null); + } + + return $result; + } + + /** + * Tests if the property set in unlessCondition exists. + * + * @return boolean true if the property specified + * in $this->unlessCondition exists; + * false otherwise + */ + private function testUnlessCondition() { + if ($this->unlessCondition === "") { + return true; + } + + $properties = explode(",", $this->unlessCondition); + + $result = true; + foreach ($properties as $property) { + $test = ProjectConfigurator::replaceProperties($this->getProject(), $property, $this->project->getProperties()); + $result = $result && ($this->project->getProperty($test) === null); + } + return $result; + } + +} diff --git a/library/phing/Task.php b/library/phing/Task.php new file mode 100644 index 000000000..9e49c3671 --- /dev/null +++ b/library/phing/Task.php @@ -0,0 +1,265 @@ +. + */ + +require_once 'phing/ProjectComponent.php'; +include_once 'phing/RuntimeConfigurable.php'; + +/** + * The base class for all Tasks. + * + * Use {@link Project#createTask} to register a new Task. + * + * @author Andreas Aderhold + * @copyright � 2001,2002 THYRELL. All rights reserved + * @version $Revision: 905 $ + * @see Project#createTask() + * @package phing + */ +abstract class Task extends ProjectComponent { + + /** + * @var Target owning Target object + */ + protected $target; + + /** + * @var string description of the task + */ + protected $description; + + /** + * @var string internal taskname (req) + */ + protected $taskType; + + /** + * @var string Taskname for logger + */ + protected $taskName; + + /** + * @var Location stored buildfile location + */ + protected $location; + + /** + * @var RuntimeConfigurable wrapper of the task + */ + protected $wrapper; + + /** + * Sets the owning target this task belongs to. + * + * @param Target Reference to owning target + */ + public function setOwningTarget(Target $target) { + $this->target = $target; + } + + /** + * Returns the owning target of this task. + * + * @return Target The target object that owns this task + */ + public function getOwningTarget() { + return $this->target; + } + + /** + * Returns the name of task, used only for log messages + * + * @return string Name of this task + */ + public function getTaskName() { + if ($this->taskName === null) { + // if no task name is set, then it's possible + // this task was created from within another task. We don't + // therefore know the XML tag name for this task, so we'll just + // use the class name stripped of "task" suffix. This is only + // for log messages, so we don't have to worry much about accuracy. + return preg_replace('/task$/i', '', get_class($this)); + } + return $this->taskName; + } + + /** + * Sets the name of this task for log messages + * + * @return string A string representing the name of this task for log + */ + public function setTaskName($name) { + $this->taskName = (string) $name; + } + + /** + * Returns the name of the task under which it was invoked, + * usually the XML tagname + * + * @return string The type of this task (XML Tag) + */ + public function getTaskType() { + return $this->taskType; + } + + /** + * Sets the type of the task. Usually this is the name of the XML tag + * + * @param string The type of this task (XML Tag) + */ + public function setTaskType($name) { + $this->taskType = (string) $name; + } + + /** + * Returns a name + * + */ + protected function getRegisterSlot($slotName) { + return Register::getSlot('task.' . $this->getTaskName() . '.' . $slotName); + } + + /** + * Provides a project level log event to the task. + * + * @param string The message to log + * @param integer The priority of the message + * @see BuildEvent + * @see BuildListener + */ + function log($msg, $level = Project::MSG_INFO) { + $this->project->logObject($this, $msg, $level); + } + + /** + * Sets a textual description of the task + * + * @param string $desc The text describing the task + */ + public function setDescription($desc) { + $this->description = $desc; + } + + /** + * Returns the textual description of the task + * + * @return string The text description of the task + */ + public function getDescription() { + return $this->description; + } + + /** + * Called by the parser to let the task initialize properly. + * Should throw a BuildException if something goes wrong with the build + * + * This is abstract here, but may not be overloaded by subclasses. + * + * @throws BuildException + */ + public function init() { + } + + /** + * Called by the project to let the task do it's work. This method may be + * called more than once, if the task is invoked more than once. For + * example, if target1 and target2 both depend on target3, then running + * phing target1 target2 will run all tasks in target3 twice. + * + * Should throw a BuildException if someting goes wrong with the build + * + * This is abstract here. Must be overloaded by real tasks. + */ + abstract public function main(); + + /** + * Returns the location within the buildfile this task occurs. Used + * by {@link BuildException} to give detailed error messages. + * + * @return Location The location object describing the position of this + * task within the buildfile. + */ + function getLocation() { + return $this->location; + } + + /** + * Sets the location within the buildfile this task occurs. Called by + * the parser to set location information. + * + * @param Location $location The location object describing the position of this + * task within the buildfile. + */ + function setLocation(Location $location) { + $this->location = $location; + } + + /** + * Returns the wrapper object for runtime configuration + * + * @return RuntimeConfigurable The wrapper object used by this task + */ + function getRuntimeConfigurableWrapper() { + if ($this->wrapper === null) { + $this->wrapper = new RuntimeConfigurable($this, $this->getTaskName()); + } + return $this->wrapper; + } + + /** + * Sets the wrapper object this task should use for runtime + * configurable elements. + * + * @param RuntimeConfigurable $wrapper The wrapper object this task should use + */ + function setRuntimeConfigurableWrapper(RuntimeConfigurable $wrapper) { + $this->wrapper = $wrapper; + } + + /** + * Configure this task if it hasn't been done already. + */ + public function maybeConfigure() { + if ($this->wrapper !== null) { + $this->wrapper->maybeConfigure($this->project); + } + } + + /** + * Perfrom this task + */ + public function perform() { + + try { // try executing task + $this->project->fireTaskStarted($this); + $this->maybeConfigure(); + $this->main(); + $this->project->fireTaskFinished($this, $null=null); + } catch (Exception $exc) { + if ($exc instanceof BuildException) { + if ($exc->getLocation() === null) { + $exc->setLocation($this->getLocation()); + } + } + $this->project->fireTaskFinished($this, $exc); + throw $exc; + } + } +} diff --git a/library/phing/TaskAdapter.php b/library/phing/TaskAdapter.php new file mode 100644 index 000000000..ad13f1683 --- /dev/null +++ b/library/phing/TaskAdapter.php @@ -0,0 +1,84 @@ +. + */ + +require_once 'phing/Task.php'; + +/** + * Use introspection to "adapt" an arbitrary ( not extending Task, but with + * similar patterns). + * + * @author Andreas Aderhold + * @copyright © 2001,2002 THYRELL. All rights reserved + * @version $Revision: 905 $ + * @package phing + */ +class TaskAdapter extends Task { + + /** target object */ + private $proxy; + + /** + * Main entry point. + * @return void + */ + function main() { + + if (method_exists($this->proxy, "setProject")) { + try { // try to set project + $this->proxy->setProject($this->project); + } catch (Exception $ex) { + $this->log("Error setting project in " . get_class($this->proxy) . Project::MSG_ERR); + throw new BuildException($ex); + } + } else { + throw new Exception("Error setting project in class " . get_class($this->proxy)); + } + + if (method_exists($this->proxy, "main")) { + try { //try to call main + $this->proxy->main($this->project); + } catch (Exception $ex) { + $this->log("Error in " . get_class($this->proxy), Project::MSG_ERR); + throw new BuildException($ex->getMessage()); + } + } else { + throw new BuildException("Your task-like class '" . get_class($this->proxy) ."' does not have a main() method"); + } + } + + /** + * Set the target object. + * @param object $o + * @return void + */ + function setProxy($o) { + $this->proxy = $o; + } + + /** + * Gets the target object. + * @return object + */ + function getProxy() { + return $this->proxy; + } + +} diff --git a/library/phing/TaskContainer.php b/library/phing/TaskContainer.php new file mode 100644 index 000000000..0ca7c6c18 --- /dev/null +++ b/library/phing/TaskContainer.php @@ -0,0 +1,42 @@ +. + */ + +/** + * Abstract interface for objects which can contain tasks (targets) + * Used to check if a class can contain tasks (via instanceof) + * + * @author Andreas Aderhold + * @copyright © 2001,2002 THYRELL. All rights reserved + * @version $Revision: 905 $ $Date: 2010-10-05 18:28:03 +0200 (Tue, 05 Oct 2010) $ + * @access public + * @package phing + */ +interface TaskContainer { + + /** + * Adds a task to this task container. Must be implemented + * by derived class + * + * @param object The task to be added to the container + * @access public + */ + function addTask(Task $task); +} diff --git a/library/phing/UnknownElement.php b/library/phing/UnknownElement.php new file mode 100644 index 000000000..e82ffbfdf --- /dev/null +++ b/library/phing/UnknownElement.php @@ -0,0 +1,215 @@ +. + */ + +require_once 'phing/Task.php'; + +/** + * Wrapper class that holds all information necessary to create a task + * that did not exist when Phing started. + * + * This has something to do with phing encountering an task XML element + * it is not aware of at start time. This is a situation where special steps + * need to be taken so that the element is then known. + * + * @author Andreas Aderhold + * @author Hans Lellelid + * @version $Revision: 905 $ + * @package phing + */ +class UnknownElement extends Task { + + private $elementName; + private $realThing; + private $children = array(); + + /** + * Constructs a UnknownElement object + * + * @param string The XML element name that is unknown + * @access public + */ + function __construct($elementName) { + $this->elementName = (string) $elementName; + } + + /** + * Return the XML element name that this UnnownElement + * handles. + * + * @return string The XML element name that is unknown + */ + public function getTag() { + return (string) $this->elementName; + } + + /** + * Tries to configure the unknown element + * + * @throws BuildException if the element can not be configured + */ + public function maybeConfigure() { + + $this->realThing = $this->makeObject($this, $this->wrapper); + $this->wrapper->setProxy($this->realThing); + if ($this->realThing instanceof Task) { + $this->realThing->setRuntimeConfigurableWrapper($this->wrapper); + } + + $this->handleChildren($this->realThing, $this->wrapper); + $this->wrapper->maybeConfigure($this->getProject()); + + } + + /** + * Called when the real task has been configured for the first time. + * + * @throws BuildException if the task can not be created + */ + public function main() { + + if ($this->realThing === null) { + // plain impossible to get here, maybeConfigure should + // have thrown an exception. + throw new BuildException("Should not be executing UnknownElement::main() -- task/type: {$this->elementName}"); + } + + if ($this->realThing instanceof Task) { + $this->realThing->main(); + } + + } + + /** + * Add a child element to the unknown element + * + * @param object The object representing the child element + */ + public function addChild(UnknownElement $child) { + $this->children[] = $child; + } + + /** + * Handle child elemets of the unknown element, if any. + * + * @param ProjectComponent The parent object the unkown element belongs to + * @param object The parent wrapper object + */ + function handleChildren(ProjectComponent $parent, $parentWrapper) { + + if ($parent instanceof TaskAdapter) { + $parent = $parent->getProxy(); + } + + $parentClass = get_class($parent); + $ih = IntrospectionHelper::getHelper($parentClass); + + for ($i=0, $childrenCount=count($this->children); $i < $childrenCount; $i++) { + + $childWrapper = $parentWrapper->getChild($i); + $child = $this->children[$i]; + $realChild = null; + if ($parent instanceof TaskContainer) { + $realChild = $this->makeTask($child, $childWrapper, false); + $parent->addTask($realChild); + } else { + $project = $this->project === null ? $parent->project : $this->project; + $realChild = $ih->createElement($project, $parent, $child->getTag()); + } + + $childWrapper->setProxy($realChild); + if ($realChild instanceof Task) { + $realChild->setRuntimeConfigurableWrapper($childWrapper); + } + + if ($realChild instanceof ProjectComponent) { + $child->handleChildren($realChild, $childWrapper); + } + + if ($realChild instanceof Task) { + $realChild->maybeConfigure(); + } + } + } + + /** + * Creates a named task or data type. If the real object is a task, + * it is configured up to the init() stage. + * + * @param UnknownElement $ue The unknown element to create the real object for. + * Must not be null. + * @param RuntimeConfigurable $w Ignored in this implementation. + * @return object The Task or DataType represented by the given unknown element. + */ + protected function makeObject(UnknownElement $ue, RuntimeConfigurable $w) { + $o = $this->makeTask($ue, $w, true); + if ($o === null) { + $o = $this->project->createDataType($ue->getTag()); + } + if ($o === null) { + throw new BuildException("Could not create task/type: '".$ue->getTag()."'. Make sure that this class has been declared using taskdef / typedef."); + } + return $o; + } + + /** + * Create a named task and configure it up to the init() stage. + * + * @param UnknownElement $ue The unknwon element to create a task from + * @param RuntimeConfigurable $w The wrapper object + * @param boolean $onTopLevel Whether to treat this task as if it is top-level. + * @return Task The freshly created task + */ + protected function makeTask(UnknownElement $ue, RuntimeConfigurable $w, $onTopLevel = false) { + + $task = $this->project->createTask($ue->getTag()); + + if ($task === null) { + if (!$onTopLevel) { + throw new BuildException("Could not create task of type: '".$this->elementName."'. Make sure that this class has been declared using taskdef."); + } + return null; + } + + // used to set the location within the xmlfile so that exceptions can + // give detailed messages + + $task->setLocation($this->getLocation()); + $attrs = $w->getAttributes(); + if (isset($attrs['id'])) { + $this->project->addReference($attrs['id'], $task); + } + + // UnknownElement always has an associated target + $task->setOwningTarget($this->target); + + $task->init(); + return $task; + } + + /** + * Get the name of the task to use in logging messages. + * + * @return string The task's name + */ + function getTaskName() { + return $this->realThing === null ? parent::getTaskName() : $this->realThing->getTaskName(); + } +} diff --git a/library/phing/filters/BaseFilterReader.php b/library/phing/filters/BaseFilterReader.php new file mode 100644 index 000000000..4e06a93d0 --- /dev/null +++ b/library/phing/filters/BaseFilterReader.php @@ -0,0 +1,157 @@ +. +*/ + +include_once 'phing/system/io/FilterReader.php'; +include_once 'phing/system/io/StringReader.php'; + + +/** + * Base class for core filter readers. + * + * @author Yannick Lecaillez + * @version $Revision: 905 $ $Date: 2010-10-05 18:28:03 +0200 (Tue, 05 Oct 2010) $ + * @access public + * @see FilterReader + * @package phing.filters + */ +class BaseFilterReader extends FilterReader { + + /** Have the parameters passed been interpreted? */ + protected $initialized = false; + + /** The Phing project this filter is part of. */ + protected $project = null; + + /** + * Constructor used by Phing's introspection mechanism. + * The original filter reader is only used for chaining + * purposes, never for filtering purposes (and indeed + * it would be useless for filtering purposes, as it has + * no real data to filter). ChainedReaderHelper uses + * this placeholder instance to create a chain of real filters. + * + * @param Reader $in + */ + function __construct($in = null) { + if ($in === null) { + $dummy = ""; + $in = new StringReader($dummy); + } + parent::__construct($in); + } + + /** + * Returns the initialized status. + * + * @return boolean whether or not the filter is initialized + */ + function getInitialized() { + return $this->initialized; + } + + /** + * Sets the initialized status. + * + * @param boolean $initialized Whether or not the filter is initialized. + */ + function setInitialized($initialized) { + $this->initialized = (boolean) $initialized; + } + + /** + * Sets the project to work with. + * + * @param object $project The project this filter is part of. + * Should not be null. + */ + function setProject(Project $project) { + // type check, error must never occur, bad code of it does + $this->project = $project; + } + + /** + * Returns the project this filter is part of. + * + * @return object The project this filter is part of + */ + function getProject() { + return $this->project; + } + + /** + * Reads characters. + * + * @param off Offset at which to start storing characters. + * @param len Maximum number of characters to read. + * + * @return Characters read, or -1 if the end of the stream + * has been reached + * + * @throws IOException If an I/O error occurs + */ + function read($len = null) { + return $this->in->read($len); + } + + /** + * Reads a line of text ending with '\n' (or until the end of the stream). + * The returned String retains the '\n'. + * + * @return the line read, or null if the end of the + stream has already been reached + * + * @throws IOException if the underlying reader throws one during + * reading + */ + function readLine() { + $line = null; + + while ( ($ch = $this->in->read(1)) !== -1 ) { + $line .= $ch; + if ( $ch === "\n" ) + break; + } + + return $line; + } + + /** + * Returns whether the end of file has been reached with input stream. + * @return boolean + */ + function eof() { + return $this->in->eof(); + } + + /** + * Convenience method to support logging in filters. + * @param string $msg Message to log. + * @param int $level Priority level. + */ + function log($msg, $level = Project::MSG_INFO) { + if ($this->project !== null) { + $this->project->log("[filter:".get_class($this)."] ".$msg, $level); + } + } +} + + diff --git a/library/phing/filters/BaseParamFilterReader.php b/library/phing/filters/BaseParamFilterReader.php new file mode 100644 index 000000000..24b451633 --- /dev/null +++ b/library/phing/filters/BaseParamFilterReader.php @@ -0,0 +1,69 @@ +. +*/ + +include_once 'phing/filters/BaseFilterReader.php'; +include_once 'phing/types/Parameterizable.php'; +include_once 'phing/types/Parameter.php'; + +/** + * Base class for core filter readers. + * + * @author Yannick Lecaillez + * @copyright © 2003 seasonfive. All rights reserved + * @version $Revision: 905 $ $Date: 2010-10-05 18:28:03 +0200 (Tue, 05 Oct 2010) $ + * @access public + * @see FilterReader + * @package phing.filters + */ +class BaseParamFilterReader extends BaseFilterReader implements Parameterizable { + + /** The passed in parameter array. */ + protected $_parameters = array(); + + /* + * Sets the parameters used by this filter, and sets + * the filter to an uninitialized status. + * + * @param array Array of parameters to be used by this filter. + * Should not be null. + */ + function setParameters($parameters) { + // type check, error must never occur, bad code of it does + if ( !is_array($parameters) ) { + throw new Exception("Expected parameters array got something else"); + } + + $this->_parameters = $parameters; + $this->setInitialized(false); + } + + /* + * Returns the parameters to be used by this filter. + * + * @return the parameters to be used by this filter + */ + function &getParameters() { + return $this->_parameters; + } +} + + diff --git a/library/phing/filters/ChainableReader.php b/library/phing/filters/ChainableReader.php new file mode 100644 index 000000000..2e31679ff --- /dev/null +++ b/library/phing/filters/ChainableReader.php @@ -0,0 +1,43 @@ +. +*/ + +/** + * Interface indicating that a reader may be chained to another one. + * + * @author Magesh Umasankar + * @package phing.filters + */ +interface ChainableReader { + + /** + * Returns a reader with the same configuration as this one, + * but filtering input from the specified reader. + * + * @param Reader $rdr the reader which the returned reader should be filtering + * + * @return Reader A reader with the same configuration as this one, but + * filtering input from the specified reader + */ + public function chain(Reader $rdr); +} + + diff --git a/library/phing/filters/ExpandProperties.php b/library/phing/filters/ExpandProperties.php new file mode 100644 index 000000000..c80a6bcad --- /dev/null +++ b/library/phing/filters/ExpandProperties.php @@ -0,0 +1,82 @@ +. +*/ + +require_once 'phing/filters/BaseFilterReader.php'; +include_once 'phing/filters/ChainableReader.php'; + +/** + * Expands Phing Properties, if any, in the data. + *

    + * Example:
    + *

    + * Or: + *
    .
    +*/
    +
    +include_once 'phing/filters/BaseParamFilterReader.php';
    +include_once 'phing/filters/ChainableReader.php';
    +
    +/**
    + * Reads the first n lines of a stream.
    + * (Default is first 10 lines.)
    + * 

    + * Example: + *

    + * Or: + *
    
    + *    
    + * 
    + * + * @author Yannick Lecaillez + * @author hans lellelid, hans@velum.net + * @version $Revision: 905 $ $Date: 2010-10-05 18:28:03 +0200 (Tue, 05 Oct 2010) $ + * @access public + * @see FilterReader + * @package phing.filters + */ +class HeadFilter extends BaseParamFilterReader implements ChainableReader { + + /** + * Parameter name for the number of lines to be returned. + */ + const LINES_KEY = "lines"; + + /** + * Number of lines currently read in. + * @var integer + */ + private $_linesRead = 0; + + /** + * Number of lines to be returned in the filtered stream. + * @var integer + */ + private $_lines = 10; + + /** + * Returns first n lines of stream. + * @return the resulting stream, or -1 + * if the end of the resulting stream has been reached + * + * @exception IOException if the underlying stream throws an IOException + * during reading + */ + function read($len = null) { + + if ( !$this->getInitialized() ) { + $this->_initialize(); + $this->setInitialized(true); + } + + // note, if buffer contains fewer lines than + // $this->_lines this code will not work. + + if($this->_linesRead < $this->_lines) { + + $buffer = $this->in->read($len); + + if($buffer === -1) { + return -1; + } + + // now grab first X lines from buffer + + $lines = explode("\n", $buffer); + + $linesCount = count($lines); + + // must account for possibility that the num lines requested could + // involve more than one buffer read. + $len = ($linesCount > $this->_lines ? $this->_lines - $this->_linesRead : $linesCount); + $filtered_buffer = implode("\n", array_slice($lines, 0, $len) ); + $this->_linesRead += $len; + + return $filtered_buffer; + + } + + return -1; // EOF, since the file is "finished" as far as subsequent filters are concerned. + } + + /** + * Sets the number of lines to be returned in the filtered stream. + * + * @param integer $lines the number of lines to be returned in the filtered stream. + */ + function setLines($lines) { + $this->_lines = (int) $lines; + } + + /** + * Returns the number of lines to be returned in the filtered stream. + * + * @return integer The number of lines to be returned in the filtered stream. + */ + function getLines() { + return $this->_lines; + } + + /** + * Creates a new HeadFilter using the passed in + * Reader for instantiation. + * + * @param object A Reader object providing the underlying stream. + * Must not be null. + * + * @return object A new filter based on this configuration, but filtering + * the specified reader. + */ + function chain(Reader $reader) { + $newFilter = new HeadFilter($reader); + $newFilter->setLines($this->getLines()); + $newFilter->setInitialized(true); + $newFilter->setProject($this->getProject()); + return $newFilter; + } + + /** + * Scans the parameters list for the "lines" parameter and uses + * it to set the number of lines to be returned in the filtered stream. + */ + private function _initialize() { + $params = $this->getParameters(); + if ( $params !== null ) { + for($i = 0, $_i=count($params) ; $i < $_i; $i++) { + if ( self::LINES_KEY == $params[$i]->getName() ) { + $this->_lines = (int) $params[$i]->getValue(); + break; + } + } + } + } +} + + diff --git a/library/phing/filters/LineContains.php b/library/phing/filters/LineContains.php new file mode 100644 index 000000000..844ef43b5 --- /dev/null +++ b/library/phing/filters/LineContains.php @@ -0,0 +1,260 @@ +. + */ + +include_once 'phing/filters/BaseParamFilterReader.php'; +include_once 'phing/filters/BaseFilterReader.php'; +include_once 'phing/filters/ChainableReader.php'; + +/** + * Filter which includes only those lines that contain all the user-specified + * strings. + * + * Example: + * + *
    
    + *   
    + *   
    + * 
    + * + * Or: + * + *
    
    + *    
    + *    
    + * 
    + * + * This will include only those lines that contain foo and + * bar. + * + * @author Yannick Lecaillez + * @author Hans Lellelid + * @version $Revision: 905 $ + * @see PhingFilterReader + * @package phing.filters +*/ +class LineContains extends BaseParamFilterReader implements ChainableReader { + + /** + * The parameter name for the string to match on. + * @var string + */ + const CONTAINS_KEY = "contains"; + + /** + * Array of Contains objects. + * @var array + */ + private $_contains = array(); + + /** + * [Deprecated] + * @var string + */ + private $_line = null; + + /** + * Returns all lines in a buffer that contain specified strings. + * @return mixed buffer, -1 on EOF + */ + function read($len = null) { + if ( !$this->getInitialized() ) { + $this->_initialize(); + $this->setInitialized(true); + } + + $buffer = $this->in->read($len); + + if ($buffer === -1) { + return -1; + } + + $lines = explode("\n", $buffer); + $matched = array(); + $containsSize = count($this->_contains); + + foreach($lines as $line) { + for($i = 0 ; $i < $containsSize ; $i++) { + $containsStr = $this->_contains[$i]->getValue(); + if ( strstr($line, $containsStr) === false ) { + $line = null; + break; + } + } + if($line !== null) { + $matched[] = $line; + } + } + $filtered_buffer = implode("\n", $matched); + return $filtered_buffer; + } + + /** + * [Deprecated. For reference only, used to be read() method.] + * Returns the next character in the filtered stream, only including + * lines from the original stream which contain all of the specified words. + * + * @return the next character in the resulting stream, or -1 + * if the end of the resulting stream has been reached + * + * @exception IOException if the underlying stream throws an IOException + * during reading + */ + function readChar() { + if ( !$this->getInitialized() ) { + $this->_initialize(); + $this->setInitialized(true); + } + + $ch = -1; + + if ( $this->_line !== null ) { + $ch = substr($this->_line, 0, 1); + if ( strlen($this->_line) === 1 ) + $this->_line = null; + else + $this->_line = substr($this->_line, 1); + } else { + $this->_line = $this->readLine(); + if ( $this->_line === null ) { + $ch = -1; + } else { + $containsSize = count($this->_contains); + for($i = 0 ; $i < $containsSize ; $i++) { + $containsStr = $this->_contains[$i]->getValue(); + if ( strstr($this->_line, $containsStr) === false ) { + $this->_line = null; + break; + } + } + return $this->readChar(); + } + } + + return $ch; + } + + /** + * Adds a nested element. + * + * @return Contains The contains element added. + * Must not be null. + */ + function createContains() { + $num = array_push($this->_contains, new Contains()); + return $this->_contains[$num-1]; + } + + /** + * Sets the array of words which must be contained within a line read + * from the original stream in order for it to match this filter. + * + * @param array $contains An array of words which must be contained + * within a line in order for it to match in this filter. + * Must not be null. + */ + function setContains($contains) { + // type check, error must never occur, bad code of it does + if ( !is_array($contains) ) { + throw new Exception("Excpected array got something else"); + } + + $this->_contains = $contains; + } + + /** + * Returns the vector of words which must be contained within a line read + * from the original stream in order for it to match this filter. + * + * @return array The array of words which must be contained within a line read + * from the original stream in order for it to match this filter. The + * returned object is "live" - in other words, changes made to the + * returned object are mirrored in the filter. + */ + function getContains() { + return $this->_contains; + } + + /** + * Creates a new LineContains using the passed in + * Reader for instantiation. + * + * @param object A Reader object providing the underlying stream. + * Must not be null. + * + * @return object A new filter based on this configuration, but filtering + * the specified reader + */ + function chain(Reader $reader) { + $newFilter = new LineContains($reader); + $newFilter->setContains($this->getContains()); + $newFilter->setInitialized(true); + $newFilter->setProject($this->getProject()); + return $newFilter; + } + + /** + * Parses the parameters to add user-defined contains strings. + */ + private function _initialize() { + $params = $this->getParameters(); + if ( $params !== null ) { + foreach($params as $param) { + if ( self::CONTAINS_KEY == $param->getType() ) { + $cont = new Contains(); + $cont->setValue($param->getValue()); + array_push($this->_contains, $cont); + break; // because we only support a single contains + } + } + } + } +} + +/** + * Holds a contains element. + * + * @package phing.filters + */ +class Contains { + + /** + * @var string + */ + private $_value; + + /** + * Set 'contains' value. + * @param string $contains + */ + function setValue($contains) { + $this->_value = (string) $contains; + } + + /** + * Returns 'contains' value. + * @return string + */ + function getValue() { + return $this->_value; + } +} + diff --git a/library/phing/filters/LineContainsRegexp.php b/library/phing/filters/LineContainsRegexp.php new file mode 100644 index 000000000..c98639c09 --- /dev/null +++ b/library/phing/filters/LineContainsRegexp.php @@ -0,0 +1,179 @@ +. +*/ + +include_once 'phing/filters/BaseParamFilterReader.php'; +include_once 'phing/types/RegularExpression.php'; +include_once 'phing/filters/ChainableReader.php'; + +/** + * Filter which includes only those lines that contain the user-specified + * regular expression matching strings. + * + * Example: + *
    
    + *   
    + * 
    + * + * Or: + * + *
    
    + *    
    + * 
    + * + * This will fetch all those lines that contain the pattern foo + * + * @author Yannick Lecaillez + * @author Hans Lellelid + * @version $Revision: 905 $ + * @see FilterReader + * @package phing.filters + */ +class LineContainsRegexp extends BaseParamFilterReader implements ChainableReader { + + /** + * Parameter name for regular expression. + * @var string + */ + const REGEXP_KEY = "regexp"; + + /** + * Regular expressions that are applied against lines. + * @var array + */ + private $_regexps = array(); + + /** + * Returns all lines in a buffer that contain specified strings. + * @return mixed buffer, -1 on EOF + */ + function read($len = null) { + + if ( !$this->getInitialized() ) { + $this->_initialize(); + $this->setInitialized(true); + } + + $buffer = $this->in->read($len); + + if ($buffer === -1) { + return -1; + } + + $lines = explode("\n", $buffer); + $matched = array(); + + $regexpsSize = count($this->_regexps); + foreach($lines as $line) { + for($i = 0 ; $i<$regexpsSize ; $i++) { + $regexp = $this->_regexps[$i]; + $re = $regexp->getRegexp($this->getProject()); + $matches = $re->matches($line); + if ( !$matches ) { + $line = null; + break; + } + } + if($line !== null) { + $matched[] = $line; + } + } + $filtered_buffer = implode("\n", $matched); + return $filtered_buffer; + } + + /** + * Adds a regexp element. + * + * @return object regExp The regexp element added. + */ + function createRegexp() { + $num = array_push($this->_regexps, new RegularExpression()); + return $this->_regexps[$num-1]; + } + + /** + * Sets the vector of regular expressions which must be contained within + * a line read from the original stream in order for it to match this + * filter. + * + * @param regexps An array of regular expressions which must be contained + * within a line in order for it to match in this filter. Must not be + * null. + */ + function setRegexps($regexps) { + // type check, error must never occur, bad code of it does + if ( !is_array($regexps) ) { + throw new Exception("Excpected an 'array', got something else"); + } + $this->_regexps = $regexps; + } + + /** + * Returns the array of regular expressions which must be contained within + * a line read from the original stream in order for it to match this + * filter. + * + * @return array The array of regular expressions which must be contained within + * a line read from the original stream in order for it to match this + * filter. The returned object is "live" - in other words, changes made to + * the returned object are mirrored in the filter. + */ + function getRegexps() { + return $this->_regexps; + } + + /** + * Creates a new LineContainsRegExp using the passed in + * Reader for instantiation. + * + * @param object A Reader object providing the underlying stream. + * Must not be null. + * + * @return object A new filter based on this configuration, but filtering + * the specified reader + */ + function chain(Reader $reader) { + $newFilter = new LineContainsRegExp($reader); + $newFilter->setRegexps($this->getRegexps()); + $newFilter->setInitialized(true); + $newFilter->setProject($this->getProject()); + return $newFilter; + } + + /** + * Parses parameters to add user defined regular expressions. + */ + private function _initialize() { + $params = $this->getParameters(); + if ( $params !== null ) { + for($i = 0 ; $igetType() ) { + $pattern = $params[$i]->getValue(); + $regexp = new RegularExpression(); + $regexp->setPattern($pattern); + array_push($this->_regexps, $regexp); + } + } + } + } +} + + diff --git a/library/phing/filters/PrefixLines.php b/library/phing/filters/PrefixLines.php new file mode 100644 index 000000000..5042637c0 --- /dev/null +++ b/library/phing/filters/PrefixLines.php @@ -0,0 +1,142 @@ +. +*/ + +include_once 'phing/filters/BaseParamFilterReader.php'; +include_once 'phing/filters/ChainableReader.php'; + +/** + * Attaches a prefix to every line. + * + * Example: + *
    + * + * Or: + * + *
    
    + *  
    + * 
    + * + * @author Yannick Lecaillez + * @author hans lellelid, hans@velum.net + * @version $Revision: 905 $ $Date: 2010-10-05 18:28:03 +0200 (Tue, 05 Oct 2010) $ + * @access public + * @see FilterReader + * @package phing.filters +*/ +class PrefixLines extends BaseParamFilterReader implements ChainableReader { + + /** + * Parameter name for the prefix. + * @var string + */ + const PREFIX_KEY = "lines"; + + /** + * The prefix to be used. + * @var string + */ + private $_prefix = null; + + /** + * Adds a prefix to each line of input stream and returns resulting stream. + * + * @return mixed buffer, -1 on EOF + */ + function read($len = null) { + if ( !$this->getInitialized() ) { + $this->_initialize(); + $this->setInitialized(true); + } + + $buffer = $this->in->read($len); + + if ($buffer === -1) { + return -1; + } + + $lines = explode("\n", $buffer); + $filtered = array(); + + foreach($lines as $line) { + $line = $this->_prefix . $line; + $filtered[] = $line; + } + + $filtered_buffer = implode("\n", $filtered); + return $filtered_buffer; + } + + /** + * Sets the prefix to add at the start of each input line. + * + * @param string $prefix The prefix to add at the start of each input line. + * May be null, in which case no prefix + * is added. + */ + function setPrefix($prefix) { + $this->_prefix = (string) $prefix; + } + + /** + * Returns the prefix which will be added at the start of each input line. + * + * @return string The prefix which will be added at the start of each input line + */ + function getPrefix() { + return $this->_prefix; + } + + /** + * Creates a new PrefixLines filter using the passed in + * Reader for instantiation. + * + * @param object A Reader object providing the underlying stream. + * Must not be null. + * + * @return object A new filter based on this configuration, but filtering + * the specified reader + */ + function chain(Reader $reader) { + $newFilter = new PrefixLines($reader); + $newFilter->setPrefix($this->getPrefix()); + $newFilter->setInitialized(true); + $newFilter->setProject($this->getProject()); + return $newFilter; + } + + /** + * Initializes the prefix if it is available from the parameters. + */ + private function _initialize() { + $params = $this->getParameters(); + if ( $params !== null ) { + for($i = 0, $_i=count($params) ; $i < $_i ; $i++) { + if ( self::PREFIX_KEY == $params[$i]->getName() ) { + $this->_prefix = (string) $params[$i]->getValue(); + break; + } + } + } + } +} + + diff --git a/library/phing/filters/ReplaceRegexp.php b/library/phing/filters/ReplaceRegexp.php new file mode 100644 index 000000000..a9bdd1bdc --- /dev/null +++ b/library/phing/filters/ReplaceRegexp.php @@ -0,0 +1,129 @@ +. +*/ + +require_once 'phing/filters/BaseFilterReader.php'; +include_once 'phing/filters/ChainableReader.php'; +include_once 'phing/types/RegularExpression.php'; + +/** + * Performs a regexp find/replace on stream. + *

    + * Example:
    + *

    + * 
    + *    
    + *    
    + * 
    + * 
    + * + * @author Hans Lellelid + * @version $Revision: 905 $ + * @package phing.filters + */ +class ReplaceRegexp extends BaseFilterReader implements ChainableReader { + + /** + * @var array RegularExpression[] + */ + private $regexps = array(); + + /** + * Creator method handles nested tags. + * @return RegularExpression + */ + function createRegexp() { + $num = array_push($this->regexps, new RegularExpression()); + return $this->regexps[$num-1]; + } + + /** + * Sets the current regexps. + * (Used when, e.g., cloning/chaining the method.) + * @param array RegularExpression[] + */ + function setRegexps($regexps) { + $this->regexps = $regexps; + } + + /** + * Gets the current regexps. + * (Used when, e.g., cloning/chaining the method.) + * @return array RegularExpression[] + */ + function getRegexps() { + return $this->regexps; + } + + /** + * Returns the filtered stream. + * The original stream is first read in fully, and the regex replace is performed. + * + * @param int $len Required $len for Reader compliance. + * + * @return mixed The filtered stream, or -1 if the end of the resulting stream has been reached. + * + * @exception IOException if the underlying stream throws an IOException + * during reading + */ + function read($len = null) { + + $buffer = $this->in->read($len); + + if($buffer === -1) { + return -1; + } + + // perform regex replace here ... + foreach($this->regexps as $exptype) { + $regexp = $exptype->getRegexp($this->project); + try { + $buffer = $regexp->replace($buffer); + $this->log("Performing regexp replace: /".$regexp->getPattern()."/".$regexp->getReplace()."/g".$regexp->getModifiers(), Project::MSG_VERBOSE); + } catch (Exception $e) { + // perhaps mismatch in params (e.g. no replace or pattern specified) + $this->log("Error performing regexp replace: " . $e->getMessage(), Project::MSG_WARN); + } + } + + return $buffer; + } + + /** + * Creates a new ReplaceRegExp filter using the passed in + * Reader for instantiation. + * + * @param Reader $reader A Reader object providing the underlying stream. + * Must not be null. + * + * @return ReplaceRegExp A new filter based on this configuration, but filtering + * the specified reader + */ + function chain(Reader $reader) { + $newFilter = new ReplaceRegExp($reader); + $newFilter->setProject($this->getProject()); + $newFilter->setRegexps($this->getRegexps()); + return $newFilter; + } + +} + + diff --git a/library/phing/filters/ReplaceTokens.php b/library/phing/filters/ReplaceTokens.php new file mode 100644 index 000000000..b360b08ef --- /dev/null +++ b/library/phing/filters/ReplaceTokens.php @@ -0,0 +1,435 @@ +. +*/ + +include_once 'phing/filters/BaseParamFilterReader.php'; +include_once 'phing/types/TokenSource.php'; +include_once 'phing/filters/ChainableReader.php'; + +/** + * Replaces tokens in the original input with user-supplied values. + * + * Example: + * + *
    ;
    + *   
    + * 
    + * + * Or: + * + *
    
    + *   
    + *   
    + *   
    + * 
    + * + * @author Yannick Lecaillez + * @author hans lellelid, hans@velum.net + * @version $Revision: 905 $ $Date: 2010-10-05 18:28:03 +0200 (Tue, 05 Oct 2010) $ + * @access public + * @see BaseParamFilterReader + * @package phing.filters + */ +class ReplaceTokens extends BaseParamFilterReader implements ChainableReader { + + /** + * Default "begin token" character. + * @var string + */ + const DEFAULT_BEGIN_TOKEN = "@"; + + /** + * Default "end token" character. + * @var string + */ + const DEFAULT_END_TOKEN = "@"; + + /** + * [Deprecated] Data that must be read from, if not null. + * @var string + */ + private $_queuedData = null; + + /** + * Array to hold the replacee-replacer pairs (String to String). + * @var array + */ + private $_tokens = array(); + + /** + * Array to hold the token sources that make tokens from + * different sources available + * @var array + */ + private $_tokensources = array(); + + /** + * Array holding all tokens given directly to the Filter and + * those passed via a TokenSource. + * @var array + */ + private $_alltokens = null; + + /** + * Character marking the beginning of a token. + * @var string + */ + private $_beginToken = "@"; // self::DEFAULT_BEGIN_TOKEN; + + /** + * Character marking the end of a token. + * @var string + */ + private $_endToken = "@"; //self::DEFAULT_END_TOKEN; + + /** + * Performs lookup on key and returns appropriate replacement string. + * @param array $matches Array of 1 el containing key to search for. + * @return string Text with which to replace key or value of key if none is found. + * @access private + */ + private function replaceTokenCallback($matches) { + + $key = $matches[1]; + + /* Get tokens from tokensource and merge them with the + * tokens given directly via build file. This should be + * done a bit more elegantly + */ + if ($this->_alltokens === null) { + $this->_alltokens = array(); + + $count = count($this->_tokensources); + for ($i = 0; $i < $count; $i++) { + $source = $this->_tokensources[$i]; + $this->_alltokens = array_merge($this->_alltokens, $source->getTokens()); + } + + + $this->_alltokens = array_merge($this->_tokens, $this->_alltokens); + } + + $tokens = $this->_alltokens; + + $replaceWith = null; + $count = count($tokens); + + for ($i = 0; $i < $count; $i++) { + if ($tokens[$i]->getKey() === $key) { + $replaceWith = $tokens[$i]->getValue(); + } + } + + if ($replaceWith === null) { + $replaceWith = $this->_beginToken . $key . $this->_endToken; + $this->log("No token defined for key \"".$this->_beginToken . $key . $this->_endToken."\""); + } else { + $this->log("Replaced \"".$this->_beginToken . $key . $this->_endToken ."\" with \"".$replaceWith."\""); + } + + return $replaceWith; + } + + /** + * Returns stream with tokens having been replaced with appropriate values. + * If a replacement value is not found for a token, the token is left in the stream. + * + * @return mixed filtered stream, -1 on EOF. + */ + function read($len = null) { + if ( !$this->getInitialized() ) { + $this->_initialize(); + $this->setInitialized(true); + } + + // read from next filter up the chain + $buffer = $this->in->read($len); + + if($buffer === -1) { + return -1; + } + + // filter buffer + $buffer = preg_replace_callback( + "/".preg_quote($this->_beginToken)."([\w\.\-:]+?)".preg_quote($this->_endToken)."/", + array($this, 'replaceTokenCallback'), $buffer); + + return $buffer; + } + + /** + * Sets the "begin token" character. + * + * @param string $beginToken the character used to denote the beginning of a token. + */ + function setBeginToken($beginToken) { + $this->_beginToken = (string) $beginToken; + } + + /** + * Returns the "begin token" character. + * + * @return string The character used to denote the beginning of a token. + */ + function getBeginToken() { + return $this->_beginToken; + } + + /** + * Sets the "end token" character. + * + * @param string $endToken the character used to denote the end of a token + */ + function setEndToken($endToken) { + $this->_endToken = (string) $endToken; + } + + /** + * Returns the "end token" character. + * + * @return the character used to denote the beginning of a token + */ + function getEndToken() { + return $this->_endToken; + } + + /** + * Adds a token element to the map of tokens to replace. + * + * @return object The token added to the map of replacements. + * Must not be null. + */ + function createToken() { + $num = array_push($this->_tokens, new Token()); + return $this->_tokens[$num-1]; + } + + /** + * Adds a token source to the sources of this filter. + * + * @return object A Reference to the source just added. + */ + function createTokensource() { + $num = array_push($this->_tokensources, new TokenSource()); + return $this->_tokensources[$num-1]; + } + + /** + * Sets the map of tokens to replace. + * ; used by ReplaceTokens::chain() + * + * @param array A map (String->String) of token keys to replacement + * values. Must not be null. + */ + function setTokens($tokens) { + // type check, error must never occur, bad code of it does + if ( !is_array($tokens) ) { + throw new Exception("Excpected 'array', got something else"); + } + + $this->_tokens = $tokens; + } + + /** + * Returns the map of tokens which will be replaced. + * ; used by ReplaceTokens::chain() + * + * @return array A map (String->String) of token keys to replacement values. + */ + function getTokens() { + return $this->_tokens; + } + + /** + * Sets the tokensources to use; used by ReplaceTokens::chain() + * + * @param array An array of token sources. + */ + function setTokensources($sources) { + // type check + if ( !is_array($sources)) { + throw new Exception("Exspected 'array', got something else"); + } + $this->_tokensources = $sources; + } + + /** + * Returns the token sources used by this filter; used by ReplaceTokens::chain() + * + * @return array + */ + function getTokensources() { + return $this->_tokensources; + } + + /** + * Creates a new ReplaceTokens using the passed in + * Reader for instantiation. + * + * @param object A Reader object providing the underlying stream. + * Must not be null. + * + * @return object A new filter based on this configuration, but filtering + * the specified reader + */ + function chain(Reader $reader) { + $newFilter = new ReplaceTokens($reader); + $newFilter->setProject($this->getProject()); + $newFilter->setBeginToken($this->getBeginToken()); + $newFilter->setEndToken($this->getEndToken()); + $newFilter->setTokens($this->getTokens()); + $newFilter->setTokensources($this->getTokensources()); + $newFilter->setInitialized(true); + return $newFilter; + } + + /** + * Initializes tokens and loads the replacee-replacer hashtable. + * This method is only called when this filter is used through + * a tag in build file. + */ + private function _initialize() { + $params = $this->getParameters(); + if ( $params !== null ) { + for($i = 0 ; $igetType(); + if ( $type === "tokenchar" ) { + $name = $params[$i]->getName(); + if ( $name === "begintoken" ) { + $this->_beginToken = substr($params[$i]->getValue(), 0, 1); + } else if ( $name === "endtoken" ) { + $this->_endToken = substr($params[$i]->getValue(), 0, 1); + } + } else if ( $type === "token" ) { + $name = $params[$i]->getName(); + $value = $params[$i]->getValue(); + + $tok = new Token(); + $tok->setKey($name); + $tok->setValue($value); + + array_push($this->_tokens, $tok); + } else if ( $type === "tokensource" ) { + // Store data from nested tags in local array + $arr = array(); $subparams = $params[$i]->getParams(); + $count = count($subparams); + for ($i = 0; $i < $count; $i++) { + $arr[$subparams[$i]->getName()] = $subparams[$i]->getValue(); + } + + // Create TokenSource + $tokensource = new TokenSource(); + if (isset($arr["classname"])) + $tokensource->setClassname($arr["classname"]); + + // Copy other parameters 1:1 to freshly created TokenSource + foreach ($arr as $key => $value) { + if (strtolower($key) === "classname") + continue; + $param = $tokensource->createParam(); + $param->setName($key); + $param->setValue($value); + } + + $this->_tokensources[] = $tokensource; + } + } + } + } + } +} + +/** + * Holds a token. + * + * @package phing.filters + */ +class Token { + + /** + * Token key. + * @var string + */ + private $_key; + + /** + * Token value. + * @var string + */ + private $_value; + + /** + * Sets the token key. + * + * @param string $key The key for this token. Must not be null. + */ + function setKey($key) { + $this->_key = (string) $key; + } + + /** + * Sets the token value. + * + * @param string $value The value for this token. Must not be null. + */ + function setValue($value) { + // special case for boolean values + if (is_bool($value)) { + if ($value) { + $this->_value = "true"; + } else { + $this->_value = "false"; + } + } else { + $this->_value = (string) $value; + } + } + + /** + * Returns the key for this token. + * + * @return string The key for this token. + */ + function getKey() { + return $this->_key; + } + + /** + * Returns the value for this token. + * + * @return string The value for this token. + */ + function getValue() { + return $this->_value; + } + + /** + * Sets the token value from text. + * + * @param string $value The value for this token. Must not be null. + */ + function addText($value) { + $this->setValue($value); + } +} + + diff --git a/library/phing/filters/ReplaceTokensWithFile.php b/library/phing/filters/ReplaceTokensWithFile.php new file mode 100644 index 000000000..31f21410e --- /dev/null +++ b/library/phing/filters/ReplaceTokensWithFile.php @@ -0,0 +1,325 @@ +. +*/ + +include_once 'phing/filters/BaseParamFilterReader.php'; +include_once 'phing/filters/ChainableReader.php'; + +/* + * Replaces tokens in the original input with the contents of a file. + * The file to be used is controlled by the name of the token which + * corresponds to the basename of the file to be used together with + * the optional pre and postfix strings that is possible to set. + * + * By default all HTML entities in the file is replaced by the + * corresponding HTML entities. This behaviour can be controlled by + * the "translatehtml" parameter. + * + * Supported parameters are: + *
    + *  prefix         string Text to be prefixed to token before using as filename
    + *  postfix        string Text to be prefixed to token before using as filename
    + *  dir            string The directory where the files should be read from
    + *  translatehtml  bool   If we should translate all HTML entities in the file.
    + * 
    + * Example: + * + *
    
    + *   
    + *   
    + * 
    + * + * @author johan persson, johanp@aditus.nu + * @version $Id: ReplaceTokensWithFile.php 905 2010-10-05 16:28:03Z mrook $ + * @access public + * @see ReplaceTokensWithFile + * @package phing.filters + */ +class ReplaceTokensWithFile extends BaseParamFilterReader implements ChainableReader { + + /** + * Default "begin token" character. + * @var string + */ + const DEFAULT_BEGIN_TOKEN = "#@#"; + + /** + * Default "end token" character. + * @var string + */ + const DEFAULT_END_TOKEN = "#@#"; + + /** + * Array to hold the token sources that make tokens from + * different sources available + * @var array + */ + private $_tokensources = array(); + + /** + * Character marking the beginning of a token. + * @var string + */ + private $_beginToken = ReplaceTokensWithFile::DEFAULT_BEGIN_TOKEN; + + /** + * Character marking the end of a token. + * @var string + */ + private $_endToken = ReplaceTokensWithFile::DEFAULT_END_TOKEN; + + /** + * File prefix to be inserted in front of the token to create the + * file name to be used. + * @var string + */ + private $_prefix = ''; + + /** + * File postfix to be inserted in front of the token to create the + * file name to be used. + * @var string + */ + private $_postfix = ''; + + /** + * Directory where to look for the files. The default is to look in the + * current file. + * + * @var string + */ + private $_dir = './'; + + /** + * Translate all HTML entities in the file to the corresponding HTML + * entities before it is used as replacements. For example all '<' + * will be translated to < before the content is inserted. + * + * @var boolean + */ + private $_translatehtml = true; + + + /** + * Sets the drectory where to look for the files to use for token replacement + * + * @param string $dir + */ + function setTranslateHTML($translate) { + $this->_translatehtml = (bool) $translate; + } + + /** + * Sets the drectory where to look for the files to use for token replacement + * + * @param string $dir + */ + function setDir($dir) { + $this->_dir = (string) $dir; + } + + /** + * Sets the prefix that is prepended to the token in order to create the file + * name. For example if the token is 01 and the prefix is "example" then + * the filename to look for will be "example01" + * + * @param string $prefix + */ + function setPrefix($prefix) { + $this->_prefix = (string) $prefix; + } + + /** + * Sets the postfix that is added to the token in order to create the file + * name. For example if the token is 01 and the postfix is ".php" then + * the filename to look for will be "01.php" + * + * @param string $postfix + */ + function setPostfix($postfix) { + $this->_postfix = (string) $postfix; + } + + /** + * Sets the "begin token" character. + * + * @param string $beginToken the character used to denote the beginning of a token. + */ + function setBeginToken($beginToken) { + $this->_beginToken = (string) $beginToken; + } + + /** + * Returns the "begin token" character. + * + * @return string The character used to denote the beginning of a token. + */ + function getBeginToken() { + return $this->_beginToken; + } + + /** + * Sets the "end token" character. + * + * @param string $endToken the character used to denote the end of a token + */ + function setEndToken($endToken) { + $this->_endToken = (string) $endToken; + } + + /** + * Returns the "end token" character. + * + * @return the character used to denote the beginning of a token + */ + function getEndToken() { + return $this->_endToken; + } + + /** + * Replace the token found with the appropriate file contents + * @param array $matches Array of 1 el containing key to search for. + * @return string Text with which to replace key or value of key if none is found. + * @access private + */ + private function replaceTokenCallback($matches) { + + $filetoken = $matches[1]; + + // We look in all specified directories for the named file and use + // the first directory which has the file. + $dirs = explode(';',$this->_dir); + + $ndirs = count($dirs); + $n = 0; + $file = $dirs[$n] . $this->_prefix . $filetoken . $this->_postfix; + + while ( $n < $ndirs && ! is_readable($file) ) { + ++$n; + } + + if( ! is_readable($file) || $n >= $ndirs ) { + $this->log("Can not read or find file \"$file\". Searched in directories: {$this->_dir}", Project::MSG_WARN); + //return $this->_beginToken . $filetoken . $this->_endToken; + return "[Phing::Filters::ReplaceTokensWithFile: Can not find file " . '"' . $filetoken . $this->_postfix . '"' . "]"; + } + + $buffer = file_get_contents($file); + if( $this->_translatehtml ) { + $buffer = htmlentities($buffer); + } + + if ($buffer === null) { + $buffer = $this->_beginToken . $filetoken . $this->_endToken; + $this->log("No corresponding file found for key \"$buffer\"", Project::MSG_WARN); + } else { + $this->log("Replaced \"".$this->_beginToken . $filetoken . $this->_endToken."\" with content from file \"$file\""); + } + + return $buffer; + } + + /** + * Returns stream with tokens having been replaced with appropriate values. + * If a replacement value is not found for a token, the token is left in the stream. + * + * @return mixed filtered stream, -1 on EOF. + */ + function read($len = null) { + if ( !$this->getInitialized() ) { + $this->_initialize(); + $this->setInitialized(true); + } + + // read from next filter up the chain + $buffer = $this->in->read($len); + + if($buffer === -1) { + return -1; + } + + // filter buffer + $buffer = preg_replace_callback( + "/".preg_quote($this->_beginToken)."([\w\.\-:\/]+?)".preg_quote($this->_endToken)."/", + array($this, 'replaceTokenCallback'), $buffer); + + return $buffer; + } + + /** + * Creates a new ReplaceTokensWithFile using the passed in + * Reader for instantiation. + * + * @param object A Reader object providing the underlying stream. + * Must not be null. + * + * @return object A new filter based on this configuration, but filtering + * the specified reader + */ + function chain(Reader $reader) { + $newFilter = new ReplaceTokensWithFile($reader); + $newFilter->setProject($this->getProject()); + $newFilter->setBeginToken($this->getBeginToken()); + $newFilter->setEndToken($this->getEndToken()); + $newFilter->setInitialized(true); + return $newFilter; + } + + /** + * Initializes parameters + * This method is only called when this filter is used through + * a tag in build file. + */ + private function _initialize() { + $params = $this->getParameters(); + $n = count($params); + + if ( $params !== null ) { + for($i = 0 ; $i < $n ; $i++) { + if ( $params[$i] !== null ) { + $name = $params[$i]->getName(); + switch( $name ) { + case 'begintoken' : + $this->_beginToken = $params[$i]->getValue(); + break; + case 'endtoken' : + $this->_endToken = $params[$i]->getValue(); + break; + case 'dir': + $this->_dir = $params[$i]->getValue(); + break; + case 'prefix': + $this->_prefix = $params[$i]->getValue(); + break; + case 'postfix': + $this->_postfix = $params[$i]->getValue(); + break; + case 'translatehtml': + $this->_translatehtml = $params[$i]->getValue(); + break; + } + } + } + } + } +} + + diff --git a/library/phing/filters/StripLineBreaks.php b/library/phing/filters/StripLineBreaks.php new file mode 100644 index 000000000..10379198e --- /dev/null +++ b/library/phing/filters/StripLineBreaks.php @@ -0,0 +1,148 @@ +. +*/ + +include_once 'phing/filters/BaseParamFilterReader.php'; +include_once 'phing/filters/ChainableReader.php'; + +/** + * Filter to flatten the stream to a single line. + * + * Example: + * + *
    + * + * Or: + * + *
    + * + * @author Yannick Lecaillez + * @author hans lellelid, hans@velum.net + * @version $Revision: 905 $ $Date: 2010-10-05 18:28:03 +0200 (Tue, 05 Oct 2010) $ + * @access public + * @see BaseParamFilterReader + * @package phing.filters + */ +class StripLineBreaks extends BaseParamFilterReader implements ChainableReader { + + /** + * Default line-breaking characters. + * @var string + */ + const DEFAULT_LINE_BREAKS = "\r\n"; + + /** + * Parameter name for the line-breaking characters parameter. + * @var string + */ + const LINES_BREAKS_KEY = "linebreaks"; + + /** + * The characters that are recognized as line breaks. + * @var string + */ + private $_lineBreaks = "\r\n"; // self::DEFAULT_LINE_BREAKS; + + /** + * Returns the filtered stream, only including + * characters not in the set of line-breaking characters. + * + * @return mixed the resulting stream, or -1 + * if the end of the resulting stream has been reached. + * + * @exception IOException if the underlying stream throws an IOException + * during reading + */ + function read($len = null) { + if ( !$this->getInitialized() ) { + $this->_initialize(); + $this->setInitialized(true); + } + + $buffer = $this->in->read($len); + if($buffer === -1) { + return -1; + } + + $buffer = preg_replace("/[".$this->_lineBreaks."]/", '', $buffer); + + return $buffer; + } + + /** + * Sets the line-breaking characters. + * + * @param string $lineBreaks A String containing all the characters to be + * considered as line-breaking. + */ + function setLineBreaks($lineBreaks) { + $this->_lineBreaks = (string) $lineBreaks; + } + + /** + * Gets the line-breaking characters. + * + * @return string A String containing all the characters that are considered as line-breaking. + */ + function getLineBreaks() { + return $this->_lineBreaks; + } + + /** + * Creates a new StripLineBreaks using the passed in + * Reader for instantiation. + * + * @param object A Reader object providing the underlying stream. + * Must not be null. + * + * @return object A new filter based on this configuration, but filtering + * the specified reader + */ + function chain(Reader $reader) { + $newFilter = new StripLineBreaks($reader); + $newFilter->setLineBreaks($this->getLineBreaks()); + $newFilter->setInitialized(true); + $newFilter->setProject($this->getProject()); + return $newFilter; + } + + /** + * Parses the parameters to set the line-breaking characters. + */ + private function _initialize() { + $userDefinedLineBreaks = null; + $params = $this->getParameters(); + if ( $params !== null ) { + for($i = 0 ; $igetName() ) { + $userDefinedLineBreaks = $params[$i]->getValue(); + break; + } + } + } + + if ( $userDefinedLineBreaks !== null ) { + $this->_lineBreaks = $userDefinedLineBreaks; + } + } +} + + diff --git a/library/phing/filters/StripLineComments.php b/library/phing/filters/StripLineComments.php new file mode 100644 index 000000000..ae737198b --- /dev/null +++ b/library/phing/filters/StripLineComments.php @@ -0,0 +1,207 @@ +. +*/ + +include_once 'phing/filters/BaseParamFilterReader.php'; +include_once 'phing/filters/ChainableReader.php'; + +/** + * This filter strips line comments. + * + * Example: + * + *
    
    + *   
    + *   
    + *   
    + *   
    + *   
    + * 
    + * + * Or: + * + *
    
    + *   
    + *   
    + *   
    + *   
    + *   
    + * 
    + * + * @author Yannick Lecaillez + * @author hans lellelid, hans@velum.net + * @version $Revision: 905 $ $Date: 2010-10-05 18:28:03 +0200 (Tue, 05 Oct 2010) $ + * @access public + * @see BaseParamFilterReader + * @package phing.filters + */ +class StripLineComments extends BaseParamFilterReader implements ChainableReader { + + /** Parameter name for the comment prefix. */ + const COMMENTS_KEY = "comment"; + + /** Array that holds the comment prefixes. */ + private $_comments = array(); + + /** + * Returns stream only including + * lines from the original stream which don't start with any of the + * specified comment prefixes. + * + * @return mixed the resulting stream, or -1 + * if the end of the resulting stream has been reached. + * + * @throws IOException if the underlying stream throws an IOException + * during reading + */ + function read($len = null) { + + if ( !$this->getInitialized() ) { + $this->_initialize(); + $this->setInitialized(true); + } + + $buffer = $this->in->read($len); + + if ($buffer === -1) { + return -1; + } + + $lines = explode("\n", $buffer); + $filtered = array(); + + $commentsSize = count($this->_comments); + + foreach($lines as $line) { + for($i = 0; $i < $commentsSize; $i++) { + $comment = $this->_comments[$i]->getValue(); + if ( StringHelper::startsWith($comment, ltrim($line)) ) { + $line = null; + break; + } + } + if ($line !== null) { + $filtered[] = $line; + } + } + + $filtered_buffer = implode("\n", $filtered); + return $filtered_buffer; + } + + /* + * Adds a comment element to the list of prefixes. + * + * @return comment The comment element added to the + * list of comment prefixes to strip. + */ + function createComment() { + $num = array_push($this->_comments, new Comment()); + return $this->_comments[$num-1]; + } + + /* + * Sets the list of comment prefixes to strip. + * + * @param comments A list of strings, each of which is a prefix + * for a comment line. Must not be null. + */ + function setComments($lineBreaks) { + if (!is_array($lineBreaks)) { + throw new Exception("Excpected 'array', got something else"); + } + $this->_comments = $lineBreaks; + } + + /* + * Returns the list of comment prefixes to strip. + * + * @return array The list of comment prefixes to strip. + */ + function getComments() { + return $this->_comments; + } + + /* + * Creates a new StripLineComments using the passed in + * Reader for instantiation. + * + * @param reader A Reader object providing the underlying stream. + * Must not be null. + * + * @return a new filter based on this configuration, but filtering + * the specified reader + */ + function chain(Reader $reader) { + $newFilter = new StripLineComments($reader); + $newFilter->setComments($this->getComments()); + $newFilter->setInitialized(true); + $newFilter->setProject($this->getProject()); + return $newFilter; + } + + /* + * Parses the parameters to set the comment prefixes. + */ + private function _initialize() { + $params = $this->getParameters(); + if ( $params !== null ) { + for($i = 0 ; $igetType() ) { + $comment = new Comment(); + $comment->setValue($params[$i]->getValue()); + array_push($this->_comments, $comment); + } + } + } + } +} + +/** + * The class that holds a comment representation. + * + * @package phing.filters + */ +class Comment { + + /** The prefix for a line comment. */ + private $_value; + + /* + * Sets the prefix for this type of line comment. + * + * @param string $value The prefix for a line comment of this type. + * Must not be null. + */ + function setValue($value) { + $this->_value = (string) $value; + } + + /* + * Returns the prefix for this type of line comment. + * + * @return string The prefix for this type of line comment. + */ + function getValue() { + return $this->_value; + } +} + diff --git a/library/phing/filters/StripPhpComments.php b/library/phing/filters/StripPhpComments.php new file mode 100644 index 000000000..8a095aa77 --- /dev/null +++ b/library/phing/filters/StripPhpComments.php @@ -0,0 +1,188 @@ +. +*/ + +include_once 'phing/filters/BaseFilterReader.php'; +include_once 'phing/filters/ChainableReader.php'; + +/** + * This is a Php comment and string stripper reader that filters + * those lexical tokens out for purposes of simple Php parsing. + * (if you have more complex Php parsing needs, use a real lexer). + * Since this class heavily relies on the single char read function, + * you are reccomended to make it work on top of a buffered reader. + * + * @author Yannick Lecaillez + * @author hans lellelid, hans@velum.net + * @version $Revision: 905 $ $Date: 2010-10-05 18:28:03 +0200 (Tue, 05 Oct 2010) $ + * @access public + * @see FilterReader + * @package phing.filters + */ +class StripPhpComments extends BaseFilterReader implements ChainableReader { + /** + * The read-ahead character, used for effectively pushing a single + * character back. -1 indicates that no character is in the buffer. + */ + private $_readAheadCh = -1; + + /** + * Whether or not the parser is currently in the middle of a string + * literal. + * @var boolean + */ + private $_inString = false; + + /** + * Returns the stream without Php comments. + * + * @return the resulting stream, or -1 + * if the end of the resulting stream has been reached + * + * @throws IOException if the underlying stream throws an IOException + * during reading + */ + function read($len = null) { + + $buffer = $this->in->read($len); + if($buffer === -1) { + return -1; + } + + // This regex replace /* */ and // style comments + $buffer = preg_replace('/\/\*[^*]*\*+([^\/*][^*]*\*+)*\/|\/\/[^\n]*|("(\\\\.|[^"\\\\])*"|\'(\\\\.|[^\'\\\\])*\'|.[^\/"\'\\\\]*)/s', "$2", $buffer); + + // The regex above is not identical to, but is based on the expression below: + // + // created by Jeffrey Friedl + // and later modified by Fred Curtis. + // s{ + // /\* ## Start of /* ... */ comment + // [^*]*\*+ ## Non-* followed by 1-or-more *'s + // ( + // [^/*][^*]*\*+ + // )* ## 0-or-more things which don't start with / + // ## but do end with '*' + // / ## End of /* ... */ comment + // + // | ## OR various things which aren't comments: + // + // ( + // " ## Start of " ... " string + // ( + // \\. ## Escaped char + // | ## OR + // [^"\\] ## Non "\ + // )* + // " ## End of " ... " string + // + // | ## OR + // + // ' ## Start of ' ... ' string + // ( + // \\. ## Escaped char + // | ## OR + // [^'\\] ## Non '\ + // )* + // ' ## End of ' ... ' string + // + // | ## OR + // + // . ## Anything other char + // [^/"'\\]* ## Chars which doesn't start a comment, string or escape + // ) + // }{$2}gxs; + + return $buffer; + } + + + /* + * Returns the next character in the filtered stream, not including + * Php comments. + * + * @return the next character in the resulting stream, or -1 + * if the end of the resulting stream has been reached + * + * @throws IOException if the underlying stream throws an IOException + * during reading + * @deprecated + */ + function readChar() { + $ch = -1; + + if ( $this->_readAheadCh !== -1 ) { + $ch = $this->_readAheadCh; + $this->_readAheadCh = -1; + } else { + $ch = $this->in->readChar(); + if ( $ch === "\"" ) { + $this->_inString = !$this->_inString; + } else { + if ( !$this->_inString ) { + if ( $ch === "/" ) { + $ch = $this->in->readChar(); + if ( $ch === "/" ) { + while ( $ch !== "\n" && $ch !== -1 ) { + $ch = $this->in->readChar(); + } + } else if ( $ch === "*" ) { + while ( $ch !== -1 ) { + $ch = $this->in->readChar(); + while ( $ch === "*" && $ch !== -1 ) { + $ch = $this->in->readChar(); + } + + if ( $ch === "/" ) { + $ch = $this->readChar(); + echo "$ch\n"; + break; + } + } + } else { + $this->_readAheadCh = $ch; + $ch = "/"; + } + } + } + } + } + + return $ch; + } + + /** + * Creates a new StripPhpComments using the passed in + * Reader for instantiation. + * + * @param reader A Reader object providing the underlying stream. + * Must not be null. + * + * @return a new filter based on this configuration, but filtering + * the specified reader + */ + function chain(Reader $reader) { + $newFilter = new StripPhpComments($reader); + $newFilter->setProject($this->getProject()); + return $newFilter; + } +} + diff --git a/library/phing/filters/StripWhitespace.php b/library/phing/filters/StripWhitespace.php new file mode 100644 index 000000000..badc4d196 --- /dev/null +++ b/library/phing/filters/StripWhitespace.php @@ -0,0 +1,95 @@ +. +*/ + +include_once 'phing/filters/BaseFilterReader.php'; +include_once 'phing/filters/ChainableReader.php'; + +/** + * Strips whitespace from [php] files using PHP stripwhitespace() method. + * + * @author Hans Lellelid, hans@velum.net + * @version $Revision: 905 $ $Date: 2010-10-05 18:28:03 +0200 (Tue, 05 Oct 2010) $ + * @see FilterReader + * @package phing.filters + * @todo -c use new PHP functions to perform this instead of regex. + */ +class StripWhitespace extends BaseFilterReader implements ChainableReader { + + private $processed = false; + + /** + * Returns the stream without Php comments and whitespace. + * + * @return the resulting stream, or -1 + * if the end of the resulting stream has been reached + * + * @throws IOException if the underlying stream throws an IOException + * during reading + */ + function read($len = null) { + + if ($this->processed === true) { + return -1; // EOF + } + + // Read XML + $php = null; + while ( ($buffer = $this->in->read($len)) !== -1 ) { + $php .= $buffer; + } + + if ($php === null ) { // EOF? + return -1; + } + + if(empty($php)) { + $this->log("PHP file is empty!", Project::MSG_WARN); + return ''; // return empty string, don't attempt to strip whitespace + } + + // write buffer to a temporary file, since php_strip_whitespace() needs a filename + $file = new PhingFile(tempnam(PhingFile::getTempDir(), 'stripwhitespace')); + file_put_contents($file->getAbsolutePath(), $php); + $output = php_strip_whitespace($file->getAbsolutePath()); + unlink($file->getAbsolutePath()); + + $this->processed = true; + + return $output; + } + + /** + * Creates a new StripWhitespace using the passed in + * Reader for instantiation. + * + * @param reader A Reader object providing the underlying stream. + * Must not be null. + * + * @return a new filter based on this configuration, but filtering + * the specified reader + */ + public function chain(Reader $reader) { + $newFilter = new StripWhitespace($reader); + $newFilter->setProject($this->getProject()); + return $newFilter; + } +} diff --git a/library/phing/filters/TabToSpaces.php b/library/phing/filters/TabToSpaces.php new file mode 100644 index 000000000..1d6996d33 --- /dev/null +++ b/library/phing/filters/TabToSpaces.php @@ -0,0 +1,144 @@ +. +*/ + +require_once 'phing/filters/BaseParamFilterReader.php'; +require_once 'phing/filters/ChainableReader.php'; + +/** + * Converts tabs to spaces. + * + * Example: + * + *
    + * + * Or: + * + *
    
    + *   
    + * 
    + * + * @author Yannick Lecaillez + * @author Hans Lellelid + * @version $Revision: 905 $ + * @see BaseParamFilterReader + * @package phing.filters + */ +class TabToSpaces extends BaseParamFilterReader implements ChainableReader { + + /** + * The default tab length. + * @var int + */ + const DEFAULT_TAB_LENGTH = 8; + + /** + * Parameter name for the length of a tab. + * @var string + */ + const TAB_LENGTH_KEY = "tablength"; + + /** + * Tab length in this filter. + * @var int + */ + private $tabLength = 8; //self::DEFAULT_TAB_LENGTH; + + /** + * Returns stream after converting tabs to the specified number of spaces. + * + * @return the resulting stream, or -1 + * if the end of the resulting stream has been reached + * + * @exception IOException if the underlying stream throws an IOException + * during reading + */ + function read($len = null) { + + if ( !$this->getInitialized() ) { + $this->_initialize(); + $this->setInitialized(true); + } + + $buffer = $this->in->read($len); + + if($buffer === -1) { + return -1; + } + + $buffer = str_replace("\t", str_repeat(' ', $this->tabLength), $buffer); + + return $buffer; + } + + /** + * Sets the tab length. + * + * @param int $tabLength The number of spaces to be used when converting a tab. + */ + function setTablength($tabLength) { + $this->tabLength = (int) $tabLength; + } + + /** + * Returns the tab length. + * + * @return int The number of spaces used when converting a tab + */ + function getTablength() { + return $this->tabLength; + } + + /** + * Creates a new TabsToSpaces using the passed in + * Reader for instantiation. + * + * @param Reader $reader A Reader object providing the underlying stream. + * Must not be null. + * + * @return Reader A new filter based on this configuration, but filtering + * the specified reader + */ + function chain(Reader $reader) { + $newFilter = new TabToSpaces($reader); + $newFilter->setTablength($this->getTablength()); + $newFilter->setInitialized(true); + $newFilter->setProject($this->getProject()); + return $newFilter; + } + + /** + * Parses the parameters to set the tab length. + */ + private function _initialize() { + $params = $this->getParameters(); + if ( $params !== null ) { + for($i = 0 ; $igetName()) { + $this->tabLength = $params[$i]->getValue(); + break; + } + } + } + } +} + + diff --git a/library/phing/filters/TailFilter.php b/library/phing/filters/TailFilter.php new file mode 100644 index 000000000..f93f7af56 --- /dev/null +++ b/library/phing/filters/TailFilter.php @@ -0,0 +1,157 @@ +. +*/ + +require_once 'phing/filters/BaseParamFilterReader.php'; + +/** + * Reads the last n lines of a stream. (Default is last10 lines.) + * + * Example: + * + *
    + * + * Or: + * + *
    
    + *   
    + * 
    + * + * @author Yannick Lecaillez + * @author hans lellelid, hans@velum.net + * @copyright © 2003 seasonfive. All rights reserved + * @version $Revision: 905 $ + * @see BaseParamFilterReader + * @package phing.filters + */ +class TailFilter extends BaseParamFilterReader implements ChainableReader { + + /** + * Parameter name for the number of lines to be returned. + * @var string + */ + const LINES_KEY = "lines"; + + + /** + * Number of lines to be returned in the filtered stream. + * @var integer + */ + private $_lines = 10; + + /** + * Array to hold lines. + * @var array + */ + private $_lineBuffer = array(); + + /** + * Returns the last n lines of a file. + * @param int $len Num chars to read. + * @return mixed The filtered buffer or -1 if EOF. + */ + function read($len = null) { + + while ( ($buffer = $this->in->read($len)) !== -1 ) { + // Remove the last "\n" from buffer for + // prevent explode to add an empty cell at + // the end of array + $buffer= trim($buffer, "\n"); + + $lines = explode("\n", $buffer); + + if ( count($lines) >= $this->_lines ) { + // Buffer have more (or same) number of lines than needed. + // Fill lineBuffer with the last "$this->_lines" lasts ones. + $off = count($lines)-$this->_lines; + $this->_lineBuffer = array_slice($lines, $off); + } else { + // Some new lines ... + // Prepare space for insert these new ones + $this->_lineBuffer = array_slice($this->_lineBuffer, count($lines)-1); + $this->_lineBuffer = array_merge($this->_lineBuffer, $lines); + } + } + + if ( empty($this->_lineBuffer) ) + $ret = -1; + else { + $ret = implode("\n", $this->_lineBuffer); + $this->_lineBuffer = array(); + } + + return $ret; + } + + /** + * Sets the number of lines to be returned in the filtered stream. + * + * @param integer $lines the number of lines to be returned in the filtered stream. + */ + function setLines($lines) { + $this->_lines = (int) $lines; + } + + /** + * Returns the number of lines to be returned in the filtered stream. + * + * @return integer The number of lines to be returned in the filtered stream. + */ + function getLines() { + return $this->_lines; + } + + /** + * Creates a new TailFilter using the passed in + * Reader for instantiation. + * + * @param object A Reader object providing the underlying stream. + * Must not be null. + * + * @return object A new filter based on this configuration, but filtering + * the specified reader. + */ + function chain(Reader $reader) { + $newFilter = new TailFilter($reader); + $newFilter->setLines($this->getLines()); + $newFilter->setInitialized(true); + $newFilter->setProject($this->getProject()); + return $newFilter; + } + + /** + * Scans the parameters list for the "lines" parameter and uses + * it to set the number of lines to be returned in the filtered stream. + */ + private function _initialize() { + $params = $this->getParameters(); + if ( $params !== null ) { + for($i=0, $_i=count($params); $i < $_i; $i++) { + if ( self::LINES_KEY == $params[$i]->getName() ) { + $this->_lines = (int) $params[$i]->getValue(); + break; + } + } + } + } +} + + diff --git a/library/phing/filters/TidyFilter.php b/library/phing/filters/TidyFilter.php new file mode 100644 index 000000000..09e187b38 --- /dev/null +++ b/library/phing/filters/TidyFilter.php @@ -0,0 +1,162 @@ +. +*/ + +include_once 'phing/filters/BaseParamFilterReader.php'; +include_once 'phing/filters/ChainableReader.php'; + +/** + * This filter uses the bundled-with-PHP Tidy extension to filter input. + * + *

    + * Example:
    + *

    + * 
    + *   
    + *   
    + * 
    + * 
    + * + * @author Hans Lellelid + * @version $Revision: 905 $ $Date: 2010-10-05 18:28:03 +0200 (Tue, 05 Oct 2010) $ + * @package phing.filters + */ +class TidyFilter extends BaseParamFilterReader implements ChainableReader { + + /** @var string Encoding of resulting document. */ + private $encoding = 'utf8'; + + /** @var array Parameter[] */ + private $configParameters = array(); + + /** + * Set the encoding for resulting (X)HTML document. + * @param string $v + */ + public function setEncoding($v) { + $this->encoding = $v; + } + + /** + * Sets the config params. + * @param array Parameter[] + * @see chain() + */ + public function setConfigParameters($params) + { + $this->configParameters = $params; + } + + /** + * Adds a element (which is a Parameter). + * @return Parameter + */ + public function createConfig() { + $num = array_push($this->configParameters, new Parameter()); + return $this->configParameters[$num-1]; + } + + /** + * Converts the Parameter objects being used to store configuration into a simle assoc array. + * @return array + */ + private function getDistilledConfig() { + $config = array(); + foreach($this->configParameters as $p) { + $config[$p->getName()] = $p->getValue(); + } + return $config; + } + + /** + * Reads input and returns Tidy-filtered output. + * + * @return the resulting stream, or -1 if the end of the resulting stream has been reached + * + * @throws IOException if the underlying stream throws an IOException + * during reading + */ + function read($len = null) { + + if (!class_exists('Tidy')) { + throw new BuildException("You must enable the 'tidy' extension in your PHP configuration in order to use the Tidy filter."); + } + + if ( !$this->getInitialized() ) { + $this->_initialize(); + $this->setInitialized(true); + } + + $buffer = $this->in->read($len); + if($buffer === -1) { + return -1; + } + + $config = $this->getDistilledConfig(); + + $tidy = new Tidy(); + $tidy->parseString($buffer, $config, $this->encoding); + $tidy->cleanRepair(); + + return tidy_get_output($tidy); + + } + + + /** + * Creates a new TidyFilter using the passed in Reader for instantiation. + * + * @param reader A Reader object providing the underlying stream. + * Must not be null. + * + * @return a new filter based on this configuration, but filtering + * the specified reader + */ + public function chain(Reader $reader) { + $newFilter = new TidyFilter($reader); + $newFilter->setConfigParameters($this->configParameters); + $newFilter->setEncoding($this->encoding); + $newFilter->setProject($this->getProject()); + return $newFilter; + } + + /** + * Initializes any parameters (e.g. config options). + * This method is only called when this filter is used through a tag in build file. + */ + private function _initialize() { + $params = $this->getParameters(); + if ($params) { + foreach($params as $param) { + if ($param->getType() == "config") { + $this->configParameters[] = $param; + } else { + + if ($param->getName() == "encoding") { + $this->setEncoding($param->getValue()); + } + + } + + } + } + } + +} diff --git a/library/phing/filters/TranslateGettext.php b/library/phing/filters/TranslateGettext.php new file mode 100644 index 000000000..4f734cc5a --- /dev/null +++ b/library/phing/filters/TranslateGettext.php @@ -0,0 +1,285 @@ +. +*/ + +require_once 'phing/filters/BaseParamFilterReader.php'; +include_once 'phing/filters/ChainableReader.php'; + +/** + * Replaces gettext("message id") and _("message id") with the translated string. + * + * Gettext is great for creating multi-lingual sites, but in some cases (e.g. for + * performance reasons) you may wish to replace the gettext calls with the translations + * of the strings; that's what this task is for. Note that this is similar to + * ReplaceTokens, but both the find and the replace aspect is more complicated -- hence + * this is a separate, stand-alone filter. + * + *

    + * Example:
    + *

    + * 
    + * 
    + * + * @author Hans Lellelid + * @version $Revision: 905 $ $Date: 2010-10-05 18:28:03 +0200 (Tue, 05 Oct 2010) $ + * @access public + * @see BaseFilterReader + * @package phing.filters + */ +class TranslateGettext extends BaseParamFilterReader implements ChainableReader { + + // constants for specifying keys to expect + // when this is called using + const DOMAIN_KEY = "domain"; + const DIR_KEY = "dir"; + const LOCALE_KEY = "locale"; + + /** The domain to use */ + private $domain = 'messages'; + + /** The dir containing LC_MESSAGES */ + private $dir; + + /** The locale to use */ + private $locale; + + /** The system locale before it was changed for this filter. */ + private $storedLocale; + + /** + * Set the text domain to use. + * The text domain must correspond to the name of the compiled .mo files. + * E.g. "messages" ==> $dir/LC_MESSAGES/messages.mo + * "mydomain" ==> $dir/LC_MESSAGES/mydomain.mo + * @param string $domain + */ + function setDomain($domain) { + $this->domain = $domain; + } + + /** + * Get the current domain. + * @return string + */ + function getDomain() { + return $this->domain; + } + + /** + * Sets the root locale directory. + * @param PhingFile $dir + */ + function setDir(PhingFile $dir) { + $this->dir = $dir; + } + + /** + * Gets the root locale directory. + * @return PhingFile + */ + function getDir() { + return $this->dir; + } + + /** + * Sets the locale to use for translation. + * Note that for gettext() to work, you have to make sure this locale + * is specific enough for your system (e.g. some systems may allow an 'en' locale, + * but others will require 'en_US', etc.). + * @param string $locale + */ + function setLocale($locale) { + $this->locale = $locale; + } + + /** + * Gets the locale to use for translation. + * @return string + */ + function getLocale() { + return $this->locale; + } + + /** + * Make sure that required attributes are set. + * @throws BuldException - if any required attribs aren't set. + */ + protected function checkAttributes() { + if (!$this->domain || !$this->locale || !$this->dir) { + throw new BuildException("You must specify values for domain, locale, and dir attributes."); + } + } + + /** + * Initialize the gettext/locale environment. + * This method will change some env vars and locale settings; the + * restoreEnvironment should put them all back :) + * + * @return void + * @throws BuildException - if locale cannot be set. + * @see restoreEnvironment() + */ + protected function initEnvironment() { + $this->storedLocale = getenv("LANG"); + + $this->log("Setting locale to " . $this->locale, Project::MSG_DEBUG); + putenv("LANG=".$this->locale); + $ret = setlocale(LC_ALL, $this->locale); + if ($ret === false) { + $msg = "Could not set locale to " . $this->locale + . ". You may need to use fully qualified name" + . " (e.g. en_US instead of en)."; + throw new BuildException($msg); + } + + $this->log("Binding domain '".$this->domain."' to " . $this->dir, Project::MSG_DEBUG); + bindtextdomain($this->domain, $this->dir->getAbsolutePath()); + textdomain($this->domain); + } + + /** + * Restores environment settings and locale. + * This does _not_ restore any gettext-specific settings + * (e.g. textdomain()). + * + * @return void + */ + protected function restoreEnvironment() { + putenv("LANG=".$this->storedLocale); + setlocale(LC_ALL, $this->storedLocale); + } + + /** + * Performs gettext translation of msgid and returns translated text. + * + * This function simply wraps gettext() call, but provides ability to log + * string replacements. (alternative would be using preg_replace with /e which + * would probably be faster, but no ability to debug/log.) + * + * @param array $matches Array of matches; we're interested in $matches[2]. + * @return string Translated text + */ + private function xlateStringCallback($matches) { + $charbefore = $matches[1]; + $msgid = $matches[2]; + $translated = gettext($msgid); + $this->log("Translating \"$msgid\" => \"$translated\"", Project::MSG_DEBUG); + return $charbefore . '"' . $translated . '"'; + } + + /** + * Returns the filtered stream. + * The original stream is first read in fully, and then translation is performed. + * + * @return mixed the filtered stream, or -1 if the end of the resulting stream has been reached. + * + * @throws IOException - if the underlying stream throws an IOException during reading + * @throws BuildException - if the correct params are not supplied + */ + function read($len = null) { + + if ( !$this->getInitialized() ) { + $this->_initialize(); + $this->setInitialized(true); + } + + // Make sure correct params/attribs have been set + $this->checkAttributes(); + + $buffer = $this->in->read($len); + if($buffer === -1) { + return -1; + } + + // Setup the locale/gettext environment + $this->initEnvironment(); + + + // replace any occurrences of _("") or gettext("") with + // the translated value. + // + // ([^\w]|^)_\("((\\"|[^"])*)"\) + // --$1--- -----$2---- + // ---$3-- [match escaped quotes or any char that's not a quote] + // + // also match gettext() -- same as above + + $buffer = preg_replace_callback('/([^\w]|^)_\("((\\\"|[^"])*)"\)/', array($this, 'xlateStringCallback'), $buffer); + $buffer = preg_replace_callback('/([^\w]|^)gettext\("((\\\"|[^"])*)"\)/', array($this, 'xlateStringCallback'), $buffer); + + // Check to see if there are any _('') calls and flag an error + + // Check to see if there are any unmatched gettext() calls -- and flag an error + + $matches = array(); + if (preg_match('/([^\w]|^)(gettext\([^\)]+\))/', $buffer, $matches)) { + $this->log("Unable to perform translation on: " . $matches[2], Project::MSG_WARN); + } + + $this->restoreEnvironment(); + + return $buffer; + } + + /** + * Creates a new TranslateGettext filter using the passed in + * Reader for instantiation. + * + * @param Reader $reader A Reader object providing the underlying stream. + * Must not be null. + * + * @return TranslateGettext A new filter based on this configuration, but filtering + * the specified reader + */ + function chain(Reader $reader) { + $newFilter = new TranslateGettext($reader); + $newFilter->setProject($this->getProject()); + $newFilter->setDomain($this->getDomain()); + $newFilter->setLocale($this->getLocale()); + $newFilter->setDir($this->getDir()); + return $newFilter; + } + + /** + * Parses the parameters if this filter is being used in "generic" mode. + */ + private function _initialize() { + $params = $this->getParameters(); + if ( $params !== null ) { + foreach($params as $param) { + switch($param->getType()) { + case self::DOMAIN_KEY: + $this->setDomain($param->getValue()); + break; + case self::DIR_KEY: + $this->setDir($this->project->resolveFile($param->getValue())); + break; + + case self::LOCALE_KEY: + $this->setLocale($param->getValue()); + break; + } // switch + } + } // if params !== null + } +} + + diff --git a/library/phing/filters/XincludeFilter.php b/library/phing/filters/XincludeFilter.php new file mode 100644 index 000000000..86af7f6d1 --- /dev/null +++ b/library/phing/filters/XincludeFilter.php @@ -0,0 +1,176 @@ +. + */ + +include_once 'phing/filters/BaseParamFilterReader.php'; +include_once 'phing/filters/ChainableReader.php'; + +/** + * Applies Xinclude parsing to incoming text. + * + * Uses PHP DOM XML support + * + * @author Bill Karwin + * @version $Id: XincludeFilter.php 905 2010-10-05 16:28:03Z mrook $ + * @see FilterReader + * @package phing.filters + */ +class XincludeFilter extends BaseParamFilterReader implements ChainableReader { + + private $basedir = null; + + /** + * @var bool + */ + private $processed = false; + + /** + * Whether to resolve entities. + * + * @var bool + * + * @since 2.4 + */ + private $resolveExternals = false; + + /** + * Whether to resolve entities. + * + * @param $resolveExternals + * + * @since 2.4 + */ + public function setResolveExternals($resolveExternals) + { + $this->resolveExternals = (bool)$resolveExternals; + } + + /** + * @return bool + * + * @since 2.4 + */ + public function getResolveExternals() + { + return $this->resolveExternals; + } + + public function setBasedir(PhingFile $dir) + { + $this->basedir = $dir; + } + + public function getBasedir() + { + return $this->basedir; + } + + /** + * Reads stream, applies XSLT and returns resulting stream. + * @return string transformed buffer. + * @throws BuildException - if XSLT support missing, if error in xslt processing + */ + function read($len = null) { + + if (!class_exists('DomDocument')) { + throw new BuildException("Could not find the DomDocument class. Make sure PHP has been compiled/configured to support DOM XML."); + } + + if ($this->processed === true) { + return -1; // EOF + } + + // Read XML + $_xml = null; + while ( ($data = $this->in->read($len)) !== -1 ) + $_xml .= $data; + + if ($_xml === null ) { // EOF? + return -1; + } + + if (empty($_xml)) { + $this->log("XML file is empty!", Project::MSG_WARN); + return ''; + } + + $this->log("Transforming XML " . $this->in->getResource() . " using Xinclude ", Project::MSG_VERBOSE); + + $out = ''; + try { + $out = $this->process($_xml); + $this->processed = true; + } catch (IOException $e) { + throw new BuildException($e); + } + + return $out; + } + + /** + * Try to process the Xinclude transformation + * + * @param string XML to process. + * + * @throws BuildException On errors + */ + protected function process($xml) { + + if ($this->basedir) { + $cwd = getcwd(); + chdir($this->basedir); + } + + // Create and setup document. + $xmlDom = new DomDocument(); + $xmlDom->resolveExternals = $this->resolveExternals; + + $xmlDom->loadXML($xml); + + $xmlDom->xinclude(); + + if ($this->basedir) { + chdir($cwd); + } + + return $xmlDom->saveXML(); + } + + /** + * Creates a new XincludeFilter using the passed in + * Reader for instantiation. + * + * @param Reader A Reader object providing the underlying stream. + * Must not be null. + * + * @return Reader A new filter based on this configuration, but filtering + * the specified reader + */ + function chain(Reader $reader) { + $newFilter = new XincludeFilter($reader); + $newFilter->setProject($this->getProject()); + $newFilter->setBasedir($this->getBasedir()); + return $newFilter; + } + +} + + diff --git a/library/phing/filters/XsltFilter.php b/library/phing/filters/XsltFilter.php new file mode 100644 index 000000000..ac8a333af --- /dev/null +++ b/library/phing/filters/XsltFilter.php @@ -0,0 +1,408 @@ +. +*/ + +include_once 'phing/filters/BaseParamFilterReader.php'; +include_once 'phing/filters/ChainableReader.php'; + +/** + * Applies XSL stylesheet to incoming text. + * + * Uses PHP XSLT support (libxslt). + * + * @author Hans Lellelid + * @author Yannick Lecaillez + * @author Andreas Aderhold + * @version $Id: XsltFilter.php 905 2010-10-05 16:28:03Z mrook $ + * @see FilterReader + * @package phing.filters + */ +class XsltFilter extends BaseParamFilterReader implements ChainableReader { + + /** + * Path to XSL stylesheet. + * @var string + */ + private $xslFile = null; + + /** + * Whether XML file has been transformed. + * @var boolean + */ + private $processed = false; + + /** + * XSLT Params. + * @var array + */ + private $xsltParams = array(); + + /** + * Whether to use loadHTML() to parse the input XML file. + */ + private $html = false; + + /** + * Whether to resolve entities in the XML document (see + * {@link http://www.php.net/manual/en/class.domdocument.php#domdocument.props.resolveexternals} + * for more details). + * + * @var bool + * + * @since 2.4 + */ + private $resolveDocumentExternals = false; + + /** + * Whether to resolve entities in the stylesheet. + * + * @var bool + * + * @since 2.4 + */ + private $resolveStylesheetExternals = false; + + /** + * Create new XSLT Param object, to handle the nested element. + * @return XSLTParam + */ + function createParam() { + $num = array_push($this->xsltParams, new XSLTParam()); + return $this->xsltParams[$num-1]; + } + + /** + * Sets the XSLT params for this class. + * This is used to "clone" this class, in the chain() method. + * @param array $params + */ + function setParams($params) { + $this->xsltParams = $params; + } + + /** + * Returns the XSLT params set for this class. + * This is used to "clone" this class, in the chain() method. + * @return array + */ + function getParams() { + return $this->xsltParams; + } + + /** + * Set the XSLT stylesheet. + * @param mixed $file PhingFile object or path. + */ + function setStyle(PhingFile $file) { + $this->xslFile = $file; + } + + /** + * Whether to use HTML parser for the XML. + * This is supported in libxml2 -- Yay! + * @return boolean + */ + function getHtml() { + return $this->html; + } + + /** + * Whether to use HTML parser for XML. + * @param boolean $b + */ + function setHtml($b) { + $this->html = (boolean) $b; + } + + /** + * Get the path to XSLT stylesheet. + * @return mixed XSLT stylesheet path. + */ + function getStyle() { + return $this->xslFile; + } + + /** + * Whether to resolve entities in document. + * + * @param bool $resolveExternals + * + * @since 2.4 + */ + function setResolveDocumentExternals($resolveExternals) { + $this->resolveDocumentExternals = (bool)$resolveExternals; + } + + /** + * @return bool + * + * @since 2.4 + */ + function getResolveDocumentExternals() { + return $this->resolveDocumentExternals; + } + + /** + * Whether to resolve entities in stylesheet. + * + * @param bool $resolveExternals + * + * @since 2.4 + */ + function setResolveStylesheetExternals($resolveExternals) { + $this->resolveStylesheetExternals = (bool)$resolveExternals; + } + + /** + * @return bool + * + * @since 2.4 + */ + function getResolveStylesheetExternals() { + return $this->resolveStylesheetExternals; + } + + /** + * Reads stream, applies XSLT and returns resulting stream. + * @return string transformed buffer. + * @throws BuildException - if XSLT support missing, if error in xslt processing + */ + function read($len = null) { + + if (!class_exists('XSLTProcessor')) { + throw new BuildException("Could not find the XSLTProcessor class. Make sure PHP has been compiled/configured to support XSLT."); + } + + if ($this->processed === true) { + return -1; // EOF + } + + if ( !$this->getInitialized() ) { + $this->_initialize(); + $this->setInitialized(true); + } + + // Read XML + $_xml = null; + while ( ($data = $this->in->read($len)) !== -1 ) + $_xml .= $data; + + if ($_xml === null ) { // EOF? + return -1; + } + + if(empty($_xml)) { + $this->log("XML file is empty!", Project::MSG_WARN); + return ''; // return empty string, don't attempt to apply XSLT + } + + // Read XSLT + $_xsl = null; + $xslFr = new FileReader($this->xslFile); + $xslFr->readInto($_xsl); + + $this->log("Tranforming XML " . $this->in->getResource() . " using style " . $this->xslFile->getPath(), Project::MSG_VERBOSE); + + $out = ''; + try { + $out = $this->process($_xml, $_xsl); + $this->processed = true; + } catch (IOException $e) { + throw new BuildException($e); + } + + return $out; + } + + // {{{ method _ProcessXsltTransformation($xml, $xslt) throws BuildException + /** + * Try to process the XSLT transformation + * + * @param string XML to process. + * @param string XSLT sheet to use for the processing. + * + * @throws BuildException On XSLT errors + */ + protected function process($xml, $xsl) { + + $processor = new XSLTProcessor(); + + // Create and setup document. + $xmlDom = new DOMDocument(); + $xmlDom->resolveExternals = $this->resolveDocumentExternals; + + // Create and setup stylesheet. + $xslDom = new DOMDocument(); + $xslDom->resolveExternals = $this->resolveStylesheetExternals; + + if ($this->html) { + $xmlDom->loadHTML($xml); + } else { + $xmlDom->loadXML($xml); + } + + $xslDom->loadxml($xsl); + + $processor->importStylesheet($xslDom); + + // ignoring param "type" attrib, because + // we're only supporting direct XSL params right now + foreach($this->xsltParams as $param) { + $this->log("Setting XSLT param: " . $param->getName() . "=>" . $param->getExpression(), Project::MSG_DEBUG); + $processor->setParameter(null, $param->getName(), $param->getExpression()); + } + + $errorlevel = error_reporting(); + error_reporting($errorlevel & ~E_WARNING); + @$result = $processor->transformToXML($xmlDom); + error_reporting($errorlevel); + + if (false === $result) { + //$errno = xslt_errno($processor); + //$err = xslt_error($processor); + throw new BuildException("XSLT Error"); + } else { + return $result; + } + } + + /** + * Creates a new XsltFilter using the passed in + * Reader for instantiation. + * + * @param Reader A Reader object providing the underlying stream. + * Must not be null. + * + * @return Reader A new filter based on this configuration, but filtering + * the specified reader + */ + function chain(Reader $reader) { + $newFilter = new XsltFilter($reader); + $newFilter->setProject($this->getProject()); + $newFilter->setStyle($this->getStyle()); + $newFilter->setInitialized(true); + $newFilter->setParams($this->getParams()); + $newFilter->setHtml($this->getHtml()); + return $newFilter; + } + + /** + * Parses the parameters to get stylesheet path. + */ + private function _initialize() { + $params = $this->getParameters(); + if ( $params !== null ) { + for($i = 0, $_i=count($params) ; $i < $_i; $i++) { + if ( $params[$i]->getType() === null ) { + if ($params[$i]->getName() === "style") { + $this->setStyle($params[$i]->getValue()); + } + } elseif ($params[$i]->getType() == "param") { + $xp = new XSLTParam(); + $xp->setName($params[$i]->getName()); + $xp->setExpression($params[$i]->getValue()); + $this->xsltParams[] = $xp; + } + } + } + } + +} + + +/** + * Class that holds an XSLT parameter. + * + * @package phing.filters + */ +class XSLTParam { + + private $name; + + private $expr; + + /** + * Sets param name. + * @param string $name + */ + public function setName($name) { + $this->name = $name; + } + + /** + * Get param name. + * @return string + */ + public function getName() { + return $this->name; + } + + /** + * Sets expression value (alias to the setExpression()) method. + * + * @param string $v + * @see setExpression() + */ + public function setValue($v) + { + $this->setExpression($v); + } + + /** + * Gets expression value (alias to the getExpression()) method. + * + * @param string $v + * @see getExpression() + */ + public function getValue() + { + return $this->getExpression(); + } + + /** + * Sets expression value. + * @param string $expr + */ + public function setExpression($expr) { + $this->expr = $expr; + } + + /** + * Sets expression to dynamic register slot. + * @param RegisterSlot $expr + */ + public function setListeningExpression(RegisterSlot $expr) { + $this->expr = $expr; + } + + /** + * Returns expression value -- performs lookup if expr is registerslot. + * @return string + */ + public function getExpression() { + if ($this->expr instanceof RegisterSlot) { + return $this->expr->getValue(); + } else { + return $this->expr; + } + } +} + diff --git a/library/phing/filters/util/ChainReaderHelper.php b/library/phing/filters/util/ChainReaderHelper.php new file mode 100644 index 000000000..9da0bce32 --- /dev/null +++ b/library/phing/filters/util/ChainReaderHelper.php @@ -0,0 +1,183 @@ +. +*/ + +include_once 'phing/Project.php'; +include_once 'phing/filters/BaseFilterReader.php'; +include_once 'phing/types/PhingFilterReader.php'; +include_once 'phing/types/FilterChain.php'; +include_once 'phing/types/Parameter.php'; +include_once 'phing/util/FileUtils.php'; +include_once 'phing/util/StringHelper.php'; +include_once 'phing/filters/ChainableReader.php'; + +/** + * Process a FilterReader chain. + * + * Here, the interesting method is 'getAssembledReader'. + * The purpose of this one is to create a simple Reader object which + * apply all filters on another primary Reader object. + * + * For example : In copyFile (phing.util.FileUtils) the primary Reader + * is a FileReader object (more accuratly, a BufferedReader) previously + * setted for the source file to copy. So, consider this filterchain : + * + * + * + * + * + * + * + * + * + * getAssembledReader will return a Reader object wich read on each + * of these filters. Something like this : ('->' = 'which read data from') : + * + * [TABTOSPACES] -> [LINECONTAINS] -> [STRIPPHPCOMMENTS] -> [FILEREADER] + * (primary reader) + * + * So, getAssembledReader will return the TABTOSPACES Reader object. Then + * each read done with this Reader object will follow this path. + * + * Hope this explanation is clear :) + * + * TODO: Implement the classPath feature. + * + * @author Yannick Lecaillez + * @version $Revision: 905 $ $Date: 2010-10-05 18:28:03 +0200 (Tue, 05 Oct 2010) $ + * @access public + * @package phing.filters.util +*/ +class ChainReaderHelper { + + /** Primary reader to wich the reader chain is to be attached */ + private $primaryReader = null; + + /** The site of the buffer to be used. */ + private $bufferSize = 8192; + + /** Chain of filters */ + private $filterChains = array(); + + /** The Phing project */ + private $project; + + /* + * Sets the primary reader + */ + function setPrimaryReader(Reader $reader) { + $this->primaryReader = $reader; + } + + /* + * Set the project to work with + */ + function setProject(Project $project) { + $this->project = $project; + } + + /* + * Get the project + */ + function getProject() { + return $this->project; + } + + /* + * Sets the buffer size to be used. Defaults to 8192, + * if this method is not invoked. + */ + function setBufferSize($size) { + $this->bufferSize = $size; + } + + /* + * Sets the collection of filter reader sets + */ + function setFilterChains(&$fchain) { + $this->filterChains = &$fchain; + } + + /* + * Assemble the reader + */ + function getAssembledReader() { + + $instream = $this->primaryReader; + $filterReadersCount = count($this->filterChains); + $finalFilters = array(); + + // Collect all filter readers of all filter chains used ... + for($i = 0 ; $i<$filterReadersCount ; $i++) { + $filterchain = &$this->filterChains[$i]; + $filterReaders = $filterchain->getFilterReaders(); + $readerCount = count($filterReaders); + for($j = 0 ; $j<$readerCount ; $j++) { + $finalFilters[] = $filterReaders[$j]; + } + } + + // ... then chain the filter readers. + $filtersCount = count($finalFilters); + if ( $filtersCount > 0 ) { + for($i = 0 ; $i<$filtersCount ; $i++) { + $filter = $finalFilters[$i]; + + if ( $filter instanceof PhingFilterReader ) { + + // This filter reader is an external class. + $className = $filter->getClassName(); + $classpath = $filter->getClasspath(); + $project = $filter->getProject(); + + if ( $className !== null ) { + $cls = Phing::import($className, $classpath); + $impl = new $cls(); + } + + if ( !($impl instanceof FilterReader) ) { + throw new Exception($className." does not extend phing.system.io.FilterReader"); + } + + $impl->setReader($instream); // chain + $impl->setProject($this->getProject()); // what about $project above ? + + if ( $impl instanceof Parameterizable ) { + $impl->setParameters($filter->getParams()); + } + + $instream = $impl; // now that it's been chained + + } elseif (($filter instanceof ChainableReader) && ($filter instanceof Reader)) { + if ( $this->getProject() !== null && ($filter instanceof BaseFilterReader) ) { + $filter->setProject($this->getProject()); + } + $instream = $filter->chain($instream); + } else { + throw new Exception("Cannot chain invalid filter: " . get_class($filter)); + } + } + } + + return $instream; + } + +} + diff --git a/library/phing/filters/util/IniFileTokenReader.php b/library/phing/filters/util/IniFileTokenReader.php new file mode 100644 index 000000000..a595b57b7 --- /dev/null +++ b/library/phing/filters/util/IniFileTokenReader.php @@ -0,0 +1,97 @@ +. +*/ + +include_once 'phing/types/TokenReader.php'; +include_once 'phing/system/io/IOException.php'; +include_once 'phing/filters/ReplaceTokens.php'; // For class Token + +/** + * Class that allows reading tokens from INI files. + * + * @author Manuel Holtgewe + * @version $Revision: 905 $ + * @package phing.filters.util + */ +class IniFileTokenReader extends TokenReader { + + /** + * Holds the path to the INI file that is to be read. + * @var object Reference to a PhingFile Object representing + * the path to the INI file. + */ + private $file = null; + + /** + * @var string Sets the section to load from the INI file. + * if omitted, all sections are loaded. + */ + private $section = null; + + /** + * Reads the next token from the INI file + * + * @throws IOException On error + * @return Token + */ + function readToken() { + if ($this->file === null) { + throw new BuildException("No File set for IniFileTokenReader"); + } + + static $tokens = null; + if ($tokens === null) { + $tokens = array(); + $arr = parse_ini_file($this->file->getAbsolutePath(), true); + if ($this->section === null) { + foreach ($arr as $sec_name => $values) { + foreach($arr[$sec_name] as $key => $value) { + $tok = new Token; + $tok->setKey($key); + $tok->setValue($value); + $tokens[] = $tok; + } + } + } else if (isset($arr[$this->section])) { + foreach ($arr[$this->section] as $key => $value) { + $tok = new Token; + $tok->setKey($key); + $tok->setValue($value); + $tokens[] = $tok; + } + } + } + + if (count($tokens) > 0) { + return array_pop($tokens); + } else + return null; + } + + function setFile(PhingFile $file) { + $this->file = $file; + } + + function setSection($str) { + $this->section = (string) $str; + } +} + + diff --git a/library/phing/input/DefaultInputHandler.php b/library/phing/input/DefaultInputHandler.php new file mode 100644 index 000000000..d851f57ad --- /dev/null +++ b/library/phing/input/DefaultInputHandler.php @@ -0,0 +1,81 @@ +. + */ + +require_once 'phing/input/InputHandler.php'; +include_once 'phing/system/io/ConsoleReader.php'; + +/** + * Prompts using print(); reads input from Console. + * + * @author Hans Lellelid (Phing) + * @author Stefan Bodewig (Ant) + * @version $Revision: 905 $ + * @package phing.input + */ +class DefaultInputHandler implements InputHandler { + + /** + * Prompts and requests input. May loop until a valid input has + * been entered. + * @throws BuildException + */ + public function handleInput(InputRequest $request) { + $prompt = $this->getPrompt($request); + $in = new ConsoleReader(); + do { + print $prompt; + try { + $input = $in->readLine(); + if ($input === "" && ($request->getDefaultValue() !== null) ) { + $input = $request->getDefaultValue(); + } + $request->setInput($input); + } catch (Exception $e) { + throw new BuildException("Failed to read input from Console.", $e); + } + } while (!$request->isInputValid()); + } + + /** + * Constructs user prompt from a request. + * + *

    This implementation adds (choice1,choice2,choice3,...) to the + * prompt for MultipleChoiceInputRequests.

    + * + * @param $request the request to construct the prompt for. + * Must not be null. + */ + protected function getPrompt(InputRequest $request) { + $prompt = $request->getPrompt(); + + if ($request instanceof YesNoInputRequest) { + $prompt .= '(' . implode('/', $request->getChoices()) .')'; + } elseif ($request instanceof MultipleChoiceInputRequest) { // (a,b,c,d) + $prompt .= '(' . implode(',', $request->getChoices()) . ')'; + } + if ($request->getDefaultValue() !== null) { + $prompt .= ' ['.$request->getDefaultValue().']'; + } + $pchar = $request->getPromptChar(); + return $prompt . ($pchar ? $pchar . ' ' : ' '); + } +} diff --git a/library/phing/input/InputHandler.php b/library/phing/input/InputHandler.php new file mode 100644 index 000000000..7c021772b --- /dev/null +++ b/library/phing/input/InputHandler.php @@ -0,0 +1,45 @@ +. + */ + +/** + * Plugin to Phing to handle requests for user input. + * + * @author Stefan Bodewig + * @version $Revision: 905 $ + * @package phing.input + */ +interface InputHandler { + + /** + * Handle the request encapsulated in the argument. + * + *

    Precondition: the request.getPrompt will return a non-null + * value.

    + * + *

    Postcondition: request.getInput will return a non-null + * value, request.isInputValid will return true.

    + * @return void + * @throws BuildException + */ + public function handleInput(InputRequest $request); + +} diff --git a/library/phing/input/InputRequest.php b/library/phing/input/InputRequest.php new file mode 100644 index 000000000..8af93ff78 --- /dev/null +++ b/library/phing/input/InputRequest.php @@ -0,0 +1,107 @@ +. + */ + +/** + * Encapsulates an input request. + * + * @author Hans Lellelid (Phing) + * @author Stefan Bodewig (Ant) + * @version $Revision: 905 $ + * @package phing.input + */ +class InputRequest { + + protected $prompt; + protected $input; + protected $defaultValue; + protected $promptChar; + + /** + * @param string $prompt The prompt to show to the user. Must not be null. + */ + public function __construct($prompt) { + if ($prompt === null) { + throw new BuildException("prompt must not be null"); + } + $this->prompt = $prompt; + } + + /** + * Retrieves the prompt text. + */ + public function getPrompt() { + return $this->prompt; + } + + /** + * Sets the user provided input. + */ + public function setInput($input) { + $this->input = $input; + } + + /** + * Is the user input valid? + */ + public function isInputValid() { + return true; + } + + /** + * Retrieves the user input. + */ + public function getInput() { + return $this->input; + } + + /** + * Set the default value to use. + * @param mixed $v + */ + public function setDefaultValue($v) { + $this->defaultValue = $v; + } + + /** + * Return the default value to use. + * @return mixed + */ + public function getDefaultValue() { + return $this->defaultValue; + } + + /** + * Set the default value to use. + * @param string $c + */ + public function setPromptChar($c) { + $this->promptChar = $c; + } + + /** + * Return the default value to use. + * @return string + */ + public function getPromptChar() { + return $this->promptChar; + } +} diff --git a/library/phing/input/MultipleChoiceInputRequest.php b/library/phing/input/MultipleChoiceInputRequest.php new file mode 100644 index 000000000..e852f0729 --- /dev/null +++ b/library/phing/input/MultipleChoiceInputRequest.php @@ -0,0 +1,58 @@ +. + */ + +require_once 'phing/input/InputRequest.php'; + +/** + * Encapsulates an input request. + * + * @author Stefan Bodewig + * @version $Revision: 905 $ + * @package phing.input + */ +class MultipleChoiceInputRequest extends InputRequest { + + protected $choices = array(); + + /** + * @param string $prompt The prompt to show to the user. Must not be null. + * @param array $choices holds all input values that are allowed. + * Must not be null. + */ + public function __construct($prompt, $choices) { + parent::__construct($prompt); + $this->choices = $choices; + } + + /** + * @return The possible values. + */ + public function getChoices() { + return $this->choices; + } + + /** + * @return true if the input is one of the allowed values. + */ + public function isInputValid() { + return in_array($this->getInput(), $this->choices); // not strict (?) + } +} diff --git a/library/phing/input/YesNoInputRequest.php b/library/phing/input/YesNoInputRequest.php new file mode 100644 index 000000000..5b2c3f144 --- /dev/null +++ b/library/phing/input/YesNoInputRequest.php @@ -0,0 +1,47 @@ +. + */ + +require_once 'phing/input/MultipleChoiceInputRequest.php'; + +/** + * Encapsulates an input request that returns a boolean (yes/no). + * + * @author Hans Lellelid + * @version $Revision: 905 $ + * @package phing.input + */ +class YesNoInputRequest extends MultipleChoiceInputRequest { + + /** + * @return true if the input is one of the allowed values. + */ + public function isInputValid() { + return StringHelper::isBoolean($this->input); + } + + /** + * Converts input to boolean. + * @return boolean + */ + public function getInput() { + return StringHelper::booleanValue($this->input); + } +} diff --git a/library/phing/lib/Capsule.php b/library/phing/lib/Capsule.php new file mode 100644 index 000000000..29b6ac990 --- /dev/null +++ b/library/phing/lib/Capsule.php @@ -0,0 +1,267 @@ + + * @version $Revision: 905 $ + * @package phing.lib + */ +class Capsule { + + /** + * Look for templates here (if relative path provided). + * @var string + */ + protected $templatePath; + + /** + * Where should output files be written? + * (This is named inconsistently to be compatible w/ Texen.) + * @var string + */ + protected $outputDirectory; + + /** + * The variables that can be used by the templates. + * @var array Hash of variables. + */ + public $vars = array(); + + /** + * Has template been initialized. + */ + protected $initialized = false; + + /** + * Stores the pre-parse() include_path. + * @var string + */ + private $old_include_path; + + function __construct() { + } + + /** + * Clears one or several or all variables. + * @param mixed $which String name of var, or array of names. + * @return void + */ + function clear($which = null) { + if ($which === null) { + $this->vars = array(); + } elseif (is_array($which)) { + foreach($which as $var) { + unset($this->vars[$var]); + } + } else { + unset($this->vars[$which]); + } + } + + /** + * Set the basepath to use for template lookups. + * @param string $v + */ + function setTemplatePath($v) { + $this->templatePath = rtrim($v, DIRECTORY_SEPARATOR.'/'); + } + + /** + * Get the basepath to use for template lookups. + * @return string + */ + function getTemplatePath() { + return $this->templatePath; + } + + /** + * Set a basepath to use for output file creation. + * @param string $v + */ + function setOutputDirectory($v) { + $this->outputDirectory = rtrim($v, DIRECTORY_SEPARATOR.'/'); + } + + /** + * Get basepath to use for output file creation. + * @return string + */ + function getOutputDirectory() { + return $this->outputDirectory; + } + + /** + * Low overhead (no output buffering) method to simply dump template + * to buffer. + * + * @param string $__template + * @return void + * @throws Exception - if template cannot be found + */ + function display($__template) { + + // Prepend "private" variable names with $__ in this function + // to keep namespace conflict potential to a minimum. + + // Alias this class to $generator. + $generator = $this; + + if (isset($this->vars['this'])) { + throw new Exception("Assigning a variable named \$this to a context conflicts with class namespace."); + } + + // extract variables into local namespace + extract($this->vars); + + // prepend template path to include path, + // so that include "path/relative/to/templates"; can be used within templates + $__old_inc_path = ini_get('include_path'); + ini_set('include_path', $this->templatePath . PATH_SEPARATOR . $__old_inc_path); + + @ini_set('track_errors', true); + include $__template; + @ini_restore('track_errors'); + + // restore the include path + ini_set('include_path', $__old_inc_path); + + if (!empty($php_errormsg)) { + throw new Exception("Unable to parse template " . $__template . ": " . $php_errormsg); + } + } + + /** + * Fetches the results of a tempalte parse and either returns + * the string or writes results to a specified output file. + * + * @param string $template The template filename (relative to templatePath or absolute). + * @param string $outputFile If specified, contents of template will also be written to this file. + * @param boolean $append Should output be appended to source file? + * @return string The "parsed" template output. + * @throws Exception - if template not found. + */ + function parse($template, $outputFile = null, $append = false) { + + // main work done right here: + // hopefully this works recursively ... fingers crossed. + ob_start(); + + try { + $this->display($template); + } catch (Exception $e) { + ob_end_flush(); // flush the output on error (so we can see up to what point it parsed everything) + throw $e; + } + + $output = ob_get_contents(); + ob_end_clean(); + + if ($outputFile !== null) { + $outputFile = $this->resolvePath($outputFile, $this->outputDirectory); + + $flags = null; + if ($append) $flags = FILE_APPEND; + + if (!file_put_contents($outputFile, $output, $flags) && $output != "") { + throw new Exception("Unable to write output to " . $outputFile); + } + } + + return $output; + } + + /** + * This returns a "best guess" path for the given file. + * + * @param string $file File name or possibly absolute path. + * @param string $basepath The basepath that should be prepended if $file is not absolute. + * @return string "Best guess" path for this file. + */ + protected function resolvePath($file, $basepath) { + if ( !($file{0} == DIRECTORY_SEPARATOR || $file{0} == '/') + // also account for C:\ style path + && !($file{1} == ':' && ($file{2} == DIRECTORY_SEPARATOR || $file{2} == '/'))) { + if ($basepath != null) { + $file = $basepath . DIRECTORY_SEPARATOR . $file; + } + } + return $file; + } + + /** + * Gets value of specified var or NULL if var has not been put(). + * @param string $name Variable name to retrieve. + * @return mixed + */ + function get($name) { + if (!isset($this->vars[$name])) return null; + return $this->vars[$name]; + } + + /** + * Merges in passed hash to vars array. + * + * Given an array like: + * + * array( 'myvar' => 'Hello', + * 'myvar2' => 'Hello') + * + * Resulting template will have access to $myvar and $myvar2. + * + * @param array $vars + * @param boolean $recursiveMerge Should matching keys be recursively merged? + * @return void + */ + function putAll($vars, $recursiveMerge = false) { + if ($recursiveMerge) { + $this->vars = array_merge_recursive($this->vars, $vars); + } else { + $this->vars = array_merge($this->vars, $vars); + } + } + + /** + * Adds a variable to the context. + * + * Resulting template will have access to ${$name$} variable. + * + * @param string $name + * @param mixed $value + */ + function put($name, $value) { + $this->vars[$name] = $value; + } + + /** + * Put a variable into the context, assigning it by reference. + * This means that if the template modifies the variable, then it + * will also be modified in the context. + * + * @param $name + * @param &$value + */ + function putRef($name, &$value) { + $this->vars[$name] = &$value; + } + + /** + * Makes a copy of the value and puts it into the context. + * This is primarily to force copying (cloning) of objects, rather + * than the default behavior which is to assign them by reference. + * @param string $name + * @param mixed $value + */ + function putCopy($name, $value) { + if (is_object($value)) { + $value = clone $value; + } + $this->vars[$name] = $value; + } + +} \ No newline at end of file diff --git a/library/phing/listener/AnsiColorLogger.php b/library/phing/listener/AnsiColorLogger.php new file mode 100644 index 000000000..a02a24c5d --- /dev/null +++ b/library/phing/listener/AnsiColorLogger.php @@ -0,0 +1,234 @@ +. + */ + +require_once 'phing/listener/DefaultLogger.php'; +include_once 'phing/system/util/Properties.php'; + +/** + * Uses ANSI Color Code Sequences to colorize messages + * sent to the console. + * + * If used with the -logfile option, the output file + * will contain all the necessary escape codes to + * display the text in colorized mode when displayed + * in the console using applications like cat, more, + * etc. + * + * This is designed to work on terminals that support ANSI + * color codes. It works on XTerm, ETerm, Mindterm, etc. + * It also works on Win9x (with ANSI.SYS loaded.) + * + * NOTE: + * It doesn't work on WinNT's COMMAND.COM even with + * ANSI.SYS loaded. + * + * The default colors used for differentiating + * the message levels can be changed by editing the + * phing/listener/defaults.properties file. + * + * This file contains 5 key/value pairs: + * AnsiColorLogger.ERROR_COLOR=2;31 + * AnsiColorLogger.WARNING_COLOR=2;35 + * AnsiColorLogger.INFO_COLOR=2;36 + * AnsiColorLogger.VERBOSE_COLOR=2;32 + * AnsiColorLogger.DEBUG_COLOR=2;34 + * + * Another option is to pass a system variable named + * ant.logger.defaults, with value set to the path of + * the file that contains user defined Ansi Color + * Codes, to the java command using -D option. + * + * To change these colors use the following chart: + * + * ANSI COLOR LOGGER CONFIGURATION + * + * Format for AnsiColorLogger.*= + * Attribute;Foreground;Background + * + * Attribute is one of the following: + * 0 -> Reset All Attributes (return to normal mode) + * 1 -> Bright (Usually turns on BOLD) + * 2 -> Dim + * 3 -> Underline + * 5 -> link + * 7 -> Reverse + * 8 -> Hidden + * + * Foreground is one of the following: + * 30 -> Black + * 31 -> Red + * 32 -> Green + * 33 -> Yellow + * 34 -> Blue + * 35 -> Magenta + * 36 -> Cyan + * 37 -> White + * + * Background is one of the following: + * 40 -> Black + * 41 -> Red + * 42 -> Green + * 43 -> Yellow + * 44 -> Blue + * 45 -> Magenta + * 46 -> Cyan + * 47 -> White + * + * @author Hans Lellelid (Phing) + * @author Magesh Umasankar (Ant) + * @package phing.listener + * @version $Revision: 905 $ + */ +class AnsiColorLogger extends DefaultLogger { + + const ATTR_NORMAL = 0; + const ATTR_BRIGHT = 1; + const ATTR_DIM = 2; + const ATTR_UNDERLINE = 3; + const ATTR_BLINK = 5; + const ATTR_REVERSE = 7; + const ATTR_HIDDEN = 8; + + const FG_BLACK = 30; + const FG_RED = 31; + const FG_GREEN = 32; + const FG_YELLOW = 33; + const FG_BLUE = 34; + const FG_MAGENTA = 35; + const FG_CYAN = 36; + const FG_WHITE = 37; + + const BG_BLACK = 40; + const BG_RED = 41; + const BG_GREEN = 42; + const BG_YELLOW = 44; + const BG_BLUE = 44; + const BG_MAGENTA = 45; + const BG_CYAN = 46; + const BG_WHITE = 47; + + const PREFIX = "\x1b["; + const SUFFIX = "m"; + const SEPARATOR = ';'; + const END_COLOR = "\x1b[m"; // self::PREFIX . self::SUFFIX; + + private $errColor; + private $warnColor; + private $infoColor; + private $verboseColor; + private $debugColor; + + private $colorsSet = false; + + /** + * Construct new AnsiColorLogger + * Perform initializations that cannot be done in var declarations. + */ + public function __construct() { + parent::__construct(); + $this->errColor = self::PREFIX . self::ATTR_NORMAL . self::SEPARATOR . self::FG_RED . self::SUFFIX; + $this->warnColor = self::PREFIX . self::ATTR_NORMAL . self::SEPARATOR . self::FG_MAGENTA . self::SUFFIX; + $this->infoColor = self::PREFIX . self::ATTR_NORMAL . self::SEPARATOR . self::FG_CYAN . self::SUFFIX; + $this->verboseColor = self::PREFIX . self::ATTR_NORMAL . self::SEPARATOR . self::FG_GREEN . self::SUFFIX; + $this->debugColor = self::PREFIX . self::ATTR_NORMAL . self::SEPARATOR . self::FG_BLUE . self::SUFFIX; + } + + /** + * Set the colors to use from a property file specified by the + * special ant property ant.logger.defaults + */ + private final function setColors() { + + $userColorFile = Phing::getProperty("phing.logger.defaults"); + $systemColorFile = new PhingFile(Phing::getResourcePath("phing/listener/defaults.properties")); + + $in = null; + + try { + $prop = new Properties(); + + if ($userColorFile !== null) { + $prop->load($userColorFile); + } else { + $prop->load($systemColorFile); + } + + $err = $prop->getProperty("AnsiColorLogger.ERROR_COLOR"); + $warn = $prop->getProperty("AnsiColorLogger.WARNING_COLOR"); + $info = $prop->getProperty("AnsiColorLogger.INFO_COLOR"); + $verbose = $prop->getProperty("AnsiColorLogger.VERBOSE_COLOR"); + $debug = $prop->getProperty("AnsiColorLogger.DEBUG_COLOR"); + if ($err !== null) { + $this->errColor = self::PREFIX . $err . self::SUFFIX; + } + if ($warn !== null) { + $this->warnColor = self::PREFIX . $warn . self::SUFFIX; + } + if ($info !== null) { + $this->infoColor = self::PREFIX . $info . self::SUFFIX; + } + if ($verbose !== null) { + $this->verboseColor = self::PREFIX . $verbose . self::SUFFIX; + } + if ($debug !== null) { + $this->debugColor = self::PREFIX . $debug . self::SUFFIX; + } + } catch (IOException $ioe) { + //Ignore exception - we will use the defaults. + } + } + + /** + * @see DefaultLogger#printMessage + * @param string $message + * @param OutputStream $stream + * @param int $priority + */ + protected final function printMessage($message, OutputStream $stream, $priority) { + if ($message !== null) { + + if (!$this->colorsSet) { + $this->setColors(); + $this->colorsSet = true; + } + + switch ($priority) { + case Project::MSG_ERR: + $message = $this->errColor . $message . self::END_COLOR; + break; + case Project::MSG_WARN: + $message = $this->warnColor . $message . self::END_COLOR; + break; + case Project::MSG_INFO: + $message = $this->infoColor . $message . self::END_COLOR; + break; + case Project::MSG_VERBOSE: + $message = $this->verboseColor . $message . self::END_COLOR; + break; + case Project::MSG_DEBUG: + $message = $this->debugColor . $message . self::END_COLOR; + break; + } + + $stream->write($message . PHP_EOL); + } + } +} diff --git a/library/phing/listener/DefaultLogger.php b/library/phing/listener/DefaultLogger.php new file mode 100644 index 000000000..f5d38024b --- /dev/null +++ b/library/phing/listener/DefaultLogger.php @@ -0,0 +1,279 @@ +. + */ + +require_once 'phing/listener/StreamRequiredBuildLogger.php'; +include_once 'phing/BuildEvent.php'; + +/** + * Writes a build event to the console. + * + * Currently, it only writes which targets are being executed, and + * any messages that get logged. + * + * @author Andreas Aderhold + * @copyright � 2001,2002 THYRELL. All rights reserved + * @version $Revision: 905 $ $Date: 2010-10-05 18:28:03 +0200 (Tue, 05 Oct 2010) $ + * @see BuildEvent + * @package phing.listener + */ +class DefaultLogger implements StreamRequiredBuildLogger { + + /** + * Size of the left column in output. The default char width is 12. + * @var int + */ + const LEFT_COLUMN_SIZE = 12; + + /** + * The message output level that should be used. The default is + * Project::MSG_VERBOSE. + * @var int + */ + protected $msgOutputLevel = Project::MSG_ERR; + + /** + * Time that the build started + * @var int + */ + protected $startTime; + + /** + * @var OutputStream Stream to use for standard output. + */ + protected $out; + + /** + * @var OutputStream Stream to use for error output. + */ + protected $err; + + /** + * Construct a new default logger. + */ + public function __construct() { + + } + + /** + * Set the msgOutputLevel this logger is to respond to. + * + * Only messages with a message level lower than or equal to the given + * level are output to the log. + * + *

    Constants for the message levels are in Project.php. The order of + * the levels, from least to most verbose, is: + * + *

      + *
    • Project::MSG_ERR
    • + *
    • Project::MSG_WARN
    • + *
    • Project::MSG_INFO
    • + *
    • Project::MSG_VERBOSE
    • + *
    • Project::MSG_DEBUG
    • + *
    + * + * The default message level for DefaultLogger is Project::MSG_ERR. + * + * @param int $level The logging level for the logger. + * @see BuildLogger#setMessageOutputLevel() + */ + public function setMessageOutputLevel($level) { + $this->msgOutputLevel = (int) $level; + } + + /** + * Sets the output stream. + * @param OutputStream $output + * @see BuildLogger#setOutputStream() + */ + public function setOutputStream(OutputStream $output) { + $this->out = $output; + } + + /** + * Sets the error stream. + * @param OutputStream $err + * @see BuildLogger#setErrorStream() + */ + public function setErrorStream(OutputStream $err) { + $this->err = $err; + } + + /** + * Sets the start-time when the build started. Used for calculating + * the build-time. + * + * @param object The BuildEvent + * @access public + */ + public function buildStarted(BuildEvent $event) { + $this->startTime = Phing::currentTimeMillis(); + if ($this->msgOutputLevel >= Project::MSG_INFO) { + $this->printMessage("Buildfile: ".$event->getProject()->getProperty("phing.file"), $this->out, Project::MSG_INFO); + } + } + + /** + * Prints whether the build succeeded or failed, and any errors that + * occured during the build. Also outputs the total build-time. + * + * @param object The BuildEvent + * @see BuildEvent::getException() + */ + public function buildFinished(BuildEvent $event) { + $error = $event->getException(); + if ($error === null) { + $msg = PHP_EOL . $this->getBuildSuccessfulMessage() . PHP_EOL; + } else { + $msg = PHP_EOL . $this->getBuildFailedMessage() . PHP_EOL; + if (Project::MSG_VERBOSE <= $this->msgOutputLevel || !($error instanceof BuildException)) { + $msg .= $error->__toString().PHP_EOL; + } else { + $msg .= $error->getMessage(); + } + } + $msg .= PHP_EOL . "Total time: " .self::formatTime(Phing::currentTimeMillis() - $this->startTime) . PHP_EOL; + + if ($error === null) { + $this->printMessage($msg, $this->out, Project::MSG_VERBOSE); + } else { + $this->printMessage($msg, $this->err, Project::MSG_ERR); + } + } + + /** + * Get the message to return when a build failed. + * @return string The classic "BUILD FAILED" + */ + protected function getBuildFailedMessage() { + return "BUILD FAILED"; + } + + /** + * Get the message to return when a build succeeded. + * @return string The classic "BUILD FINISHED" + */ + protected function getBuildSuccessfulMessage() { + return "BUILD FINISHED"; + } + + /** + * Prints the current target name + * + * @param object The BuildEvent + * @access public + * @see BuildEvent::getTarget() + */ + public function targetStarted(BuildEvent $event) { + if (Project::MSG_INFO <= $this->msgOutputLevel) { + $showLongTargets = $event->getProject()->getProperty("phing.showlongtargets"); + $msg = PHP_EOL . $event->getProject()->getName() . ' > ' . $event->getTarget()->getName() . ($showLongTargets ? ' [' . $event->getTarget()->getDescription() . ']' : '') . ':' . PHP_EOL; + $this->printMessage($msg, $this->out, $event->getPriority()); + } + } + + /** + * Fired when a target has finished. We don't need specific action on this + * event. So the methods are empty. + * + * @param object The BuildEvent + * @see BuildEvent::getException() + */ + public function targetFinished(BuildEvent $event) {} + + /** + * Fired when a task is started. We don't need specific action on this + * event. So the methods are empty. + * + * @param object The BuildEvent + * @access public + * @see BuildEvent::getTask() + */ + public function taskStarted(BuildEvent $event) {} + + /** + * Fired when a task has finished. We don't need specific action on this + * event. So the methods are empty. + * + * @param object The BuildEvent + * @access public + * @see BuildEvent::getException() + */ + public function taskFinished(BuildEvent $event) {} + + /** + * Print a message to the stdout. + * + * @param object The BuildEvent + * @access public + * @see BuildEvent::getMessage() + */ + public function messageLogged(BuildEvent $event) { + $priority = $event->getPriority(); + if ($priority <= $this->msgOutputLevel) { + $msg = ""; + if ($event->getTask() !== null) { + $name = $event->getTask(); + $name = $name->getTaskName(); + $msg = str_pad("[$name] ", self::LEFT_COLUMN_SIZE, " ", STR_PAD_LEFT); + } + + $msg .= $event->getMessage(); + + if ($priority != Project::MSG_ERR) { + $this->printMessage($msg, $this->out, $priority); + } else { + $this->printMessage($msg, $this->err, $priority); + } + } + } + + /** + * Formats a time micro integer to human readable format. + * + * @param integer The time stamp + * @access private + */ + public static function formatTime($micros) { + $seconds = $micros; + $minutes = $seconds / 60; + if ($minutes > 1) { + return sprintf("%1.0f minute%s %0.2f second%s", + $minutes, ($minutes === 1 ? " " : "s "), + $seconds - floor($seconds/60) * 60, ($seconds%60 === 1 ? "" : "s")); + } else { + return sprintf("%0.4f second%s", $seconds, ($seconds%60 === 1 ? "" : "s")); + } + } + + /** + * Prints a message to console. + * + * @param string $message The message to print. + * Should not be null. + * @param resource $stream The stream to use for message printing. + * @param int $priority The priority of the message. + * (Ignored in this implementation.) + * @return void + */ + protected function printMessage($message, OutputStream $stream, $priority) { + $stream->write($message . PHP_EOL); + } +} diff --git a/library/phing/listener/NoBannerLogger.php b/library/phing/listener/NoBannerLogger.php new file mode 100644 index 000000000..89d1a505b --- /dev/null +++ b/library/phing/listener/NoBannerLogger.php @@ -0,0 +1,59 @@ +. + */ + +require_once 'phing/listener/DefaultLogger.php'; + +/** + * Extends DefaultLogger to strip out empty targets. + * + * @author Andreas Aderhold + * @copyright � 2001,2002 THYRELL. All rights reserved + * @version $Revision: 905 $ $Date: 2010-10-05 18:28:03 +0200 (Tue, 05 Oct 2010) $ + * @package phing.listener + */ +class NoBannerLogger extends DefaultLogger { + + private $targetName = null; + + function targetStarted(BuildEvent $event) { + $target = $event->getTarget(); + $this->targetName = $target->getName(); + } + + function targetFinished(BuildEvent $event) { + $this->targetName = null; + } + + function messageLogged(BuildEvent $event) { + + if ($event->getPriority() > $this->msgOutputLevel || null === $event->getMessage() || trim($event->getMessage() === "")) { + return; + } + + if ($this->targetName !== null) { + $msg = PHP_EOL . $event->getProject()->getName() . ' > ' . $this->targetName . ':' . PHP_EOL; + $this->printMessage($msg, $this->out, $event->getPriority()); + $this->targetName = null; + } + + parent::messageLogged($event); + } +} diff --git a/library/phing/listener/PearLogListener.php b/library/phing/listener/PearLogListener.php new file mode 100644 index 000000000..b62bc0007 --- /dev/null +++ b/library/phing/listener/PearLogListener.php @@ -0,0 +1,197 @@ +. + */ + +require_once 'phing/BuildListener.php'; + +/** + * Writes build messages to PEAR Log. + * + * By default it will log to file in current directory w/ name 'phing.log'. You can customize + * this behavior by setting properties: + * - pear.log.type + * - pear.log.name + * - pear.log.ident (note that this class changes ident to project name) + * - pear.log.conf (note that array values are currently unsupported in Phing property files) + * + * + * phing -f build.xml -logger phing.listener.PearLogger -Dpear.log.type=file -Dpear.log.name=/path/to/log.log + * + * + * @author Hans Lellelid + * @version $Revision: 905 $ $Date: 2010-10-05 18:28:03 +0200 (Tue, 05 Oct 2010) $ + * @see BuildEvent + * @package phing.listener + */ +class PearLogListener implements BuildListener { + + /** + * Size of the left column in output. The default char width is 12. + * @var int + */ + const LEFT_COLUMN_SIZE = 12; + + /** + * Time that the build started + * @var int + */ + protected $startTime; + + /** + * Maps Phing Project::MSG_* constants to PEAR_LOG_* constants. + * @var array + */ + protected static $levelMap = array( Project::MSG_DEBUG => PEAR_LOG_DEBUG, + Project::MSG_INFO => PEAR_LOG_INFO, + Project::MSG_VERBOSE => PEAR_LOG_NOTICE, + Project::MSG_WARN => PEAR_LOG_WARNING, + Project::MSG_ERR => PEAR_LOG_ERR + ); + /** + * Whether logging has been configured. + * @var boolean + */ + protected $logConfigured = false; + + /** + * @var Log PEAR Log object. + */ + protected $logger; + + /** + * Configure the logger. + */ + protected function configureLogging() { + + $type = Phing::getDefinedProperty('pear.log.type'); + $name = Phing::getDefinedProperty('pear.log.name'); + $ident = Phing::getDefinedProperty('pear.log.ident'); + $conf = Phing::getDefinedProperty('pear.log.conf'); + + if ($type === null) $type = 'file'; + if ($name === null) $name = 'phing.log'; + if ($ident === null) $ident = 'phing'; + if ($conf === null) $conf = array(); + + include_once 'Log.php'; + if (!class_exists('Log')) { + throw new BuildException("Cannot find PEAR Log class for use by PearLogger."); + } + + $this->logger = Log::singleton($type, $name, $ident, $conf, self::$levelMap[$this->msgOutputLevel]); + } + + /** + * Get the configured PEAR logger to use. + * This method just ensures that logging has been configured and returns the configured logger. + * @return Log + */ + protected function logger() { + if (!$this->logConfigured) { + $this->configureLogging(); + } + return $this->logger; + } + + /** + * Sets the start-time when the build started. Used for calculating + * the build-time. + * + * @param BuildEvent The BuildEvent + */ + public function buildStarted(BuildEvent $event) { + $this->startTime = Phing::currentTimeMillis(); + $this->logger()->setIdent($event->getProject()->getName()); + $this->logger()->info("Starting build with buildfile: ". $event->getProject()->getProperty("phing.file")); + } + + /** + * Logs whether the build succeeded or failed, and any errors that + * occured during the build. Also outputs the total build-time. + * + * @param BuildEvent The BuildEvent + * @see BuildEvent::getException() + */ + public function buildFinished(BuildEvent $event) { + $error = $event->getException(); + if ($error === null) { + $msg = "Finished successful build."; + } else { + $msg = "Build failed. [reason: " . $error->getMessage() ."]"; + } + $this->logger()->log($msg . " Total time: " . DefaultLogger::formatTime(Phing::currentTimeMillis() - $this->startTime)); + } + + /** + * Logs the current target name + * + * @param BuildEvent The BuildEvent + * @see BuildEvent::getTarget() + */ + public function targetStarted(BuildEvent $event) {} + + /** + * Fired when a target has finished. We don't need specific action on this + * event. So the methods are empty. + * + * @param BuildEvent The BuildEvent + * @access public + * @see BuildEvent::getException() + */ + public function targetFinished(BuildEvent $event) {} + + /** + * Fired when a task is started. We don't need specific action on this + * event. So the methods are empty. + * + * @param BuildEvent The BuildEvent + * @access public + * @see BuildEvent::getTask() + */ + public function taskStarted(BuildEvent $event) {} + + /** + * Fired when a task has finished. We don't need specific action on this + * event. So the methods are empty. + * + * @param BuildEvent The BuildEvent + * @see BuildEvent::getException() + */ + public function taskFinished(BuildEvent $event) {} + + /** + * Logs a message to the configured PEAR logger. + * + * @param BuildEvent The BuildEvent + * @see BuildEvent::getMessage() + */ + public function messageLogged(BuildEvent $event) { + if ($event->getPriority() <= $this->msgOutputLevel) { + $msg = ""; + if ($event->getTask() !== null) { + $name = $event->getTask(); + $name = $name->getTaskName(); + $msg = str_pad("[$name] ", self::LEFT_COLUMN_SIZE, " ", STR_PAD_LEFT); + } + $msg .= $event->getMessage(); + $this->logger()->log($msg, self::$levelMap[$event->getPriority()]); + } + } +} diff --git a/library/phing/listener/StreamRequiredBuildLogger.php b/library/phing/listener/StreamRequiredBuildLogger.php new file mode 100644 index 000000000..ab62e8cc3 --- /dev/null +++ b/library/phing/listener/StreamRequiredBuildLogger.php @@ -0,0 +1,39 @@ +. + */ + +require_once 'phing/BuildLogger.php'; + +/** + * Interface for build loggers that require that out/err streams be set in order to function. + * + * This is just an empty sub-interface to BuildLogger, but is used by Phing to throw + * graceful errors when classes like phing.listener.DefaultLogger are being used as + * -listener. + * + * @author Hans Lellelid + * @version $Revision: 905 $ + * @see BuildEvent + * @see Project::addBuildListener() + * @package phing + */ +interface StreamRequiredBuildLogger extends BuildLogger { + +} \ No newline at end of file diff --git a/library/phing/listener/XmlLogger.php b/library/phing/listener/XmlLogger.php new file mode 100644 index 000000000..5d0f4e121 --- /dev/null +++ b/library/phing/listener/XmlLogger.php @@ -0,0 +1,354 @@ +. + */ + +require_once 'phing/BuildLogger.php'; +require_once 'phing/listener/DefaultLogger.php'; +require_once 'phing/system/util/Timer.php'; + +/** + * Generates a file in the current directory with + * an XML description of what happened during a build. + * The default filename is "log.xml", but this can be overridden + * with the property XmlLogger.file. + * + * @author Michiel Rook + * @version $Id: XmlLogger.php 905 2010-10-05 16:28:03Z mrook $ + * @package phing.listener + */ +class XmlLogger implements BuildLogger { + + /** XML element name for a build. */ + const BUILD_TAG = "build"; + + /** XML element name for a target. */ + const TARGET_TAG = "target"; + + /** XML element name for a task. */ + const TASK_TAG = "task"; + + /** XML element name for a message. */ + const MESSAGE_TAG = "message"; + + /** XML attribute name for a name. */ + const NAME_ATTR = "name"; + + /** XML attribute name for a time. */ + const TIME_ATTR = "time"; + + /** XML attribute name for a message priority. */ + const PRIORITY_ATTR = "priority"; + + /** XML attribute name for a file location. */ + const LOCATION_ATTR = "location"; + + /** XML attribute name for an error description. */ + const ERROR_ATTR = "error"; + + /** XML element name for a stack trace. */ + const STACKTRACE_TAG = "stacktrace"; + + /** + * @var DOMDocument The XML document created by this logger. + */ + private $doc; + + /** + * @var int Start time for entire build. + */ + private $buildTimerStart = 0; + + /** + * @var DOMElement Top-level (root) build element + */ + private $buildElement; + + /** + * @var array DOMElement[] The parent of the element being processed. + */ + private $elementStack = array(); + + /** + * @var array int[] Array of millisecond times for the various elements being processed. + */ + private $timesStack = array(); + + /** + * @var int + */ + private $msgOutputLevel = Project::MSG_DEBUG; + + /** + * @var OutputStream Stream to use for standard output. + */ + private $out; + + /** + * @var OutputStream Stream to use for error output. + */ + private $err; + + /** + * @var string Name of filename to create. + */ + private $outFilename; + + /** + * Constructs a new BuildListener that logs build events to an XML file. + */ + public function __construct() { + $this->doc = new DOMDocument("1.0", "UTF-8"); + $this->doc->formatOutput = true; + } + + /** + * Fired when the build starts, this builds the top-level element for the + * document and remembers the time of the start of the build. + * + * @param BuildEvent Ignored. + */ + function buildStarted(BuildEvent $event) { + $this->buildTimerStart = Phing::currentTimeMillis(); + $this->buildElement = $this->doc->createElement(XmlLogger::BUILD_TAG); + array_push($this->elementStack, $this->buildElement); + array_push($this->timesStack, $this->buildTimerStart); + } + + /** + * Fired when the build finishes, this adds the time taken and any + * error stacktrace to the build element and writes the document to disk. + * + * @param BuildEvent $event An event with any relevant extra information. + * Will not be null. + */ + public function buildFinished(BuildEvent $event) { + + $elapsedTime = Phing::currentTimeMillis() - $this->buildTimerStart; + + $this->buildElement->setAttribute(XmlLogger::TIME_ATTR, DefaultLogger::formatTime($elapsedTime)); + + if ($event->getException() != null) { + $this->buildElement->setAttribute(XmlLogger::ERROR_ATTR, $event->getException()->getMessage()); + $errText = $this->doc->createCDATASection($event->getException()->getTraceAsString()); + $stacktrace = $this->doc->createElement(XmlLogger::STACKTRACE_TAG); + $stacktrace->appendChild($errText); + $this->buildElement->appendChild($stacktrace); + } + + $this->doc->appendChild($this->buildElement); + + $outFilename = $event->getProject()->getProperty("XmlLogger.file"); + if ($outFilename == null) { + $outFilename = "log.xml"; + } + + try { + $stream = $this->out; + if ($stream === null) { + $stream = new FileOutputStream($outFilename); + } + + // Yes, we could just stream->write() but this will eventually be the better + // way to do this (when we need to worry about charset conversions. + $writer = new OutputStreamWriter($stream); + $writer->write($this->doc->saveXML()); + $writer->close(); + } catch (IOException $exc) { + try { + $stream->close(); // in case there is a stream open still ... + } catch (Exception $x) {} + throw new BuildException("Unable to write log file.", $exc); + } + + // cleanup:remove the buildElement + $this->buildElement = null; + + array_pop($this->elementStack); + array_pop($this->timesStack); + } + + + /** + * Fired when a target starts building, remembers the current time and the name of the target. + * + * @param BuildEvent $event An event with any relevant extra information. + * Will not be null. + */ + public function targetStarted(BuildEvent $event) { + $target = $event->getTarget(); + + $targetElement = $this->doc->createElement(XmlLogger::TARGET_TAG); + $targetElement->setAttribute(XmlLogger::NAME_ATTR, $target->getName()); + + array_push($this->timesStack, Phing::currentTimeMillis()); + array_push($this->elementStack, $targetElement); + } + + /** + * Fired when a target finishes building, this adds the time taken + * to the appropriate target element in the log. + * + * @param BuildEvent $event An event with any relevant extra information. + * Will not be null. + */ + public function targetFinished(BuildEvent $event) { + $targetTimerStart = array_pop($this->timesStack); + $targetElement = array_pop($this->elementStack); + + $elapsedTime = Phing::currentTimeMillis() - $targetTimerStart; + $targetElement->setAttribute(XmlLogger::TIME_ATTR, DefaultLogger::formatTime($elapsedTime)); + + $parentElement = $this->elementStack[ count($this->elementStack) - 1 ]; + $parentElement->appendChild($targetElement); + } + + /** + * Fired when a task starts building, remembers the current time and the name of the task. + * + * @param BuildEvent $event An event with any relevant extra information. + * Will not be null. + */ + public function taskStarted(BuildEvent $event) { + $task = $event->getTask(); + + $taskElement = $this->doc->createElement(XmlLogger::TASK_TAG); + $taskElement->setAttribute(XmlLogger::NAME_ATTR, $task->getTaskName()); + $taskElement->setAttribute(XmlLogger::LOCATION_ATTR, $task->getLocation()->toString()); + + array_push($this->timesStack, Phing::currentTimeMillis()); + array_push($this->elementStack, $taskElement); + } + + /** + * Fired when a task finishes building, this adds the time taken + * to the appropriate task element in the log. + * + * @param BuildEvent $event An event with any relevant extra information. + * Will not be null. + */ + public function taskFinished(BuildEvent $event) { + $taskTimerStart = array_pop($this->timesStack); + $taskElement = array_pop($this->elementStack); + + $elapsedTime = Phing::currentTimeMillis() - $taskTimerStart; + $taskElement->setAttribute(XmlLogger::TIME_ATTR, DefaultLogger::formatTime($elapsedTime)); + + $parentElement = $this->elementStack[ count($this->elementStack) - 1 ]; + $parentElement->appendChild($taskElement); + } + + /** + * Fired when a message is logged, this adds a message element to the + * most appropriate parent element (task, target or build) and records + * the priority and text of the message. + * + * @param BuildEvent An event with any relevant extra information. + * Will not be null. + */ + public function messageLogged(BuildEvent $event) + { + $priority = $event->getPriority(); + + if ($priority > $this->msgOutputLevel) { + return; + } + + $messageElement = $this->doc->createElement(XmlLogger::MESSAGE_TAG); + + switch ($priority) { + case Project::MSG_ERR: + $name = "error"; + break; + case Project::MSG_WARN: + $name = "warn"; + break; + case Project::MSG_INFO: + $name = "info"; + break; + default: + $name = "debug"; + break; + } + + $messageElement->setAttribute(XmlLogger::PRIORITY_ATTR, $name); + + if (function_exists('mb_convert_encoding')) + { + $messageConverted = mb_convert_encoding($event->getMessage(), 'UTF-8'); + } + else + { + $messageConverted = utf8_encode($event->getMessage()); + } + + $messageText = $this->doc->createCDATASection($messageConverted); + + $messageElement->appendChild($messageText); + + if (!empty($this->elementStack)) { + $this->elementStack[count($this->elementStack)-1]->appendChild($messageElement); + } + } + + /** + * Set the msgOutputLevel this logger is to respond to. + * + * Only messages with a message level lower than or equal to the given + * level are output to the log. + * + *

    Constants for the message levels are in Project.php. The order of + * the levels, from least to most verbose, is: + * + *

      + *
    • Project::MSG_ERR
    • + *
    • Project::MSG_WARN
    • + *
    • Project::MSG_INFO
    • + *
    • Project::MSG_VERBOSE
    • + *
    • Project::MSG_DEBUG
    • + *
    + * + * The default message level for DefaultLogger is Project::MSG_ERR. + * + * @param int $level The logging level for the logger. + * @see BuildLogger#setMessageOutputLevel() + */ + public function setMessageOutputLevel($level) { + $this->msgOutputLevel = (int) $level; + } + + /** + * Sets the output stream. + * @param OutputStream $output + * @see BuildLogger#setOutputStream() + */ + public function setOutputStream(OutputStream $output) { + $this->out = $output; + } + + /** + * Sets the error stream. + * @param OutputStream $err + * @see BuildLogger#setErrorStream() + */ + public function setErrorStream(OutputStream $err) { + $this->err = $err; + } + +} diff --git a/library/phing/mappers/FileNameMapper.php b/library/phing/mappers/FileNameMapper.php new file mode 100644 index 000000000..99f4c2be4 --- /dev/null +++ b/library/phing/mappers/FileNameMapper.php @@ -0,0 +1,59 @@ +. + */ + +/** + * Interface for filename mapper classes. + * + * @author Andreas Aderhold, andi@binarycloud.com + * @author Hans Lellelid + * @version $Revision: 905 $ + * @package phing.mappers + */ +interface FileNameMapper { + + /** + * The mapper implementation. + * + * @param mixed $sourceFileName The data the mapper works on. + * @return array The data after the mapper has been applied; must be in array format (for some reason). + */ + public function main($sourceFileName); + + /** + * Accessor. Sets the to property. The actual implementation + * depends on the child class. + * + * @param string $to To what this mapper should convert the from string + * @return void + */ + public function setTo($to); + + /** + * Accessor. Sets the from property. What this mapper should + * recognize. The actual implementation is dependent upon the + * child class + * + * @param string $from On what this mapper should work + * @return void + */ + public function setFrom($from); + +} diff --git a/library/phing/mappers/FlattenMapper.php b/library/phing/mappers/FlattenMapper.php new file mode 100644 index 000000000..91e5ac521 --- /dev/null +++ b/library/phing/mappers/FlattenMapper.php @@ -0,0 +1,55 @@ +. + */ + +require_once 'phing/mappers/FileNameMapper.php'; + +/** + * Removes any directory information from the passed path. + * + * @author Andreas Aderhold + * @version $Revision: 905 $ + * @package phing.mappers + */ +class FlattenMapper implements FileNameMapper { + + /** + * The mapper implementation. Returns string with source filename + * but without leading directory information + * + * @param string $sourceFileName The data the mapper works on + * @return array The data after the mapper has been applied + */ + function main($sourceFileName) { + $f = new PhingFile($sourceFileName); + return array($f->getName()); + } + + /** + * Ignored here. + */ + function setTo($to) {} + + /** + * Ignored here. + */ + function setFrom($from) {} + +} diff --git a/library/phing/mappers/GlobMapper.php b/library/phing/mappers/GlobMapper.php new file mode 100644 index 000000000..ffc212f41 --- /dev/null +++ b/library/phing/mappers/GlobMapper.php @@ -0,0 +1,113 @@ +. + */ + +include_once 'phing/mappers/FileNameMapper.php'; + +/** + * description here + * + * @author Andreas Aderhold, andi@binarycloud.com + * @version $Revision: 905 $ + * @package phing.mappers + */ +class GlobMapper implements FileNameMapper { + + /** + * Part of "from" pattern before the *. + */ + private $fromPrefix = null; + + /** + * Part of "from" pattern after the *. + */ + private $fromPostfix = null; + + /** + * Length of the prefix ("from" pattern). + */ + private $prefixLength; + + /** + * Length of the postfix ("from" pattern). + */ + private $postfixLength; + + /** + * Part of "to" pattern before the *. + */ + private $toPrefix = null; + + /** + * Part of "to" pattern after the *. + */ + private $toPostfix = null; + + + function main($_sourceFileName) { + if (($this->fromPrefix === null) + || !StringHelper::startsWith($this->fromPrefix, $_sourceFileName) + || !StringHelper::endsWith($this->fromPostfix, $_sourceFileName)) { + return null; + } + $varpart = $this->_extractVariablePart($_sourceFileName); + $substitution = $this->toPrefix.$varpart.$this->toPostfix; + return array($substitution); + } + + + + function setFrom($from) { + $index = strrpos($from, '*'); + + if ($index === false) { + $this->fromPrefix = $from; + $this->fromPostfix = ""; + } else { + $this->fromPrefix = substr($from, 0, $index); + $this->fromPostfix = substr($from, $index+1); + } + $this->prefixLength = strlen($this->fromPrefix); + $this->postfixLength = strlen($this->fromPostfix); + } + + /** + * Sets the "to" pattern. Required. + */ + function setTo($to) { + $index = strrpos($to, '*'); + if ($index === false) { + $this->toPrefix = $to; + $this->toPostfix = ""; + } else { + $this->toPrefix = substr($to, 0, $index); + $this->toPostfix = substr($to, $index+1); + } + } + + private function _extractVariablePart($_name) { + // ergh, i really hate php's string functions .... all but natural + $start = ($this->prefixLength === 0) ? 0 : $this->prefixLength; + $end = ($this->postfixLength === 0) ? strlen($_name) : strlen($_name) - $this->postfixLength; + $len = $end-$start; + return substr($_name, $start, $len); + } + +} diff --git a/library/phing/mappers/IdentityMapper.php b/library/phing/mappers/IdentityMapper.php new file mode 100644 index 000000000..96887bf3b --- /dev/null +++ b/library/phing/mappers/IdentityMapper.php @@ -0,0 +1,54 @@ +. + */ + +require_once 'phing/mappers/FileNameMapper.php'; + +/** + * This mapper does nothing ;) + * + * @author Andreas Aderhold + * @author Hans Lellelid + * @version $Revision: 905 $ + * @package phing.mappers + */ +class IdentityMapper implements FileNameMapper { + + /** + * The mapper implementation. Basically does nothing in this case. + * + * @param string $sourceFileName The data the mapper works on. + * @return array The data after the mapper has been applied + */ + function main($sourceFileName) { + return array($sourceFileName); + } + + /** + * Ignored here. + */ + function setTo($to) {} + + /** + * Ignored here. + */ + function setFrom($from) {} + +} diff --git a/library/phing/mappers/MergeMapper.php b/library/phing/mappers/MergeMapper.php new file mode 100644 index 000000000..f2a85e57b --- /dev/null +++ b/library/phing/mappers/MergeMapper.php @@ -0,0 +1,69 @@ +. + */ + +include_once 'phing/mappers/FileNameMapper.php'; + +/** + * For merging files into a single file. In practice just returns whatever value + * was set for "to". + * + * @author Andreas Aderhold + * @version $Revision: 905 $ + * @package phing.mappers + */ +class MergeMapper implements FileNameMapper { + + /** the merge */ + private $mergedFile; + + /** + * The mapper implementation. Basically does nothing in this case. + * + * @param mixed The data the mapper works on + * @returns mixed The data after the mapper has been applied + * @access public + * @author Andreas Aderhold, andi@binarycloud.com + */ + function main($sourceFileName) { + if ($this->mergedFile === null) { + throw new BuildException("MergeMapper error, to attribute not set"); + } + return array($this->mergedFile); + } + + /** + * Accessor. Sets the to property + * + * @param string To what this mapper should convert the from string + * @returns boolean True + * @access public + * @author Andreas Aderhold, andi@binarycloud.com + */ + function setTo($to) { + $this->mergedFile = $to; + } + + /** + * Ignored. + */ + function setFrom($from) {} + +} diff --git a/library/phing/mappers/RegexpMapper.php b/library/phing/mappers/RegexpMapper.php new file mode 100644 index 000000000..3ee433901 --- /dev/null +++ b/library/phing/mappers/RegexpMapper.php @@ -0,0 +1,97 @@ +. + */ + +require_once 'phing/mappers/FileNameMapper.php'; +include_once 'phing/util/StringHelper.php'; +include_once 'phing/util/regexp/Regexp.php'; + +/** + * Uses regular expressions to perform filename transformations. + * + * @author Andreas Aderhold + * @author Hans Lellelid + * @version $Revision: 905 $ + * @package phing.mappers + */ +class RegexpMapper implements FileNameMapper { + + /** + * @var string + */ + private $to; + + /** + * The Regexp engine. + * @var Regexp + */ + private $reg; + + function __construct() { + // instantiage regexp matcher here + $this->reg = new Regexp(); + } + + /** + * Sets the "from" pattern. Required. + */ + function setFrom($from) { + $this->reg->SetPattern($from); + } + + /** + * Sets the "to" pattern. Required. + */ + function setTo($to) { + + // [HL] I'm changing the way this works for now to just use string + //$this->to = StringHelper::toCharArray($to); + + $this->to = $to; + } + + function main($sourceFileName) { + if ($this->reg === null || $this->to === null || !$this->reg->matches((string) $sourceFileName)) { + return null; + } + return array($this->replaceReferences($sourceFileName)); + } + + /** + * Replace all backreferences in the to pattern with the matched groups. + * groups of the source. + * @param string $source The source filename. + */ + private function replaceReferences($source) { + + // FIXME + // Can't we just use engine->replace() to handle this? the Preg engine + // will automatically convert \1 references to $1 + + // the expression has already been processed (when ->matches() was run in Main()) + // so no need to pass $source again to the engine. + $groups = (array) $this->reg->getGroups(); + + // replace \1 with value of $groups[1] and return the modified "to" string + return preg_replace('/\\\([\d]+)/e', "\$groups[$1]", $this->to); + } + +} + diff --git a/library/phing/parser/AbstractHandler.php b/library/phing/parser/AbstractHandler.php new file mode 100644 index 000000000..9529ed584 --- /dev/null +++ b/library/phing/parser/AbstractHandler.php @@ -0,0 +1,98 @@ +. + */ + +include_once 'phing/parser/ExpatParseException.php'; + +/** + * This is an abstract class all SAX handler classes must extend + * + * @author Andreas Aderhold + * @copyright © 2001,2002 THYRELL. All rights reserved + * @version $Revision: 905 $ + * @package phing.parser + */ +abstract class AbstractHandler { + + public $parentHandler = null; + public $parser = null; + + /** + * Constructs a SAX handler parser. + * + * The constructor must be called by all derived classes. + * + * @param object the parser object + * @param object the parent handler of this handler + */ + protected function __construct($parser, $parentHandler) { + $this->parentHandler = $parentHandler; + $this->parser = $parser; + $this->parser->setHandler($this); + } + + /** + * Gets invoked when a XML open tag occurs + * + * Must be overloaded by the child class. Throws an ExpatParseException + * if there is no handler registered for an element. + * + * @param string the name of the XML element + * @param array the attributes of the XML element + */ + public function startElement($name, $attribs) { + throw new ExpatParseException("Unexpected element $name"); + } + + /** + * Gets invoked when element closes method. + * + */ + protected function finished() {} + + /** + * Gets invoked when a XML element ends. + * + * Can be overloaded by the child class. But should not. It hands + * over control to the parentHandler of this. + * + * @param string the name of the XML element + */ + public function endElement($name) { + $this->finished(); + $this->parser->setHandler($this->parentHandler); + } + + /** + * Invoked by occurance of #PCDATA. + * + * @param string the name of the XML element + * @exception ExpatParserException if there is no CDATA but method + * was called + * @access public + */ + public function characters($data) { + $s = trim($data); + if (strlen($s) > 0) { + throw new ExpatParseException("Unexpected text '$s'", $this->parser->getLocation()); + } + } +} diff --git a/library/phing/parser/AbstractSAXParser.php b/library/phing/parser/AbstractSAXParser.php new file mode 100644 index 000000000..022b91011 --- /dev/null +++ b/library/phing/parser/AbstractSAXParser.php @@ -0,0 +1,116 @@ +. + */ + +/** + * The abstract SAX parser class. + * + * This class represents a SAX parser. It is a abstract calss that must be + * implemented by the real parser that must extend this class + * + * @author Andreas Aderhold + * @author Hans Lellelid + * @copyright � 2001,2002 THYRELL. All rights reserved + * @version $Revision: 905 $ + * @package phing.parser + */ +abstract class AbstractSAXParser { + + /** The AbstractHandler object. */ + protected $handler; + + /** + * Constructs a SAX parser + */ + function __construct() {} + + /** + * Sets options for PHP interal parser. Must be implemented by the parser + * class if it should be used. + */ + abstract function parserSetOption($opt, $val); + + /** + * Sets the current element handler object for this parser. Usually this + * is an object using extending "AbstractHandler". + * + * @param AbstractHandler $obj The handler object. + */ + function setHandler( $obj) { + $this->handler = $obj; + } + + /** + * Method that gets invoked when the parser runs over a XML start element. + * + * This method is called by PHP's internal parser functions and registered + * in the actual parser implementation. + * It gives control to the current active handler object by calling the + * startElement() method. + * + * @param object the php's internal parser handle + * @param string the open tag name + * @param array the tag's attributes if any + * @throws Exception - Exceptions may be thrown by the Handler + */ + function startElement($parser, $name, $attribs) { + $this->handler->startElement($name, $attribs); + } + + /** + * Method that gets invoked when the parser runs over a XML close element. + * + * This method is called by PHP's internal parser funcitons and registered + * in the actual parser implementation. + * + * It gives control to the current active handler object by calling the + * endElement() method. + * + * @param object the php's internal parser handle + * @param string the closing tag name + * @throws Exception - Exceptions may be thrown by the Handler + */ + function endElement($parser, $name) { + $this->handler->endElement($name); + } + + /** + * Method that gets invoked when the parser runs over CDATA. + * + * This method is called by PHP's internal parser functions and registered + * in the actual parser implementation. + * + * It gives control to the current active handler object by calling the + * characters() method. That processes the given CDATA. + * + * @param resource $parser php's internal parser handle. + * @param string $data the CDATA + * @throws Exception - Exceptions may be thrown by the Handler + */ + function characters($parser, $data) { + $this->handler->characters($data); + } + + /** + * Entrypoint for parser. This method needs to be implemented by the + * child classt that utilizes the concrete parser + */ + abstract function parse(); +} diff --git a/library/phing/parser/DataTypeHandler.php b/library/phing/parser/DataTypeHandler.php new file mode 100644 index 000000000..2f038ea52 --- /dev/null +++ b/library/phing/parser/DataTypeHandler.php @@ -0,0 +1,144 @@ +. + */ + +include_once 'phing/RuntimeConfigurable.php'; + +/** + * Configures a Project (complete with Targets and Tasks) based on + * a XML build file. + *

    + * Design/ZE2 migration note: + * If PHP would support nested classes. All the phing/parser/*Filter + * classes would be nested within this class + * + * @author Andreas Aderhold + * @copyright © 2001,2002 THYRELL. All rights reserved + * @version $Revision: 905 $ $Date: 2010-10-05 18:28:03 +0200 (Tue, 05 Oct 2010) $ + * @access public + * @package phing.parser + */ + +class DataTypeHandler extends AbstractHandler { + + private $target; + private $element; + private $wrapper; + + /** + * Constructs a new DataTypeHandler and sets up everything. + * + * @param AbstractSAXParser $parser The XML parser (default: ExpatParser) + * @param AbstractHandler $parentHandler The parent handler that invoked this handler. + * @param ProjectConfigurator $configurator The ProjectConfigurator object + * @param Target $target The target object this datatype is contained in (null for top-level datatypes). + */ + function __construct(AbstractSAXParser $parser, AbstractHandler $parentHandler, ProjectConfigurator $configurator, $target = null) { // FIXME b2 typehinting + parent::__construct($parser, $parentHandler); + $this->target = $target; + $this->configurator = $configurator; + } + + /** + * Executes initialization actions required to setup the data structures + * related to the tag. + *

    + * This includes: + *

      + *
    • creation of the datatype object
    • + *
    • calling the setters for attributes
    • + *
    • adding the type to the target object if any
    • + *
    • adding a reference to the task (if id attribute is given)
    • + *
    + * + * @param string the tag that comes in + * @param array attributes the tag carries + * @throws ExpatParseException if attributes are incomplete or invalid + * @access public + */ + function init($propType, $attrs) { + // shorthands + $project = $this->configurator->project; + $configurator = $this->configurator; + + try {//try + $this->element = $project->createDataType($propType); + + if ($this->element === null) { + throw new BuildException("Unknown data type $propType"); + } + + if ($this->target !== null) { + $this->wrapper = new RuntimeConfigurable($this->element, $propType); + $this->wrapper->setAttributes($attrs); + $this->target->addDataType($this->wrapper); + } else { + $configurator->configure($this->element, $attrs, $project); + $configurator->configureId($this->element, $attrs); + } + + } catch (BuildException $exc) { + throw new ExpatParseException($exc, $this->parser->getLocation()); + } + } + + /** + * Handles character data. + * + * @param string the CDATA that comes in + * @access public + */ + function characters($data) { + $project = $this->configurator->project; + try {//try + $this->configurator->addText($project, $this->element, $data); + } catch (BuildException $exc) { + throw new ExpatParseException($exc->getMessage(), $this->parser->getLocation()); + } + } + + /** + * Checks for nested tags within the current one. Creates and calls + * handlers respectively. + * + * @param string the tag that comes in + * @param array attributes the tag carries + * @access public + */ + function startElement($name, $attrs) { + $nef = new NestedElementHandler($this->parser, $this, $this->configurator, $this->element, $this->wrapper, $this->target); + $nef->init($name, $attrs); + } + + /** + * Overrides endElement for data types. Tells the type + * handler that processing the element had been finished so + * handlers know they can perform actions that need to be + * based on the data contained within the element. + * + * @param string the name of the XML element + * @return void + */ + function endElement($name) { + $this->element->parsingComplete(); + parent::endElement($name); + } + +} diff --git a/library/phing/parser/ExpatParseException.php b/library/phing/parser/ExpatParseException.php new file mode 100644 index 000000000..0948b6eb4 --- /dev/null +++ b/library/phing/parser/ExpatParseException.php @@ -0,0 +1,31 @@ +. + */ + +require_once 'phing/BuildException.php'; + +/** + * This class throws errors for Expat, the XML processor. + * + * @author Andreas Aderhold, andi@binarycloud.com + * @version $Revision: 905 $ $Date: 2010-10-05 18:28:03 +0200 (Tue, 05 Oct 2010) $ + * @package phing.parser + */ +class ExpatParseException extends BuildException {} diff --git a/library/phing/parser/ExpatParser.php b/library/phing/parser/ExpatParser.php new file mode 100644 index 000000000..1647df7d9 --- /dev/null +++ b/library/phing/parser/ExpatParser.php @@ -0,0 +1,140 @@ +. + */ + +require_once 'phing/parser/AbstractSAXParser.php'; +include_once 'phing/parser/ExpatParseException.php'; +include_once 'phing/system/io/IOException.php'; +include_once 'phing/system/io/FileReader.php'; + +/** + * This class is a wrapper for the PHP's internal expat parser. + * + * It takes an XML file represented by a abstract path name, and starts + * parsing the file and calling the different "trap" methods inherited from + * the AbstractParser class. + * + * Those methods then invoke the represenatative methods in the registered + * handler classes. + * + * @author Andreas Aderhold + * @copyright © 2001,2002 THYRELL. All rights reserved + * @version $Revision: 905 $ $Date: 2010-10-05 18:28:03 +0200 (Tue, 05 Oct 2010) $ + * @access public + * @package phing.parser + */ + +class ExpatParser extends AbstractSAXParser { + + /** @var resource */ + private $parser; + + /** @var Reader */ + private $reader; + + private $file; + + private $buffer = 4096; + + private $error_string = ""; + + private $line = 0; + + /** @var Location Current cursor pos in XML file. */ + private $location; + + /** + * Constructs a new ExpatParser object. + * + * The constructor accepts a PhingFile object that represents the filename + * for the file to be parsed. It sets up php's internal expat parser + * and options. + * + * @param Reader $reader The Reader Object that is to be read from. + * @param string $filename Filename to read. + * @throws Exception if the given argument is not a PhingFile object + */ + function __construct(Reader $reader, $filename=null) { + + $this->reader = $reader; + if ($filename !== null) { + $this->file = new PhingFile($filename); + } + $this->parser = xml_parser_create(); + $this->buffer = 4096; + $this->location = new Location(); + xml_set_object($this->parser, $this); + xml_set_element_handler($this->parser, array($this,"startElement"),array($this,"endElement")); + xml_set_character_data_handler($this->parser, array($this, "characters")); + } + + /** + * Override PHP's parser default settings, created in the constructor. + * + * @param string the option to set + * @throws mixed the value to set + * @return boolean true if the option could be set, otherwise false + * @access public + */ + function parserSetOption($opt, $val) { + return xml_parser_set_option($this->parser, $opt, $val); + } + + /** + * Returns the location object of the current parsed element. It describes + * the location of the element within the XML file (line, char) + * + * @return object the location of the current parser + * @access public + */ + function getLocation() { + if ($this->file !== null) { + $path = $this->file->getAbsolutePath(); + } else { + $path = $this->reader->getResource(); + } + $this->location = new Location($path, xml_get_current_line_number($this->parser), xml_get_current_column_number($this->parser)); + return $this->location; + } + + /** + * Starts the parsing process. + * + * @param string the option to set + * @return int 1 if the parsing succeeded + * @throws ExpatParseException if something gone wrong during parsing + * @throws IOException if XML file can not be accessed + * @access public + */ + function parse() { + + while ( ($data = $this->reader->read()) !== -1 ) { + if (!xml_parse($this->parser, $data, $this->reader->eof())) { + $error = xml_error_string(xml_get_error_code($this->parser)); + $e = new ExpatParseException($error, $this->getLocation()); + xml_parser_free($this->parser); + throw $e; + } + } + xml_parser_free($this->parser); + + return 1; + } +} diff --git a/library/phing/parser/Location.php b/library/phing/parser/Location.php new file mode 100644 index 000000000..d48d22c21 --- /dev/null +++ b/library/phing/parser/Location.php @@ -0,0 +1,76 @@ +. + */ + +/** + * Stores the file name and line number of a XML file + * + * @author Andreas Aderhold + * @copyright © 2001,2002 THYRELL. All rights reserved + * @version $Revision: 905 $ $Date: 2010-10-05 18:28:03 +0200 (Tue, 05 Oct 2010) $ + * @access public + * @package phing.parser + */ + +class Location { + + private $fileName; + private $lineNumber; + private $columnNumber; + + /** + * Constructs the location consisting of a file name and line number + * + * @param string the filename + * @param integer the line number + * @param integer the column number + * @access public + */ + function Location($fileName = null, $lineNumber = null, $columnNumber = null) { + $this->fileName = $fileName; + $this->lineNumber = $lineNumber; + $this->columnNumber = $columnNumber; + } + + /** + * Returns the file name, line number and a trailing space. + * + * An error message can be appended easily. For unknown locations, + * returns empty string. + * + * @return string the string representation of this Location object + * @access public + */ + function toString() { + $buf = ""; + if ($this->fileName !== null) { + $buf.=$this->fileName; + if ($this->lineNumber !== null) { + $buf.= ":".$this->lineNumber; + } + $buf.=":".$this->columnNumber; + } + return (string) $buf; + } + + function __toString () { + return $this->toString(); + } +} diff --git a/library/phing/parser/NestedElementHandler.php b/library/phing/parser/NestedElementHandler.php new file mode 100644 index 000000000..1b78e9140 --- /dev/null +++ b/library/phing/parser/NestedElementHandler.php @@ -0,0 +1,186 @@ +. + */ + +include_once 'phing/IntrospectionHelper.php'; +include_once 'phing/TaskContainer.php'; + +/** + * The nested element handler class. + * + * This class handles the occurance of runtime registered tags like + * datatypes (fileset, patternset, etc) and it's possible nested tags. It + * introspects the implementation of the class and sets up the data structures. + * + * @author Andreas Aderhold + * @copyright © 2001,2002 THYRELL. All rights reserved + * @version $Revision: 905 $ $Date: 2010-10-05 18:28:03 +0200 (Tue, 05 Oct 2010) $ + * @access public + * @package phing.parser + */ + +class NestedElementHandler extends AbstractHandler { + + /** + * Reference to the parent object that represents the parent tag + * of this nested element + * @var object + */ + private $parent; + + /** + * Reference to the child object that represents the child tag + * of this nested element + * @var object + */ + private $child; + + /** + * Reference to the parent wrapper object + * @var object + */ + private $parentWrapper; + + /** + * Reference to the child wrapper object + * @var object + */ + private $childWrapper; + + /** + * Reference to the related target object + * @var object the target instance + */ + private $target; + + /** + * Constructs a new NestedElement handler and sets up everything. + * + * @param object the ExpatParser object + * @param object the parent handler that invoked this handler + * @param object the ProjectConfigurator object + * @param object the parent object this element is contained in + * @param object the parent wrapper object + * @param object the target object this task is contained in + * @access public + */ + function __construct($parser, $parentHandler, $configurator, $parent, $parentWrapper, $target) { + parent::__construct($parser, $parentHandler); + $this->configurator = $configurator; + if ($parent instanceof TaskAdapter) { + $this->parent = $parent->getProxy(); + } else { + $this->parent = $parent; + } + $this->parentWrapper = $parentWrapper; + $this->target = $target; + } + + /** + * Executes initialization actions required to setup the data structures + * related to the tag. + *

    + * This includes: + *

      + *
    • creation of the nested element
    • + *
    • calling the setters for attributes
    • + *
    • adding the element to the container object
    • + *
    • adding a reference to the element (if id attribute is given)
    • + *
    + * + * @param string the tag that comes in + * @param array attributes the tag carries + * @throws ExpatParseException if the setup process fails + * @access public + */ + function init($propType, $attrs) { + $configurator = $this->configurator; + $project = $this->configurator->project; + + // introspect the parent class that is custom + $parentClass = get_class($this->parent); + $ih = IntrospectionHelper::getHelper($parentClass); + try { + if ($this->parent instanceof UnknownElement) { + $this->child = new UnknownElement(strtolower($propType)); + $this->parent->addChild($this->child); + } else { + $this->child = $ih->createElement($project, $this->parent, strtolower($propType)); + } + + $configurator->configureId($this->child, $attrs); + + if ($this->parentWrapper !== null) { + $this->childWrapper = new RuntimeConfigurable($this->child, $propType); + $this->childWrapper->setAttributes($attrs); + $this->parentWrapper->addChild($this->childWrapper); + } else { + $configurator->configure($this->child, $attrs, $project); + $ih->storeElement($project, $this->parent, $this->child, strtolower($propType)); + } + } catch (BuildException $exc) { + throw new ExpatParseException("Error initializing nested element <$propType>", $exc, $this->parser->getLocation()); + } + } + + /** + * Handles character data. + * + * @param string the CDATA that comes in + * @throws ExpatParseException if the CDATA could not be set-up properly + * @access public + */ + function characters($data) { + + $configurator = $this->configurator; + $project = $this->configurator->project; + + if ($this->parentWrapper === null) { + try { + $configurator->addText($project, $this->child, $data); + } catch (BuildException $exc) { + throw new ExpatParseException($exc->getMessage(), $this->parser->getLocation()); + } + } else { + $this->childWrapper->addText($data); + } + } + + /** + * Checks for nested tags within the current one. Creates and calls + * handlers respectively. + * + * @param string the tag that comes in + * @param array attributes the tag carries + * @access public + */ + function startElement($name, $attrs) { + //print(get_class($this) . " name = $name, attrs = " . implode(",",$attrs) . "\n"); + if ($this->child instanceof TaskContainer) { + // taskcontainer nested element can contain other tasks - no other + // nested elements possible + $tc = new TaskHandler($this->parser, $this, $this->configurator, $this->child, $this->childWrapper, $this->target); + $tc->init($name, $attrs); + } else { + $neh = new NestedElementHandler($this->parser, $this, $this->configurator, $this->child, $this->childWrapper, $this->target); + $neh->init($name, $attrs); + } + } +} diff --git a/library/phing/parser/PhingXMLContext.php b/library/phing/parser/PhingXMLContext.php new file mode 100644 index 000000000..def71c0c9 --- /dev/null +++ b/library/phing/parser/PhingXMLContext.php @@ -0,0 +1,81 @@ +. + */ + +/** + * Track the current state of the Xml parse operation. + * + * @author Bryan Davis + * @version $Revision: 905 $ $Date: 2010-10-05 18:28:03 +0200 (Tue, 05 Oct 2010) $ + * @access public + * @package phing.parser + */ +class PhingXMLContext { + + /** + * Constructor + * @param $project the project to which this antxml context belongs to + */ + public function __construct ($project) { + $this->project = $project; + } + + /** The project to configure. */ + private $project; + + private $configurators = array(); + + public function startConfigure ($cfg) { + $this->configurators[] = $cfg; + } + + public function endConfigure () { + array_pop($this->configurators); + } + + public function getConfigurator () { + $l = count($this->configurators); + if (0 == $l) { + return null; + } else { + return $this->configurators[$l - 1]; + } + } + + /** Impoerted files */ + private $importStack = array(); + + public function addImport ($file) { + $this->importStack[] = $file; + } + + public function getImportStack () { + return $this->importStack; + } + + /** + * find out the project to which this context belongs + * @return project + */ + public function getProject() { + return $this->project; + } + +} //end PhingXMLContext diff --git a/library/phing/parser/ProjectConfigurator.php b/library/phing/parser/ProjectConfigurator.php new file mode 100644 index 000000000..cf4ccdbdb --- /dev/null +++ b/library/phing/parser/ProjectConfigurator.php @@ -0,0 +1,373 @@ +. + */ + +include_once 'phing/system/io/BufferedReader.php'; +include_once 'phing/system/io/FileReader.php'; +include_once 'phing/BuildException.php'; +include_once 'phing/system/lang/FileNotFoundException.php'; +include_once 'phing/system/io/PhingFile.php'; +include_once 'phing/parser/PhingXMLContext.php'; +include_once 'phing/IntrospectionHelper.php'; + +/** + * The datatype handler class. + * + * This class handles the occurance of registered datatype tags like + * FileSet + * + * @author Andreas Aderhold + * @copyright � 2001,2002 THYRELL. All rights reserved + * @version $Revision: 905 $ $Date: 2010-10-05 18:28:03 +0200 (Tue, 05 Oct 2010) $ + * @access public + * @package phing.parser + */ +class ProjectConfigurator { + + public $project; + public $locator; + + public $buildFile; + public $buildFileParent; + + /** Targets in current file */ + private $currentTargets; + + /** Synthetic target that will be called at the end to the parse phase */ + private $parseEndTarget; + + /** Name of the current project */ + private $currentProjectName; + + private $isParsing = true; + + /** + * Indicates whether the project tag attributes are to be ignored + * when processing a particular build file. + */ + private $ignoreProjectTag = false; + + /** + * Static call to ProjectConfigurator. Use this to configure a + * project. Do not use the new operator. + * + * @param object the Project instance this configurator should use + * @param object the buildfile object the parser should use + * @access public + */ + public static function configureProject(Project $project, PhingFile $buildFile) { + $pc = new ProjectConfigurator($project, $buildFile); + $pc->parse(); + } + + /** + * Constructs a new ProjectConfigurator object + * This constructor is private. Use a static call to + * configureProject to configure a project. + * + * @param object the Project instance this configurator should use + * @param object the buildfile object the parser should use + * @access private + */ + function __construct(Project $project, PhingFile $buildFile) { + $this->project = $project; + $this->buildFile = new PhingFile($buildFile->getAbsolutePath()); + $this->buildFileParent = new PhingFile($this->buildFile->getParent()); + $this->currentTargets = array(); + $this->parseEndTarget = new Target(); + } + + /** + * find out the build file + * @return the build file to which the xml context belongs + */ + public function getBuildFile() { + return $this->buildFile; + } + + /** + * find out the parent build file of this build file + * @return the parent build file of this build file + */ + public function getBuildFileParent() { + return $this->buildFileParent; + } + + /** + * find out the current project name + * @return current project name + */ + public function getCurrentProjectName() { + return $this->currentProjectName; + } + + /** + * set the name of the current project + * @param name name of the current project + */ + public function setCurrentProjectName($name) { + $this->currentProjectName = $name; + } + + /** + * tells whether the project tag is being ignored + * @return whether the project tag is being ignored + */ + public function isIgnoringProjectTag() { + return $this->ignoreProjectTag; + } + + /** + * sets the flag to ignore the project tag + * @param flag to ignore the project tag + */ + public function setIgnoreProjectTag($flag) { + $this->ignoreProjectTag = $flag; + } + + public function &getCurrentTargets () { + return $this->currentTargets; + } + + public function isParsing () { + return $this->isParsing; + } + + /** + * Creates the ExpatParser, sets root handler and kick off parsing + * process. + * + * @throws BuildException if there is any kind of execption during + * the parsing process + * @access private + */ + protected function parse() { + try { + // get parse context + $ctx = $this->project->getReference("phing.parsing.context"); + if (null == $ctx) { + // make a new context and register it with project + $ctx = new PhingXMLContext($this->project); + $this->project->addReference("phing.parsing.context", $ctx); + } + + //record this parse with context + $ctx->addImport($this->buildFile); + + if (count($ctx->getImportStack()) > 1) { + // this is an imported file + // modify project tag parse behavior + $this->setIgnoreProjectTag(true); + } + // push action onto global stack + $ctx->startConfigure($this); + + $reader = new BufferedReader(new FileReader($this->buildFile)); + $parser = new ExpatParser($reader); + $parser->parserSetOption(XML_OPTION_CASE_FOLDING,0); + $parser->setHandler(new RootHandler($parser, $this)); + $this->project->log("parsing buildfile ".$this->buildFile->getName(), Project::MSG_VERBOSE); + $parser->parse(); + $reader->close(); + + // mark parse phase as completed + $this->isParsing = false; + // execute delayed tasks + $this->parseEndTarget->main(); + // pop this action from the global stack + $ctx->endConfigure(); + } catch (Exception $exc) { + throw new BuildException("Error reading project file", $exc); + } + } + + /** + * Delay execution of a task until after the current parse phase has + * completed. + * + * @param Task $task Task to execute after parse + */ + public function delayTaskUntilParseEnd ($task) { + $this->parseEndTarget->addTask($task); + } + + /** + * Configures an element and resolves eventually given properties. + * + * @param object the element to configure + * @param array the element's attributes + * @param object the project this element belongs to + * @throws Exception if arguments are not valid + * @throws BuildException if attributes can not be configured + * @access public + */ + public static function configure($target, $attrs, Project $project) { + + if ($target instanceof TaskAdapter) { + $target = $target->getProxy(); + } + + // if the target is an UnknownElement, this means that the tag had not been registered + // when the enclosing element (task, target, etc.) was configured. It is possible, however, + // that the tag was registered (e.g. using ) after the original configuration. + // ... so, try to load it again: + if ($target instanceof UnknownElement) { + $tryTarget = $project->createTask($target->getTaskType()); + if ($tryTarget) { + $target = $tryTarget; + } + } + + $bean = get_class($target); + $ih = IntrospectionHelper::getHelper($bean); + + foreach ($attrs as $key => $value) { + if ($key == 'id') { + continue; + // throw new BuildException("Id must be set Extermnally"); + } + $value = self::replaceProperties($project, $value, $project->getProperties()); + try { // try to set the attribute + $ih->setAttribute($project, $target, strtolower($key), $value); + } catch (BuildException $be) { + // id attribute must be set externally + if ($key !== "id") { + throw $be; + } + } + } + } + + /** + * Configures the #CDATA of an element. + * + * @param object the project this element belongs to + * @param object the element to configure + * @param string the element's #CDATA + * @access public + */ + public static function addText($project, $target, $text = null) { + if ($text === null || strlen(trim($text)) === 0) { + return; + } + $ih = IntrospectionHelper::getHelper(get_class($target)); + $text = self::replaceProperties($project, $text, $project->getProperties()); + $ih->addText($project, $target, $text); + } + + /** + * Stores a configured child element into its parent object + * + * @param object the project this element belongs to + * @param object the parent element + * @param object the child element + * @param string the XML tagname + * @access public + */ + public static function storeChild($project, $parent, $child, $tag) { + $ih = IntrospectionHelper::getHelper(get_class($parent)); + $ih->storeElement($project, $parent, $child, $tag); + } + + // The following two properties are a sort of hack + // to enable a static function to serve as the callback + // for preg_replace_callback(). Clearly we cannot use object + // variables, since the replaceProperties() is called statically. + // This is IMO better than using global variables in the callback. + + private static $propReplaceProject; + private static $propReplaceProperties; + + /** + * Replace ${} style constructions in the given value with the + * string value of the corresponding data types. This method is + * static. + * + * @param object the project that should be used for property look-ups + * @param string the string to be scanned for property references + * @param array proeprty keys + * @return string the replaced string or null if the string + * itself was null + */ + public static function replaceProperties(Project $project, $value, $keys) { + + if ($value === null) { + return null; + } + + // These are a "hack" to support static callback for preg_replace_callback() + + // make sure these get initialized every time + self::$propReplaceProperties = $keys; + self::$propReplaceProject = $project; + + // Because we're not doing anything special (like multiple passes), + // regex is the simplest / fastest. PropertyTask, though, uses + // the old parsePropertyString() method, since it has more stringent + // requirements. + + $sb = $value; + $iteration = 0; + + // loop to recursively replace tokens + while (strpos($sb, '${') !== false) + { + $sb = preg_replace_callback('/\$\{([^\$}]+)\}/', array('ProjectConfigurator', 'replacePropertyCallback'), $sb); + + // keep track of iterations so we can break out of otherwise infinite loops. + $iteration++; + if ($iteration == 5) + { + return $sb; + } + } + + return $sb; + } + + /** + * Private [static] function for use by preg_replace_callback to replace a single param. + * This method makes use of a static variable to hold the + */ + private static function replacePropertyCallback($matches) + { + $propertyName = $matches[1]; + if (!isset(self::$propReplaceProperties[$propertyName])) { + self::$propReplaceProject->log('Property ${'.$propertyName.'} has not been set.', Project::MSG_VERBOSE); + return $matches[0]; + } else { + self::$propReplaceProject->log('Property ${'.$propertyName.'} => ' . self::$propReplaceProperties[$propertyName], Project::MSG_DEBUG); + } + return self::$propReplaceProperties[$propertyName]; + } + + /** + * Scan Attributes for the id attribute and maybe add a reference to + * project. + * + * @param object the element's object + * @param array the element's attributes + */ + public function configureId($target, $attr) { + if (isset($attr['id']) && $attr['id'] !== null) { + $this->project->addReference($attr['id'], $target); + } + } +} diff --git a/library/phing/parser/ProjectHandler.php b/library/phing/parser/ProjectHandler.php new file mode 100644 index 000000000..6e6ec5697 --- /dev/null +++ b/library/phing/parser/ProjectHandler.php @@ -0,0 +1,176 @@ +. + */ + +require_once 'phing/parser/AbstractHandler.php'; +require_once 'phing/system/io/PhingFile.php'; + +/** + * Handler class for the XML element This class handles all elements + * under the element. + * + * @author Andreas Aderhold + * @copyright (c) 2001,2002 THYRELL. All rights reserved + * @version $Revision: 905 $ $Date: 2010-10-05 18:28:03 +0200 (Tue, 05 Oct 2010) $ + * @access public + * @package phing.parser + */ +class ProjectHandler extends AbstractHandler { + + /** + * The phing project configurator object. + * @var ProjectConfigurator + */ + private $configurator; + + /** + * Constructs a new ProjectHandler + * + * @param object the ExpatParser object + * @param object the parent handler that invoked this handler + * @param object the ProjectConfigurator object + * @access public + */ + function __construct($parser, $parentHandler, $configurator) { + $this->configurator = $configurator; + parent::__construct($parser, $parentHandler); + } + + /** + * Executes initialization actions required to setup the project. Usually + * this method handles the attributes of a tag. + * + * @param string the tag that comes in + * @param array attributes the tag carries + * @param object the ProjectConfigurator object + * @throws ExpatParseException if attributes are incomplete or invalid + * @access public + */ + function init($tag, $attrs) { + $def = null; + $name = null; + $id = null; + $desc = null; + $baseDir = null; + $ver = null; + + // some shorthands + $project = $this->configurator->project; + $buildFileParent = $this->configurator->buildFileParent; + + foreach ($attrs as $key => $value) { + if ($key === "default") { + $def = $value; + } elseif ($key === "name") { + $name = $value; + } elseif ($key === "id") { + $id = $value; + } elseif ($key === "basedir") { + $baseDir = $value; + } elseif ($key === "description") { + $desc = $value; + } elseif ($key === "phingVersion") { + $ver = $value; + } else { + throw new ExpatParseException("Unexpected attribute '$key'"); + } + } + // these things get done no matter what + if (null != $name) { + $canonicalName = self::canonicalName($name); + $this->configurator->setCurrentProjectName($canonicalName); + $project->setUserProperty("phing.file.{$canonicalName}", + (string) $this->configurator->getBuildFile()); + } + + if (!$this->configurator->isIgnoringProjectTag()) { + if ($def === null) { + throw new ExpatParseException( + "The default attribute of project is required"); + } + $project->setDefaultTarget($def); + + if ($name !== null) { + $project->setName($name); + $project->addReference($name, $project); + + } + + if ($id !== null) { + $project->addReference($id, $project); + } + + if ($desc !== null) { + $project->setDescription($desc); + } + + if($ver !== null) { + $project->setPhingVersion($ver); + } + + if ($project->getProperty("project.basedir") !== null) { + $project->setBasedir($project->getProperty("project.basedir")); + } else { + if ($baseDir === null) { + $project->setBasedir($buildFileParent->getAbsolutePath()); + } else { + // check whether the user has specified an absolute path + $f = new PhingFile($baseDir); + if ($f->isAbsolute()) { + $project->setBasedir($baseDir); + } else { + $project->setBaseDir($project->resolveFile($baseDir, new PhingFile(getcwd()))); + } + } + } + } + } + + /** + * Handles start elements within the tag by creating and + * calling the required handlers for the detected element. + * + * @param string the tag that comes in + * @param array attributes the tag carries + * @throws ExpatParseException if a unxepected element occurs + * @access public + */ + function startElement($name, $attrs) { + + $project = $this->configurator->project; + $types = $project->getDataTypeDefinitions(); + + if ($name == "target") { + $tf = new TargetHandler($this->parser, $this, $this->configurator); + $tf->init($name, $attrs); + } elseif (isset($types[$name])) { + $tyf = new DataTypeHandler($this->parser, $this, $this->configurator); + $tyf->init($name, $attrs); + } else { + $tf = new TaskHandler($this->parser, $this, $this->configurator); + $tf->init($name, $attrs); + } + } + + static function canonicalName ($name) { + return preg_replace('/\W/', '_', strtolower($name)); + } +} + diff --git a/library/phing/parser/RootHandler.php b/library/phing/parser/RootHandler.php new file mode 100644 index 000000000..f1fb41db0 --- /dev/null +++ b/library/phing/parser/RootHandler.php @@ -0,0 +1,82 @@ +. + */ + +require_once 'phing/parser/AbstractHandler.php'; +include_once 'phing/parser/ExpatParseException.php'; +include_once 'phing/parser/ProjectHandler.php'; + +/** + * Root filter class for a phing buildfile. + * + * The root filter is called by the parser first. This is where the phing + * specific parsing starts. RootHandler decides what to do next. + * + * @author Andreas Aderhold + * @copyright © 2001,2002 THYRELL. All rights reserved + * @version $Revision: 905 $ + * @package phing.parser + */ +class RootHandler extends AbstractHandler { + + /** + * The phing project configurator object + */ + private $configurator; + + /** + * Constructs a new RootHandler + * + * The root filter is required so the parser knows what to do. It's + * called by the ExpatParser that is instatiated in ProjectConfigurator. + * + * It recieves the expat parse object ref and a reference to the + * configurator + * + * @param AbstractSAXParser $parser The ExpatParser object. + * @param ProjectConfigurator $configurator The ProjectConfigurator object. + */ + function __construct(AbstractSAXParser $parser, ProjectConfigurator $configurator) { + $this->configurator = $configurator; + parent::__construct($parser, $this); + } + + /** + * Kick off a custom action for a start element tag. + * + * The root element of our buildfile is the <project> element. The + * root filter handles this element if it occurs, creates ProjectHandler + * to handle any nested tags & attributes of the <project> tag, + * and calls init. + * + * @param string $tag The xml tagname + * @param array $attrs The attributes of the tag + * @throws ExpatParseException if the first element within our build file + * is not the >project< element + */ + function startElement($tag, $attrs) { + if ($tag === "project") { + $ph = new ProjectHandler($this->parser, $this, $this->configurator); + $ph->init($tag, $attrs); + } else { + throw new ExpatParseException("Unexpected tag <$tag> in top-level of build file.", $this->parser->getLocation()); + } + } +} diff --git a/library/phing/parser/TargetHandler.php b/library/phing/parser/TargetHandler.php new file mode 100644 index 000000000..026653e55 --- /dev/null +++ b/library/phing/parser/TargetHandler.php @@ -0,0 +1,180 @@ +. + */ + +require_once 'phing/parser/AbstractHandler.php'; + +/** + * The target handler class. + * + * This class handles the occurance of a tag and it's possible + * nested tags (datatypes and tasks). + * + * @author Andreas Aderhold + * @copyright 2001,2002 THYRELL. All rights reserved + * @version $Revision: 905 $ + * @package phing.parser + */ +class TargetHandler extends AbstractHandler { + + /** + * Reference to the target object that represents the currently parsed + * target. + * @var object the target instance + */ + private $target; + + /** + * The phing project configurator object + * @var ProjectConfigurator + */ + private $configurator; + + /** + * Constructs a new TargetHandler + * + * @param object the ExpatParser object + * @param object the parent handler that invoked this handler + * @param object the ProjectConfigurator object + */ + function __construct(AbstractSAXParser $parser, AbstractHandler $parentHandler, ProjectConfigurator $configurator) { + parent::__construct($parser, $parentHandler); + $this->configurator = $configurator; + } + + /** + * Executes initialization actions required to setup the data structures + * related to the tag. + *

    + * This includes: + *

      + *
    • creation of the target object
    • + *
    • calling the setters for attributes
    • + *
    • adding the target to the project
    • + *
    • adding a reference to the target (if id attribute is given)
    • + *
    + * + * @param string the tag that comes in + * @param array attributes the tag carries + * @throws ExpatParseException if attributes are incomplete or invalid + */ + function init($tag, $attrs) { + $name = null; + $depends = ""; + $ifCond = null; + $unlessCond = null; + $id = null; + $description = null; + + foreach($attrs as $key => $value) { + if ($key==="name") { + $name = (string) $value; + } else if ($key==="depends") { + $depends = (string) $value; + } else if ($key==="if") { + $ifCond = (string) $value; + } else if ($key==="unless") { + $unlessCond = (string) $value; + } else if ($key==="id") { + $id = (string) $value; + } else if ($key==="description") { + $description = (string)$value; + } else { + throw new ExpatParseException("Unexpected attribute '$key'", $this->parser->getLocation()); + } + } + + if ($name === null) { + throw new ExpatParseException("target element appears without a name attribute", $this->parser->getLocation()); + } + + // shorthand + $project = $this->configurator->project; + + // check to see if this target is a dup within the same file + if (isset($this->configurator->getCurrentTargets[$name])) { + throw new BuildException("Duplicate target: $targetName", + $this->parser->getLocation()); + } + + $this->target = new Target(); + $this->target->setName($name); + $this->target->setIf($ifCond); + $this->target->setUnless($unlessCond); + $this->target->setDescription($description); + // take care of dependencies + if (strlen($depends) > 0) { + $this->target->setDepends($depends); + } + + $usedTarget = false; + // check to see if target with same name is already defined + $projectTargets = $project->getTargets(); + if (isset($projectTargets[$name])) { + $project->log("Already defined in main or a previous import, " . + "ignore {$name}", Project::MSG_VERBOSE); + } else { + $project->addTarget($name, $this->target); + if ($id !== null && $id !== "") { + $project->addReference($id, $this->target); + } + $usedTarget = true; + } + + if ($this->configurator->isIgnoringProjectTag() && + $this->configurator->getCurrentProjectName() != null && + strlen($this->configurator->getCurrentProjectName()) != 0) { + // In an impored file (and not completely + // ignoring the project tag) + $newName = $this->configurator->getCurrentProjectName() . "." . $name; + if ($usedTarget) { + // clone needs to make target->children a shared reference + $newTarget = clone $this->target; + } else { + $newTarget = $this->target; + } + $newTarget->setName($newName); + $ct = $this->configurator->getCurrentTargets(); + $ct[$newName] = $newTarget; + $project->addTarget($newName, $newTarget); + } + } + + /** + * Checks for nested tags within the current one. Creates and calls + * handlers respectively. + * + * @param string the tag that comes in + * @param array attributes the tag carries + */ + function startElement($name, $attrs) { + // shorthands + $project = $this->configurator->project; + $types = $project->getDataTypeDefinitions(); + + if (isset($types[$name])) { + $th = new DataTypeHandler($this->parser, $this, $this->configurator, $this->target); + $th->init($name, $attrs); + } else { + $tmp = new TaskHandler($this->parser, $this, $this->configurator, $this->target, null, $this->target); + $tmp->init($name, $attrs); + } + } +} diff --git a/library/phing/parser/TaskHandler.php b/library/phing/parser/TaskHandler.php new file mode 100644 index 000000000..8e3ecb8a3 --- /dev/null +++ b/library/phing/parser/TaskHandler.php @@ -0,0 +1,234 @@ +. + */ + +include_once 'phing/UnknownElement.php'; + +/** + * The task handler class. + * + * This class handles the occurance of a tag and it's possible + * nested tags (datatypes and tasks) that may be unknown off bat and are + * initialized on the fly. + * + * @author Andreas Aderhold + * @copyright � 2001,2002 THYRELL. All rights reserved + * @version $Revision: 905 $ + * @package phing.parser + */ +class TaskHandler extends AbstractHandler { + + /** + * Reference to the target object that contains the currently parsed + * task + * @var object the target instance + */ + private $target; + + /** + * Reference to the target object that represents the currently parsed + * target. This must not necessarily be a target, hence extra variable. + * @var object the target instance + */ + private $container; + + /** + * Reference to the task object that represents the currently parsed + * target. + * @var Task + */ + private $task; + + /** + * Wrapper for the parent element, if any. The wrapper for this + * element will be added to this wrapper as a child. + * @var RuntimeConfigurable + */ + private $parentWrapper; + + /** + * Wrapper for this element which takes care of actually configuring + * the element, if this element is contained within a target. + * Otherwise the configuration is performed with the configure method. + * @see ProjectHelper::configure(Object,AttributeList,Project) + */ + private $wrapper; + + /** + * The phing project configurator object + * @var ProjectConfigurator + */ + private $configurator; + + /** + * Constructs a new TaskHandler and sets up everything. + * + * @param AbstractSAXParser The ExpatParser object + * @param object $parentHandler The parent handler that invoked this handler + * @param ProjectConfigurator $configurator + * @param TaskContainer $container The container object this task is contained in (null for top-level tasks). + * @param RuntimeConfigurable $parentWrapper Wrapper for the parent element, if any. + * @param Target $target The target object this task is contained in (null for top-level tasks). + */ + function __construct(AbstractSAXParser $parser, $parentHandler, ProjectConfigurator $configurator, $container = null, $parentWrapper = null, $target = null) { + + parent::__construct($parser, $parentHandler); + + if (($container !== null) && !($container instanceof TaskContainer)) { + throw new Exception("Argument expected to be a TaskContainer, got something else"); + } + if (($parentWrapper !== null) && !($parentWrapper instanceof RuntimeConfigurable)) { + throw new Exception("Argument expected to be a RuntimeConfigurable, got something else."); + } + if (($target !== null) && !($target instanceof Target)) { + throw new Exception("Argument expected to be a Target, got something else"); + } + + $this->configurator = $configurator; + $this->container = $container; + $this->parentWrapper = $parentWrapper; + $this->target = $target; + } + + /** + * Executes initialization actions required to setup the data structures + * related to the tag. + *

    + * This includes: + *

      + *
    • creation of the task object
    • + *
    • calling the setters for attributes
    • + *
    • adding the task to the container object
    • + *
    • adding a reference to the task (if id attribute is given)
    • + *
    • executing the task if the container is the <project> + * element
    • + *
    + * + * @param string $tag The tag that comes in + * @param array $attrs Attributes the tag carries + * @throws ExpatParseException if attributes are incomplete or invalid + */ + function init($tag, $attrs) { + // shorthands + try { + $configurator = $this->configurator; + $project = $this->configurator->project; + + $this->task = $project->createTask($tag); + } catch (BuildException $be) { + // swallow here, will be thrown again in + // UnknownElement->maybeConfigure if the problem persists. + print("Swallowing exception: ".$be->getMessage() . "\n"); + } + + // the task is not known of bat, try to load it on thy fly + if ($this->task === null) { + $this->task = new UnknownElement($tag); + $this->task->setProject($project); + $this->task->setTaskType($tag); + $this->task->setTaskName($tag); + } + + // add file position information to the task (from parser) + // should be used in task exceptions to provide details + $this->task->setLocation($this->parser->getLocation()); + $configurator->configureId($this->task, $attrs); + + if ($this->container) { + $this->container->addTask($this->task); + } + + // Top level tasks don't have associated targets + // FIXME: if we do like Ant 1.6 and create an implicitTarget in the projectconfigurator object + // then we don't need to check for null here ... but there's a lot of stuff that will break if we + // do that at this point. + if ($this->target !== null) { + $this->task->setOwningTarget($this->target); + $this->task->init(); + $this->wrapper = $this->task->getRuntimeConfigurableWrapper(); + $this->wrapper->setAttributes($attrs); + /* + Commenting this out as per thread on Premature configurate of ReuntimeConfigurables + with Matthias Pigulla: http://phing.tigris.org/servlets/ReadMsg?list=dev&msgNo=251 + + if ($this->parentWrapper !== null) { // this may not make sense only within this if-block, but it + // seems to address current use cases adequately + $this->parentWrapper->addChild($this->wrapper); + } + */ + } else { + $this->task->init(); + $configurator->configure($this->task, $attrs, $project); + } + } + + /** + * Executes the task at once if it's directly beneath the tag. + */ + protected function finished() { + if ($this->task !== null && $this->target === null && $this->container === null) { + try { + $this->task->perform(); + } catch (Exception $e) { + $this->task->log($e->getMessage(), Project::MSG_ERR); + throw $e; + } + } + } + + /** + * Handles character data. + * + * @param string $data The CDATA that comes in + */ + function characters($data) { + if ($this->wrapper === null) { + $configurator = $this->configurator; + $project = $this->configurator->project; + try { // try + $configurator->addText($project, $this->task, $data); + } catch (BuildException $exc) { + throw new ExpatParseException($exc->getMessage(), $this->parser->getLocation()); + } + } else { + $this->wrapper->addText($data); + } + } + + /** + * Checks for nested tags within the current one. Creates and calls + * handlers respectively. + * + * @param string $name The tag that comes in + * @param array $attrs Attributes the tag carries + */ + function startElement($name, $attrs) { + $project = $this->configurator->project; + if ($this->task instanceof TaskContainer) { + //print("TaskHandler::startElement() (TaskContainer) name = $name, attrs = " . implode(",",$attrs) . "\n"); + $th = new TaskHandler($this->parser, $this, $this->configurator, $this->task, $this->wrapper, $this->target); + $th->init($name, $attrs); + } else { + //print("TaskHandler::startElement() name = $name, attrs = " . implode(",",$attrs) . "\n"); + $tmp = new NestedElementHandler($this->parser, $this, $this->configurator, $this->task, $this->wrapper, $this->target); + $tmp->init($name, $attrs); + } + } +} diff --git a/library/phing/system/io/BufferedReader.php b/library/phing/system/io/BufferedReader.php new file mode 100644 index 000000000..38df1e230 --- /dev/null +++ b/library/phing/system/io/BufferedReader.php @@ -0,0 +1,168 @@ +. +*/ + +include_once 'phing/system/io/Reader.php'; + +/** + * Convenience class for reading files. + * + * @author Yannick Lecaillez + * @version $Revision: 905 $ $Date: 2010-10-05 18:28:03 +0200 (Tue, 05 Oct 2010) $ + * @access public + * @see FilterReader + * @package phing.system.io + */ +class BufferedReader extends Reader { + + private $bufferSize = 0; + private $buffer = null; + private $bufferPos = 0; + + /** + * The Reader we are buffering for. + */ + private $in; + + /** + * + * @param object $reader The reader (e.g. FileReader). + * @param integer $buffsize The size of the buffer we should use for reading files. + * A large buffer ensures that most files (all scripts?) are parsed in 1 buffer. + */ + function __construct(Reader $reader, $buffsize = 65536) { + $this->in = $reader; + $this->bufferSize = $buffsize; + } + + /** + * Reads and returns a chunk of data. + * @param int $len Number of bytes to read. Default is to read configured buffer size number of bytes. + * @return mixed buffer or -1 if EOF. + */ + function read($len = null) { + + // if $len is specified, we'll use that; otherwise, use the configured buffer size. + if ($len === null) $len = $this->bufferSize; + + if ( ($data = $this->in->read($len)) !== -1 ) { + + // not all files end with a newline character, so we also need to check EOF + if (!$this->in->eof()) { + + $notValidPart = strrchr($data, "\n"); + $notValidPartSize = strlen($notValidPart); + + if ( $notValidPartSize > 1 ) { + // Block doesn't finish on a EOL + // Find the last EOL and forget all following stuff + $dataSize = strlen($data); + $validSize = $dataSize - $notValidPartSize + 1; + + $data = substr($data, 0, $validSize); + + // Rewind to the begining of the forgotten stuff. + $this->in->skip(-$notValidPartSize+1); + } + + } // if !EOF + } + return $data; + } + + function skip($n) { + return $this->in->skip($n); + } + + function reset() { + return $this->in->reset(); + } + + function close() { + return $this->in->close(); + } + + function open() { + return $this->in->open(); + } + + /** + * Read a line from input stream. + */ + function readLine() { + $line = null; + while ( ($ch = $this->readChar()) !== -1 ) { + if ( $ch === "\n" ) { + break; + } + $line .= $ch; + } + + // Warning : Not considering an empty line as an EOF + if ( $line === null && $ch !== -1 ) + return ""; + + return $line; + } + + /** + * Reads a single char from the reader. + * @return string single char or -1 if EOF. + */ + function readChar() { + + if ( $this->buffer === null ) { + // Buffer is empty, fill it ... + $read = $this->in->read($this->bufferSize); + if ($read === -1) { + $ch = -1; + } else { + $this->buffer = $read; + return $this->readChar(); // recurse + } + } else { + // Get next buffered char ... + // handle case where buffer is read-in, but is empty. The next readChar() will return -1 EOF, + // so we just return empty string (char) at this point. (Probably could also return -1 ...?) + $ch = ($this->buffer !== "") ? $this->buffer{$this->bufferPos} : ''; + $this->bufferPos++; + if ( $this->bufferPos >= strlen($this->buffer) ) { + $this->buffer = null; + $this->bufferPos = 0; + } + } + + return $ch; + } + + /** + * Returns whether eof has been reached in stream. + * This is important, because filters may want to know if the end of the file (and not just buffer) + * has been reached. + * @return boolean + */ + function eof() { + return $this->in->eof(); + } + + function getResource() { + return $this->in->getResource(); + } +} diff --git a/library/phing/system/io/BufferedWriter.php b/library/phing/system/io/BufferedWriter.php new file mode 100644 index 000000000..f1a985ba0 --- /dev/null +++ b/library/phing/system/io/BufferedWriter.php @@ -0,0 +1,71 @@ +. + */ + +include_once 'phing/system/io/Writer.php'; + +/** + * Convenience class for writing files. + * + * @author Hans Lellelid + * @version $Revision: 905 $ + * @package phing.system.io + */ +class BufferedWriter extends Writer { + + /** + * The size of the buffer in kb. + */ + private $bufferSize = 0; + + /** + * @var Writer The Writer we are buffering output to. + */ + private $out; + + public function __construct(Writer $writer, $buffsize = 8192) { + $this->out = $writer; + $this->bufferSize = $buffsize; + } + + public function write($buf, $off = null, $len = null) { + return $this->out->write($buf, $off, $len); + } + + public function newLine() { + $this->write(PHP_EOL); + } + + public function getResource() { + return $this->out->getResource(); + } + + public function flush() { + $this->out->flush(); + } + + /** + * Close attached stream. + */ + public function close() { + return $this->out->close(); + } + +} diff --git a/library/phing/system/io/ConsoleReader.php b/library/phing/system/io/ConsoleReader.php new file mode 100644 index 000000000..8a234f595 --- /dev/null +++ b/library/phing/system/io/ConsoleReader.php @@ -0,0 +1,84 @@ +. + */ + +include_once 'phing/system/io/Reader.php'; + +/** + * Convenience class for reading console input. + * + * @author Hans Lellelid + * @author Matthew Hershberger + * @version $Revision: 905 $ + * @package phing.system.io + */ +class ConsoleReader extends Reader { + + function readLine() { + + $out = fgets(STDIN); // note: default maxlen is 1kb + $out = rtrim($out); + + return $out; + } + + /** + * + * @param int $len Num chars to read. + * @return string chars read or -1 if eof. + */ + function read($len = null) { + + $out = fread(STDIN, $len); + + + return $out; + // FIXME + // read by chars doesn't work (yet?) with PHP stdin. Maybe + // this is just a language feature, maybe there's a way to get + // ability to read chars w/o ? + + } + + function close() { + // STDIN is always open + } + + function open() { + // STDIN is always open + } + + /** + * Whether eof has been reached with stream. + * @return boolean + */ + function eof() { + return feof(STDIN); + } + + /** + * Returns path to file we are reading. + * @return string + */ + function getResource() { + return "console"; + } +} + diff --git a/library/phing/system/io/FileInputStream.php b/library/phing/system/io/FileInputStream.php new file mode 100644 index 000000000..54bdfd675 --- /dev/null +++ b/library/phing/system/io/FileInputStream.php @@ -0,0 +1,76 @@ +. + */ + +require_once 'phing/system/io/InputStream.php'; +require_once 'phing/system/io/PhingFile.php'; + +/** + * Input stream subclass for file streams. + * + * @package phing.system.io + */ +class FileInputStream extends InputStream { + + /** + * @var PhingFile The associated file. + */ + protected $file; + + /** + * Construct a new FileInputStream. + * @param mixed $file + * @throws Exception - if invalid argument specified. + * @throws IOException - if unable to open file. + */ + public function __construct($file, $append = false) { + if ($file instanceof PhingFile) { + $this->file = $file; + } elseif (is_string($file)) { + $this->file = new PhingFile($file); + } else { + throw new Exception("Invalid argument type for \$file."); + } + + $stream = @fopen($this->file->getAbsolutePath(), "rb"); + if ($stream === false) { + throw new IOException("Unable to open " . $this->file->__toString() . " for reading: " . $php_errormsg); + } + + parent::__construct($stream); + } + + /** + * Returns a string representation of the attached file. + * @return string + */ + public function __toString() { + return $this->file->getPath(); + } + + /** + * Mark is supported by FileInputStream. + * @return boolean TRUE + */ + public function markSupported() { + return true; + } +} + diff --git a/library/phing/system/io/FileOutputStream.php b/library/phing/system/io/FileOutputStream.php new file mode 100644 index 000000000..5eb58e441 --- /dev/null +++ b/library/phing/system/io/FileOutputStream.php @@ -0,0 +1,71 @@ +. + */ + +require_once 'phing/system/io/OutputStream.php'; +require_once 'phing/system/io/PhingFile.php'; + +/** + * Output stream subclass for file streams. + * + * @package phing.system.io + */ +class FileOutputStream extends OutputStream { + + /** + * @var PhingFile The associated file. + */ + protected $file; + + /** + * Construct a new FileOutputStream. + * @param mixed $file + * @param boolean $append Whether to append bytes to end of file rather than beginning. + * @throws Exception - if invalid argument specified. + * @throws IOException - if unable to open file. + */ + public function __construct($file, $append = false) { + if ($file instanceof PhingFile) { + $this->file = $file; + } elseif (is_string($file)) { + $this->file = new PhingFile($file); + } else { + throw new Exception("Invalid argument type for \$file."); + } + if ($append) { + $stream = @fopen($this->file->getAbsolutePath(), "ab"); + } else { + $stream = @fopen($this->file->getAbsolutePath(), "wb"); + } + if ($stream === false) { + throw new IOException("Unable to open " . $this->file->__toString() . " for writing: " . $php_errormsg); + } + parent::__construct($stream); + } + + /** + * Returns a string representation of the attached file. + * @return string + */ + public function __toString() { + return $this->file->getPath(); + } +} + diff --git a/library/phing/system/io/FileReader.php b/library/phing/system/io/FileReader.php new file mode 100644 index 000000000..5dd749166 --- /dev/null +++ b/library/phing/system/io/FileReader.php @@ -0,0 +1,41 @@ +. + */ + +require_once 'phing/system/io/InputStreamReader.php'; +require_once 'phing/system/io/FileInputStream.php'; + +/** + * Convenience class for reading files. + * @package phing.system.io + */ +class FileReader extends InputStreamReader { + + /** + * Construct a new FileReader. + * @param mixed $file PhingFile or string pathname. + */ + public function __construct($file) { + $in = new FileInputStream($file); + parent::__construct($in); + } + +} + diff --git a/library/phing/system/io/FileSystem.php b/library/phing/system/io/FileSystem.php new file mode 100644 index 000000000..ee68c97b3 --- /dev/null +++ b/library/phing/system/io/FileSystem.php @@ -0,0 +1,757 @@ +. + */ + +/** + * This is an abstract class for platform specific filesystem implementations + * you have to implement each method in the platform specific filesystem implementation + * classes Your local filesytem implementation must extend this class. + * You should also use this class as a template to write your local implementation + * Some native PHP filesystem specific methods are abstracted here as well. Anyway + * you _must_ always use this methods via a PhingFile object (that by nature uses the + * *FileSystem drivers to access the real filesystem via this class using natives. + * + * FIXME: + * - Error handling reduced to min fallthrough runtime excetions + * more precise errorhandling is done by the PhingFile class + * + * @author Charlie Killian + * @author Hans Lellelid + * @version $Revision: 905 $ + * @package phing.system.io + */ +abstract class FileSystem { + + /* properties for simple boolean attributes */ + const BA_EXISTS = 0x01; + const BA_REGULAR = 0x02; + const BA_DIRECTORY = 0x04; + const BA_HIDDEN = 0x08; + + /** Instance for getFileSystem() method. */ + private static $fs; + + /** + * Static method to return the FileSystem singelton representing + * this platform's local filesystem driver. + * @return FileSystem + */ + public static function getFileSystem() { + if (self::$fs === null) { + switch(Phing::getProperty('host.fstype')) { + case 'UNIX': + include_once 'phing/system/io/UnixFileSystem.php'; + self::$fs = new UnixFileSystem(); + break; + case 'WIN32': + include_once 'phing/system/io/Win32FileSystem.php'; + self::$fs = new Win32FileSystem(); + break; + case 'WINNT': + include_once 'phing/system/io/WinNTFileSystem.php'; + self::$fs = new WinNTFileSystem(); + break; + default: + throw new Exception("Host uses unsupported filesystem, unable to proceed"); + } + } + return self::$fs; + } + + /* -- Normalization and construction -- */ + + /** + * Return the local filesystem's name-separator character. + */ + abstract function getSeparator(); + + /** + * Return the local filesystem's path-separator character. + */ + abstract function getPathSeparator(); + + /** + * Convert the given pathname string to normal form. If the string is + * already in normal form then it is simply returned. + */ + abstract function normalize($strPath); + + /** + * Compute the length of this pathname string's prefix. The pathname + * string must be in normal form. + */ + abstract function prefixLength($pathname); + + /** + * Resolve the child pathname string against the parent. + * Both strings must be in normal form, and the result + * will be a string in normal form. + */ + abstract function resolve($parent, $child); + + /** + * Resolve the given abstract pathname into absolute form. Invoked by the + * getAbsolutePath and getCanonicalPath methods in the PhingFile class. + */ + abstract function resolveFile(PhingFile $f); + + /** + * Return the parent pathname string to be used when the parent-directory + * argument in one of the two-argument PhingFile constructors is the empty + * pathname. + */ + abstract function getDefaultParent(); + + /** + * Post-process the given URI path string if necessary. This is used on + * win32, e.g., to transform "/c:/foo" into "c:/foo". The path string + * still has slash separators; code in the PhingFile class will translate them + * after this method returns. + */ + abstract function fromURIPath($path); + + /* -- Path operations -- */ + + /** + * Tell whether or not the given abstract pathname is absolute. + */ + abstract function isAbsolute(PhingFile $f); + + /** + * canonicalize filename by checking on disk + * @return mixed Canonical path or false if the file doesn't exist. + */ + function canonicalize($strPath) { + return @realpath($strPath); + } + + /* -- Attribute accessors -- */ + + /** + * Return the simple boolean attributes for the file or directory denoted + * by the given abstract pathname, or zero if it does not exist or some + * other I/O error occurs. + */ + function getBooleanAttributes($f) { + throw new Exception("SYSTEM ERROR method getBooleanAttributes() not implemented by fs driver"); + } + + /** + * Check whether the file or directory denoted by the given abstract + * pathname may be accessed by this process. If the second argument is + * false, then a check for read access is made; if the second + * argument is true, then a check for write (not read-write) + * access is made. Return false if access is denied or an I/O error + * occurs. + */ + function checkAccess(PhingFile $f, $write = false) { + // we clear stat cache, its expensive to look up from scratch, + // but we need to be sure + @clearstatcache(); + + + // Shouldn't this be $f->GetAbsolutePath() ? + // And why doesn't GetAbsolutePath() work? + + $strPath = (string) $f->getPath(); + + // FIXME + // if file object does denote a file that yet not existst + // path rights are checked + if (!@file_exists($strPath) && !is_dir($strPath)) { + $strPath = $f->getParent(); + if ($strPath === null || !is_dir($strPath)) { + $strPath = Phing::getProperty("user.dir"); + } + //$strPath = dirname($strPath); + } + + if (!$write) { + return (boolean) @is_readable($strPath); + } else { + return (boolean) @is_writable($strPath); + } + } + + /** + * Whether file can be deleted. + * @param PhingFile $f + * @return boolean + */ + function canDelete(PhingFile $f) + { + clearstatcache(); + $dir = dirname($f->getAbsolutePath()); + return (bool) @is_writable($dir); + } + + /** + * Return the time at which the file or directory denoted by the given + * abstract pathname was last modified, or zero if it does not exist or + * some other I/O error occurs. + */ + function getLastModifiedTime(PhingFile $f) { + + if (!$f->exists()) { + return 0; + } + + @clearstatcache(); + $strPath = (string) $f->getPath(); + $mtime = @filemtime($strPath); + if (false === $mtime) { + // FAILED. Log and return err. + $msg = "FileSystem::Filemtime() FAILED. Cannot can not get modified time of $strPath. $php_errormsg"; + throw new Exception($msg); + } else { + return (int) $mtime; + } + } + + /** + * Return the length in bytes of the file denoted by the given abstract + * pathname, or zero if it does not exist, is a directory, or some other + * I/O error occurs. + */ + function getLength(PhingFile $f) { + $strPath = (string) $f->getAbsolutePath(); + $fs = filesize((string) $strPath); + if ($fs !== false) { + return $fs; + } else { + $msg = "FileSystem::Read() FAILED. Cannot get filesize of $strPath. $php_errormsg"; + throw new Exception($msg); + } + } + + /* -- File operations -- */ + + /** + * Create a new empty file with the given pathname. Return + * true if the file was created and false if a + * file or directory with the given pathname already exists. Throw an + * IOException if an I/O error occurs. + * + * @param string Path of the file to be created. + * + * @throws IOException + */ + function createNewFile($strPathname) { + if (@file_exists($strPathname)) + return false; + + // Create new file + $fp = @fopen($strPathname, "w"); + if ($fp === false) { + throw new IOException("The file \"$strPathname\" could not be created"); + } + @fclose($fp); + return true; + } + + /** + * Delete the file or directory denoted by the given abstract pathname, + * returning true if and only if the operation succeeds. + */ + function delete(PhingFile $f, $recursive = false) { + if ($f->isDirectory()) { + return $this->rmdir($f->getPath(), $recursive); + } else { + return $this->unlink($f->getPath()); + } + } + + /** + * Arrange for the file or directory denoted by the given abstract + * pathname to be deleted when Phing::shutdown is called, returning + * true if and only if the operation succeeds. + */ + function deleteOnExit($f) { + throw new Exception("deleteOnExit() not implemented by local fs driver"); + } + + /** + * List the elements of the directory denoted by the given abstract + * pathname. Return an array of strings naming the elements of the + * directory if successful; otherwise, return null. + */ + function listDir(PhingFile $f) { + $strPath = (string) $f->getAbsolutePath(); + $d = @dir($strPath); + if (!$d) { + return null; + } + $list = array(); + while($entry = $d->read()) { + if ($entry != "." && $entry != "..") { + array_push($list, $entry); + } + } + $d->close(); + unset($d); + return $list; + } + + /** + * Create a new directory denoted by the given abstract pathname, + * returning true if and only if the operation succeeds. + * + * NOTE: umask() is reset to 0 while executing mkdir(), and restored afterwards + */ + function createDirectory(&$f, $mode = 0755) { + $old_umask = umask(0); + $return = @mkdir($f->getAbsolutePath(), $mode); + umask($old_umask); + return $return; + } + + /** + * Rename the file or directory denoted by the first abstract pathname to + * the second abstract pathname, returning true if and only if + * the operation succeeds. + * + * @param PhingFile $f1 abstract source file + * @param PhingFile $f2 abstract destination file + * @return void + * @throws Exception if rename cannot be performed + */ + function rename(PhingFile $f1, PhingFile $f2) { + // get the canonical paths of the file to rename + $src = $f1->getAbsolutePath(); + $dest = $f2->getAbsolutePath(); + if (false === @rename($src, $dest)) { + $msg = "Rename FAILED. Cannot rename $src to $dest. $php_errormsg"; + throw new Exception($msg); + } + } + + /** + * Set the last-modified time of the file or directory denoted by the + * given abstract pathname returning true if and only if the + * operation succeeds. + * @return void + * @throws Exception + */ + function setLastModifiedTime(PhingFile $f, $time) { + $path = $f->getPath(); + $success = @touch($path, $time); + if (!$success) { + throw new Exception("Could not touch '" . $path . "' due to: $php_errormsg"); + } + } + + /** + * Mark the file or directory denoted by the given abstract pathname as + * read-only, returning true if and only if the operation + * succeeds. + */ + function setReadOnly($f) { + throw new Exception("setReadonle() not implemented by local fs driver"); + } + + /* -- Filesystem interface -- */ + + /** + * List the available filesystem roots, return array of PhingFile objects + */ + function listRoots() { + throw new Exception("SYSTEM ERROR [listRoots() not implemented by local fs driver]"); + } + + /* -- Basic infrastructure -- */ + + /** + * Compare two abstract pathnames lexicographically. + */ + function compare($f1, $f2) { + throw new Exception("SYSTEM ERROR [compare() not implemented by local fs driver]"); + } + + /** + * Copy a file. + * + * @param PhingFile $src Source path and name file to copy. + * @param PhingFile $dest Destination path and name of new file. + * + * @return void + * @throws Exception if file cannot be copied. + */ + function copy(PhingFile $src, PhingFile $dest) { + global $php_errormsg; + + // Recursively copy a directory + if($src->isDirectory()) { + return $this->copyr($src->getAbsolutePath(), $dest->getAbsolutePath()); + } + + $srcPath = $src->getAbsolutePath(); + $destPath = $dest->getAbsolutePath(); + + if (false === @copy($srcPath, $destPath)) { // Copy FAILED. Log and return err. + // Add error from php to end of log message. $php_errormsg. + $msg = "FileSystem::copy() FAILED. Cannot copy $srcPath to $destPath. $php_errormsg"; + throw new Exception($msg); + } + + try { + $dest->setMode($src->getMode()); + } catch(Exception $exc) { + // [MA] does chmod returns an error on systems that do not support it ? + // eat it up for now. + } + } + + /** + * Copy a file, or recursively copy a folder and its contents + * + * @author Aidan Lister + * @version 1.0.1 + * @link http://aidanlister.com/repos/v/function.copyr.php + * @param string $source Source path + * @param string $dest Destination path + * @return bool Returns TRUE on success, FALSE on failure + */ + function copyr($source, $dest) + { + // Check for symlinks + if (is_link($source)) { + return symlink(readlink($source), $dest); + } + + // Simple copy for a file + if (is_file($source)) { + return copy($source, $dest); + } + + // Make destination directory + if (!is_dir($dest)) { + mkdir($dest); + } + + // Loop through the folder + $dir = dir($source); + while (false !== $entry = $dir->read()) { + // Skip pointers + if ($entry == '.' || $entry == '..') { + continue; + } + + // Deep copy directories + $this->copyr("$source/$entry", "$dest/$entry"); + } + + // Clean up + $dir->close(); + return true; + } + + /** + * Change the ownership on a file or directory. + * + * @param string $pathname Path and name of file or directory. + * @param string $user The user name or number of the file or directory. See http://us.php.net/chown + * + * @return void + * @throws Exception if operation failed. + */ + function chown($pathname, $user) { + if (false === @chown($pathname, $user)) {// FAILED. + $msg = "FileSystem::chown() FAILED. Cannot chown $pathname. User $user." . (isset($php_errormsg) ? ' ' . $php_errormsg : ""); + throw new Exception($msg); + } + } + + /** + * Change the group on a file or directory. + * + * @param string $pathname Path and name of file or directory. + * @param string $group The group of the file or directory. See http://us.php.net/chgrp + * + * @return void + * @throws Exception if operation failed. + */ + function chgrp($pathname, $group) { + if (false === @chgrp($pathname, $group)) {// FAILED. + $msg = "FileSystem::chgrp() FAILED. Cannot chown $pathname. Group $group." . (isset($php_errormsg) ? ' ' . $php_errormsg : ""); + throw new Exception($msg); + } + } + + /** + * Change the permissions on a file or directory. + * + * @param pathname String. Path and name of file or directory. + * @param mode Int. The mode (permissions) of the file or + * directory. If using octal add leading 0. eg. 0777. + * Mode is affected by the umask system setting. + * + * @return void + * @throws Exception if operation failed. + */ + function chmod($pathname, $mode) { + $str_mode = decoct($mode); // Show octal in messages. + if (false === @chmod($pathname, $mode)) {// FAILED. + $msg = "FileSystem::chmod() FAILED. Cannot chmod $pathname. Mode $str_mode." . (isset($php_errormsg) ? ' ' . $php_errormsg : ""); + throw new Exception($msg); + } + } + + /** + * Locks a file and throws an Exception if this is not possible. + * @return void + * @throws Exception + */ + function lock(PhingFile $f) { + $filename = $f->getPath(); + $fp = @fopen($filename, "w"); + $result = @flock($fp, LOCK_EX); + @fclose($fp); + if (!$result) { + throw new Exception("Could not lock file '$filename'"); + } + } + + /** + * Unlocks a file and throws an IO Error if this is not possible. + * + * @throws Exception + * @return void + */ + function unlock(PhingFile $f) { + $filename = $f->getPath(); + $fp = @fopen($filename, "w"); + $result = @flock($fp, LOCK_UN); + fclose($fp); + if (!$result) { + throw new Exception("Could not unlock file '$filename'"); + } + } + + /** + * Delete a file. + * + * @param file String. Path and/or name of file to delete. + * + * @return void + * @throws Exception - if an error is encountered. + */ + function unlink($file) { + global $php_errormsg; + if (false === @unlink($file)) { + $msg = "FileSystem::unlink() FAILED. Cannot unlink '$file'. $php_errormsg"; + throw new Exception($msg); + } + } + + /** + * Symbolically link a file to another name. + * + * Currently symlink is not implemented on Windows. Don't use if the application is to be portable. + * + * @param string $target Path and/or name of file to link. + * @param string $link Path and/or name of link to be created. + * @return void + */ + function symlink($target, $link) { + + // If Windows OS then symlink() will report it is not supported in + // the build. Use this error instead of checking for Windows as the OS. + + if (false === @symlink($target, $link)) { + // Add error from php to end of log message. $php_errormsg. + $msg = "FileSystem::Symlink() FAILED. Cannot symlink '$target' to '$link'. $php_errormsg"; + throw new Exception($msg); + } + + } + + /** + * Set the modification and access time on a file to the present time. + * + * @param string $file Path and/or name of file to touch. + * @param int $time + * @return void + */ + function touch($file, $time = null) { + global $php_errormsg; + + if (null === $time) { + $error = @touch($file); + } else { + $error = @touch($file, $time); + } + + if (false === $error) { // FAILED. + // Add error from php to end of log message. $php_errormsg. + $msg = "FileSystem::touch() FAILED. Cannot touch '$file'. $php_errormsg"; + throw new Exception($msg); + } + } + + /** + * Delete an empty directory OR a directory and all of its contents. + * + * @param dir String. Path and/or name of directory to delete. + * @param children Boolean. False: don't delete directory contents. + * True: delete directory contents. + * + * @return void + */ + function rmdir($dir, $children = false) { + global $php_errormsg; + + // If children=FALSE only delete dir if empty. + if (false === $children) { + + if (false === @rmdir($dir)) { // FAILED. + // Add error from php to end of log message. $php_errormsg. + $msg = "FileSystem::rmdir() FAILED. Cannot rmdir $dir. $php_errormsg"; + throw new Exception($msg); + } + + } else { // delete contents and dir. + + $handle = @opendir($dir); + + if (false === $handle) { // Error. + + $msg = "FileSystem::rmdir() FAILED. Cannot opendir() $dir. $php_errormsg"; + throw new Exception($msg); + + } else { // Read from handle. + + // Don't error on readdir(). + while (false !== ($entry = @readdir($handle))) { + + if ($entry != '.' && $entry != '..') { + + // Only add / if it isn't already the last char. + // This ONLY serves the purpose of making the Logger + // output look nice:) + + if (strpos(strrev($dir), DIRECTORY_SEPARATOR) === 0) {// there is a / + $next_entry = $dir . $entry; + } else { // no / + $next_entry = $dir . DIRECTORY_SEPARATOR . $entry; + } + + // NOTE: As of php 4.1.1 is_dir doesn't return FALSE it + // returns 0. So use == not ===. + + // Don't error on is_dir() + if (false == @is_dir($next_entry)) { // Is file. + + try { + self::unlink($next_entry); // Delete. + } catch (Exception $e) { + $msg = "FileSystem::Rmdir() FAILED. Cannot FileSystem::Unlink() $next_entry. ". $e->getMessage(); + throw new Exception($msg); + } + + } else { // Is directory. + + try { + self::rmdir($next_entry, true); // Delete + } catch (Exception $e) { + $msg = "FileSystem::rmdir() FAILED. Cannot FileSystem::rmdir() $next_entry. ". $e->getMessage(); + throw new Exception($msg); + } + + } // end is_dir else + } // end .. if + } // end while + } // end handle if + + // Don't error on closedir() + @closedir($handle); + + if (false === @rmdir($dir)) { // FAILED. + // Add error from php to end of log message. $php_errormsg. + $msg = "FileSystem::rmdir() FAILED. Cannot rmdir $dir. $php_errormsg"; + throw new Exception($msg); + } + + } + + } + + /** + * Set the umask for file and directory creation. + * + * @param mode Int. Permissions ususally in ocatal. Use leading 0 for + * octal. Number between 0 and 0777. + * + * @return void + * @throws Exception if there is an error performing operation. + */ + function umask($mode) { + global $php_errormsg; + + // CONSIDERME: + // Throw a warning if mode is 0. PHP converts illegal octal numbers to + // 0 so 0 might not be what the user intended. + + $str_mode = decoct($mode); // Show octal in messages. + + if (false === @umask($mode)) { // FAILED. + // Add error from php to end of log message. $php_errormsg. + $msg = "FileSystem::Umask() FAILED. Value $mode. $php_errormsg"; + throw new Exception($msg); + } + } + + /** + * Compare the modified time of two files. + * + * @param file1 String. Path and name of file1. + * @param file2 String. Path and name of file2. + * + * @return Int. 1 if file1 is newer. + * -1 if file2 is newer. + * 0 if files have the same time. + * Err object on failure. + * + * @throws Exception - if cannot get modified time of either file. + */ + function compareMTimes($file1, $file2) { + + $mtime1 = filemtime($file1); + $mtime2 = filemtime($file2); + + if ($mtime1 === false) { // FAILED. Log and return err. + // Add error from php to end of log message. $php_errormsg. + $msg = "FileSystem::compareMTimes() FAILED. Cannot can not get modified time of $file1."; + throw new Exception($msg); + } elseif ($mtime2 === false) { // FAILED. Log and return err. + // Add error from php to end of log message. $php_errormsg. + $msg = "FileSystem::compareMTimes() FAILED. Cannot can not get modified time of $file2."; + throw new Exception($msg); + } else { // Worked. Log and return compare. + // Compare mtimes. + if ($mtime1 == $mtime2) { + return 0; + } else { + return ($mtime1 < $mtime2) ? -1 : 1; + } // end compare + } + } + +} diff --git a/library/phing/system/io/FileWriter.php b/library/phing/system/io/FileWriter.php new file mode 100644 index 000000000..ae6e32fef --- /dev/null +++ b/library/phing/system/io/FileWriter.php @@ -0,0 +1,42 @@ +. + */ + +require_once 'phing/system/io/OutputStreamWriter.php'; +require_once 'phing/system/io/FileOutputStream.php'; + +/** + * Convenience class for performing file write operations. + * + * @package phing.system.io + */ +class FileWriter extends OutputStreamWriter { + + /** + * Construct a new FileWriter. + * @param mixed $file PhingFile or string pathname. + * @param boolean $append Append to existing file? + */ + function __construct($file, $append = false) { + $out = new FileOutputStream($file, $append); + parent::__construct($out); + } +} + diff --git a/library/phing/system/io/FilterReader.php b/library/phing/system/io/FilterReader.php new file mode 100644 index 000000000..b7b2fb228 --- /dev/null +++ b/library/phing/system/io/FilterReader.php @@ -0,0 +1,68 @@ +. + */ + +require_once 'phing/system/io/Reader.php'; + +/** + * Wrapper class for readers, which can be used to apply filters. + * @package phing.system.io + */ +class FilterReader extends Reader { + + /** + * @var Reader + */ + protected $in; + + function __construct(Reader $in = null) { + $this->in = $in; + } + + public function setReader(Reader $in) { + $this->in = $in; + } + + public function skip($n) { + return $this->in->skip($n); + } + + /** + * Read data from source. + * FIXME: Clean up this function signature, as it a) params aren't being used + * and b) it doesn't make much sense. + */ + public function read($len = null) { + return $this->in->read($len); + } + + public function reset() { + return $this->in->reset(); + } + + public function close() { + return $this->in->close(); + } + + function getResource() { + return $this->in->getResource(); + } +} + diff --git a/library/phing/system/io/IOException.php b/library/phing/system/io/IOException.php new file mode 100644 index 000000000..14c7b5104 --- /dev/null +++ b/library/phing/system/io/IOException.php @@ -0,0 +1,27 @@ +. + */ + +/** + * Extends Exception to take advantage of methods therein. + * + * @package phing.system.io + */ +class IOException extends Exception {} diff --git a/library/phing/system/io/InputStream.php b/library/phing/system/io/InputStream.php new file mode 100644 index 000000000..7fe3c0e20 --- /dev/null +++ b/library/phing/system/io/InputStream.php @@ -0,0 +1,178 @@ +. + */ + +/** + * Wrapper class for PHP stream that supports read operations. + * + * @package phing.system.io + */ +class InputStream { + + /** + * @var resource The attached PHP stream. + */ + protected $stream; + + /** + * @var int Position of stream cursor. + */ + protected $currentPosition = 0; + + /** + * @var int Marked position of stream cursor. + */ + protected $mark = 0; + + /** + * Construct a new InputStream. + * @param resource $stream Configured PHP stream for writing. + */ + public function __construct($stream) { + if (!is_resource($stream)) { + throw new IOException("Passed argument is not a valid stream."); + } + $this->stream = $stream; + } + + /** + * Skip over $n bytes. + * @param int $n + */ + public function skip($n) { + $start = $this->currentPosition; + + $ret = @fseek($this->stream, $n, SEEK_CUR); + if ( $ret === -1 ) + return -1; + + $this->currentPosition = ftell($this->stream); + + if ( $start > $this->currentPosition ) + $skipped = $start - $this->currentPosition; + else + $skipped = $this->currentPosition - $start; + + return $skipped; + } + + /** + * Read data from stream until $len chars or EOF. + * @param int $len Num chars to read. If not specified this stream will read until EOF. + * @return string chars read or -1 if eof. + */ + public function read($len = null) { + + if ($this->eof()) { + return -1; + } + + if ($len === null) { // we want to keep reading until we get an eof + $out = ""; + while(!$this->eof()) { + $out .= fread($this->stream, 8192); + $this->currentPosition = ftell($this->stream); + } + } else { + $out = fread($this->stream, $len); // adding 1 seems to ensure that next call to read() will return EOF (-1) + $this->currentPosition = ftell($this->stream); + } + + return $out; + } + + /** + * Marks the current position in this input stream. + * @throws IOException - if the underlying stream doesn't support this method. + */ + public function mark() { + if (!$this->markSupported()) { + throw new IOException(get_class($this) . " does not support mark() and reset() methods."); + } + $this->mark = $this->currentPosition; + } + + /** + * Whether the input stream supports mark and reset methods. + * @return boolean + */ + public function markSupported() { + return false; + } + + /** + * Repositions this stream to the position at the time the mark method was last called on this input stream. + * @throws IOException - if the underlying stream doesn't support this method. + */ + function reset() { + if (!$this->markSupported()) { + throw new IOException(get_class($this) . " does not support mark() and reset() methods."); + } + // goes back to last mark, by default this would be 0 (i.e. rewind file). + fseek($this->stream, SEEK_SET, $this->mark); + $this->mark = 0; + } + + /** + * Closes stream. + * @throws IOException if stream cannot be closed (note that calling close() on an already-closed stream will not raise an exception) + */ + public function close() { + if ($this->stream === null) { + return; + } + if (false === @fclose($this->stream)) { + // FAILED. + $msg = "Cannot fclose " . $this->file->__toString() . " $php_errormsg"; + throw new IOException($msg); + } + $this->stream = null; + } + + /** + * Whether eof has been reached with stream. + * @return boolean + */ + public function eof() { + return feof($this->stream); + } + + /** + * Reads a entire until EOF and places contents in passed-in variable. Stream is closed after read. + * + * @param string &$rBuffer String variable where read contents will be put. + * @return TRUE on success. + * @author Charlie Killian, charlie@tizac.com + * @throws IOException - if there is an error reading from stream. + * @deprecated - Instead, use the read() method or a BufferedReader. + */ + public function readInto(&$rBuffer) { + $rBuffer = $this->read(); + $this->close(); + } + + /** + * Returns string representation of attached stream. + * @return string + */ + public function __toString() { + return (string) $this->stream; + } +} diff --git a/library/phing/system/io/InputStreamReader.php b/library/phing/system/io/InputStreamReader.php new file mode 100644 index 000000000..4106ae5c8 --- /dev/null +++ b/library/phing/system/io/InputStreamReader.php @@ -0,0 +1,127 @@ +. + */ + +include_once 'phing/system/io/PhingFile.php'; +include_once 'phing/system/io/Reader.php'; + +/** + * Writer class for OutputStream objects. + * + * Unlike the Java counterpart, this class does not (yet) handle + * character set transformations. This will be an important function + * of this class with move to supporting PHP6. + * + * @package phing.system.io + */ +class InputStreamReader extends Reader { + + /** + * @var InputStream + */ + protected $inStream; + + /** + * Construct a new InputStreamReader. + * @param InputStream $$inStream InputStream to read from + */ + public function __construct(InputStream $inStream) { + $this->inStream = $inStream; + } + + /** + * Close the stream. + */ + public function close() { + return $this->inStream->close(); + } + + /** + * Skip over $n bytes. + * @param int $n + */ + public function skip($n) { + return $this->inStream->skip($n); + } + + /** + * Read data from file. + * @param int $len Num chars to read. + * @return string chars read or -1 if eof. + */ + public function read($len = null) { + return $this->inStream->read($len); + } + + /** + * Marks the current position in this input stream. + * @throws IOException - if the underlying stream doesn't support this method. + */ + public function mark() { + $this->inStream->mark(); + } + + /** + * Whether the attached stream supports mark/reset. + * @return boolean + */ + public function markSupported() { + return $this->inStream->markSupported(); + } + + /** + * Repositions this stream to the position at the time the mark method was last called on this input stream. + * @throws IOException - if the underlying stream doesn't support this method. + */ + public function reset() { + $this->inStream->reset(); + } + + /** + * Whether eof has been reached with stream. + * @return boolean + */ + public function eof() { + return $this->inStream->eof(); + } + + /** + * Reads a entire file and stores the data in the variable + * passed by reference. + * + * @param string $file String. Path and/or name of file to read. + * @param object &$rBuffer Reference. Variable of where to put contents. + * + * @return TRUE on success. Err object on failure. + * @author Charlie Killian, charlie@tizac.com + * @deprecated Use read() or BufferedReader instead. + */ + public function readInto(&$rBuffer) { + return $this->inStream->readInto($rBuffer); + } + + /** + * Returns string representation of attached stream. + * @return string + */ + public function getResource() { + return $this->inStream->__toString(); + } +} diff --git a/library/phing/system/io/OutputStream.php b/library/phing/system/io/OutputStream.php new file mode 100644 index 000000000..bfac886f2 --- /dev/null +++ b/library/phing/system/io/OutputStream.php @@ -0,0 +1,108 @@ +. + */ + +/** + * Wrapper class for PHP stream that supports write operations. + * + * @package phing.system.io + */ +class OutputStream { + + /** + * @var resource The configured PHP stream. + */ + protected $stream; + + /** + * Construct a new OutputStream. + * @param resource $stream Configured PHP stream for writing. + */ + public function __construct($stream) { + if (!is_resource($stream)) { + throw new IOException("Passed argument is not a valid stream."); + } + $this->stream = $stream; + } + + /** + * Closes attached stream, flushing output first. + * @throws IOException if cannot close stream (note that attempting to close an already closed stream will not raise an IOException) + * @return void + */ + public function close() { + if ($this->stream === null) { + return; + } + $this->flush(); + if (false === @fclose($this->stream)) { + $msg = "Cannot close " . $this->getResource() . ": $php_errormsg"; + throw new IOException($msg); + } + $this->stream = null; + } + + /** + * Flushes stream. + * + * @throws IOException if unable to flush data (e.g. stream is not open). + */ + public function flush() { + if (false === @fflush($this->stream)) { + throw new IOException("Could not flush stream: " . $php_errormsg); + } + } + + /** + * Writes data to stream. + * + * @param string $buf Binary/character data to write. + * @param int $off (Optional) offset. + * @param int $len (Optional) number of bytes/chars to write. + * @return void + * @throws IOException - if there is an error writing to stream + */ + public function write($buf, $off = null, $len = null) { + if ( $off === null && $len === null ) { + $to_write = $buf; + } elseif ($off !== null && $len === null) { + $to_write = substr($buf, $off); + } elseif ($off === null && $len !== null) { + $to_write = substr($buf, 0, $len); + } else { + $to_write = substr($buf, $off, $len); + } + + $result = @fwrite($this->stream, $to_write); + + if ( $result === false ) { + throw new IOException("Error writing to stream."); + } + } + + /** + * Returns a string representation of the attached PHP stream. + * @return string + */ + public function __toString() { + return (string) $this->stream; + } +} + diff --git a/library/phing/system/io/OutputStreamWriter.php b/library/phing/system/io/OutputStreamWriter.php new file mode 100644 index 000000000..ab545fc14 --- /dev/null +++ b/library/phing/system/io/OutputStreamWriter.php @@ -0,0 +1,84 @@ +. + */ + +include_once 'phing/system/io/PhingFile.php'; +require_once 'phing/system/io/Writer.php'; + +/** + * Writer class for OutputStream objects. + * + * Unlike the Java counterpart, this class does not (yet) handle + * character set transformations. This will be an important function + * of this class with move to supporting PHP6. + * + * @package phing.system.io + */ +class OutputStreamWriter extends Writer { + + /** + * @var OutputStream + */ + protected $outStream; + + /** + * Construct a new OutputStreamWriter. + * @param OutputStream $outStream OutputStream to write to + */ + public function __construct(OutputStream $outStream) { + $this->outStream = $outStream; + } + + /** + * Close the stream. + */ + public function close() { + return $this->outStream->close(); + } + + /** + * Write char data to stream. + * + * @param unknown_type $buf + * @param unknown_type $off + * @param unknown_type $len + * @return unknown + */ + public function write($buf, $off = null, $len = null) { + return $this->outStream->write($buf, $off, $len); + } + + /** + * Flush output to the stream. + */ + public function flush() { + $this->outStream->flush(); + } + + /** + * Gets a string representation of attached stream resource. + * + * @return string String representation of output stream + */ + public function getResource() { + return $this->outStream->__toString(); + } +} + diff --git a/library/phing/system/io/PhingFile.php b/library/phing/system/io/PhingFile.php new file mode 100644 index 000000000..847def1ae --- /dev/null +++ b/library/phing/system/io/PhingFile.php @@ -0,0 +1,951 @@ +. + */ + +include_once 'phing/system/io/FileSystem.php'; +include_once 'phing/system/lang/NullPointerException.php'; + +/** + * An abstract representation of file and directory pathnames. + * + * @version $Revision: 905 $ + * @package phing.system.io + */ +class PhingFile { + + /** separator string, static, obtained from FileSystem */ + public static $separator; + + /** path separator string, static, obtained from FileSystem (; or :)*/ + public static $pathSeparator; + + /** + * This abstract pathname's normalized pathname string. A normalized + * pathname string uses the default name-separator character and does not + * contain any duplicate or redundant separators. + */ + private $path = null; + + /** The length of this abstract pathname's prefix, or zero if it has no prefix. */ + private $prefixLength = 0; + + /** constructor */ + function __construct($arg1 = null, $arg2 = null) { + + if (self::$separator === null || self::$pathSeparator === null) { + $fs = FileSystem::getFileSystem(); + self::$separator = $fs->getSeparator(); + self::$pathSeparator = $fs->getPathSeparator(); + } + + /* simulate signature identified constructors */ + if ($arg1 instanceof PhingFile && is_string($arg2)) { + $this->_constructFileParentStringChild($arg1, $arg2); + } elseif (is_string($arg1) && ($arg2 === null)) { + $this->_constructPathname($arg1); + } elseif(is_string($arg1) && is_string($arg2)) { + $this->_constructStringParentStringChild($arg1, $arg2); + } else { + if ($arg1 === null) { + throw new NullPointerException("Argument1 to function must not be null"); + } + $this->path = (string) $arg1; + $this->prefixLength = (int) $arg2; + } + } + + /** Returns the length of this abstract pathname's prefix. */ + function getPrefixLength() { + return (int) $this->prefixLength; + } + + /* -- constructors not called by signature match, so we need some helpers --*/ + + function _constructPathname($pathname) { + // obtain ref to the filesystem layer + $fs = FileSystem::getFileSystem(); + + if ($pathname === null) { + throw new NullPointerException("Argument to function must not be null"); + } + + $this->path = (string) $fs->normalize($pathname); + $this->prefixLength = (int) $fs->prefixLength($this->path); + } + + function _constructStringParentStringChild($parent, $child = null) { + // obtain ref to the filesystem layer + $fs = FileSystem::getFileSystem(); + + if ($child === null) { + throw new NullPointerException("Argument to function must not be null"); + } + if ($parent !== null) { + if ($parent === "") { + $this->path = $fs->resolve($fs->getDefaultParent(), $fs->normalize($child)); + } else { + $this->path = $fs->resolve($fs->normalize($parent), $fs->normalize($child)); + } + } else { + $this->path = (string) $fs->normalize($child); + } + $this->prefixLength = (int) $fs->prefixLength($this->path); + } + + function _constructFileParentStringChild($parent, $child = null) { + // obtain ref to the filesystem layer + $fs = FileSystem::getFileSystem(); + + if ($child === null) { + throw new NullPointerException("Argument to function must not be null"); + } + + if ($parent !== null) { + if ($parent->path === "") { + $this->path = $fs->resolve($fs->getDefaultParent(), $fs->normalize($child)); + } else { + $this->path = $fs->resolve($parent->path, $fs->normalize($child)); + } + } else { + $this->path = $fs->normalize($child); + } + $this->prefixLength = $fs->prefixLength($this->path); + } + + /* -- Path-component accessors -- */ + + /** + * Returns the name of the file or directory denoted by this abstract + * pathname. This is just the last name in the pathname's name + * sequence. If the pathname's name sequence is empty, then the empty + * string is returned. + * + * @return The name of the file or directory denoted by this abstract + * pathname, or the empty string if this pathname's name sequence + * is empty + */ + function getName() { + // that's a lastIndexOf + $index = ((($res = strrpos($this->path, self::$separator)) === false) ? -1 : $res); + if ($index < $this->prefixLength) { + return substr($this->path, $this->prefixLength); + } + return substr($this->path, $index + 1); + } + + /** + * Returns the pathname string of this abstract pathname's parent, or + * null if this pathname does not name a parent directory. + * + * The parent of an abstract pathname consists of the pathname's prefix, + * if any, and each name in the pathname's name sequence except for the last. + * If the name sequence is empty then the pathname does not name a parent + * directory. + * + * @return The pathname string of the parent directory named by this + * abstract pathname, or null if this pathname does not name a parent + */ + function getParent() { + // that's a lastIndexOf + $index = ((($res = strrpos($this->path, self::$separator)) === false) ? -1 : $res); + if ($index < $this->prefixLength) { + if (($this->prefixLength > 0) && (strlen($this->path) > $this->prefixLength)) { + return substr($this->path, 0, $this->prefixLength); + } + return null; + } + return substr($this->path, 0, $index); + } + + /** + * Returns the abstract pathname of this abstract pathname's parent, + * or null if this pathname does not name a parent directory. + * + * The parent of an abstract pathname consists of the pathname's prefix, + * if any, and each name in the pathname's name sequence except for the + * last. If the name sequence is empty then the pathname does not name + * a parent directory. + * + * @return The abstract pathname of the parent directory named by this + * abstract pathname, or null if this pathname + * does not name a parent + */ + function getParentFile() { + $p = $this->getParent(); + if ($p === null) { + return null; + } + return new PhingFile((string) $p, (int) $this->prefixLength); + } + + /** + * Converts this abstract pathname into a pathname string. The resulting + * string uses the default name-separator character to separate the names + * in the name sequence. + * + * @return The string form of this abstract pathname + */ + function getPath() { + return (string) $this->path; + } + + /** + * Returns path without leading basedir. + * + * @param string $basedir Base directory to strip + * + * @return string Path without basedir + * + * @uses getPath() + */ + function getPathWithoutBase($basedir) + { + if (!StringHelper::endsWith(self::$separator, $basedir)) { + $basedir .= self::$separator; + } + $path = $this->getPath(); + if (!substr($path, 0, strlen($basedir)) == $basedir) { + //path does not begin with basedir, we don't modify it + return $path; + } + return substr($path, strlen($basedir)); + } + + /** + * Tests whether this abstract pathname is absolute. The definition of + * absolute pathname is system dependent. On UNIX systems, a pathname is + * absolute if its prefix is "/". On Win32 systems, a pathname is absolute + * if its prefix is a drive specifier followed by "\\", or if its prefix + * is "\\". + * + * @return true if this abstract pathname is absolute, false otherwise + */ + function isAbsolute() { + return ($this->prefixLength !== 0); + } + + + /** + * Returns the absolute pathname string of this abstract pathname. + * + * If this abstract pathname is already absolute, then the pathname + * string is simply returned as if by the getPath method. + * If this abstract pathname is the empty abstract pathname then + * the pathname string of the current user directory, which is named by the + * system property user.dir, is returned. Otherwise this + * pathname is resolved in a system-dependent way. On UNIX systems, a + * relative pathname is made absolute by resolving it against the current + * user directory. On Win32 systems, a relative pathname is made absolute + * by resolving it against the current directory of the drive named by the + * pathname, if any; if not, it is resolved against the current user + * directory. + * + * @return The absolute pathname string denoting the same file or + * directory as this abstract pathname + * @see #isAbsolute() + */ + function getAbsolutePath() { + $fs = FileSystem::getFileSystem(); + return $fs->resolveFile($this); + } + + /** + * Returns the absolute form of this abstract pathname. Equivalent to + * getAbsolutePath. + * + * @return The absolute abstract pathname denoting the same file or + * directory as this abstract pathname + */ + function getAbsoluteFile() { + return new PhingFile((string) $this->getAbsolutePath()); + } + + + /** + * Returns the canonical pathname string of this abstract pathname. + * + * A canonical pathname is both absolute and unique. The precise + * definition of canonical form is system-dependent. This method first + * converts this pathname to absolute form if necessary, as if by invoking the + * getAbsolutePath() method, and then maps it to its unique form in a + * system-dependent way. This typically involves removing redundant names + * such as "." and .. from the pathname, resolving symbolic links + * (on UNIX platforms), and converting drive letters to a standard case + * (on Win32 platforms). + * + * Every pathname that denotes an existing file or directory has a + * unique canonical form. Every pathname that denotes a nonexistent file + * or directory also has a unique canonical form. The canonical form of + * the pathname of a nonexistent file or directory may be different from + * the canonical form of the same pathname after the file or directory is + * created. Similarly, the canonical form of the pathname of an existing + * file or directory may be different from the canonical form of the same + * pathname after the file or directory is deleted. + * + * @return The canonical pathname string denoting the same file or + * directory as this abstract pathname + */ + function getCanonicalPath() { + $fs = FileSystem::getFileSystem(); + return $fs->canonicalize($this->path); + } + + + /** + * Returns the canonical form of this abstract pathname. Equivalent to + * getCanonicalPath(. + * + * @return PhingFile The canonical pathname string denoting the same file or + * directory as this abstract pathname + */ + function getCanonicalFile() { + return new PhingFile($this->getCanonicalPath()); + } + + /** + * Converts this abstract pathname into a file: URL. The + * exact form of the URL is system-dependent. If it can be determined that + * the file denoted by this abstract pathname is a directory, then the + * resulting URL will end with a slash. + * + * Usage note: This method does not automatically escape + * characters that are illegal in URLs. It is recommended that new code + * convert an abstract pathname into a URL by first converting it into a + * URI, via the toURI() method, and then converting the URI + * into a URL via the URI::toURL() + * + * @return A URL object representing the equivalent file URL + * + * + */ + function toURL() { + /* + // URL class not implemented yet + return new URL("file", "", $this->_slashify($this->getAbsolutePath(), $this->isDirectory())); + */ + } + + /** + * Constructs a file: URI that represents this abstract pathname. + * Not implemented yet + */ + function toURI() { + /* + $f = $this->getAbsoluteFile(); + $sp = (string) $this->slashify($f->getPath(), $f->isDirectory()); + if (StringHelper::startsWith('//', $sp)) + $sp = '//' + sp; + return new URI('file', null, $sp, null); + */ + } + + function _slashify($path, $isDirectory) { + $p = (string) $path; + + if (self::$separator !== '/') { + $p = str_replace(self::$separator, '/', $p); + } + + if (!StringHelper::startsWith('/', $p)) { + $p = '/'.$p; + } + + if (!StringHelper::endsWith('/', $p) && $isDirectory) { + $p = $p.'/'; + } + + return $p; + } + + /* -- Attribute accessors -- */ + + /** + * Tests whether the application can read the file denoted by this + * abstract pathname. + * + * @return true if and only if the file specified by this + * abstract pathname exists and can be read by the + * application; false otherwise + */ + function canRead() { + $fs = FileSystem::getFileSystem(); + + if ($fs->checkAccess($this)) { + return (boolean) @is_readable($this->getAbsolutePath()); + } + return false; + } + + /** + * Tests whether the application can modify to the file denoted by this + * abstract pathname. + * + * @return true if and only if the file system actually + * contains a file denoted by this abstract pathname and + * the application is allowed to write to the file; + * false otherwise. + * + */ + function canWrite() { + $fs = FileSystem::getFileSystem(); + return $fs->checkAccess($this, true); + } + + /** + * Tests whether the file denoted by this abstract pathname exists. + * + * @return true if and only if the file denoted by this + * abstract pathname exists; false otherwise + * + */ + function exists() { + clearstatcache(); + + if (is_link($this->path)) { + return true; + } else if ($this->isFile()) { + return @file_exists($this->path) || is_link($this->path); + } else { + return @is_dir($this->path); + } + } + + /** + * Tests whether the file denoted by this abstract pathname is a + * directory. + * + * @return true if and only if the file denoted by this + * abstract pathname exists and is a directory; + * false otherwise + * + */ + function isDirectory() { + clearstatcache(); + $fs = FileSystem::getFileSystem(); + if ($fs->checkAccess($this) !== true) { + throw new IOException("No read access to ".$this->path); + } + return @is_dir($this->path) && !@is_link($this->path); + } + + /** + * Tests whether the file denoted by this abstract pathname is a normal + * file. A file is normal if it is not a directory and, in + * addition, satisfies other system-dependent criteria. Any non-directory + * file created by a Java application is guaranteed to be a normal file. + * + * @return true if and only if the file denoted by this + * abstract pathname exists and is a normal file; + * false otherwise + */ + function isFile() { + clearstatcache(); + //$fs = FileSystem::getFileSystem(); + return @is_file($this->path); + } + + /** + * Tests whether the file named by this abstract pathname is a hidden + * file. The exact definition of hidden is system-dependent. On + * UNIX systems, a file is considered to be hidden if its name begins with + * a period character ('.'). On Win32 systems, a file is considered to be + * hidden if it has been marked as such in the filesystem. Currently there + * seems to be no way to dermine isHidden on Win file systems via PHP + * + * @return true if and only if the file denoted by this + * abstract pathname is hidden according to the conventions of the + * underlying platform + */ + function isHidden() { + $fs = FileSystem::getFileSystem(); + if ($fs->checkAccess($this) !== true) { + throw new IOException("No read access to ".$this->path); + } + return (($fs->getBooleanAttributes($this) & $fs->BA_HIDDEN) !== 0); + } + + /** + * Tests whether the file denoted by this abstract pathname is a symbolic link. + * + * @return true if and only if the file denoted by this + * abstract pathname exists and is a symbolic link; + * false otherwise + */ + public function isLink() + { + clearstatcache(); + $fs = FileSystem::getFileSystem(); + if ($fs->checkAccess($this) !== true) { + throw new IOException("No read access to ".$this->path); + } + return @is_link($this->path); + } + + /** + * Returns the target of the symbolic link denoted by this abstract pathname + * + * @return the target of the symbolic link denoted by this abstract pathname + */ + public function getLinkTarget() + { + return @readlink($this->path); + } + + /** + * Returns the time that the file denoted by this abstract pathname was + * last modified. + * + * @return A integer value representing the time the file was + * last modified, measured in milliseconds since the epoch + * (00:00:00 GMT, January 1, 1970), or 0 if the + * file does not exist or if an I/O error occurs + */ + function lastModified() { + $fs = FileSystem::getFileSystem(); + if ($fs->checkAccess($this) !== true) { + throw new IOException("No read access to " . $this->path); + } + return $fs->getLastModifiedTime($this); + } + + /** + * Returns the length of the file denoted by this abstract pathname. + * The return value is unspecified if this pathname denotes a directory. + * + * @return The length, in bytes, of the file denoted by this abstract + * pathname, or 0 if the file does not exist + */ + function length() { + $fs = FileSystem::getFileSystem(); + if ($fs->checkAccess($this) !== true) { + throw new IOException("No read access to ".$this->path."\n"); + } + return $fs->getLength($this); + } + + /** + * Convenience method for returning the contents of this file as a string. + * This method uses file_get_contents() to read file in an optimized way. + * @return string + * @throws Exception - if file cannot be read + */ + function contents() { + if (!$this->canRead() || !$this->isFile()) { + throw new IOException("Cannot read file contents!"); + } + return file_get_contents($this->getAbsolutePath()); + } + + /* -- File operations -- */ + + /** + * Atomically creates a new, empty file named by this abstract pathname if + * and only if a file with this name does not yet exist. The check for the + * existence of the file and the creation of the file if it does not exist + * are a single operation that is atomic with respect to all other + * filesystem activities that might affect the file. + * + * @return true if the named file does not exist and was + * successfully created; false if the named file + * already exists + * @throws IOException if file can't be created + */ + function createNewFile($parents=true, $mode=0777) { + $file = FileSystem::getFileSystem()->createNewFile($this->path); + return $file; + } + + /** + * Deletes the file or directory denoted by this abstract pathname. If + * this pathname denotes a directory, then the directory must be empty in + * order to be deleted. + * + * @return true if and only if the file or directory is + * successfully deleted; false otherwise + */ + function delete($recursive = false) { + $fs = FileSystem::getFileSystem(); + if ($fs->canDelete($this) !== true) { + throw new IOException("Cannot delete " . $this->path . "\n"); + } + return $fs->delete($this, $recursive); + } + + /** + * Requests that the file or directory denoted by this abstract pathname + * be deleted when php terminates. Deletion will be attempted only for + * normal termination of php and if and if only Phing::shutdown() is + * called. + * + * Once deletion has been requested, it is not possible to cancel the + * request. This method should therefore be used with care. + * + */ + function deleteOnExit() { + $fs = FileSystem::getFileSystem(); + $fs->deleteOnExit($this); + } + + /** + * Returns an array of strings naming the files and directories in the + * directory denoted by this abstract pathname. + * + * If this abstract pathname does not denote a directory, then this + * method returns null Otherwise an array of strings is + * returned, one for each file or directory in the directory. Names + * denoting the directory itself and the directory's parent directory are + * not included in the result. Each string is a file name rather than a + * complete path. + * + * There is no guarantee that the name strings in the resulting array + * will appear in any specific order; they are not, in particular, + * guaranteed to appear in alphabetical order. + * + * @return An array of strings naming the files and directories in the + * directory denoted by this abstract pathname. The array will be + * empty if the directory is empty. Returns null if + * this abstract pathname does not denote a directory, or if an + * I/O error occurs. + * + */ + function listDir($filter = null) { + $fs = FileSystem::getFileSystem(); + return $fs->lister($this, $filter); + } + + function listFiles($filter = null) { + $ss = $this->listDir($filter); + if ($ss === null) { + return null; + } + $n = count($ss); + $fs = array(); + for ($i = 0; $i < $n; $i++) { + $fs[$i] = new PhingFile((string)$this->path, (string)$ss[$i]); + } + return $fs; + } + + /** + * Creates the directory named by this abstract pathname, including any + * necessary but nonexistent parent directories. Note that if this + * operation fails it may have succeeded in creating some of the necessary + * parent directories. + * + * @return true if and only if the directory was created, + * along with all necessary parent directories; false + * otherwise + * @throws IOException + */ + function mkdirs($mode = 0755) { + if ($this->exists()) { + return false; + } + try { + if ($this->mkdir($mode)) { + return true; + } + } catch (IOException $ioe) { + // IOException from mkdir() means that directory propbably didn't exist. + } + $parentFile = $this->getParentFile(); + return (($parentFile !== null) && ($parentFile->mkdirs($mode) && $this->mkdir($mode))); + } + + /** + * Creates the directory named by this abstract pathname. + * + * @return true if and only if the directory was created; false otherwise + * @throws IOException + */ + function mkdir($mode = 0755) { + $fs = FileSystem::getFileSystem(); + + if ($fs->checkAccess(new PhingFile($this->path), true) !== true) { + throw new IOException("No write access to " . $this->getPath()); + } + return $fs->createDirectory($this, $mode); + } + + /** + * Renames the file denoted by this abstract pathname. + * + * @param destFile The new abstract pathname for the named file + * @return true if and only if the renaming succeeded; false otherwise + */ + function renameTo(PhingFile $destFile) { + $fs = FileSystem::getFileSystem(); + if ($fs->checkAccess($this) !== true) { + throw new IOException("No write access to ".$this->getPath()); + } + return $fs->rename($this, $destFile); + } + + /** + * Simple-copies file denoted by this abstract pathname into another + * PhingFile + * + * @param PhingFile $destFile The new abstract pathname for the named file + * @return true if and only if the renaming succeeded; false otherwise + */ + function copyTo(PhingFile $destFile) { + $fs = FileSystem::getFileSystem(); + + if ($fs->checkAccess($this) !== true) { + throw new IOException("No read access to ".$this->getPath()."\n"); + } + + if ($fs->checkAccess($destFile, true) !== true) { + throw new IOException("File::copyTo() No write access to ".$destFile->getPath()); + } + return $fs->copy($this, $destFile); + } + + /** + * Sets the last-modified time of the file or directory named by this + * abstract pathname. + * + * All platforms support file-modification times to the nearest second, + * but some provide more precision. The argument will be truncated to fit + * the supported precision. If the operation succeeds and no intervening + * operations on the file take place, then the next invocation of the + * lastModified method will return the (possibly truncated) time argument + * that was passed to this method. + * + * @param time The new last-modified time, measured in milliseconds since + * the epoch (00:00:00 GMT, January 1, 1970) + * @return true if and only if the operation succeeded; false otherwise + */ + function setLastModified($time) { + $time = (int) $time; + if ($time < 0) { + throw new Exception("IllegalArgumentException, Negative $time\n"); + } + + $fs = FileSystem::getFileSystem(); + return $fs->setLastModifiedTime($this, $time); + } + + /** + * Marks the file or directory named by this abstract pathname so that + * only read operations are allowed. After invoking this method the file + * or directory is guaranteed not to change until it is either deleted or + * marked to allow write access. Whether or not a read-only file or + * directory may be deleted depends upon the underlying system. + * + * @return true if and only if the operation succeeded; false otherwise + */ + function setReadOnly() { + $fs = FileSystem::getFileSystem(); + if ($fs->checkAccess($this, true) !== true) { + // Error, no write access + throw new IOException("No write access to " . $this->getPath()); + } + return $fs->setReadOnly($this); + } + + /** + * Sets the owner of the file. + * @param mixed $user User name or number. + */ + public function setUser($user) { + $fs = FileSystem::getFileSystem(); + return $fs->chown($this->getPath(), $user); + } + + /** + * Retrieve the owner of this file. + * @return int User ID of the owner of this file. + */ + function getUser() { + return @fileowner($this->getPath()); + } + + /** + * Sets the group of the file. + * @param mixed $user User name or number. + */ + public function setGroup($group) { + $fs = FileSystem::getFileSystem(); + return $fs->chgrp($this->getPath(), $group); + } + + /** + * Retrieve the group of this file. + * @return int User ID of the owner of this file. + */ + function getGroup() { + return @filegroup($this->getPath()); + } + + /** + * Sets the mode of the file + * @param int $mode Ocatal mode. + */ + function setMode($mode) { + $fs = FileSystem::getFileSystem(); + return $fs->chmod($this->getPath(), $mode); + } + + /** + * Retrieve the mode of this file. + * @return int + */ + function getMode() { + return @fileperms($this->getPath()); + } + + /* -- Filesystem interface -- */ + + /** + * List the available filesystem roots. + * + * A particular platform may support zero or more hierarchically-organized + * file systems. Each file system has a root directory from which all + * other files in that file system can be reached. + * Windows platforms, for example, have a root directory for each active + * drive; UNIX platforms have a single root directory, namely "/". + * The set of available filesystem roots is affected by various system-level + * operations such the insertion or ejection of removable media and the + * disconnecting or unmounting of physical or virtual disk drives. + * + * This method returns an array of PhingFile objects that + * denote the root directories of the available filesystem roots. It is + * guaranteed that the canonical pathname of any file physically present on + * the local machine will begin with one of the roots returned by this + * method. + * + * The canonical pathname of a file that resides on some other machine + * and is accessed via a remote-filesystem protocol such as SMB or NFS may + * or may not begin with one of the roots returned by this method. If the + * pathname of a remote file is syntactically indistinguishable from the + * pathname of a local file then it will begin with one of the roots + * returned by this method. Thus, for example, PhingFile objects + * denoting the root directories of the mapped network drives of a Windows + * platform will be returned by this method, while PhingFile + * objects containing UNC pathnames will not be returned by this method. + * + * @return An array of PhingFile objects denoting the available + * filesystem roots, or null if the set of roots + * could not be determined. The array will be empty if there are + * no filesystem roots. + */ + function listRoots() { + $fs = FileSystem::getFileSystem(); + return (array) $fs->listRoots(); + } + + /* -- Tempfile management -- */ + + /** + * Returns the path to the temp directory. + */ + function getTempDir() { + return Phing::getProperty('php.tmpdir'); + } + + /** + * Static method that creates a unique filename whose name begins with + * $prefix and ends with $suffix in the directory $directory. $directory + * is a reference to a PhingFile Object. + * Then, the file is locked for exclusive reading/writing. + * + * @author manuel holtgrewe, grin@gmx.net + * @throws IOException + * @access public + */ + function createTempFile($prefix, $suffix, PhingFile $directory) { + + // quick but efficient hack to create a unique filename ;-) + $result = null; + do { + $result = new PhingFile($directory, $prefix . substr(md5(time()), 0, 8) . $suffix); + } while (file_exists($result->getPath())); + + $fs = FileSystem::getFileSystem(); + $fs->createNewFile($result->getPath()); + $fs->lock($result); + + return $result; + } + + /** + * If necessary, $File the lock on $File is removed and then the file is + * deleted + * + * @access public + */ + function removeTempFile() { + $fs = FileSystem::getFileSystem(); + // catch IO Exception + $fs->unlock($this); + $this->delete(); + } + + + /* -- Basic infrastructure -- */ + + /** + * Compares two abstract pathnames lexicographically. The ordering + * defined by this method depends upon the underlying system. On UNIX + * systems, alphabetic case is significant in comparing pathnames; on Win32 + * systems it is not. + * + * @param PhingFile $file Th file whose pathname sould be compared to the pathname of this file. + * + * @return int Zero if the argument is equal to this abstract pathname, a + * value less than zero if this abstract pathname is + * lexicographically less than the argument, or a value greater + * than zero if this abstract pathname is lexicographically + * greater than the argument + */ + function compareTo(PhingFile $file) { + $fs = FileSystem::getFileSystem(); + return $fs->compare($this, $file); + } + + /** + * Tests this abstract pathname for equality with the given object. + * Returns true if and only if the argument is not + * null and is an abstract pathname that denotes the same file + * or directory as this abstract pathname. Whether or not two abstract + * pathnames are equal depends upon the underlying system. On UNIX + * systems, alphabetic case is significant in comparing pathnames; on Win32 + * systems it is not. + * @return boolean + */ + function equals($obj) { + if (($obj !== null) && ($obj instanceof PhingFile)) { + return ($this->compareTo($obj) === 0); + } + return false; + } + + /** Backwards compatibility -- use PHP5's native __tostring method. */ + function toString() { + return $this->getPath(); + } + + /** PHP5's native method. */ + function __toString() { + return $this->getPath(); + } +} + diff --git a/library/phing/system/io/Reader.php b/library/phing/system/io/Reader.php new file mode 100644 index 000000000..c8d3214b1 --- /dev/null +++ b/library/phing/system/io/Reader.php @@ -0,0 +1,91 @@ +. +*/ + +/** + * Abstract class for reading character streams. + * + * @author Hans Lellelid + * @author Yannick Lecaillez + * @version $Revision: 905 $ + * @package phing.system.io + */ +abstract class Reader { + + /** + * Read data from source. + * + * If length is specified, then only that number of chars is read, + * otherwise stream is read until EOF. + * + * @param int $len + */ + abstract public function read($len = null); + + /** + * Close stream. + * @throws IOException if there is an error closing stream + */ + abstract public function close(); + + /** + * Returns the filename, url, etc. that is being read from. + * This is critical for, e.g., ExpatParser's ability to know + * the filename that is throwing an ExpatParserException, etc. + * @return string + */ + abstract function getResource(); + + /** + * Move stream position relative to current pos. + * @param int $n + */ + public function skip($n) {} + + /** + * Reset the current position in stream to beginning or last mark (if supported). + */ + public function reset() {} + + /** + * If supported, places a "marker" (like a bookmark) at current stream position. + * A subsequent call to reset() will move stream position back + * to last marker (if supported). + */ + public function mark() {} + + /** + * Whether marking is supported. + * @return boolean + */ + public function markSupported() { + return false; + } + + /** + * Is stream ready for reading. + * @return boolean + */ + public function ready() { + return true; + } + +} + diff --git a/library/phing/system/io/StringReader.php b/library/phing/system/io/StringReader.php new file mode 100644 index 000000000..1538496db --- /dev/null +++ b/library/phing/system/io/StringReader.php @@ -0,0 +1,84 @@ +. + */ + +/** + * Dummy class for reading from string of characters. + * @package phing.system.io + */ +class StringReader extends Reader { + + /** + * @var string + */ + private $_string; + + /** + * @var int + */ + private $mark = 0; + + /** + * @var int + */ + private $currPos = 0; + + function __construct($string) { + $this->_string = $string; + } + + function skip($n) {} + + function read($len = null) { + if ($len === null) { + return $this->_string; + } else { + if ($this->currPos >= strlen($this->_string)) { + return -1; + } + $out = substr($this->_string, $this->currPos, $len); + $this->currPos += $len; + return $out; + } + } + + function mark() { + $this->mark = $this->currPos; + } + + function reset() { + $this->currPos = $this->mark; + } + + function close() {} + + function open() {} + + function ready() {} + + function markSupported() { + return true; + } + + function getResource() { + return '(string) "'.$this->_string . '"'; + } +} + diff --git a/library/phing/system/io/UnixFileSystem.php b/library/phing/system/io/UnixFileSystem.php new file mode 100644 index 000000000..716d43844 --- /dev/null +++ b/library/phing/system/io/UnixFileSystem.php @@ -0,0 +1,306 @@ +. + */ + +include_once 'phing/system/io/FileSystem.php'; + +/** + * UnixFileSystem class. This class encapsulates the basic file system functions + * for platforms using the unix (posix)-stylish filesystem. It wraps php native + * functions suppressing normal PHP error reporting and instead uses Exception + * to report and error. + * + * This class is part of a oop based filesystem abstraction and targeted to run + * on all supported php platforms. + * + * Note: For debugging turn track_errors on in the php.ini. The error messages + * and log messages from this class will then be clearer because $php_errormsg + * is passed as part of the message. + * + * FIXME: + * - Comments + * - Error handling reduced to min, error are handled by PhingFile mainly + * + * @author Andreas Aderhold, andi@binarycloud.com + * @version $Revision: 905 $ + * @package phing.system.io + */ +class UnixFileSystem extends FileSystem { + + /** + * returns OS dependant path separator char + */ + function getSeparator() { + return '/'; + } + + /** + * returns OS dependant directory separator char + */ + function getPathSeparator() { + return ':'; + } + + /** + * A normal Unix pathname contains no duplicate slashes and does not end + * with a slash. It may be the empty string. + * + * Check that the given pathname is normal. If not, invoke the real + * normalizer on the part of the pathname that requires normalization. + * This way we iterate through the whole pathname string only once. + */ + function normalize($strPathname) { + + if (!strlen($strPathname)) { + return; + } + + // Resolve home directories. We assume /home is where all home + // directories reside, b/c there is no other way to do this with + // PHP AFAIK. + if ($strPathname{0} === "~") { + if ($strPathname{1} === "/") { // like ~/foo => /home/user/foo + $strPathname = "/home/" . get_current_user() . substr($strPathname, 1); + } else { // like ~foo => /home/foo + $pos = strpos($strPathname, "/"); + $name = substr($strPathname, 1, $pos - 2); + $strPathname = "/home/" . $name . substr($strPathname, $pos); + } + } + + $n = strlen($strPathname); + $prevChar = 0; + for ($i=0; $i < $n; $i++) { + $c = $strPathname{$i}; + if (($prevChar === '/') && ($c === '/')) { + return self::normalizer($strPathname, $n, $i - 1); + } + $prevChar = $c; + } + if ($prevChar === '/') { + return self::normalizer($strPathname, $n, $n - 1); + } + return $strPathname; + } + + /** + * Normalize the given pathname, whose length is $len, starting at the given + * $offset; everything before this offset is already normal. + */ + protected function normalizer($pathname, $len, $offset) { + if ($len === 0) { + return $pathname; + } + $n = (int) $len; + while (($n > 0) && ($pathname{$n-1} === '/')) { + $n--; + } + if ($n === 0) { + return '/'; + } + $sb = ""; + + if ($offset > 0) { + $sb .= substr($pathname, 0, $offset); + } + $prevChar = 0; + for ($i = $offset; $i < $n; $i++) { + $c = $pathname{$i}; + if (($prevChar === '/') && ($c === '/')) { + continue; + } + $sb .= $c; + $prevChar = $c; + } + return $sb; + } + + /** + * Compute the length of the pathname string's prefix. The pathname + * string must be in normal form. + */ + function prefixLength($pathname) { + if (strlen($pathname === 0)) { + return 0; + } + return (($pathname{0} === '/') ? 1 : 0); + } + + /** + * Resolve the child pathname string against the parent. + * Both strings must be in normal form, and the result + * will be in normal form. + */ + function resolve($parent, $child) { + + if ($child === "") { + return $parent; + } + + if ($child{0} === '/') { + if ($parent === '/') { + return $child; + } + return $parent.$child; + } + + if ($parent === '/') { + return $parent.$child; + } + + return $parent.'/'.$child; + } + + function getDefaultParent() { + return '/'; + } + + function isAbsolute(PhingFile $f) { + return ($f->getPrefixLength() !== 0); + } + + /** + * the file resolver + */ + function resolveFile(PhingFile $f) { + // resolve if parent is a file oject only + if ($this->isAbsolute($f)) { + return $f->getPath(); + } else { + return $this->resolve(Phing::getProperty("user.dir"), $f->getPath()); + } + } + + /* -- most of the following is mapped to the php natives wrapped by FileSystem */ + + /* -- Attribute accessors -- */ + function getBooleanAttributes(&$f) { + //$rv = getBooleanAttributes0($f); + $name = $f->getName(); + $hidden = (strlen($name) > 0) && ($name{0} == '.'); + return ($hidden ? $this->BA_HIDDEN : 0); + } + + /** + * set file readonly on unix + */ + function setReadOnly($f) { + if ($f instanceof File) { + $strPath = (string) $f->getPath(); + $perms = (int) (@fileperms($strPath) & 0444); + return FileSystem::Chmod($strPath, $perms); + } else { + throw new Exception("IllegalArgutmentType: Argument is not File"); + } + } + + /** + * compares file paths lexicographically + */ + function compare($f1, $f2) { + if ( ($f1 instanceof PhingFile) && ($f2 instanceof PhingFile) ) { + $f1Path = $f1->getPath(); + $f2Path = $f2->getPath(); + return (boolean) strcmp((string) $f1Path, (string) $f2Path); + } else { + throw new Exception("IllegalArgutmentType: Argument is not PhingFile"); + } + } + + /** + * Copy a file, takes care of symbolic links + * + * @param PhingFile $src Source path and name file to copy. + * @param PhingFile $dest Destination path and name of new file. + * + * @return void + * @throws Exception if file cannot be copied. + */ + function copy(PhingFile $src, PhingFile $dest) { + global $php_errormsg; + + if (!$src->isLink()) + { + return parent::copy($src, $dest); + } + + $srcPath = $src->getAbsolutePath(); + $destPath = $dest->getAbsolutePath(); + + $linkTarget = $src->getLinkTarget(); + if (false === @symlink($linkTarget, $destPath)) + { + $msg = "FileSystem::copy() FAILED. Cannot create symlink from $destPath to $linkTarget."; + throw new Exception($msg); + } + } + + /* -- fs interface --*/ + + function listRoots() { + if (!$this->checkAccess('/', false)) { + die ("Can not access root"); + } + return array(new PhingFile("/")); + } + + /** + * returns the contents of a directory in an array + */ + function lister($f) { + $dir = @opendir($f->getAbsolutePath()); + if (!$dir) { + throw new Exception("Can't open directory " . $f->__toString()); + } + $vv = array(); + while (($file = @readdir($dir)) !== false) { + if ($file == "." || $file == "..") { + continue; + } + $vv[] = (string) $file; + } + @closedir($dir); + return $vv; + } + + function fromURIPath($p) { + if (StringHelper::endsWith("/", $p) && (strlen($p) > 1)) { + + // "/foo/" --> "/foo", but "/" --> "/" + $p = substr($p, 0, strlen($p) - 1); + + } + + return $p; + } + + /** + * Whether file can be deleted. + * @param PhingFile $f + * @return boolean + */ + function canDelete(PhingFile $f) + { + @clearstatcache(); + $dir = dirname($f->getAbsolutePath()); + return (bool) @is_writable($dir); + } + +} diff --git a/library/phing/system/io/Win32FileSystem.php b/library/phing/system/io/Win32FileSystem.php new file mode 100644 index 000000000..910e19d87 --- /dev/null +++ b/library/phing/system/io/Win32FileSystem.php @@ -0,0 +1,477 @@ +. + */ + +include_once 'phing/system/io/FileSystem.php'; + +/** + * @package phing.system.io + */ +class Win32FileSystem extends FileSystem { + + protected $slash; + protected $altSlash; + protected $semicolon; + + private static $driveDirCache = array(); + + function __construct() { + $this->slash = self::getSeparator(); + $this->semicolon = self::getPathSeparator(); + $this->altSlash = ($this->slash === '\\') ? '/' : '\\'; + } + + function isSlash($c) { + return ($c == '\\') || ($c == '/'); + } + + function isLetter($c) { + return ((ord($c) >= ord('a')) && (ord($c) <= ord('z'))) + || ((ord($c) >= ord('A')) && (ord($c) <= ord('Z'))); + } + + function slashify($p) { + if ((strlen($p) > 0) && ($p{0} != $this->slash)) { + return $this->slash.$p; + } + else { + return $p; + } + } + + /* -- Normalization and construction -- */ + + function getSeparator() { + // the ascii value of is the \ + return chr(92); + } + + function getPathSeparator() { + return ';'; + } + + /** + * A normal Win32 pathname contains no duplicate slashes, except possibly + * for a UNC prefix, and does not end with a slash. It may be the empty + * string. Normalized Win32 pathnames have the convenient property that + * the length of the prefix almost uniquely identifies the type of the path + * and whether it is absolute or relative: + * + * 0 relative to both drive and directory + * 1 drive-relative (begins with '\\') + * 2 absolute UNC (if first char is '\\'), else directory-relative (has form "z:foo") + * 3 absolute local pathname (begins with "z:\\") + */ + function normalizePrefix($strPath, $len, &$sb) { + $src = 0; + while (($src < $len) && $this->isSlash($strPath{$src})) { + $src++; + } + $c = ""; + if (($len - $src >= 2) + && $this->isLetter($c = $strPath{$src}) + && $strPath{$src + 1} === ':') { + /* Remove leading slashes if followed by drive specifier. + * This hack is necessary to support file URLs containing drive + * specifiers (e.g., "file://c:/path"). As a side effect, + * "/c:/path" can be used as an alternative to "c:/path". */ + $sb .= $c; + $sb .= ':'; + $src += 2; + } + else { + $src = 0; + if (($len >= 2) + && $this->isSlash($strPath{0}) + && $this->isSlash($strPath{1})) { + /* UNC pathname: Retain first slash; leave src pointed at + * second slash so that further slashes will be collapsed + * into the second slash. The result will be a pathname + * beginning with "\\\\" followed (most likely) by a host + * name. */ + $src = 1; + $sb.=$this->slash; + } + } + return $src; + } + + /** Normalize the given pathname, whose length is len, starting at the given + offset; everything before this offset is already normal. */ + protected function normalizer($strPath, $len, $offset) { + if ($len == 0) { + return $strPath; + } + if ($offset < 3) { + $offset = 0; //Avoid fencepost cases with UNC pathnames + } + $src = 0; + $slash = $this->slash; + $sb = ""; + + if ($offset == 0) { + // Complete normalization, including prefix + $src = $this->normalizePrefix($strPath, $len, $sb); + } else { + // Partial normalization + $src = $offset; + $sb .= substr($strPath, 0, $offset); + } + + // Remove redundant slashes from the remainder of the path, forcing all + // slashes into the preferred slash + while ($src < $len) { + $c = $strPath{$src++}; + if ($this->isSlash($c)) { + while (($src < $len) && $this->isSlash($strPath{$src})) { + $src++; + } + if ($src === $len) { + /* Check for trailing separator */ + $sn = (int) strlen($sb); + if (($sn == 2) && ($sb{1} === ':')) { + // "z:\\" + $sb .= $slash; + break; + } + if ($sn === 0) { + // "\\" + $sb .= $slash; + break; + } + if (($sn === 1) && ($this->isSlash($sb{0}))) { + /* "\\\\" is not collapsed to "\\" because "\\\\" marks + the beginning of a UNC pathname. Even though it is + not, by itself, a valid UNC pathname, we leave it as + is in order to be consistent with the win32 APIs, + which treat this case as an invalid UNC pathname + rather than as an alias for the root directory of + the current drive. */ + $sb .= $slash; + break; + } + // Path does not denote a root directory, so do not append + // trailing slash + break; + } else { + $sb .= $slash; + } + } else { + $sb.=$c; + } + } + $rv = (string) $sb; + return $rv; + } + + /** + * Check that the given pathname is normal. If not, invoke the real + * normalizer on the part of the pathname that requires normalization. + * This way we iterate through the whole pathname string only once. + * @param string $strPath + * @return string + */ + function normalize($strPath) { + $n = strlen($strPath); + $slash = $this->slash; + $altSlash = $this->altSlash; + $prev = 0; + for ($i = 0; $i < $n; $i++) { + $c = $strPath{$i}; + if ($c === $altSlash) { + return $this->normalizer($strPath, $n, ($prev === $slash) ? $i - 1 : $i); + } + if (($c === $slash) && ($prev === $slash) && ($i > 1)) { + return $this->normalizer($strPath, $n, $i - 1); + } + if (($c === ':') && ($i > 1)) { + return $this->normalizer($strPath, $n, 0); + } + $prev = $c; + } + if ($prev === $slash) { + return $this->normalizer($strPath, $n, $n - 1); + } + return $strPath; + } + + function prefixLength($strPath) { + $path = (string) $strPath; + $slash = (string) $this->slash; + $n = (int) strlen($path); + if ($n === 0) { + return 0; + } + $c0 = $path{0}; + $c1 = ($n > 1) ? $path{1} : + 0; + if ($c0 === $slash) { + if ($c1 === $slash) { + return 2; // absolute UNC pathname "\\\\foo" + } + return 1; // drive-relative "\\foo" + } + + if ($this->isLetter($c0) && ($c1 === ':')) { + if (($n > 2) && ($path{2}) === $slash) { + return 3; // Absolute local pathname "z:\\foo" */ + } + return 2; // Directory-relative "z:foo" + } + return 0; // Completely relative + } + + function resolve($parent, $child) { + $parent = (string) $parent; + $child = (string) $child; + $slash = (string) $this->slash; + + $pn = (int) strlen($parent); + if ($pn === 0) { + return $child; + } + $cn = (int) strlen($child); + if ($cn === 0) { + return $parent; + } + + $c = $child; + if (($cn > 1) && ($c{0} === $slash)) { + if ($c{1} === $slash) { + // drop prefix when child is a UNC pathname + $c = substr($c, 2); + } + else { + //Drop prefix when child is drive-relative */ + $c = substr($c, 1); + } + } + + $p = $parent; + if ($p{$pn - 1} === $slash) { + $p = substr($p, 0, $pn - 1); + } + return $p.$this->slashify($c); + } + + function getDefaultParent() { + return (string) ("".$this->slash); + } + + function fromURIPath($strPath) { + $p = (string) $strPath; + if ((strlen($p) > 2) && ($p{2} === ':')) { + + // "/c:/foo" --> "c:/foo" + $p = substr($p,1); + + // "c:/foo/" --> "c:/foo", but "c:/" --> "c:/" + if ((strlen($p) > 3) && StringHelper::endsWith('/', $p)) { + $p = substr($p, 0, strlen($p) - 1); + } + } elseif ((strlen($p) > 1) && StringHelper::endsWith('/', $p)) { + // "/foo/" --> "/foo" + $p = substr($p, 0, strlen($p) - 1); + } + return (string) $p; + } + + + /* -- Path operations -- */ + + function isAbsolute(PhingFile $f) { + $pl = (int) $f->getPrefixLength(); + $p = (string) $f->getPath(); + return ((($pl === 2) && ($p{0} === $this->slash)) || ($pl === 3) || ($pl === 1 && $p{0} === $this->slash)); + } + + /** private */ + function _driveIndex($d) { + $d = (string) $d{0}; + if ((ord($d) >= ord('a')) && (ord($d) <= ord('z'))) { + return ord($d) - ord('a'); + } + if ((ord($d) >= ord('A')) && (ord($d) <= ord('Z'))) { + return ord($d) - ord('A'); + } + return -1; + } + + /** private */ + function _getDriveDirectory($drive) { + $drive = (string) $drive{0}; + $i = (int) $this->_driveIndex($drive); + if ($i < 0) { + return null; + } + + $s = (isset(self::$driveDirCache[$i]) ? self::$driveDirCache[$i] : null); + + if ($s !== null) { + return $s; + } + + $s = $this->_getDriveDirectory($i + 1); + self::$driveDirCache[$i] = $s; + return $s; + } + + function _getUserPath() { + //For both compatibility and security, we must look this up every time + return (string) $this->normalize(Phing::getProperty("user.dir")); + } + + function _getDrive($path) { + $path = (string) $path; + $pl = $this->prefixLength($path); + return ($pl === 3) ? substr($path, 0, 2) : null; + } + + function resolveFile(PhingFile $f) { + $path = $f->getPath(); + $pl = (int) $f->getPrefixLength(); + + if (($pl === 2) && ($path{0} === $this->slash)) { + return $path; // UNC + } + + if ($pl === 3) { + return $path; // Absolute local + } + + if ($pl === 0) { + return (string) ($this->_getUserPath().$this->slashify($path)); //Completely relative + } + + if ($pl === 1) { // Drive-relative + $up = (string) $this->_getUserPath(); + $ud = (string) $this->_getDrive($up); + if ($ud !== null) { + return (string) $ud.$path; + } + return (string) $up.$path; //User dir is a UNC path + } + + if ($pl === 2) { // Directory-relative + $up = (string) $this->_getUserPath(); + $ud = (string) $this->_getDrive($up); + if (($ud !== null) && StringHelper::startsWith($ud, $path)) { + return (string) ($up . $this->slashify(substr($path,2))); + } + $drive = (string) $path{0}; + $dir = (string) $this->_getDriveDirectory($drive); + + $np = (string) ""; + if ($dir !== null) { + /* When resolving a directory-relative path that refers to a + drive other than the current drive, insist that the caller + have read permission on the result */ + $p = (string) $drive . (':'.$dir.$this->slashify(substr($path,2))); + + if (!$this->checkAccess($p, false)) { + // FIXME + // throw security error + die("Can't resolve path $p"); + } + return $p; + } + return (string) $drive.':'.$this->slashify(substr($path,2)); //fake it + } + + throw new Exception("Unresolvable path: " . $path); + } + + /* -- most of the following is mapped to the functions mapped th php natives in FileSystem */ + + /* -- Attribute accessors -- */ + + function setReadOnly($f) { + // dunno how to do this on win + throw new Exception("WIN32FileSystem doesn't support read-only yet."); + } + + /* -- Filesystem interface -- */ + + protected function _access($path) { + if (!$this->checkAccess($path, false)) { + throw new Exception("Can't resolve path $p"); + } + return true; + } + + function _nativeListRoots() { + // FIXME + } + + function listRoots() { + $ds = _nativeListRoots(); + $n = 0; + for ($i = 0; $i < 26; $i++) { + if ((($ds >> $i) & 1) !== 0) { + if (!$this->access((string)( chr(ord('A') + $i) . ':' . $this->slash))) { + $ds &= ~(1 << $i); + } else { + $n++; + } + } + } + $fs = array(); + $j = (int) 0; + $slash = (string) $this->slash; + for ($i = 0; $i < 26; $i++) { + if ((($ds >> $i) & 1) !== 0) { + $fs[$j++] = new PhingFile(chr(ord('A') + $i) . ':' . $this->slash); + } + } + return $fs; + } + + /* -- Basic infrastructure -- */ + + /** compares file paths lexicographically */ + function compare(PhingFile $f1, PhingFile $f2) { + $f1Path = $f1->getPath(); + $f2Path = $f2->getPath(); + return (boolean) strcasecmp((string) $f1Path, (string) $f2Path); + } + + + /** + * returns the contents of a directory in an array + */ + function lister($f) { + $dir = @opendir($f->getAbsolutePath()); + if (!$dir) { + throw new Exception("Can't open directory " . $f->__toString()); + } + $vv = array(); + while (($file = @readdir($dir)) !== false) { + if ($file == "." || $file == "..") { + continue; + } + $vv[] = (string) $file; + } + @closedir($dir); + return $vv; + } + +} + + diff --git a/library/phing/system/io/WinNTFileSystem.php b/library/phing/system/io/WinNTFileSystem.php new file mode 100644 index 000000000..d2cb63453 --- /dev/null +++ b/library/phing/system/io/WinNTFileSystem.php @@ -0,0 +1,35 @@ +. + */ + +include_once 'phing/system/io/Win32FileSystem.php'; + +/** + * FileSystem for Windows NT/2000. + * @package phing.system.io + */ + +class WinNTFileSystem extends Win32FileSystem { + + /* -- class only for convenience and future use everything is inherinted --*/ + + +} + diff --git a/library/phing/system/io/Writer.php b/library/phing/system/io/Writer.php new file mode 100644 index 000000000..cdb7587d3 --- /dev/null +++ b/library/phing/system/io/Writer.php @@ -0,0 +1,53 @@ +. + */ + +/** + * Abstract class for writing character streams. + * + * @package phing.system.io + */ +abstract class Writer { + + /** + * Writes data to output stream. + * @param string $buf + * @param int $off + * @param int $len + */ + abstract public function write($buf, $off = null, $len = null); + + /** + * Close the stream. + * @throws IOException - if there is an error closing stream. + */ + abstract public function close(); + + /** + * Flush the stream, if supported by the stream. + */ + public function flush() {} + + /** + * Returns a string representation of resource filename, url, etc. that is being written to. + * @return string + */ + abstract public function getResource(); +} diff --git a/library/phing/system/lang/Character.php b/library/phing/system/lang/Character.php new file mode 100644 index 000000000..27c1e7feb --- /dev/null +++ b/library/phing/system/lang/Character.php @@ -0,0 +1,49 @@ +. + */ + +/** + * @package phing.system.lang + */ +class Character { + + // this class might be extended with plenty of ordinal char constants + // and the like to support the multibyte aware datatype (char) in php + // in form of an object. + // anyway just a thought + + public static function isLetter($char) { + + if (strlen($char) !== 1) + $char = 0; + + $char = (int) ord($char); + + if ($char >= ord('A') && $char <= ord('Z')) + return true; + + if ($char >= ord('a') && $char <= ord('z')) + return true; + + return false; + } + +} + diff --git a/library/phing/system/lang/EventObject.php b/library/phing/system/lang/EventObject.php new file mode 100644 index 000000000..e2db2a6d1 --- /dev/null +++ b/library/phing/system/lang/EventObject.php @@ -0,0 +1,52 @@ +. + */ + +/** + * @package phing.system.lang + */ +class EventObject { + + /** The object on which the Event initially occurred. */ + protected $source; + + /** Constructs a prototypical Event. */ + function __construct($source) { + if ($source === null) { + throw new Exception("Null source"); + } + $this->source = $source; + } + + /** The object on which the Event initially occurred. */ + function getSource() { + return $this->source; + } + + /** Returns a String representation of this EventObject.*/ + function toString() { + if (method_exists($this->source, "toString")) { + return get_class($this)."[source=".$this->source->toString()."]"; + } else { + return get_class($this)."[source=".get_class($this->source)."]"; + } + } +} + diff --git a/library/phing/system/lang/FileNotFoundException.php b/library/phing/system/lang/FileNotFoundException.php new file mode 100644 index 000000000..b90132380 --- /dev/null +++ b/library/phing/system/lang/FileNotFoundException.php @@ -0,0 +1,26 @@ +. + */ + +/** + * @package phing.system.lang + */ +class FileNotFoundException extends Exception {} + diff --git a/library/phing/system/lang/NullPointerException.php b/library/phing/system/lang/NullPointerException.php new file mode 100644 index 000000000..0d5bc1fa3 --- /dev/null +++ b/library/phing/system/lang/NullPointerException.php @@ -0,0 +1,26 @@ +. + */ + +/** + * @package phing.system.lang + */ +class NullPointerException extends Exception {} + diff --git a/library/phing/system/lang/SecurityException.php b/library/phing/system/lang/SecurityException.php new file mode 100644 index 000000000..aecef7a20 --- /dev/null +++ b/library/phing/system/lang/SecurityException.php @@ -0,0 +1,26 @@ +. + */ + +/** + * @package phing.system.lang + */ +class SecurityException extends Exception {} + diff --git a/library/phing/system/util/Properties.php b/library/phing/system/util/Properties.php new file mode 100644 index 000000000..435c055dd --- /dev/null +++ b/library/phing/system/util/Properties.php @@ -0,0 +1,289 @@ +. + */ + +include_once 'phing/system/io/PhingFile.php'; +include_once 'phing/system/io/FileWriter.php'; + +/** + * Convenience class for reading and writing property files. + * + * FIXME + * - Add support for arrays (separated by ',') + * + * @package phing.system.util + * @version $Revision: 905 $ + */ +class Properties { + + private $properties = array(); + + /** + * Load properties from a file. + * + * @param PhingFile $file + * @return void + * @throws IOException - if unable to read file. + */ + function load(PhingFile $file) { + if ($file->canRead()) { + $this->parse($file->getPath(), false); + } else { + throw new IOException("Can not read file ".$file->getPath()); + } + + } + + /** + * Replaces parse_ini_file() or better_parse_ini_file(). + * Saves a step since we don't have to parse and then check return value + * before throwing an error or setting class properties. + * + * @param string $filePath + * @param boolean $processSections Whether to honor [SectionName] sections in INI file. + * @return array Properties loaded from file (no prop replacements done yet). + */ + protected function parse($filePath) { + + // load() already made sure that file is readable + // but we'll double check that when reading the file into + // an array + + if (($lines = @file($filePath)) === false) { + throw new IOException("Unable to parse contents of $filePath"); + } + + $this->properties = array(); + $sec_name = ""; + + foreach($lines as $line) { + + $line = trim($line); + + if($line == "") + continue; + + if ($line{0} == '#' or $line{0} == ';') { + // it's a comment, so continue to next line + continue; + } else { + $pos = strpos($line, '='); + $property = trim(substr($line, 0, $pos)); + $value = trim(substr($line, $pos + 1)); + $this->properties[$property] = $this->inVal($value); + } + + } // for each line + } + + /** + * Process values when being read in from properties file. + * does things like convert "true" => true + * @param string $val Trimmed value. + * @return mixed The new property value (may be boolean, etc.) + */ + protected function inVal($val) { + if ($val === "true") { + $val = true; + } elseif ($val === "false") { + $val = false; + } + return $val; + } + + /** + * Process values when being written out to properties file. + * does things like convert true => "true" + * @param mixed $val The property value (may be boolean, etc.) + * @return string + */ + protected function outVal($val) { + if ($val === true) { + $val = "true"; + } elseif ($val === false) { + $val = "false"; + } + return $val; + } + + /** + * Create string representation that can be written to file and would be loadable using load() method. + * + * Essentially this function creates a string representation of properties that is ready to + * write back out to a properties file. This is used by store() method. + * + * @return string + */ + public function toString() { + $buf = ""; + foreach($this->properties as $key => $item) { + $buf .= $key . "=" . $this->outVal($item) . PHP_EOL; + } + return $buf; + } + + /** + * Stores current properties to specified file. + * + * @param PhingFile $file File to create/overwrite with properties. + * @param string $header Header text that will be placed (within comments) at the top of properties file. + * @return void + * @throws IOException - on error writing properties file. + */ + function store(PhingFile $file, $header = null) { + // stores the properties in this object in the file denoted + // if file is not given and the properties were loaded from a + // file prior, this method stores them in the file used by load() + try { + $fw = new FileWriter($file); + if ($header !== null) { + $fw->write( "# " . $header . PHP_EOL ); + } + $fw->write($this->toString()); + $fw->close(); + } catch (IOException $e) { + throw new IOException("Error writing property file: " . $e->getMessage()); + } + } + + /** + * Returns copy of internal properties hash. + * Mostly for performance reasons, property hashes are often + * preferable to passing around objects. + * + * @return array + */ + function getProperties() { + return $this->properties; + } + + /** + * Get value for specified property. + * This is the same as get() method. + * + * @param string $prop The property name (key). + * @return mixed + * @see get() + */ + function getProperty($prop) { + if (!isset($this->properties[$prop])) { + return null; + } + return $this->properties[$prop]; + } + + /** + * Get value for specified property. + * This function exists to provide a hashtable-like interface for + * properties. + * + * @param string $prop The property name (key). + * @return mixed + * @see getProperty() + */ + function get($prop) { + if (!isset($this->properties[$prop])) { + return null; + } + return $this->properties[$prop]; + } + + /** + * Set the value for a property. + * + * @param string $key + * @param mixed $value + * @return mixed Old property value or NULL if none was set. + */ + function setProperty($key, $value) { + $oldValue = null; + if (isset($this->properties[$key])) { + $oldValue = $this->properties[$key]; + } + $this->properties[$key] = $value; + return $oldValue; + } + + /** + * Set the value for a property. + * This function exists to provide hashtable-lie + * interface for properties. + * + * @param string $key + * @param mixed $value + */ + function put($key, $value) { + return $this->setProperty($key, $value); + } + + /** + * Appends a value to a property if it already exists with a delimiter + * + * If the property does not, it just adds it. + * + * @param string $key + * @param mixed $value + * @param string $delimiter + */ + function append($key, $value, $delimiter = ',') { + $newValue = $value; + if (isset($this->properties[$key]) && !empty($this->properties[$key]) ) { + $newValue = $this->properties[$key] . $delimiter . $value; + } + $this->properties[$key] = $newValue; + } + + /** + * Same as keys() function, returns an array of property names. + * @return array + */ + function propertyNames() { + return $this->keys(); + } + + /** + * Whether loaded properties array contains specified property name. + * @return boolean + */ + function containsKey($key) { + return isset($this->properties[$key]); + } + + /** + * Returns properties keys. + * Use this for foreach() {} iterations, as this is + * faster than looping through property values. + * @return array + */ + function keys() { + return array_keys($this->properties); + } + + /** + * Whether properties list is empty. + * @return boolean + */ + function isEmpty() { + return empty($this->properties); + } + +} + diff --git a/library/phing/system/util/Register.php b/library/phing/system/util/Register.php new file mode 100644 index 000000000..269267ab3 --- /dev/null +++ b/library/phing/system/util/Register.php @@ -0,0 +1,115 @@ + + * + * + * + * The task/type must provide a supporting setter for the attribute: + * + * + * function setListeningReplace(RegisterSlot $slot) { + * $this->replace = $slot; + * } + * + * // in main() + * if ($this->replace instanceof RegisterSlot) { + * $this->regexp->setReplace($this->replace->getValue()); + * } else { + * $this->regexp->setReplace($this->replace); + * } + * + * + * @author Hans Lellelid + * @version $Revision: 905 $ + * @package phing.system.util + */ +class Register { + + /** Slots that have been registered */ + private static $slots = array(); + + /** + * Returns RegisterSlot for specified key. + * + * If not slot exists a new one is created for key. + * + * @param string $key + * @return RegisterSlot + */ + public static function getSlot($key) { + if (!isset(self::$slots[$key])) { + self::$slots[$key] = new RegisterSlot($key); + } + return self::$slots[$key]; + } +} + + +/** + * Represents a slot in the register. + */ +class RegisterSlot { + + /** The name of this slot. */ + private $key; + + /** The value for this slot. */ + private $value; + + /** + * Constructs a new RegisterSlot, setting the key to passed param. + * @param string $key + */ + public function __construct($key) { + $this->key = (string) $key; + } + + /** + * Sets the key / name for this slot. + * @param string $k + */ + public function setKey($k) { + $this->key = (string) $k; + } + + /** + * Gets the key / name for this slot. + * @return string + */ + public function getKey() { + return $this->key; + } + + /** + * Sets the value for this slot. + * @param mixed + */ + public function setValue($v) { + $this->value = $v; + } + + /** + * Returns the value at this slot. + * @return mixed + */ + public function getValue() { + return $this->value; + } + +} + diff --git a/library/phing/system/util/Timer.php b/library/phing/system/util/Timer.php new file mode 100644 index 000000000..783150474 --- /dev/null +++ b/library/phing/system/util/Timer.php @@ -0,0 +1,96 @@ +. + */ + + +/** + * This class can be used to obtain the execution time of all of the scripts + * that are executed in the process of building a page. + * + * Example: + * To be done before any scripts execute: + * + * $Timer = new Timer; + * $Timer->Start_Timer(); + * + * To be done after all scripts have executed: + * + * $timer->Stop_Timer(); + * $timer->Get_Elapsed_Time(int number_of_places); + * + * @author Charles Killian + * @author Hans Lellelid + * @package phing.system.util + * @version $Revision: 905 $ $Date: 2010-10-05 18:28:03 +0200 (Tue, 05 Oct 2010) $ + */ +class Timer { + + /** start time */ + protected $stime; + + /** end time */ + protected $etime; + + /** + * This function sets the class variable $stime to the current time in + * microseconds. + * @return void + */ + public function start() { + $this->stime = $this->getMicrotime(); + } + + /** + * This function sets the class variable $etime to the current time in + * microseconds. + * @return void + */ + function stop() { + $this->etime = $this->getMicrotime(); + } + + /** + * This function returns the elapsed time in seconds. + * + * Call start_time() at the beginning of script execution and end_time() at + * the end of script execution. Then, call elapsed_time() to obtain the + * difference between start_time() and end_time(). + * + * @param $places decimal place precision of elapsed time (default is 5) + * @return string Properly formatted time. + */ + function getElapsedTime($places=5) { + $etime = $this->etime - $this->stime; + $format = "%0.".$places."f"; + return (sprintf ($format, $etime)); + } + + /** + * This function returns the current time in microseconds. + * + * @author Everett Michaud, Zend.com + * @return current time in microseconds + * @access private + */ + function getMicrotime() { + list($usec, $sec) = explode(" ", microtime()); + return ((float)$usec + (float)$sec); + } +} diff --git a/library/phing/tasks/ext/CapsuleTask.php b/library/phing/tasks/ext/CapsuleTask.php new file mode 100644 index 000000000..7f0e237a1 --- /dev/null +++ b/library/phing/tasks/ext/CapsuleTask.php @@ -0,0 +1,480 @@ +. + */ + +include_once 'phing/Task.php'; +include_once 'phing/BuildException.php'; +include_once 'phing/lib/Capsule.php'; +include_once 'phing/util/StringHelper.php'; + +/** + * A phing task for generating output by using Capsule. + * + * This is based on the interface to TexenTask from Apache's Velocity engine. + * + * @author Hans Lellelid + * @version $Id: CapsuleTask.php 905 2010-10-05 16:28:03Z mrook $ + * @package phing.tasks.ext + */ +class CapsuleTask extends Task { + + /** + * Capsule "template" engine. + * @var Capsule + */ + protected $context; + + /** + * Any vars assigned via the build file. + * @var array AssignedVar[] + */ + protected $assignedVars = array(); + + /** + * This is the control template that governs the output. + * It may or may not invoke the services of worker + * templates. + * @var string + */ + protected $controlTemplate; + + /** + * This is where Velocity will look for templates + * using the file template loader. + * @var string + */ + protected $templatePath; + + /** + * This is where texen will place all the output + * that is a product of the generation process. + * @var string + */ + protected $outputDirectory; + + /** + * This is the file where the generated text + * will be placed. + * @var string + */ + protected $outputFile; + + /** + *

    + * These are properties that are fed into the + * initial context from a properties file. This + * is simply a convenient way to set some values + * that you wish to make available in the context. + *

    + *

    + * These values are not critical, like the template path + * or output path, but allow a convenient way to + * set a value that may be specific to a particular + * generation task. + *

    + *

    + * For example, if you are generating scripts to allow + * user to automatically create a database, then + * you might want the $databaseName + * to be placed + * in the initial context so that it is available + * in a script that might look something like the + * following: + *

    +     * #!bin/sh
    +     * 
    +     * echo y | mysqladmin create $databaseName
    +     * 
    + * The value of $databaseName isn't critical to + * output, and you obviously don't want to change + * the ant task to simply take a database name. + * So initial context values can be set with + * properties file. + * + * @var array + */ + protected $contextProperties; + + // ----------------------------------------------------------------------- + // The following getters & setters are used by phing to set properties + // specified in the XML for the capsule task. + // ----------------------------------------------------------------------- + + /** + * [REQUIRED] Set the control template for the + * generating process. + * @param string $controlTemplate + * @return void + */ + public function setControlTemplate ($controlTemplate) { + $this->controlTemplate = $controlTemplate; + } + + /** + * Get the control template for the + * generating process. + * @return string + */ + public function getControlTemplate() { + return $this->controlTemplate; + } + + /** + * [REQUIRED] Set the path where Velocity will look + * for templates using the file template + * loader. + * @return void + * @throws Exception + */ + public function setTemplatePath($templatePath) { + $resolvedPath = ""; + $tok = strtok($templatePath, ","); + while ( $tok ) { + // resolve relative path from basedir and leave + // absolute path untouched. + $fullPath = $this->project->resolveFile($tok); + $cpath = $fullPath->getCanonicalPath(); + if ($cpath === false) { + $this->log("Template directory does not exist: " . $fullPath->getAbsolutePath()); + } else { + $resolvedPath .= $cpath; + } + $tok = strtok(","); + if ( $tok ) { + $resolvedPath .= ","; + } + } + $this->templatePath = $resolvedPath; + } + + /** + * Get the path where Velocity will look + * for templates using the file template + * loader. + * @return string + */ + public function getTemplatePath() { + return $this->templatePath; + } + + /** + * [REQUIRED] Set the output directory. It will be + * created if it doesn't exist. + * @param PhingFile $outputDirectory + * @return void + * @throws Exception + */ + public function setOutputDirectory(PhingFile $outputDirectory) { + try { + if (!$outputDirectory->exists()) { + $this->log("Output directory does not exist, creating: " . $outputDirectory->getPath(),Project::MSG_VERBOSE); + if (!$outputDirectory->mkdirs()) { + throw new IOException("Unable to create Ouptut directory: " . $outputDirectory->getAbsolutePath()); + } + } + $this->outputDirectory = $outputDirectory->getCanonicalPath(); + } catch (IOException $ioe) { + throw new BuildException($ioe); + } + } + + /** + * Get the output directory. + * @return string + */ + public function getOutputDirectory() { + return $this->outputDirectory; + } + + /** + * [REQUIRED] Set the output file for the + * generation process. + * @param string $outputFile (TODO: change this to File) + * @return void + */ + public function setOutputFile($outputFile) { + $this->outputFile = $outputFile; + } + + /** + * Get the output file for the + * generation process. + * @return string + */ + public function getOutputFile() { + return $this->outputFile; + } + + /** + * Set the context properties that will be + * fed into the initial context be the + * generating process starts. + * @param string $file + * @return void + */ + public function setContextProperties($file) { + $sources = explode(",", $file); + $this->contextProperties = new Properties(); + + // Always try to get the context properties resource + // from a file first. Templates may be taken from a JAR + // file but the context properties resource may be a + // resource in the filesystem. If this fails than attempt + // to get the context properties resource from the + // classpath. + for ($i=0, $sourcesLength=count($sources); $i < $sourcesLength; $i++) { + $source = new Properties(); + + try { + + // resolve relative path from basedir and leave + // absolute path untouched. + $fullPath = $this->project->resolveFile($sources[$i]); + $this->log("Using contextProperties file: " . $fullPath->toString()); + $source->load($fullPath); + + } catch (Exception $e) { + + throw new BuildException("Context properties file " . $sources[$i] . + " could not be found in the file system!"); + + } + + $keys = $source->keys(); + + foreach ($keys as $key) { + $name = $key; + $value = $this->project->replaceProperties($source->getProperty($name)); + $this->contextProperties->setProperty($name, $value); + } + } + } + + /** + * Get the context properties that will be + * fed into the initial context be the + * generating process starts. + * @return Properties + */ + public function getContextProperties() { + return $this->contextProperties; + } + + /** + * Creates an "AssignedVar" class. + */ + public function createAssign() { + $a = new AssignedVar(); + $this->assignedVars[] = $a; + return $a; + } + + // --------------------------------------------------------------- + // End of XML setters & getters + // --------------------------------------------------------------- + + /** + * Creates a Smarty object. + * + * @return Smarty initialized (cleared) Smarty context. + * @throws Exception the execute method will catch + * and rethrow as a BuildException + */ + public function initControlContext() { + $this->context->clear(); + foreach($this->assignedVars as $var) { + $this->context->put($var->getName(), $var->getValue()); + } + return $this->context; + } + + /** + * Execute the input script with Velocity + * + * @throws BuildException + * BuildExceptions are thrown when required attributes are missing. + * Exceptions thrown by Velocity are rethrown as BuildExceptions. + */ + public function main() { + + // Make sure the template path is set. + if (empty($this->templatePath)) { + throw new BuildException("The template path needs to be defined!"); + } + + // Make sure the control template is set. + if ($this->controlTemplate === null) { + throw new BuildException("The control template needs to be defined!"); + } + + // Make sure the output directory is set. + if ($this->outputDirectory === null) { + throw new BuildException("The output directory needs to be defined!"); + } + + // Make sure there is an output file. + if ($this->outputFile === null) { + throw new BuildException("The output file needs to be defined!"); + } + + // Setup Smarty runtime. + + // Smarty uses one object to store properties and to store + // the context for the template (unlike Velocity). We setup this object, calling it + // $this->context, and then initControlContext simply zeros out + // any assigned variables. + $this->context = new Capsule(); + + if ($this->templatePath !== null) { + $this->log("Using templatePath: " . $this->templatePath); + $this->context->setTemplatePath($this->templatePath); + } + + // Make sure the output directory exists, if it doesn't + // then create it. + $outputDir = new PhingFile($this->outputDirectory); + if (!$outputDir->exists()) { + $this->log("Output directory does not exist, creating: " . $outputDir->getAbsolutePath()); + $outputDir->mkdirs(); + } + + $this->context->setOutputDirectory($outputDir->getAbsolutePath()); + + $path = $this->outputDirectory . DIRECTORY_SEPARATOR . $this->outputFile; + $this->log("Generating to file " . $path); + + //$writer = new FileWriter($path); + + // The generator and the output path should + // be placed in the init context here and + // not in the generator class itself. + $c = $this->initControlContext(); + + // Set any variables that need to always + // be loaded + $this->populateInitialContext($c); + + // Feed all the options into the initial + // control context so they are available + // in the control/worker templates. + if ($this->contextProperties !== null) { + + foreach($this->contextProperties->keys() as $property) { + + $value = $this->contextProperties->getProperty($property); + + // Special exception (from Texen) + // for properties ending in file.contents: + // in that case we dump the contents of the file + // as the "value" for the Property. + if (preg_match('/file\.contents$/', $property)) { + // pull in contents of file specified + + $property = substr($property, 0, strpos($property, "file.contents") - 1); + + // reset value, and then + // read in teh contents of the file into that var + $value = ""; + $f = new PhingFile($project->resolveFile($value)->getCanonicalPath()); + if ($f->exists()) { + $fr = new FileReader($f); + $fr->readInto($value); + } + + } // if ends with file.contents + + if (StringHelper::isBoolean($value)) { + $value = StringHelper::booleanValue($value); + } + + $c->put($property, $value); + + } // foreach property + + } // if contextProperties !== null + + try { + $this->log("Parsing control template: " . $this->controlTemplate); + $c->parse($this->controlTemplate, $path); + } catch (Exception $ioe) { + throw new BuildException("Cannot write parsed template: ". $ioe->getMessage()); + } + + $this->cleanup(); + } + + /** + * Place useful objects into the initial context. + * + * + * @param Capsule $context The context to populate, as retrieved from + * {@link #initControlContext()}. + * @return void + * @throws Exception Error while populating context. The {@link + * #main()} method will catch and rethrow as a + * BuildException. + */ + protected function populateInitialContext(Capsule $context) { + $this->context->put("now", strftime("%c", time())); + $this->context->put("task", $this); + } + + /** + * A hook method called at the end of {@link #execute()} which can + * be overridden to perform any necessary cleanup activities (such + * as the release of database connections, etc.). By default, + * does nothing. + * @return void + * @throws Exception Problem cleaning up. + */ + protected function cleanup() { + } +} + + +/** + * An "inner" class for holding assigned var values. + * May be need to expand beyond name/value in the future. + * + * @package phing.tasks.ext + */ +class AssignedVar { + + private $name; + private $value; + + function setName($v) { + $this->name = $v; + } + + function setValue($v) { + $this->value = $v; + } + + function getName() { + return $this->name; + } + + function getValue() { + return $this->value; + } + +} \ No newline at end of file diff --git a/library/phing/tasks/ext/ExportPropertiesTask.php b/library/phing/tasks/ext/ExportPropertiesTask.php new file mode 100644 index 000000000..1fd094cc5 --- /dev/null +++ b/library/phing/tasks/ext/ExportPropertiesTask.php @@ -0,0 +1,143 @@ +. + */ + +require_once "phing/Task.php"; + +/** + * Saves currently defined properties into a specified file + * + * @author Andrei Serdeliuc + * @extends Task + * @version $Id: ExportPropertiesTask.php 905 2010-10-05 16:28:03Z mrook $ + * @package phing.tasks.ext + */ +class ExportPropertiesTask extends Task +{ + /** + * Array of project properties + * + * (default value: null) + * + * @var array + * @access private + */ + private $_properties = null; + + /** + * Target file for saved properties + * + * (default value: null) + * + * @var string + * @access private + */ + private $_targetFile = null; + + /** + * Exclude properties starting with these prefixes + * + * @var array + * @access private + */ + private $_disallowedPropertyPrefixes = array( + 'host.', + 'phing.', + 'os.', + 'php.', + 'line.', + 'env.', + 'user.' + ); + + /** + * setter for _targetFile + * + * @access public + * @param string $file + * @return bool + */ + public function setTargetFile($file) + { + if(!is_dir(dirname($file))) { + throw new BuildException("Parent directory of target file doesn't exist"); + } + + if(!is_writable(dirname($file)) && (file_exists($file) && !is_writable($file))) { + throw new BuildException("Target file isn't writable"); + } + + $this->_targetFile = $file; + return true; + } + + /** + * setter for _disallowedPropertyPrefixes + * + * @access public + * @param string $file + * @return bool + */ + public function setDisallowedPropertyPrefixes($prefixes) + { + $this->_disallowedPropertyPrefixes = explode(",", $prefixes); + return true; + } + + public function main() + { + // Sets the currently declared properties + $this->_properties = $this->getProject()->getProperties(); + + if(is_array($this->_properties) && !empty($this->_properties) && null !== $this->_targetFile) { + $propertiesString = ''; + foreach($this->_properties as $propertyName => $propertyValue) { + if(!$this->isDisallowedPropery($propertyName)) { + $propertiesString .= $propertyName . "=" . $propertyValue . PHP_EOL; + } + } + + if(!file_put_contents($this->_targetFile, $propertiesString)) { + throw new BuildException('Failed writing to ' . $this->_targetFile); + } + } + } + + /** + * Checks if a property name is disallowed + * + * @access protected + * @param string $propertyName + * @return bool + */ + protected function isDisallowedPropery($propertyName) + { + foreach($this->_disallowedPropertyPrefixes as $property) { + if(substr($propertyName, 0, strlen($property)) == $property) { + return true; + } + } + + return false; + } +} + +?> \ No newline at end of file diff --git a/library/phing/tasks/ext/ExtractBaseTask.php b/library/phing/tasks/ext/ExtractBaseTask.php new file mode 100644 index 000000000..4d6ac85ae --- /dev/null +++ b/library/phing/tasks/ext/ExtractBaseTask.php @@ -0,0 +1,183 @@ +. + */ + +require_once 'phing/tasks/system/MatchingTask.php'; + +/** + * Base class for extracting tasks such as Unzip and Untar. + * + * @author Joakim Bodin + * @version $Id: ExtractBaseTask.php 905 2010-10-05 16:28:03Z mrook $ + * @package phing.tasks.ext + * @since 2.2.0 + */ +abstract class ExtractBaseTask extends MatchingTask { + /** + * @var PhingFile $file + */ + protected $file; + /** + * @var PhingFile $todir + */ + protected $todir; + protected $removepath; + protected $filesets = array(); // all fileset objects assigned to this task + + /** + * Add a new fileset. + * @return FileSet + */ + public function createFileSet() { + $this->fileset = new FileSet(); + $this->filesets[] = $this->fileset; + return $this->fileset; + } + + /** + * Set the name of the zip file to extract. + * @param PhingFile $file zip file to extract + */ + public function setFile(PhingFile $file) { + $this->file = $file; + } + + /** + * This is the base directory to look in for things to zip. + * @param PhingFile $baseDir + */ + public function setToDir(PhingFile $todir) { + $this->todir = $todir; + } + + public function setRemovePath($removepath) + { + $this->removepath = $removepath; + } + + /** + * do the work + * @throws BuildException + */ + public function main() { + + $this->validateAttributes(); + + $filesToExtract = array(); + if ($this->file !== null) { + if(!$this->isDestinationUpToDate($this->file)) { + $filesToExtract[] = $this->file; + } else { + $this->log('Nothing to do: ' . $this->todir->getAbsolutePath() . ' is up to date for ' . $this->file->getCanonicalPath(), Project::MSG_INFO); + } + } + + foreach($this->filesets as $compressedArchiveFileset) { + $compressedArchiveDirScanner = $compressedArchiveFileset->getDirectoryScanner($this->project); + $compressedArchiveFiles = $compressedArchiveDirScanner->getIncludedFiles(); + $compressedArchiveDir = $compressedArchiveFileset->getDir($this->project); + + foreach ($compressedArchiveFiles as $compressedArchiveFilePath) { + $compressedArchiveFile = new PhingFile($compressedArchiveDir, $compressedArchiveFilePath); + if($compressedArchiveFile->isDirectory()) + { + throw new BuildException($compressedArchiveFile->getAbsolutePath() . ' compressed archive cannot be a directory.'); + } + + if(!$this->isDestinationUpToDate($compressedArchiveFile)) { + $filesToExtract[] = $compressedArchiveFile; + } else { + $this->log('Nothing to do: ' . $this->todir->getAbsolutePath() . ' is up to date for ' . $compressedArchiveFile->getCanonicalPath(), Project::MSG_INFO); + } + } + } + + foreach ($filesToExtract as $compressedArchiveFile) { + $this->extractArchive($compressedArchiveFile); + } + } + + abstract protected function extractArchive(PhingFile $compressedArchiveFile); + + /** + * @param array $files array of filenames + * @param PhingFile $dir + * @return boolean + */ + protected function isDestinationUpToDate(PhingFile $compressedArchiveFile) { + if (!$compressedArchiveFile->exists()) { + throw new BuildException("Could not find file " . $compressedArchiveFile->__toString() . " to extract."); + } + + $compressedArchiveContent = $this->listArchiveContent($compressedArchiveFile); + if(is_array($compressedArchiveContent)) { + + $fileSystem = FileSystem::getFileSystem(); + foreach ($compressedArchiveContent as $compressArchivePathInfo) { + $compressArchiveFilename = $compressArchivePathInfo['filename']; + if(!empty($this->removepath) && strlen($compressArchiveFilename) >= strlen($this->removepath)) + { + $compressArchiveFilename = preg_replace('/^' . $this->removepath . '/','', $compressArchiveFilename); + } + $compressArchivePath = new PhingFile($this->todir, $compressArchiveFilename); + + if(!$compressArchivePath->exists() || + $fileSystem->compareMTimes($compressedArchiveFile->getCanonicalPath(), $compressArchivePath->getCanonicalPath()) == 1) { + return false; + } + } + + } + + return true; + } + + abstract protected function listArchiveContent(PhingFile $compressedArchiveFile); + + /** + * Validates attributes coming in from XML + * + * @access private + * @return void + * @throws BuildException + */ + protected function validateAttributes() { + + if ($this->file === null && count($this->filesets) === 0) { + throw new BuildException("Specify at least one source compressed archive - a file or a fileset."); + } + + if ($this->todir === null) { + throw new BuildException("todir must be set."); + } + + if ($this->todir !== null && $this->todir->exists() && !$this->todir->isDirectory()) { + throw new BuildException("todir must be a directory."); + } + + if ($this->file !== null && $this->file->exists() && $this->file->isDirectory()) { + throw new BuildException("Compressed archive file cannot be a directory."); + } + + if ($this->file !== null && !$this->file->exists()) { + throw new BuildException("Could not find compressed archive file " . $this->file->__toString() . " to extract."); + } + } + +} diff --git a/library/phing/tasks/ext/FileHashTask.php b/library/phing/tasks/ext/FileHashTask.php new file mode 100644 index 000000000..6032217a4 --- /dev/null +++ b/library/phing/tasks/ext/FileHashTask.php @@ -0,0 +1,147 @@ +. + */ +require_once 'phing/Task.php'; + +/** + * fileHash + * + * Calculate either MD5 or SHA hash value of a specified file and retun the + * value in a property + * + * @author Johan Persson + * @version $Id: FileHashTask.php 905 2010-10-05 16:28:03Z mrook $ + * @package phing.tasks.ext + */ +class FileHashTask extends Task +{ + /** + * Property for File + * @var PhingFile file + */ + private $file; + + /** + * Property to be set + * @var string $property + */ + private $propertyName = "filehashvalue"; + + /** + * Specify which hash algorithm to use. + * 0 = MD5 + * 1 = SHA1 + * + * @var integer $hashtype + */ + private $hashtype=0; + + + /** + * Specify if MD5 or SHA1 hash should be used + * @param integer $type 0=MD5, 1=SHA1 + */ + public function setHashtype($type) + { + $this->hashtype = $type; + } + + /** + * Which file for calculate the has value of + * @param PhingFile $file + */ + public function setFile($file) + { + $this->file = $file; + } + + /** + * Set the name of the property to use to return the has value + * @param $property + * @return + */ + public function setPropertyName($property) + { + $this->propertyName = $property; + } + + /** + * Main-Method for the Task + * + * @return void + * @throws BuildException + */ + public function main() + { + $this->checkFile(); + $this->checkPropertyName(); + + // read file + if( (int)$this->hashtype === 0 ) { + $this->log("Calculating MD5 hash from: ".$this->file); + $hashValue = md5_file($this->file,false); + } + elseif( (int)$this->hashtype === 1 ) { + $this->log("Calculating SHA1 hash from: ".$this->file); + $hashValue = sha1_file($this->file,false); + } + else { + throw new BuildException( + sprintf('[FileHash] Unknown hashtype specified %d. Must be either 0 (=MD5) or 1 (=SHA1).',$this->hashtype)); + + } + + // publish hash value + $this->project->setProperty($this->propertyName, $hashValue); + + } + + /** + * checks file attribute + * @return void + * @throws BuildException + */ + private function checkFile() + { + // check File + if ($this->file === null || + strlen($this->file) == 0) { + throw new BuildException('[FileHash] You must specify an input file.', $this->file); + } + + if( ! is_readable($this->file) ) { + throw new BuildException(sprintf('[FileHash] Input file does not exist or is not readable: %s',$this->file)); + } + + } + + /** + * checks property attribute + * @return void + * @throws BuildException + */ + private function checkPropertyName() + { + if (is_null($this->propertyName) || + strlen($this->propertyName) === 0) { + throw new BuildException('Property name for publishing hashvalue is not set'); + } + } +} \ No newline at end of file diff --git a/library/phing/tasks/ext/FileSizeTask.php b/library/phing/tasks/ext/FileSizeTask.php new file mode 100644 index 000000000..0773c9674 --- /dev/null +++ b/library/phing/tasks/ext/FileSizeTask.php @@ -0,0 +1,120 @@ +. + */ +require_once 'phing/Task.php'; + +/** + * fileHash + * + * Calculate either MD5 or SHA hash value of a specified file and retun the + * value in a property + * + * @author Johan Persson + * @version $Id: FileSizeTask.php 905 2010-10-05 16:28:03Z mrook $ + * @package phing.tasks.ext + */ +class FileSizeTask extends Task +{ + /** + * Property for File + * @var PhingFile file + */ + private $file; + + /** + * Property where the file size will be stored + * @var string $property + */ + private $propertyName = "filesize"; + + /** + * Which file for calculate the has value of + * @param PhingFile $file + */ + public function setFile($file) + { + $this->file = $file; + } + + /** + * Set the name of the property to use to return the has value + * @param $property + * @return + */ + public function setPropertyName($property) + { + $this->propertyName = $property; + } + + /** + * Main-Method for the Task + * + * @return void + * @throws BuildException + */ + public function main() + { + $this->checkFile(); + $this->checkPropertyName(); + + $size = filesize($this->file); + + if( $size === false ) { + throw new BuildException(sprintf('[FileSize] Cannot determine size of file: %s',$this->file)); + + } + + // publish hash value + $this->project->setProperty($this->propertyName, $size); + + } + + /** + * checks file attribute + * @return void + * @throws BuildException + */ + private function checkFile() + { + // check File + if ($this->file === null || + strlen($this->file) == 0) { + throw new BuildException('[FileSize] You must specify an input file.', $this->file); + } + + if( ! is_readable($this->file) ) { + throw new BuildException(sprintf('[FileSize] Input file does not exist or is not readable: %s',$this->file)); + } + + } + + /** + * checks property attribute + * @return void + * @throws BuildException + */ + private function checkPropertyName() + { + if (is_null($this->propertyName) || + strlen($this->propertyName) === 0) { + throw new BuildException('[FileSize] Property name for publishing file size is not set'); + } + } +} \ No newline at end of file diff --git a/library/phing/tasks/ext/FtpDeployTask.php b/library/phing/tasks/ext/FtpDeployTask.php new file mode 100644 index 000000000..79cb5870d --- /dev/null +++ b/library/phing/tasks/ext/FtpDeployTask.php @@ -0,0 +1,216 @@ +. + */ + +require_once 'phing/Task.php'; + +/** + * FtpDeployTask + * + * Deploys a set of files to a remote FTP server. + * + * + * Example usage: + * + * + * + * + * + * + * + * + * + * + * + * @author Jorrit Schippers + * @version $Id: FtpDeployTask.php 905 2010-10-05 16:28:03Z mrook $ + * @since 2.3.1 + * @package phing.tasks.ext + */ +class FtpDeployTask extends Task +{ + private $host = null; + private $port = 21; + private $username = null; + private $password = null; + private $dir = null; + private $filesets; + private $completeDirMap; + private $mode = FTP_BINARY; + private $clearFirst = false; + private $passive = false; + + public function __construct() { + $this->filesets = array(); + $this->completeDirMap = array(); + } + + public function setHost($host) { + $this->host = $host; + } + + public function setPort($port) { + $this->port = (int) $port; + } + + public function setUsername($username) { + $this->username = $username; + } + + public function setPassword($password) { + $this->password = $password; + } + + public function setDir($dir) { + $this->dir = $dir; + } + + public function setMode($mode) { + switch(strtolower($mode)) { + case 'ascii': + $this->mode = FTP_ASCII; + break; + case 'binary': + case 'bin': + $this->mode = FTP_BINARY; + break; + } + } + + public function setPassive($passive) + { + $this->passive = (bool) $passive; + } + + public function setClearFirst($clearFirst) { + $this->clearFirst = (bool) $clearFirst; + } + + function createFileSet() { + $num = array_push($this->filesets, new FileSet()); + return $this->filesets[$num-1]; + } + + /** + * The init method: check if Net_FTP is available + */ + public function init() { + require_once 'PEAR.php'; + + $paths = explode(PATH_SEPARATOR, get_include_path()); + foreach($paths as $path) { + if(file_exists($path.DIRECTORY_SEPARATOR.'Net'.DIRECTORY_SEPARATOR.'FTP.php')) { + return true; + } + } + throw new BuildException('The FTP Deploy task requires the Net_FTP PEAR package.'); + } + + /** + * The main entry point method. + */ + public function main() { + $project = $this->getProject(); + + require_once 'Net/FTP.php'; + $ftp = new Net_FTP($this->host, $this->port); + $ret = $ftp->connect(); + if(@PEAR::isError($ret)) { + throw new BuildException('Could not connect to FTP server '.$this->host.' on port '.$this->port.': '.$ret->getMessage()); + } else { + $this->log('Connected to FTP server ' . $this->host . ' on port ' . $this->port, Project::MSG_VERBOSE); + } + + $ret = $ftp->login($this->username, $this->password); + if(@PEAR::isError($ret)) { + throw new BuildException('Could not login to FTP server '.$this->host.' on port '.$this->port.' with username '.$this->username.': '.$ret->getMessage()); + } else { + $this->log('Logged in to FTP server with username ' . $this->username, Project::MSG_VERBOSE); + } + + if ($this->passive) { + $this->log('Setting passive mode', Project::MSG_INFO); + $ret = $ftp->setPassive(); + if(@PEAR::isError($ret)) { + $ftp->disconnect(); + throw new BuildException('Could not set PASSIVE mode: '.$ret->getMessage()); + } + } + + // append '/' to the end if necessary + $dir = substr($this->dir, -1) == '/' ? $this->dir : $this->dir.'/'; + + if($this->clearFirst) { + // TODO change to a loop through all files and directories within current directory + $this->log('Clearing directory '.$dir, Project::MSG_INFO); + $ftp->rm($dir, true); + } + + // Create directory just in case + $ret = $ftp->mkdir($dir, true); + if(@PEAR::isError($ret)) { + $ftp->disconnect(); + throw new BuildException('Could not create directory '.$dir.': '.$ret->getMessage()); + } + + $ret = $ftp->cd($dir); + if(@PEAR::isError($ret)) { + $ftp->disconnect(); + throw new BuildException('Could not change to directory '.$dir.': '.$ret->getMessage()); + } else { + $this->log('Changed directory ' . $dir, Project::MSG_VERBOSE); + } + + $fs = FileSystem::getFileSystem(); + $convert = $fs->getSeparator() == '\\'; + + foreach($this->filesets as $fs) { + $ds = $fs->getDirectoryScanner($project); + $fromDir = $fs->getDir($project); + $srcFiles = $ds->getIncludedFiles(); + $srcDirs = $ds->getIncludedDirectories(); + foreach($srcDirs as $dirname) { + if($convert) + $dirname = str_replace('\\', '/', $dirname); + $this->log('Will create directory '.$dirname, Project::MSG_VERBOSE); + $ret = $ftp->mkdir($dirname, true); + if(@PEAR::isError($ret)) { + $ftp->disconnect(); + throw new BuildException('Could not create directory '.$dirname.': '.$ret->getMessage()); + } + } + foreach($srcFiles as $filename) { + $file = new PhingFile($fromDir->getAbsolutePath(), $filename); + if($convert) + $filename = str_replace('\\', '/', $filename); + $this->log('Will copy '.$file->getCanonicalPath().' to '.$filename, Project::MSG_VERBOSE); + $ret = $ftp->put($file->getCanonicalPath(), $filename, true, $this->mode); + if(@PEAR::isError($ret)) { + $ftp->disconnect(); + throw new BuildException('Could not deploy file '.$filename.': '.$ret->getMessage()); + } + } + } + + $ftp->disconnect(); + $this->log('Disconnected from FTP server', Project::MSG_VERBOSE); + } +} +?> diff --git a/library/phing/tasks/ext/HttpRequestTask.php b/library/phing/tasks/ext/HttpRequestTask.php new file mode 100644 index 000000000..2cafad6e9 --- /dev/null +++ b/library/phing/tasks/ext/HttpRequestTask.php @@ -0,0 +1,286 @@ +. + */ + +require_once 'phing/Task.php'; + +/** + * A HTTP request task. + * Making an HTTP request and try to match the response against an provided + * regular expression. + * + * @package phing.tasks.ext + * @author Benjamin Schultz + * @version $Id: HttpRequestTask.php 905 2010-10-05 16:28:03Z mrook $ + * @since 2.4.1 + */ +class HttpRequestTask extends Task +{ + /** + * Holds the request URL + * + * @var string + */ + protected $_url = null; + + /** + * Holds the regular expression that should match the response + * + * @var string + */ + protected $_responseRegex = ''; + + /** + * Whether to enable detailed logging + * + * @var boolean + */ + protected $_verbose = false; + + /** + * Holds additional header data + * + * @var array + */ + protected $_headers = array(); + + /** + * Holds additional config data for HTTP_Request2 + * + * @var array + */ + protected $_configData = array(); + + /** + * Holds the authentication user name + * + * @var string + */ + protected $_authUser = null; + + /** + * Holds the authentication password + * + * @var string + */ + protected $_authPassword = ''; + + /** + * Holds the authentication scheme + * + * @var string + */ + protected $_authScheme; + + /** + * Holds the events that will be logged + * + * @var array + */ + protected $_observerEvents = array( + 'connect', + 'sentHeaders', + 'sentBodyPart', + 'receivedHeaders', + 'receivedBody', + 'disconnect', + ); + + /** + * Sets the request URL + * + * @param string $url + */ + public function setUrl($url) + { + $this->_url = $url; + } + + /** + * Sets the response regex + * + * @param string $regex + */ + public function setResponseRegex($regex) + { + $this->_responseRegex = $regex; + } + + /** + * Sets the authentication user name + * + * @param string $user + */ + public function setAuthUser($user) + { + $this->_authUser = $user; + } + + /** + * Sets the authentication password + * + * @param string $password + */ + public function setAuthPassword($password) + { + $this->_authPassword = $password; + } + + /** + * Sets the authentication scheme + * + * @param string $scheme + */ + public function setAuthScheme($scheme) + { + $this->_authScheme = $scheme; + } + + /** + * Sets whether to enable detailed logging + * + * @param boolean $verbose + */ + public function setVerbose($verbose) + { + $this->_verbose = StringHelper::booleanValue($verbose); + } + + /** + * Sets a list of observer events that will be logged + * if verbose output is enabled. + * + * @param string $observerEvents List of observer events + * + * @return void + */ + public function setObserverEvents($observerEvents) + { + $this->_observerEvents = array(); + + $token = ' ,;'; + $ext = strtok($observerEvents, $token); + + while ($ext !== false) { + $this->_observerEvents[] = $ext; + $ext = strtok($token); + } + } + + /** + * Creates an additional header for this task + * + * @return Parameter The created header + */ + public function createHeader() + { + $num = array_push($this->_headers, new Parameter()); + return $this->_headers[$num-1]; + } + + /** + * Creates a config parameter for this task + * + * @return Parameter The created parameter + */ + public function createConfig() + { + $num = array_push($this->_configData, new Parameter()); + return $this->_configData[$num-1]; + } + + /** + * Load the necessary environment for running this task. + * + * @throws BuildException + */ + public function init() + { + @include_once 'HTTP/Request2.php'; + + if (! class_exists('HTTP_Request2')) { + throw new BuildException( + 'HttpRequestTask depends on HTTP_Request2 being installed ' + . 'and on include_path.', + $this->getLocation() + ); + } + + $this->_authScheme = HTTP_Request2::AUTH_BASIC; + + // Other dependencies that should only be loaded + // when class is actually used + require_once 'HTTP/Request2/Observer/Log.php'; + } + + /** + * Make the http request + */ + public function main() + { + if (!isset($this->_url)) { + throw new BuildException("Missing attribute 'url' set"); + } + + $request = new HTTP_Request2($this->_url); + + // set the authentication data + if (!empty($this->_authUser)) { + $request->setAuth( + $this->_authUser, + $this->_authPassword, + $this->_authScheme + ); + } + + foreach ($this->_configData as $config) { + $request->setConfig($config->getName(), $config->getValue()); + } + + foreach ($this->_headers as $header) { + $request->setHeader($header->getName(), $header->getValue()); + } + + if ($this->_verbose) { + $observer = new HTTP_Request2_Observer_Log(); + + // set the events we want to log + $observer->events = $this->_observerEvents; + + $request->attach($observer); + } + + $response = $request->send(); + + if ($this->_responseRegex !== '') { + $matches = array(); + preg_match($this->_responseRegex, $response->getBody(), $matches); + + if (count($matches) === 0) { + throw new BuildException( + 'The received response body did not match the ' + . 'given regular expression' + ); + } else { + $this->log('The response body matched the provided regex.'); + } + } + } +} \ No newline at end of file diff --git a/library/phing/tasks/ext/JslLintTask.php b/library/phing/tasks/ext/JslLintTask.php new file mode 100644 index 000000000..28edcaa18 --- /dev/null +++ b/library/phing/tasks/ext/JslLintTask.php @@ -0,0 +1,242 @@ +. + */ + +require_once 'phing/Task.php'; +require_once 'phing/util/DataStore.php'; + + /** + * A Javascript lint task. Checks syntax of Javascript files. + * Javascript lint (http://www.javascriptlint.com) must be in the system path. + * This class is based on Knut Urdalen's PhpLintTask. + * + * @author Stefan Priebsch + * @version $Id: JslLintTask.php 905 2010-10-05 16:28:03Z mrook $ + * @package phing.tasks.ext + */ + class JslLintTask extends Task + { + protected $file; // the source file (from xml attribute) + protected $filesets = array(); // all fileset objects assigned to this task + + protected $showWarnings = true; + protected $haltOnFailure = false; + protected $hasErrors = false; + private $badFiles = array(); + + private $cache = null; + private $conf = null; + + private $executable = "jsl"; + + /** + * Sets the flag if warnings should be shown + * @param boolean $show + */ + public function setShowWarnings($show) { + $this->showWarnings = StringHelper::booleanValue($show); + } + + /** + * The haltonfailure property + * @param boolean $aValue + */ + public function setHaltOnFailure($aValue) { + $this->haltOnFailure = $aValue; + } + + /** + * File to be performed syntax check on + * @param PhingFile $file + */ + public function setFile(PhingFile $file) { + $this->file = $file; + } + + /** + * Whether to store last-modified times in cache + * + * @param PhingFile $file + */ + public function setCacheFile(PhingFile $file) + { + $this->cache = new DataStore($file); + } + + /** + * jsl config file + * + * @param PhingFile $file + */ + public function setConfFile(PhingFile $file) + { + $this->conf = $file; + } + + public function setExecutable($path){ + $this->executable = $path; + } + + public function getExecutable(){ + return $this->executable; + } + + /** + * Nested creator, creates a FileSet for this task + * + * @return FileSet The created fileset object + */ + function createFileSet() { + $num = array_push($this->filesets, new FileSet()); + return $this->filesets[$num-1]; + } + + /** + * Execute lint check against PhingFile or a FileSet + */ + public function main() { + if(!isset($this->file) and count($this->filesets) == 0) { + throw new BuildException("Missing either a nested fileset or attribute 'file' set"); + } + + exec($this->executable, $output); + if (!preg_match('/JavaScript\sLint/', implode('', $output))) throw new BuildException('Javascript Lint not found'); + + if($this->file instanceof PhingFile) { + $this->lint($this->file->getPath()); + } else { // process filesets + $project = $this->getProject(); + foreach($this->filesets as $fs) { + $ds = $fs->getDirectoryScanner($project); + $files = $ds->getIncludedFiles(); + $dir = $fs->getDir($this->project)->getPath(); + foreach($files as $file) { + $this->lint($dir.DIRECTORY_SEPARATOR.$file); + } + } + } + + if ($this->haltOnFailure && $this->hasErrors) throw new BuildException('Syntax error(s) in JS files:' .implode(', ',$this->badFiles)); + } + + /** + * Performs the actual syntax check + * + * @param string $file + * @return void + */ + protected function lint($file) + { + $command = $this->executable . ' -output-format ' . escapeshellarg('file:__FILE__;line:__LINE__;message:__ERROR__') . ' '; + + if (isset($this->conf)) { + $command .= '-conf ' . $this->conf->getPath() . ' '; + } + + $command .= '-process '; + + if(file_exists($file)) + { + if(is_readable($file)) + { + if ($this->cache) + { + $lastmtime = $this->cache->get($file); + + if ($lastmtime >= filemtime($file)) + { + $this->log("Not linting '" . $file . "' due to cache", Project::MSG_DEBUG); + return false; + } + } + + $messages = array(); + exec($command.'"'.$file.'"', $messages); + + $summary = $messages[sizeof($messages) - 1]; + + preg_match('/(\d+)\serror/', $summary, $matches); + $errorCount = $matches[1]; + + preg_match('/(\d+)\swarning/', $summary, $matches); + $warningCount = $matches[1]; + + $errors = array(); + $warnings = array(); + if ($errorCount > 0 || $warningCount > 0) { + $last = false; + foreach ($messages as $message) { + $matches = array(); + if (preg_match('/^(\.*)\^$/', $message)) { + $column = strlen($message); + if ($last == 'error') { + $errors[count($errors) - 1]['column'] = $column; + } else if ($last == 'warning') { + $warnings[count($warnings) - 1]['column'] = $column; + } + $last = false; + } + if (!preg_match('/^file:(.+);line:(\d+);message:(.+)$/', $message, $matches)) continue; + $msg = $matches[3]; + $data = array('filename' => $matches[1], 'line' => $matches[2], 'message' => $msg); + if (preg_match('/^.*error:.+$/i', $msg)) { + $errors[] = $data; + $last = 'error'; + } else if (preg_match('/^.*warning:.+$/i', $msg)) { + $warnings[] = $data; + $last = 'warning'; + } + } + } + + if($this->showWarnings && $warningCount > 0) + { + $this->log($file . ': ' . $warningCount . ' warnings detected', Project::MSG_WARN); + foreach ($warnings as $warning) { + $this->log('- line ' . $warning['line'] . (isset($warning['column']) ? ' column ' . $warning['column'] : '') . ': ' . $warning['message'], Project::MSG_WARN); + } + } + + if($errorCount > 0) + { + $this->log($file . ': ' . $errorCount . ' errors detected', Project::MSG_ERR); + foreach ($errors as $error) { + $this->log('- line ' . $error['line'] . (isset($error['column']) ? ' column ' . $error['column'] : '') . ': ' . $error['message'], Project::MSG_ERR); + } + $this->badFiles[] = $file; + $this->hasErrors = true; + } else if (!$this->showWarnings || $warningCount == 0) { + $this->log($file . ': No syntax errors detected', Project::MSG_INFO); + } + + if ($this->cache) + { + $this->cache->put($file, filemtime($file)); + } + } else { + throw new BuildException('Permission denied: '.$file); + } + } else { + throw new BuildException('File not found: '.$file); + } + } + } + + diff --git a/library/phing/tasks/ext/MailTask.php b/library/phing/tasks/ext/MailTask.php new file mode 100644 index 000000000..79c1b9a2d --- /dev/null +++ b/library/phing/tasks/ext/MailTask.php @@ -0,0 +1,77 @@ +. + */ + +include_once 'phing/Task.php'; + +/** + * Send a message by mail() + * + * The build process is a success... + * + * @author Francois Harvey at SecuriWeb (http://www.securiweb.net) + * @version $Id: MailTask.php 905 2010-10-05 16:28:03Z mrook $ + * @package phing.tasks.ext + */ +class MailTask extends Task { + + protected $recipient; + + protected $subject; + + protected $msg; + + function main() { + $this->log('Sending mail to ' . $this->recipient ); + mail($this->recipient, $this->subject, $this->msg); + } + + /** setter for message */ + function setMsg($msg) { + $this->setMessage($msg); + } + + /** alias setter */ + function setMessage($msg) { + $this->msg = (string) $msg; + } + + /** setter for subject **/ + function setSubject($subject) { + $this->subject = (string) $subject; + } + + /** setter for recipient **/ + function setRecipient($recipient) { + $this->recipient = (string) $recipient; + } + + /** alias for recipient **/ + function setTo($recipient) { + $this->recipient = (string) $recipient; + } + + /** Supporting the Message syntax. */ + function addText($msg) + { + $this->msg = (string) $msg; + } +} + diff --git a/library/phing/tasks/ext/ManifestTask.php b/library/phing/tasks/ext/ManifestTask.php new file mode 100644 index 000000000..161aea45a --- /dev/null +++ b/library/phing/tasks/ext/ManifestTask.php @@ -0,0 +1,343 @@ +. + */ + +require_once "phing/Task.php"; +require_once 'phing/system/io/PhingFile.php'; + +/** + * ManifestTask + * + * Generates a simple Manifest file with optional checksums. + * + * + * Manifest schema: + * ... + * path/to/file CHECKSUM [CHECKSUM2] [CHECKSUM3] + * path/to/secondfile CHECKSUM [CHECKSUM2] [CHECKSUM3] + * ... + * + * Example usage: + * + * + * + * + * + * + * + * + * + * + * @author David Persson + * @package phing.tasks.ext + * @version $Id: ManifestTask.php 905 2010-10-05 16:28:03Z mrook $ + * @since 2.3.1 + */ +class ManifestTask extends Task +{ + var $taskname = 'manifest'; + + /** + * Action + * + * "w" for reading in files from fileSet + * and writing manifest + * + * or + * + * "r" for reading in files from fileSet + * and checking against manifest + * + * @var string "r" or "w" + */ + private $action = 'w'; + + /** + * The target file passed in the buildfile. + */ + private $destFile = null; + + /** + * Holds filesets + * + * @var array An Array of objects + */ + private $filesets = array(); + + /** + * Enable/Disable checksuming or/and select algorithm + * true defaults to md5 + * false disables checksuming + * string "md5,sha256,..." enables generation of multiple checksums + * string "sha256" generates sha256 checksum only + * + * @var mixed + */ + private $checksum = false; + + /** + * A string used in hashing method + * + * @var string + */ + private $salt = ''; + + /** + * Holds some data collected during runtime + * + * @var array + */ + private $meta = array('totalFileCount' => 0,'totalFileSize' => 0); + + + /** + * The setter for the attribute "file" + * This is where the manifest will be written to/read from + * + * @param string Path to readable file + * @return void + */ + public function setFile(PhingFile $file) + { + $this->file = $file; + } + + /** + * The setter for the attribute "checksum" + * + * @param mixed $mixed + * @return void + */ + public function setChecksum($mixed) + { + if(is_string($mixed)) { + $data = array(strtolower($mixed)); + + if(strpos($data[0],',')) { + $data = explode(',',$mixed); + } + + $this->checksum = $data; + + } elseif($mixed === true) { + $this->checksum = array('md5'); + + } + } + + /** + * The setter for the optional attribute "salt" + * + * @param string $string + * @return void + */ + public function setSalt($string) + { + $this->salt = $string; + } + + /** + * Nested creator, creates a FileSet for this task + * + * @access public + * @return object The created fileset object + */ + public function createFileSet() + { + $num = array_push($this->filesets, new FileSet()); + return $this->filesets[$num-1]; + } + + /** + * The init method: Do init steps. + */ + public function init() + { + // nothing to do here + } + + /** + * Delegate the work + */ + public function main() + { + $this->validateAttributes(); + + if($this->action == 'w') { + $this->write(); + + } elseif($this->action == 'r') { + $this->read(); + + } + } + + /** + * Creates Manifest file + * Writes to $this->file + * + * @throws BuildException + */ + private function write() + { + $project = $this->getProject(); + + if(!touch($this->file->getPath())) { + throw new BuildException("Unable to write to ".$this->file->getPath()."."); + } + + $this->log("Writing to " . $this->file->__toString(), Project::MSG_INFO); + + if(is_array($this->checksum)) { + $this->log("Using " . implode(', ',$this->checksum)." for checksuming.", Project::MSG_INFO); + } + + foreach($this->filesets as $fs) { + + $dir = $fs->getDir($this->project)->getPath(); + + $ds = $fs->getDirectoryScanner($project); + $fromDir = $fs->getDir($project); + $srcFiles = $ds->getIncludedFiles(); + $srcDirs = $ds->getIncludedDirectories(); + + foreach($ds->getIncludedFiles() as $file_path) { + $line = $file_path; + if($this->checksum) { + foreach($this->checksum as $algo) { + if(!$hash = $this->hashFile($dir.'/'.$file_path,$algo)) { + throw new BuildException("Hashing $dir/$file_path with $algo failed!"); + } + + $line .= "\t".$hash; + } + } + $line .= "\n"; + $manifest[] = $line; + $this->log("Adding file ".$file_path,Project::MSG_VERBOSE); + $this->meta['totalFileCount'] ++; + $this->meta['totalFileSize'] += filesize($dir.'/'.$file_path); + } + + } + + file_put_contents($this->file,$manifest); + + $this->log("Done. Total files: ".$this->meta['totalFileCount'].". Total file size: ".$this->meta['totalFileSize']." bytes.", Project::MSG_INFO); + } + + /** + * @todo implement + */ + private function read() + { + throw new BuildException("Checking against manifest not yet supported."); + } + + /** + * Wrapper method for hash generation + * Automatically selects extension + * Falls back to built-in functions + * + * @link http://www.php.net/mhash + * @link http://www.php.net/hash + * + * @param string $msg The string that should be hashed + * @param string $algo Algorithm + * @return mixed String on success, false if $algo is not available + */ + private function hash($msg,$algo) + { + if(extension_loaded('hash')) { + $algo = strtolower($algo); + + if(in_array($algo,hash_algos())) { + return hash($algo,$this->salt.$msg); + } + + } + + if(extension_loaded('mhash')) { + $algo = strtoupper($algo); + + if(defined('MHASH_'.$algo)) { + return mhash('MHASH_'.$algo,$this->salt.$msg); + + } + } + + switch(strtolower($algo)) { + case 'md5': + return md5($this->salt.$msg); + case 'crc32': + return abs(crc32($this->salt.$msg)); + } + + return false; + } + + /** + * Hash a files contents + * plus it's size an modification time + * + * @param string $file + * @param string $algo + * @return mixed String on success, false if $algo is not available + */ + private function hashFile($file,$algo) + { + if(!file_exists($file)) { + return false; + } + + $msg = file_get_contents($file).filesize($file).filemtime($file); + + return $this->hash($msg,$algo); + } + + /** + * Validates attributes coming in from XML + * + * @access private + * @return void + * @throws BuildException + */ + protected function validateAttributes() + { + if($this->action != 'r' && $this->action != 'w') { + throw new BuildException("'action' attribute has non valid value. Use 'r' or 'w'"); + } + + if(empty($this->salt)) { + $this->log("No salt provided. Specify one with the 'salt' attribute.", Project::MSG_WARN); + } + + if (is_null($this->file) && count($this->filesets) === 0) { + throw new BuildException("Specify at least sources and destination - a file or a fileset."); + } + + if (!is_null($this->file) && $this->file->exists() && $this->file->isDirectory()) { + throw new BuildException("Destination file cannot be a directory."); + } + + } +} + + diff --git a/library/phing/tasks/ext/PackageAsPathTask.php b/library/phing/tasks/ext/PackageAsPathTask.php new file mode 100644 index 000000000..9649d5470 --- /dev/null +++ b/library/phing/tasks/ext/PackageAsPathTask.php @@ -0,0 +1,65 @@ +. + */ + +require_once 'phing/Task.php'; + +/** + * Convert dot-notation packages to relative paths. + * + * @author Hans Lellelid + * @version $Id: PackageAsPathTask.php 905 2010-10-05 16:28:03Z mrook $ + * @package phing.tasks.ext + */ +class PackageAsPathTask extends Task { + + /** The package to convert. */ + protected $pckg; + + /** The value to store the conversion in. */ + protected $name; + + /** + * Executes the package to patch converstion and stores it + * in the user property value. + */ + public function main() + { + $this->project->setUserProperty($this->name, strtr($this->pckg, '.', '/')); + } + + /** + * @param string $pckg the package to convert + */ + public function setPackage($pckg) + { + $this->pckg = $pckg; + } + + /** + * @param string $name the Ant variable to store the path in + */ + public function setName($name) + { + $this->name = $name; + } + +} diff --git a/library/phing/tasks/ext/PatchTask.php b/library/phing/tasks/ext/PatchTask.php new file mode 100644 index 000000000..af38dfc79 --- /dev/null +++ b/library/phing/tasks/ext/PatchTask.php @@ -0,0 +1,279 @@ + + * @version 0.01 + * @package phing.tasks.ext + */ +require_once 'phing/Task.php'; + +/** + * Patches a file by applying a 'diff' file to it + * + * Requires "patch" to be on the execution path. + * + * @package phing.tasks.ext + */ +class PatchTask extends Task +{ + /** + * Base command to be executed + * @var string + */ + const CMD = 'patch --batch '; + + /** + * File to be patched + * @var string + */ + private $originalFile; + + /** + * Patch file + * + * @var string + */ + private $patchFile; + + /** + * Value for a "-p" option + * @var int + */ + private $strip; + + /** + * Command line arguments for patch binary + * @var array + */ + private $cmdArgs = array(); + + /** + * Halt on error return value from patch invocation. + * @var bool + */ + private $haltOnFailure = false; + + /** + * The file containing the diff output + * + * Required. + * + * @param string $file File containing the diff output + * @return void + * @throws BuildException if $file not exists + */ + public function setPatchFile($file) + { + if (!is_file($file)) + throw new BuildException(sprintf('Patchfile %s doesn\'t exist', $file)); + + $this->patchFile = $file; + } + + /** + * The file to patch + * + * Optional if it can be inferred from the diff file. + * + * @param string $file File to patch + * @return void + */ + public function setOriginalFile($file) + { + $this->originalFile = $file; + } + + /** + * The name of a file to send the output to, instead of patching + * the file(s) in place + * + * Optional. + * + * @param string $file File to send the output to + * @return void + */ + public function setDestFile($file) + { + if ($file !== null) + $this->cmdArgs []= "--output=$file"; + } + + /** + * Flag to create backups + * + * Optional, default - false + * + * @param bool $backups If true create backups + * @return void + */ + public function setBackups($backups) + { + if ($backups) + $this->cmdArgs []= '--backup'; + } + + /** + * Flag to ignore whitespace differences; + * + * Default - false + * + * @param bool $ignore If true ignore whitespace differences + * @return void + */ + public function setIgnoreWhiteSpace($ignore) + { + if ($ignore) + $this->cmdArgs []= '--ignore-whitespace'; + } + + /** + * Strip the smallest prefix containing num leading slashes + * from filenames. + * + * patch's --strip option. + * + * @param int $num number of lines to strip + * @return void + * @throws BuildException if num is < 0, or other errors + */ + public function setStrip($num) + { + if ($num < 0) + throw new BuildException('strip has to be >= 0'); + + $this->strip = $num; + } + + /** + * Work silently unless an error occurs + * + * Optional, default - false + * @param bool $flag If true suppress set the -s option on the patch command + * @return void + */ + public function setQuiet($flag) + { + if ($flag) + $this->cmdArgs []= '--silent'; + } + + /** + * Assume patch was created with old and new files swapped + * + * Optional, default - false + * + * @param bool $flag If true set the -R option on the patch command + * @return void + */ + public function setReverse($flag) + { + if ($flag) + $this->cmdArgs []= '--reverse'; + } + + /** + * The directory to run the patch command in + * + * Defaults to the project's base directory. + * + * @param string $directory Directory to run the patch command in + * @return void + */ + public function setDir($directory) + { + $this->cmdArgs []= "--directory=$directory"; + } + + /** + * Ignore patches that seem to be reversed or already applied + * + * @param bool $flag If true set the -N (--forward) option + * @return void + */ + public function setForward($flag) + { + if ($flag) + $this->cmdArgs []= "--forward"; + } + + /** + * Set the maximum fuzz factor + * + * Defaults to 0 + * + * @param string $value Value of a fuzz factor + * @return void + */ + public function setFuzz($value) + { + $this->cmdArgs []= "--fuzz=$value"; + } + + /** + * If true, stop the build process if the patch command + * exits with an error status. + * + * The default is "false" + * + * @param bool $value "true" if it should halt, otherwise "false" + * @return void + */ + public function setHaltOnFailure($value) + { + $this->haltOnFailure = $value; + } + + /** + * Main task method + * + * @return void + * @throws BuildException when it all goes a bit pear shaped + */ + public function main() + { + if ($this->patchFile == null) + throw new BuildException('patchfile argument is required'); + + // Define patch file + $this->cmdArgs []= '-i ' . $this->patchFile; + // Define strip factor + if ($this->strip != null) + $this->cmdArgs []= '--strip=' . $this->strip; + // Define original file if specified + if ($this->originalFile != null) + $this->cmdArgs []= $this->originalFile; + + $cmd = self::CMD . implode(' ', $this->cmdArgs); + + $this->log('Applying patch: ' . $this->patchFile); + + exec($cmd, $output, $exitCode); + + foreach ($output as $line) + $this->log($line, Project::MSG_VERBOSE); + + if ($exitCode != 0 && $this->haltOnFailure) + throw new BuildException( "Task exited with code $exitCode" ); + + } +} \ No newline at end of file diff --git a/library/phing/tasks/ext/PearPackage2Task.php b/library/phing/tasks/ext/PearPackage2Task.php new file mode 100644 index 000000000..e90942b37 --- /dev/null +++ b/library/phing/tasks/ext/PearPackage2Task.php @@ -0,0 +1,270 @@ +. + */ + +require_once 'phing/tasks/ext/PearPackageTask.php'; + +/** + * A task to create a PEAR package.xml version 2.0 file. + * + * This class uses the PEAR_PackageFileManager2 class to perform the work. + * + * This class is designed to be very flexible -- i.e. account for changes to the package.xml w/o + * requiring changes to this class. We've accomplished this by having generic
    + * + * @author Francois Zaninotto (Propel) + * @author Hans Lellelid (Propel) + * @author Kaspars Jaudzems (Propel) + * @author Frank Y. Kim (Torque) + * @author John D. McNally (Torque) + * @author Brett McLaughlin (Torque) + * @author Eric Dobbs (Torque) + * @author Henning P. Schmiedehausen (Torque) + * @author Sam Joseph (Torque) + * @package propel.runtime.query + */ +class Join +{ + // default comparison type + const EQUAL = "="; + + // the left parts of the join condition + protected $left = array(); + + // the right parts of the join condition + protected $right = array(); + + // the comparison operators for each pair of columns in the join condition + protected $operator = array(); + + // the type of the join (LEFT JOIN, ...), or null for an implicit join + protected $joinType = null; + + // the number of conditions in the join + protected $count = 0; + + /** + * Constructor + * Use it preferably with no arguments, and then use addCondition() and setJoinType() + * Syntax with arguments used mainly for backwards compatibility + * + * @param string $leftColumn The left column of the join condition + * (may contain an alias name) + * @param string $rightColumn The right column of the join condition + * (may contain an alias name) + * @param string $joinType The type of the join. Valid join types are null (implicit join), + * Criteria::LEFT_JOIN, Criteria::RIGHT_JOIN, and Criteria::INNER_JOIN + */ + public function __construct($leftColumn = null, $rightColumn = null, $joinType = null) + { + if(!is_null($leftColumn)) { + if (!is_array($leftColumn)) { + // simple join + $this->addCondition($leftColumn, $rightColumn); + } else { + // join with multiple conditions + if (count($leftColumn) != count($rightColumn) ) { + throw new PropelException("Unable to create join because the left column count isn't equal to the right column count"); + } + foreach ($leftColumn as $key => $value) + { + $this->addCondition($value, $rightColumn[$key]); + } + } + $this->setJoinType($joinType); + } + } + + /** + * Join condition definition + * + * @param string $left The left column of the join condition + * (may contain an alias name) + * @param string $right The right column of the join condition + * (may contain an alias name) + * @param string $operator The comparison operator of the join condition, default Join::EQUAL + */ + public function addCondition($left, $right, $operator = self::EQUAL) + { + $this->left[] = $left; + $this->right[] = $right; + $this->operator[] = $operator; + $this->count++; + } + + /** + * Retrieve the number of conditions in the join + * + * @return integer The number of conditions in the join + */ + public function countConditions() + { + return $this->count; + } + + /** + * Return an array of the join conditions + * + * @return array An array of arrays representing (left, comparison, right) for each condition + */ + public function getConditions() + { + $conditions = array(); + for ($i=0; $i < $this->count; $i++) { + $conditions[] = array( + 'left' => $this->getLeftColumn($i), + 'operator' => $this->getOperator($i), + 'right' => $this->getRightColumn($i) + ); + } + return $conditions; + } + + /** + * @return the comparison operator for the join condition + */ + public function getOperator($index = 0) + { + return $this->operator[$index]; + } + + public function getOperators() + { + return $this->operator; + } + + /** + * Set the join type + * + * @param string $joinType The type of the join. Valid join types are + * null (adding the join condition to the where clause), + * Criteria::LEFT_JOIN(), Criteria::RIGHT_JOIN(), and Criteria::INNER_JOIN() + */ + public function setJoinType($joinType = null) + { + $this->joinType = $joinType; + } + + /** + * Get the join type + * + * @return string The type of the join, i.e. Criteria::LEFT_JOIN(), ..., + * or null for adding the join condition to the where Clause + */ + public function getJoinType() + { + return $this->joinType; + } + + /** + * @return the left column of the join condition + */ + public function getLeftColumn($index = 0) + { + return $this->left[$index]; + } + + /** + * @return all right columns of the join condition + */ + public function getLeftColumns() + { + return $this->left; + } + + + public function getLeftColumnName($index = 0) + { + return substr($this->left[$index], strrpos($this->left[$index], '.') + 1); + } + + public function getLeftTableName($index = 0) + { + return substr($this->left[$index], 0, strrpos($this->left[$index], '.')); + } + + /** + * @return the right column of the join condition + */ + public function getRightColumn($index = 0) + { + return $this->right[$index]; + } + + /** + * @return all right columns of the join condition + */ + public function getRightColumns() + { + return $this->right; + } + + public function getRightColumnName($index = 0) + { + return substr($this->right[$index], strrpos($this->right[$index], '.') + 1); + } + + public function getRightTableName($index = 0) + { + return substr($this->right[$index], 0, strrpos($this->right[$index], '.')); + } + + public function equals($join) + { + return $join !== null + && $join instanceof Join + && $this->joinType == $join->getJoinType() + && $this->getConditions() == $join->getConditions(); + } + + /** + * returns a String representation of the class, + * mainly for debugging purposes + * + * @return string A String representation of the class + */ + public function toString() + { + $result = ''; + if ($this->joinType !== null) { + $result .= $this->joinType . ' : '; + } + foreach ($this->getConditions() as $index => $condition) { + $result .= implode($condition); + if ($index + 1 < $this->count) { + $result .= ' AND '; + } + } + $result .= '(ignoreCase not considered)'; + + return $result; + } + + public function __toString() + { + return $this->toString(); + } +} + \ No newline at end of file diff --git a/library/propel/runtime/lib/query/ModelCriteria.php b/library/propel/runtime/lib/query/ModelCriteria.php new file mode 100644 index 000000000..4082133a9 --- /dev/null +++ b/library/propel/runtime/lib/query/ModelCriteria.php @@ -0,0 +1,1841 @@ +join from going wrong + protected $isKeepQuery = false; // whether to clone the current object before termination methods + + /** + * Creates a new instance with the default capacity which corresponds to + * the specified database. + * + * @param string $dbName The dabase name + * @param string $modelName The phpName of a model, e.g. 'Book' + * @param string $modelAlias The alias for the model in this query, e.g. 'b' + */ + public function __construct($dbName = null, $modelName, $modelAlias = null) + { + $this->setDbName($dbName); + $this->originalDbName = $dbName; + $this->modelName = $modelName; + $this->modelPeerName = constant($this->modelName . '::PEER'); + $this->modelAlias = $modelAlias; + $this->tableMap = Propel::getDatabaseMap($this->getDbName())->getTableByPhpName($this->modelName); + } + + /** + * Returns the name of the class for this model criteria + * + * @return string + */ + public function getModelName() + { + return $this->modelName; + } + + /** + * Sets the alias for the model in this query + * + * @param string $modelAlias The model alias + * @param boolean $useAliasInSQL Whether to use the alias in the SQL code (false by default) + * + * @return ModelCriteria The current object, for fluid interface + */ + public function setModelAlias($modelAlias, $useAliasInSQL = false) + { + if ($useAliasInSQL) { + $this->addAlias($modelAlias, $this->tableMap->getName()); + $this->useAliasInSQL = true; + } + $this->modelAlias = $modelAlias; + + return $this; + } + + /** + * Returns the alias of the main class for this model criteria + * + * @return string The model alias + */ + public function getModelAlias() + { + return $this->modelAlias; + } + + /** + * Return the string to use in a clause as a model prefix for the main model + * + * @return string The model alias if it exists, the model name if not + */ + public function getModelAliasOrName() + { + return $this->modelAlias ? $this->modelAlias : $this->modelName; + } + + /** + * Returns the name of the Peer class for this model criteria + * + * @return string + */ + public function getModelPeerName() + { + return $this->modelPeerName; + } + + /** + * Returns the TabkleMap object for this Criteria + * + * @return TableMap + */ + public function getTableMap() + { + return $this->tableMap; + } + + /** + * Sets the formatter to use for the find() output + * Formatters must extend PropelFormatter + * Use the ModelCriteria constants for class names: + * + * $c->setFormatter(ModelCriteria::FORMAT_ARRAY); + * + * + * @param string|PropelFormatter $formatter a formatter class name, or a formatter instance + * @return ModelCriteria The current object, for fluid interface + */ + public function setFormatter($formatter) + { + if(is_string($formatter)) { + $formatter = new $formatter(); + } + if (!$formatter instanceof PropelFormatter) { + throw new PropelException('setFormatter() only accepts classes extending PropelFormatter'); + } + $this->formatter = $formatter; + + return $this; + } + + /** + * Gets the formatter to use for the find() output + * Defaults to an instance of ModelCriteria::$defaultFormatterClass, i.e. PropelObjectsFormatter + * + * @return PropelFormatter + */ + public function getFormatter() + { + if (null === $this->formatter) { + $formatterClass = $this->defaultFormatterClass; + $this->formatter = new $formatterClass(); + } + return $this->formatter; + } + + /** + * Adds a condition on a column based on a pseudo SQL clause + * but keeps it for later use with combine() + * Until combine() is called, the condition is not added to the query + * Uses introspection to translate the column phpName into a fully qualified name + * + * $c->condition('cond1', 'b.Title = ?', 'foo'); + * + * + * @see Criteria::add() + * + * @param string $conditionName A name to store the condition for a later combination with combine() + * @param string $clause The pseudo SQL clause, e.g. 'AuthorId = ?' + * @param mixed $value A value for the condition + * + * @return ModelCriteria The current object, for fluid interface + */ + public function condition($conditionName, $clause, $value = null) + { + $this->addCond($conditionName, $this->getCriterionForClause($clause, $value), null, null); + + return $this; + } + + /** + * Adds a condition on a column based on a column phpName and a value + * Uses introspection to translate the column phpName into a fully qualified name + * Warning: recognizes only the phpNames of the main Model (not joined tables) + * + * $c->filterBy('Title', 'foo'); + * + * + * @see Criteria::add() + * + * @param string $column A string representing thecolumn phpName, e.g. 'AuthorId' + * @param mixed $value A value for the condition + * @param string $comparison What to use for the column comparison, defaults to Criteria::EQUAL + * + * @return ModelCriteria The current object, for fluid interface + */ + public function filterBy($column, $value, $comparison = Criteria::EQUAL) + { + return $this->add($this->getRealColumnName($column), $value, $comparison); + } + + /** + * Adds a list of conditions on the columns of the current model + * Uses introspection to translate the column phpName into a fully qualified name + * Warning: recognizes only the phpNames of the main Model (not joined tables) + * + * $c->filterByArray(array( + * 'Title' => 'War And Peace', + * 'Publisher' => $publisher + * )); + * + * + * @see filterBy() + * + * @param mixed $conditions An array of conditions, using column phpNames as key + * + * @return ModelCriteria The current object, for fluid interface + */ + public function filterByArray($conditions) + { + foreach ($conditions as $column => $args) { + call_user_func_array(array($this, 'filterBy' . $column), (array) $args); + } + + return $this; + } + + /** + * Adds a condition on a column based on a pseudo SQL clause + * Uses introspection to translate the column phpName into a fully qualified name + * + * // simple clause + * $c->where('b.Title = ?', 'foo'); + * // named conditions + * $c->condition('cond1', 'b.Title = ?', 'foo'); + * $c->condition('cond2', 'b.ISBN = ?', 12345); + * $c->where(array('cond1', 'cond2'), Criteria::LOGICAL_OR); + * + * + * @see Criteria::add() + * + * @param mixed $clause A string representing the pseudo SQL clause, e.g. 'Book.AuthorId = ?' + * Or an array of condition names + * @param mixed $value A value for the condition + * + * @return ModelCriteria The current object, for fluid interface + */ + public function where($clause, $value = null) + { + if (is_array($clause)) { + // where(array('cond1', 'cond2'), Criteria::LOGICAL_OR) + $criterion = $this->getCriterionForConditions($clause, $value); + } else { + // where('Book.AuthorId = ?', 12) + $criterion = $this->getCriterionForClause($clause, $value); + } + $this->addAnd($criterion, null, null); + + return $this; + } + + /** + * Adds a condition on a column based on a pseudo SQL clause + * Uses introspection to translate the column phpName into a fully qualified name + * + * // simple clause + * $c->orWhere('b.Title = ?', 'foo'); + * // named conditions + * $c->condition('cond1', 'b.Title = ?', 'foo'); + * $c->condition('cond2', 'b.ISBN = ?', 12345); + * $c->orWhere(array('cond1', 'cond2'), Criteria::LOGICAL_OR); + * + * + * @see Criteria::addOr() + * + * @param string $clause The pseudo SQL clause, e.g. 'AuthorId = ?' + * @param mixed $value A value for the condition + * + * @return ModelCriteria The current object, for fluid interface + */ + public function orWhere($clause, $value = null) + { + if (is_array($clause)) { + // orWhere(array('cond1', 'cond2'), Criteria::LOGICAL_OR) + $criterion = $this->getCriterionForConditions($clause, $value); + } else { + // orWhere('Book.AuthorId = ?', 12) + $criterion = $this->getCriterionForClause($clause, $value); + } + $this->addOr($criterion, null, null); + + return $this; + } + + /** + * Adds a having condition on a column based on a pseudo SQL clause + * Uses introspection to translate the column phpName into a fully qualified name + * + * // simple clause + * $c->having('b.Title = ?', 'foo'); + * // named conditions + * $c->condition('cond1', 'b.Title = ?', 'foo'); + * $c->condition('cond2', 'b.ISBN = ?', 12345); + * $c->having(array('cond1', 'cond2'), Criteria::LOGICAL_OR); + * + * + * @see Criteria::addHaving() + * + * @param mixed $clause A string representing the pseudo SQL clause, e.g. 'Book.AuthorId = ?' + * Or an array of condition names + * @param mixed $value A value for the condition + * + * @return ModelCriteria The current object, for fluid interface + */ + public function having($clause, $value = null) + { + if (is_array($clause)) { + // having(array('cond1', 'cond2'), Criteria::LOGICAL_OR) + $criterion = $this->getCriterionForConditions($clause, $value); + } else { + // having('Book.AuthorId = ?', 12) + $criterion = $this->getCriterionForClause($clause, $value); + } + $this->addHaving($criterion); + + return $this; + } + + /** + * Adds an ORDER BY clause to the query + * Usability layer on top of Criteria::addAscendingOrderByColumn() and Criteria::addDescendingOrderByColumn() + * Infers $column and $order from $columnName and some optional arguments + * Examples: + * $c->orderBy('Book.CreatedAt') + * => $c->addAscendingOrderByColumn(BookPeer::CREATED_AT) + * $c->orderBy('Book.CategoryId', 'desc') + * => $c->addDescendingOrderByColumn(BookPeer::CATEGORY_ID) + * + * @param string $columnName The column to order by + * @param string $order The sorting order. Criteria::ASC by default, also accepts Criteria::DESC + * + * @return ModelCriteria The current object, for fluid interface + */ + public function orderBy($columnName, $order = Criteria::ASC) + { + list($column, $realColumnName) = $this->getColumnFromName($columnName, false); + $order = strtoupper($order); + switch ($order) { + case Criteria::ASC: + $this->addAscendingOrderByColumn($realColumnName); + break; + case Criteria::DESC: + $this->addDescendingOrderByColumn($realColumnName); + break; + default: + throw new PropelException('ModelCriteria::orderBy() only accepts Criteria::ASC or Criteria::DESC as argument'); + } + + return $this; + } + + /** + * Adds a GROUB BY clause to the query + * Usability layer on top of Criteria::addGroupByColumn() + * Infers $column $columnName + * Examples: + * $c->groupBy('Book.AuthorId') + * => $c->addGroupByColumn(BookPeer::AUTHOR_ID) + * + * @param string $columnName The column to group by + * + * @return ModelCriteria The current object, for fluid interface + */ + public function groupBy($columnName) + { + list($column, $realColumnName) = $this->getColumnFromName($columnName, false); + $this->addGroupByColumn($realColumnName); + + return $this; + } + + /** + * Adds a DISTINCT clause to the query + * Alias for Criteria::setDistinct() + * + * @return ModelCriteria The current object, for fluid interface + */ + public function distinct() + { + $this->setDistinct(); + + return $this; + } + + /** + * Adds a LIMIT clause (or its subselect equivalent) to the query + * Alias for Criteria:::setLimit() + * + * @param int $limit Maximum number of results to return by the query + * + * @return ModelCriteria The current object, for fluid interface + */ + public function limit($limit) + { + $this->setLimit($limit); + + return $this; + } + + /** + * Adds an OFFSET clause (or its subselect equivalent) to the query + * Alias for of Criteria::setOffset() + * + * @param int $offset Offset of the first result to return + * + * @return ModelCriteria The current object, for fluid interface + */ + public function offset($offset) + { + $this->setOffset($offset); + + return $this; + } + + /** + * This method returns the previousJoin for this ModelCriteria, + * by default this is null, but after useQuery this is set the to the join of that use + * + * @return Join the previousJoin for this ModelCriteria + */ + public function getPreviousJoin() + { + return $this->previousJoin; + } + + /** + * This method sets the previousJoin for this ModelCriteria, + * by default this is null, but after useQuery this is set the to the join of that use + * + * @param Join $previousJoin The previousJoin for this ModelCriteria + */ + public function setPreviousJoin(Join $previousJoin) + { + $this->previousJoin = $previousJoin; + } + + /** + * This method returns an already defined join clause from the query + * + * @param string $name The name of the join clause + * + * @return Join A join object + */ + public function getJoin($name) + { + return $this->joins[$name]; + } + + /** + * Adds a JOIN clause to the query + * Infers the ON clause from a relation name + * Uses the Propel table maps, based on the schema, to guess the related columns + * Beware that the default JOIN operator is INNER JOIN, while Criteria defaults to WHERE + * Examples: + * + * $c->join('Book.Author'); + * => $c->addJoin(BookPeer::AUTHOR_ID, AuthorPeer::ID, Criteria::INNER_JOIN); + * $c->join('Book.Author', Criteria::RIGHT_JOIN); + * => $c->addJoin(BookPeer::AUTHOR_ID, AuthorPeer::ID, Criteria::RIGHT_JOIN); + * $c->join('Book.Author a', Criteria::RIGHT_JOIN); + * => $c->addAlias('a', AuthorPeer::TABLE_NAME); + * => $c->addJoin(BookPeer::AUTHOR_ID, 'a.ID', Criteria::RIGHT_JOIN); + * + * + * @param string $relation Relation to use for the join + * @param string $joinType Accepted values are null, 'left join', 'right join', 'inner join' + * + * @return ModelCriteria The current object, for fluid interface + */ + public function join($relation, $joinType = Criteria::INNER_JOIN) + { + // relation looks like '$leftName.$relationName $relationAlias' + list($fullName, $relationAlias) = self::getClassAndAlias($relation); + if (strpos($fullName, '.') === false) { + // simple relation name, refers to the current table + $leftName = $this->getModelAliasOrName(); + $relationName = $fullName; + $previousJoin = $this->getPreviousJoin(); + $tableMap = $this->getTableMap(); + } else { + list($leftName, $relationName) = explode('.', $fullName); + // find the TableMap for the left table using the $leftName + if ($leftName == $this->getModelAliasOrName()) { + $previousJoin = $this->getPreviousJoin(); + $tableMap = $this->getTableMap(); + } elseif (isset($this->joins[$leftName])) { + $previousJoin = $this->joins[$leftName]; + $tableMap = $previousJoin->getTableMap(); + } else { + throw new PropelException('Unknown table or alias ' . $leftName); + } + } + $leftTableAlias = isset($this->aliases[$leftName]) ? $leftName : null; + + // find the RelationMap in the TableMap using the $relationName + if(!$tableMap->hasRelation($relationName)) { + throw new PropelException('Unknown relation ' . $relationName . ' on the ' . $leftName .' table'); + } + $relationMap = $tableMap->getRelation($relationName); + + // create a ModelJoin object for this join + $join = new ModelJoin(); + $join->setJoinType($joinType); + if(null !== $previousJoin) { + $join->setPreviousJoin($previousJoin); + } + $join->setRelationMap($relationMap, $leftTableAlias, $relationAlias); + + // add the ModelJoin to the current object + if($relationAlias !== null) { + $this->addAlias($relationAlias, $relationMap->getRightTable()->getName()); + $this->addJoinObject($join, $relationAlias); + } else { + $this->addJoinObject($join, $relationName); + } + + return $this; + } + + /** + * Add a join object to the Criteria + * @see Criteria::addJoinObject() + * @param Join $join A join object + * + * @return ModelCriteria The current object, for fluid interface + */ + public function addJoinObject(Join $join, $name = null) + { + if (!in_array($join, $this->joins)) { // compare equality, NOT identity + $this->joins[$name] = $join; + } + return $this; + } + + /** + * Adds a JOIN clause to the query and hydrates the related objects + * Shortcut for $c->join()->with() + * + * $c->joinWith('Book.Author'); + * => $c->join('Book.Author'); + * => $c->with('Author'); + * $c->joinWith('Book.Author a', Criteria::RIGHT_JOIN); + * => $c->join('Book.Author a', Criteria::RIGHT_JOIN); + * => $c->with('a'); + * + * + * @param string $relation Relation to use for the join + * @param string $joinType Accepted values are null, 'left join', 'right join', 'inner join' + * + * @return ModelCriteria The current object, for fluid interface + */ + public function joinWith($relation, $joinType = Criteria::INNER_JOIN) + { + $this->join($relation, $joinType); + $this->with(self::getRelationName($relation)); + + return $this; + } + + /** + * Adds a relation to hydrate together with the main object + * The relation must be initialized via a join() prior to calling with() + * Examples: + * + * $c->join('Book.Author'); + * $c->with('Author'); + * + * $c->join('Book.Author a', Criteria::RIGHT_JOIN); + * $c->with('a'); + * + * WARNING: on a one-to-many relationship, the use of with() combined with limit() + * will return a wrong number of results for the related objects + * + * @param string $relation Relation to use for the join + * + * @return ModelCriteria The current object, for fluid interface + */ + public function with($relation) + { + if (!isset($this->joins[$relation])) { + throw new PropelException('Unknown relation name or alias ' . $relation); + } + $join = $this->joins[$relation]; + if ($join->getRelationMap()->getType() == RelationMap::MANY_TO_MANY) { + throw new PropelException('with() does not allow hydration for many-to-many relationships'); + } elseif ($join->getRelationMap()->getType() == RelationMap::ONE_TO_MANY) { + // For performance reasons, the formatters will use a special routine in this case + $this->isWithOneToMany = true; + } + + // check that the columns of the main class are already added (but only if this isn't a useQuery) + if (!$this->hasSelectClause() && !$this->getPrimaryCriteria()) { + $this->addSelfSelectColumns(); + } + // add the columns of the related class + $this->addRelationSelectColumns($relation); + + // list the join for later hydration in the formatter + $this->with[$relation] = $join; + + return $this; + } + + /** + * Gets the array of ModelWith specifying which objects must be hydrated + * together with the main object. + * + * @see with() + * @return array + */ + public function getWith() + { + return $this->with; + } + + public function isWithOneToMany() + { + return $this->isWithOneToMany; + } + + /** + * Adds a supplementary column to the select clause + * These columns can later be retrieved from the hydrated objects using getVirtualColumn() + * + * @param string $clause The SQL clause with object model column names + * e.g. 'UPPER(Author.FirstName)' + * @param string $name Optional alias for the added column + * If no alias is provided, the clause is used as a column alias + * This alias is used for retrieving the column via BaseObject::getVirtualColumn($alias) + * + * @return ModelCriteria The current object, for fluid interface + */ + public function withColumn($clause, $name = null) + { + if (null === $name) { + $name = str_replace(array('.', '(', ')'), '', $clause); + } + $clause = trim($clause); + $this->replaceNames($clause); + // check that the columns of the main class are already added (if this is the primary ModelCriteria) + if (!$this->hasSelectClause() && !$this->getPrimaryCriteria()) { + $this->addSelfSelectColumns(); + } + $this->addAsColumn($name, $clause); + + return $this; + } + + /** + * Initializes a secondary ModelCriteria object, to be later merged with the current object + * + * @see ModelCriteria::endUse() + * @param string $relationName Relation name or alias + * @param string $secondCriteriaClass Classname for the ModelCriteria to be used + * + * @return ModelCriteria The secondary criteria object + */ + public function useQuery($relationName, $secondaryCriteriaClass = null) + { + if (!isset($this->joins[$relationName])) { + throw new PropelException('Unknown class or alias ' . $relationName); + } + $className = $this->joins[$relationName]->getTableMap()->getPhpName(); + if (null === $secondaryCriteriaClass) { + $secondaryCriteria = PropelQuery::from($className); + } else { + $secondaryCriteria = new $secondaryCriteriaClass(); + } + if ($className != $relationName) { + $secondaryCriteria->setModelAlias($relationName, $relationName == $this->joins[$relationName]->getRelationMap()->getName() ? false : true); + } + $secondaryCriteria->setPrimaryCriteria($this, $this->joins[$relationName]); + + return $secondaryCriteria; + } + + /** + * Finalizes a secondary criteria and merges it with its primary Criteria + * + * @see Criteria::mergeWith() + * + * @return ModelCriteria The primary criteria object + */ + public function endUse() + { + if (isset($this->aliases[$this->modelAlias])) { + $this->removeAlias($this->modelAlias); + } + $primaryCriteria = $this->getPrimaryCriteria(); + $primaryCriteria->mergeWith($this); + + return $primaryCriteria; + } + + /** + * Add the content of a Criteria to the current Criteria + * In case of conflict, the current Criteria keeps its properties + * @see Criteria::mergeWith() + * + * @param Criteria $criteria The criteria to read properties from + * @param string $operator The logical operator used to combine conditions + * Defaults to Criteria::LOGICAL_AND, also accapts Criteria::LOGICAL_OR + * + * @return ModelCriteria The primary criteria object + */ + public function mergeWith(Criteria $criteria, $operator = Criteria::LOGICAL_AND) + { + parent::mergeWith($criteria, $operator); + + // merge with + if ($criteria instanceof ModelCriteria) { + $this->with = array_merge($this->getWith(), $criteria->getWith()); + } + + return $this; + } + + /** + * Clear the conditions to allow the reuse of the query object. + * The ModelCriteria's Model and alias 'all the properties set by construct) will remain. + * + * @return ModelCriteria The primary criteria object + */ + public function clear() + { + parent::clear(); + + $this->with = array(); + $this->primaryCriteria = null; + $this->formatter=null; + + return $this; + } + /** + * Sets the primary Criteria for this secondary Criteria + * + * @param ModelCriteria $criteria The primary criteria + * @param Join $previousJoin The previousJoin for this ModelCriteria + */ + public function setPrimaryCriteria(ModelCriteria $criteria, Join $previousJoin) + { + $this->primaryCriteria = $criteria; + $this->setPreviousJoin($previousJoin); + } + + /** + * Gets the primary criteria for this secondary Criteria + * + * @return ModelCriteria The primary criteria + */ + public function getPrimaryCriteria() + { + return $this->primaryCriteria; + } + + /** + * Adds the select columns for a the current table + * + * @return ModelCriteria The current object, for fluid interface + */ + public function addSelfSelectColumns() + { + call_user_func(array($this->modelPeerName, 'addSelectColumns'), $this, $this->useAliasInSQL ? $this->modelAlias : null); + + return $this; + } + + /** + * Adds the select columns for a relation + * + * @param string $relation The relation name or alias, as defined in join() + * + * @return ModelCriteria The current object, for fluid interface + */ + public function addRelationSelectColumns($relation) + { + $join = $this->joins[$relation]; + call_user_func(array($join->getTableMap()->getPeerClassname(), 'addSelectColumns'), $this, $join->getRelationAlias()); + + return $this; + } + + /** + * Returns the class and alias of a string representing a model or a relation + * e.g. 'Book b' => array('Book', 'b') + * e.g. 'Book' => array('Book', null) + * + * @param string $class The classname to explode + * + * @return array list($className, $aliasName) + */ + public static function getClassAndAlias($class) + { + if(strpos($class, ' ') !== false) { + list($class, $alias) = explode(' ', $class); + } else { + $alias = null; + } + return array($class, $alias); + } + + /** + * Returns the name of a relation from a string. + * The input looks like '$leftName.$relationName $relationAlias' + * + * @param string $relation Relation to use for the join + * @return string the relationName used in the join + */ + public static function getRelationName($relation) + { + // get the relationName + list($fullName, $relationAlias) = self::getClassAndAlias($relation); + if ($relationAlias) { + $relationName = $relationAlias; + } elseif (false === strpos($fullName, '.')) { + $relationName = $fullName; + } else { + list($leftName, $relationName) = explode('.', $fullName); + } + + return $relationName; + } + + /** + * Triggers the automated cloning on termination. + * By default, temrination methods don't clone the current object, + * even though they modify it. If the query must be reused after termination, + * you must call this method prior to temrination. + * + * @param boolean $isKeepQuery + * + * @return ModelCriteria The current object, for fluid interface + */ + public function keepQuery($isKeepQuery = true) + { + $this->isKeepQuery = (bool) $isKeepQuery; + + return $this; + } + + /** + * Checks whether the automated cloning on termination is enabled. + * + * @return boolean true if cloning must be done before termination + */ + public function isKeepQuery() + { + return $this->isKeepQuery; + } + + /** + * Code to execute before every SELECT statement + * + * @param PropelPDO $con The connection object used by the query + */ + protected function basePreSelect(PropelPDO $con) + { + return $this->preSelect($con); + } + + protected function preSelect(PropelPDO $con) + { + } + + /** + * Issue a SELECT query based on the current ModelCriteria + * and format the list of results with the current formatter + * By default, returns an array of model objects + * + * @param PropelPDO $con an optional connection object + * + * @return PropelObjectCollection|array|mixed the list of results, formatted by the current formatter + */ + public function find($con = null) + { + $criteria = $this->isKeepQuery() ? clone $this : $this; + $stmt = $criteria->getSelectStatement($con); + + return $criteria->getFormatter()->init($criteria)->format($stmt); + } + + /** + * Issue a SELECT ... LIMIT 1 query based on the current ModelCriteria + * and format the result with the current formatter + * By default, returns a model object + * + * @param PropelPDO $con an optional connection object + * + * @return mixed the result, formatted by the current formatter + */ + public function findOne($con = null) + { + $criteria = $this->isKeepQuery() ? clone $this : $this; + $criteria->limit(1); + $stmt = $criteria->getSelectStatement($con); + + return $criteria->getFormatter()->init($criteria)->formatOne($stmt); + } + + /** + * Issue a SELECT ... LIMIT 1 query based on the current ModelCriteria + * and format the result with the current formatter + * By default, returns a model object + * + * @param PropelPDO $con an optional connection object + * + * @return mixed the result, formatted by the current formatter + */ + public function findOneOrCreate($con = null) + { + $criteria = $this->isKeepQuery() ? clone $this : $this; + $criteria->limit(1); + $stmt = $criteria->getSelectStatement($con); + if (!$ret = $this->findOne($con)) { + $class = $this->getModelName(); + $obj = new $class(); + foreach ($this->keys() as $key) { + $obj->setByName($key, $this->getValue($key), BasePeer::TYPE_COLNAME); + } + $ret = $this->getFormatter()->formatRecord($obj); + } + return $ret; + } + + /** + * Find object by primary key + * Behaves differently if the model has simple or composite primary key + * + * // simple primary key + * $book = $c->findPk(12, $con); + * // composite primary key + * $bookOpinion = $c->findPk(array(34, 634), $con); + * + * @param mixed $key Primary key to use for the query + * @param PropelPDO $con an optional connection object + * + * @return mixed the result, formatted by the current formatter + */ + public function findPk($key, $con = null) + { + $pkCols = $this->getTableMap()->getPrimaryKeyColumns(); + if (count($pkCols) == 1) { + // simple primary key + $pkCol = $pkCols[0]; + $this->add($pkCol->getFullyQualifiedName(), $key); + return $this->findOne($con); + } else { + // composite primary key + foreach ($pkCols as $pkCol) { + $keyPart = array_shift($key); + $this->add($pkCol->getFullyQualifiedName(), $keyPart); + } + return $this->findOne($con); + } + } + + /** + * Find objects by primary key + * Behaves differently if the model has simple or composite primary key + * + * // simple primary key + * $books = $c->findPks(array(12, 56, 832), $con); + * // composite primary key + * $bookOpinion = $c->findPks(array(array(34, 634), array(45, 518), array(34, 765)), $con); + * + * @param array $keys Primary keys to use for the query + * @param PropelPDO $con an optional connection object + * + * @return mixed the list of results, formatted by the current formatter + */ + public function findPks($keys, $con = null) + { + $pkCols = $this->getTableMap()->getPrimaryKeyColumns(); + if (count($pkCols) == 1) { + // simple primary key + $pkCol = array_shift($pkCols); + $this->add($pkCol->getFullyQualifiedName(), $keys, Criteria::IN); + } else { + // composite primary key + throw new PropelException('Multiple object retrieval is not implemented for composite primary keys'); + } + return $this->find($con); + } + + protected function getSelectStatement($con = null) + { + $dbMap = Propel::getDatabaseMap($this->getDbName()); + $db = Propel::getDB($this->getDbName()); + if ($con === null) { + $con = Propel::getConnection($this->getDbName(), Propel::CONNECTION_READ); + } + + // check that the columns of the main class are already added (if this is the primary ModelCriteria) + if (!$this->hasSelectClause() && !$this->getPrimaryCriteria()) { + $this->addSelfSelectColumns(); + } + + $con->beginTransaction(); + try { + $this->basePreSelect($con); + $params = array(); + $sql = BasePeer::createSelectSql($this, $params); + $stmt = $con->prepare($sql); + BasePeer::populateStmtValues($stmt, $params, $dbMap, $db); + $stmt->execute(); + $con->commit(); + } catch (Exception $e) { + if ($stmt) { + $stmt = null; // close + } + $con->rollBack(); + Propel::log($e->getMessage(), Propel::LOG_ERR); + throw new PropelException(sprintf('Unable to execute SELECT statement [%s]', $sql), $e); + } + + return $stmt; + } + + /** + * Apply a condition on a column and issues the SELECT query + * + * @see filterBy() + * @see find() + * + * @param string $column A string representing the column phpName, e.g. 'AuthorId' + * @param mixed $value A value for the condition + * @param PropelPDO $con An optional connection object + * + * @return mixed the list of results, formatted by the current formatter + */ + public function findBy($column, $value, $con = null) + { + $method = 'filterBy' . $column; + $this->$method($value); + + return $this->find($con); + } + + /** + * Apply a list of conditions on columns and issues the SELECT query + * + * $c->findByArray(array( + * 'Title' => 'War And Peace', + * 'Publisher' => $publisher + * ), $con); + * + * + * @see filterByArray() + * @see find() + * + * @param mixed $conditions An array of conditions, using column phpNames as key + * @param PropelPDO $con an optional connection object + * + * @return mixed the list of results, formatted by the current formatter + */ + public function findByArray($conditions, $con = null) + { + $this->filterByArray($conditions); + + return $this->find($con); + } + + /** + * Apply a condition on a column and issues the SELECT ... LIMIT 1 query + * + * @see filterBy() + * @see findOne() + * + * @param mixed $column A string representing thecolumn phpName, e.g. 'AuthorId' + * @param mixed $value A value for the condition + * @param PropelPDO $con an optional connection object + * + * @return mixed the result, formatted by the current formatter + */ + public function findOneBy($column, $value, $con = null) + { + $method = 'filterBy' . $column; + $this->$method($value); + + return $this->findOne($con); + } + + /** + * Apply a list of conditions on columns and issues the SELECT ... LIMIT 1 query + * + * $c->findOneByArray(array( + * 'Title' => 'War And Peace', + * 'Publisher' => $publisher + * ), $con); + * + * + * @see filterByArray() + * @see findOne() + * + * @param mixed $conditions An array of conditions, using column phpNames as key + * @param PropelPDO $con an optional connection object + * + * @return mixed the list of results, formatted by the current formatter + */ + public function findOneByArray($conditions, $con = null) + { + $this->filterByArray($conditions); + + return $this->findOne($con); + } + + /** + * Issue a SELECT COUNT(*) query based on the current ModelCriteria + * + * @param PropelPDO $con an optional connection object + * + * @return integer the number of results + */ + public function count($con = null) + { + if ($con === null) { + $con = Propel::getConnection($this->getDbName(), Propel::CONNECTION_READ); + } + + $criteria = $this->isKeepQuery() ? clone $this : $this; + $criteria->setDbName($this->getDbName()); // Set the correct dbName + $criteria->clearOrderByColumns(); // ORDER BY won't ever affect the count + + // We need to set the primary table name, since in the case that there are no WHERE columns + // it will be impossible for the BasePeer::createSelectSql() method to determine which + // tables go into the FROM clause. + $criteria->setPrimaryTableName(constant($this->modelPeerName.'::TABLE_NAME')); + + $stmt = $criteria->getCountStatement($con); + if ($row = $stmt->fetch(PDO::FETCH_NUM)) { + $count = (int) $row[0]; + } else { + $count = 0; // no rows returned; we infer that means 0 matches. + } + $stmt->closeCursor(); + + return $count; + } + + protected function getCountStatement($con = null) + { + $dbMap = Propel::getDatabaseMap($this->getDbName()); + $db = Propel::getDB($this->getDbName()); + if ($con === null) { + $con = Propel::getConnection($this->getDbName(), Propel::CONNECTION_READ); + } + + // check that the columns of the main class are already added (if this is the primary ModelCriteria) + if (!$this->hasSelectClause() && !$this->getPrimaryCriteria()) { + $this->addSelfSelectColumns(); + } + + $needsComplexCount = $this->getGroupByColumns() + || $this->getOffset() + || $this->getLimit() + || $this->getHaving() + || in_array(Criteria::DISTINCT, $this->getSelectModifiers()); + + $con->beginTransaction(); + try { + $this->basePreSelect($con); + $params = array(); + if ($needsComplexCount) { + if (BasePeer::needsSelectAliases($this)) { + if ($this->getHaving()) { + throw new PropelException('Propel cannot create a COUNT query when using HAVING and duplicate column names in the SELECT part'); + } + BasePeer::turnSelectColumnsToAliases($this); + } + $selectSql = BasePeer::createSelectSql($this, $params); + $sql = 'SELECT COUNT(*) FROM (' . $selectSql . ') propelmatch4cnt'; + } else { + // Replace SELECT columns with COUNT(*) + $this->clearSelectColumns()->addSelectColumn('COUNT(*)'); + $sql = BasePeer::createSelectSql($this, $params); + } + $stmt = $con->prepare($sql); + BasePeer::populateStmtValues($stmt, $params, $dbMap, $db); + $stmt->execute(); + $con->commit(); + } catch (PropelException $e) { + $con->rollback(); + throw $e; + } + + return $stmt; + } + + /** + * Issue a SELECT query based on the current ModelCriteria + * and uses a page and a maximum number of results per page + * to compute an offet and a limit. + * + * @param int $page number of the page to start the pager on. Page 1 means no offset + * @param int $maxPerPage maximum number of results per page. Determines the limit + * @param PropelPDO $con an optional connection object + * + * @return PropelModelPager a pager object, supporting iteration + */ + public function paginate($page = 1, $maxPerPage = 10, $con = null) + { + $criteria = $this->isKeepQuery() ? clone $this : $this; + $pager = new PropelModelPager($criteria, $maxPerPage); + $pager->setPage($page); + $pager->init(); + + return $pager; + } + + /** + * Code to execute before every DELETE statement + * + * @param PropelPDO $con The connection object used by the query + */ + protected function basePreDelete(PropelPDO $con) + { + return $this->preDelete($con); + } + + protected function preDelete(PropelPDO $con) + { + } + + /** + * Code to execute after every DELETE statement + * + * @param int $affectedRows the number of deleted rows + * @param PropelPDO $con The connection object used by the query + */ + protected function basePostDelete($affectedRows, PropelPDO $con) + { + return $this->postDelete($affectedRows, $con); + } + + protected function postDelete($affectedRows, PropelPDO $con) + { + } + + /** + * Issue a DELETE query based on the current ModelCriteria + * An optional hook on basePreDelete() can prevent the actual deletion + * + * @param PropelPDO $con an optional connection object + * + * @return integer the number of deleted rows + */ + public function delete($con = null) + { + if (count($this->getMap()) == 0) { + throw new PropelException('delete() expects a Criteria with at least one condition. Use deleteAll() to delete all the rows of a table'); + } + + if ($con === null) { + $con = Propel::getConnection($this->getDbName(), Propel::CONNECTION_READ); + } + + $criteria = $this->isKeepQuery() ? clone $this : $this; + $criteria->setDbName($this->getDbName()); + + $con->beginTransaction(); + try { + if(!$affectedRows = $criteria->basePreDelete($con)) { + $affectedRows = $criteria->doDelete($con); + } + $criteria->basePostDelete($affectedRows, $con); + $con->commit(); + } catch (PropelException $e) { + $con->rollback(); + throw $e; + } + + return $affectedRows; + } + + /** + * Issue a DELETE query based on the current ModelCriteria + * This method is called by ModelCriteria::delete() inside a transaction + * + * @param PropelPDO $con a connection object + * + * @return integer the number of deleted rows + */ + public function doDelete($con) + { + $affectedRows = call_user_func(array($this->modelPeerName, 'doDelete'), $this, $con); + + return $affectedRows; + } + + /** + * Issue a DELETE query based on the current ModelCriteria deleting all rows in the table + * An optional hook on basePreDelete() can prevent the actual deletion + * + * @param PropelPDO $con an optional connection object + * + * @return integer the number of deleted rows + */ + public function deleteAll($con = null) + { + if ($con === null) { + $con = Propel::getConnection($this->getDbName(), Propel::CONNECTION_WRITE); + } + $con->beginTransaction(); + try { + if(!$affectedRows = $this->basePreDelete($con)) { + $affectedRows = $this->doDeleteAll($con); + } + $this->basePostDelete($affectedRows, $con); + $con->commit(); + return $affectedRows; + } catch (PropelException $e) { + $con->rollBack(); + throw $e; + } + + return $affectedRows; + } + + /** + * Issue a DELETE query based on the current ModelCriteria deleting all rows in the table + * This method is called by ModelCriteria::deleteAll() inside a transaction + * + * @param PropelPDO $con a connection object + * + * @return integer the number of deleted rows + */ + public function doDeleteAll($con) + { + $affectedRows = call_user_func(array($this->modelPeerName, 'doDeleteAll'), $con); + + return $affectedRows; + } + + /** + * Code to execute before every UPDATE statement + * + * @param array $values The associatiove array of columns and values for the update + * @param PropelPDO $con The connection object used by the query + * @param boolean $forceIndividualSaves If false (default), the resulting call is a BasePeer::doUpdate(), ortherwise it is a series of save() calls on all the found objects + */ + protected function basePreUpdate(&$values, PropelPDO $con, $forceIndividualSaves = false) + { + return $this->preUpdate($values, $con, $forceIndividualSaves); + } + + protected function preUpdate(&$values, PropelPDO $con, $forceIndividualSaves = false) + { + } + + /** + * Code to execute after every UPDATE statement + * + * @param int $affectedRows the number of updated rows + * @param PropelPDO $con The connection object used by the query + */ + protected function basePostUpdate($affectedRows, PropelPDO $con) + { + return $this->postUpdate($affectedRows, $con); + } + + protected function postUpdate($affectedRows, PropelPDO $con) + { + } + + /** + * Issue an UPDATE query based the current ModelCriteria and a list of changes. + * An optional hook on basePreUpdate() can prevent the actual update. + * Beware that behaviors based on hooks in the object's save() method + * will only be triggered if you force individual saves, i.e. if you pass true as second argument. + * + * @param array $values Associative array of keys and values to replace + * @param PropelPDO $con an optional connection object + * @param boolean $forceIndividualSaves If false (default), the resulting call is a BasePeer::doUpdate(), ortherwise it is a series of save() calls on all the found objects + * + * @return Integer Number of updated rows + */ + public function update($values, $con = null, $forceIndividualSaves = false) + { + if (!is_array($values)) { + throw new PropelException('set() expects an array as first argument'); + } + if (count($this->getJoins())) { + throw new PropelException('set() does not support multitable updates, please do not use join()'); + } + + if ($con === null) { + $con = Propel::getConnection($this->getDbName(), Propel::CONNECTION_WRITE); + } + + $criteria = $this->isKeepQuery() ? clone $this : $this; + $criteria->setPrimaryTableName(constant($this->modelPeerName.'::TABLE_NAME')); + + $con->beginTransaction(); + try { + + if(!$affectedRows = $criteria->basePreUpdate($values, $con, $forceIndividualSaves)) { + $affectedRows = $criteria->doUpdate($values, $con, $forceIndividualSaves); + } + $criteria->basePostUpdate($affectedRows, $con); + + $con->commit(); + } catch (PropelException $e) { + $con->rollBack(); + throw $e; + } + + return $affectedRows; + } + + /** + * Issue an UPDATE query based the current ModelCriteria and a list of changes. + * This method is called by ModelCriteria::update() inside a transaction. + * + * @param array $values Associative array of keys and values to replace + * @param PropelPDO $con a connection object + * @param boolean $forceIndividualSaves If false (default), the resulting call is a BasePeer::doUpdate(), ortherwise it is a series of save() calls on all the found objects + * + * @return Integer Number of updated rows + */ + public function doUpdate($values, $con, $forceIndividualSaves = false) + { + if($forceIndividualSaves) { + + // Update rows one by one + $objects = $this->setFormatter(ModelCriteria::FORMAT_OBJECT)->find($con); + foreach ($objects as $object) { + foreach ($values as $key => $value) { + $object->setByName($key, $value); + } + } + $objects->save($con); + $affectedRows = count($objects); + + } else { + + // update rows in a single query + $set = new Criteria(); + foreach ($values as $columnName => $value) { + $realColumnName = $this->getTableMap()->getColumnByPhpName($columnName)->getFullyQualifiedName(); + $set->add($realColumnName, $value); + } + $affectedRows = BasePeer::doUpdate($this, $set, $con); + call_user_func(array($this->modelPeerName, 'clearInstancePool')); + call_user_func(array($this->modelPeerName, 'clearRelatedInstancePool')); + } + + return $affectedRows; + } + + /** + * Creates a Criterion object based on a list of existing condition names and a comparator + * + * @param array $conditions The list of condition names, e.g. array('cond1', 'cond2') + * @param string $comparator A comparator, Criteria::LOGICAL_AND (default) or Criteria::LOGICAL_OR + * + * @return Criterion a Criterion or ModelCriterion object + */ + protected function getCriterionForConditions($conditions, $comparator = null) + { + $comparator = (null === $comparator) ? Criteria::LOGICAL_AND : $comparator; + $this->combine($conditions, $comparator, 'propel_temp_name'); + $criterion = $this->namedCriterions['propel_temp_name']; + unset($this->namedCriterions['propel_temp_name']); + + return $criterion; + } + + /** + * Creates a Criterion object based on a SQL clause and a value + * Uses introspection to translate the column phpName into a fully qualified name + * + * @param string $clause The pseudo SQL clause, e.g. 'AuthorId = ?' + * @param mixed $value A value for the condition + * + * @return Criterion a Criterion or ModelCriterion object + */ + protected function getCriterionForClause($clause, $value) + { + $clause = trim($clause); + if($this->replaceNames($clause)) { + // at least one column name was found and replaced in the clause + // this is enough to determine the type to bind the parameter to + if (preg_match('/IN \?$/i', $clause) !== 0) { + $operator = ModelCriteria::MODEL_CLAUSE_ARRAY; + } elseif (preg_match('/LIKE \?$/i', $clause) !== 0) { + $operator = ModelCriteria::MODEL_CLAUSE_LIKE; + } elseif (substr_count($clause, '?') > 1) { + $operator = ModelCriteria::MODEL_CLAUSE_SEVERAL; + } else { + $operator = ModelCriteria::MODEL_CLAUSE; + } + $criterion = new ModelCriterion($this, $this->replacedColumns[0], $value, $operator, $clause); + if ($this->currentAlias != '') { + $criterion->setTable($this->currentAlias); + } + } else { + // no column match in clause, must be an expression like '1=1' + if (strpos($clause, '?') !== false) { + throw new PropelException("Cannot determine the column to bind to the parameter in clause '$clause'"); + } + $criterion = new Criterion($this, null, $clause, Criteria::CUSTOM); + } + return $criterion; + } + + /** + * Replaces complete column names (like Article.AuthorId) in an SQL clause + * by their exact Propel column fully qualified name (e.g. article.AUTHOR_ID) + * but ignores the column names inside quotes + * + * Note: if you know a way to do so in one step, and in an efficient way, I'm interested :) + * + * @param string $clause SQL clause to inspect (modified by the method) + * + * @return boolean Whether the method managed to find and replace at least one column name + */ + protected function replaceNames(&$clause) + { + $this->replacedColumns = array(); + $this->currentAlias = ''; + $this->foundMatch = false; + $regexp = <<foundMatch; + } + + /** + * Callback function to replace expressions containing column names with expressions using the real column names + * Handles strings properly + * e.g. 'CONCAT(Book.Title, "Book.Title") = ?' + * => 'CONCAT(book.TITLE, "Book.Title") = ?' + * + * @param array $matches Matches found by preg_replace_callback + * + * @return string the expression replacement + */ + protected function doReplaceName($matches) + { + if(!$matches[0]) { + return ''; + } + // replace names only in expressions, not in strings delimited by quotes + return $matches[1] . preg_replace_callback('/\w+\.\w+/', array($this, 'doReplaceNameInExpression'), $matches[2]); + } + + /** + * Callback function to replace column names by their real name in a clause + * e.g. 'Book.Title IN ?' + * => 'book.TITLE IN ?' + * + * @param array $matches Matches found by preg_replace_callback + * + * @return string the column name replacement + */ + protected function doReplaceNameInExpression($matches) + { + $key = $matches[0]; + list($column, $realColumnName) = $this->getColumnFromName($key); + if ($column instanceof ColumnMap) { + $this->replacedColumns[]= $column; + $this->foundMatch = true; + return $realColumnName; + } else { + return $key; + } + } + + /** + * Finds a column and a SQL translation for a pseudo SQL column name + * Respects table aliases previously registered in a join() or addAlias() + * Examples: + * + * $c->getColumnFromName('Book.Title'); + * => array($bookTitleColumnMap, 'book.TITLE') + * $c->join('Book.Author a') + * ->getColumnFromName('a.FirstName'); + * => array($authorFirstNameColumnMap, 'a.FIRST_NAME') + * + * + * @param string $phpName String representing the column name in a pseudo SQL clause, e.g. 'Book.Title' + * + * @return array List($columnMap, $realColumnName) + */ + protected function getColumnFromName($phpName, $failSilently = true) + { + if (strpos($phpName, '.') === false) { + $class = $this->getModelAliasOrName(); + } else { + list($class, $phpName) = explode('.', $phpName); + } + + if ($class == $this->getModelAliasOrName()) { + // column of the Criteria's model + $tableMap = $this->getTableMap(); + } elseif (isset($this->joins[$class])) { + // column of a relations's model + $tableMap = $this->joins[$class]->getTableMap(); + } else { + if ($failSilently) { + return array(null, null); + } else { + throw new PropelException('Unknown model or alias ' . $class); + } + } + + if ($tableMap->hasColumnByPhpName($phpName)) { + $column = $tableMap->getColumnByPhpName($phpName); + if (isset($this->aliases[$class])) { + $this->currentAlias = $class; + $realColumnName = $class . '.' . $column->getName(); + } else { + $realColumnName = $column->getFullyQualifiedName(); + } + return array($column, $realColumnName); + } elseif (isset($this->asColumns[$phpName])) { + // aliased column + return array(null, $phpName); + } else { + if ($failSilently) { + return array(null, null); + } else { + throw new PropelException('Unknown column ' . $phpName . ' on model or alias ' . $class); + } + } + } + + /** + * Return a fully qualified column name corresponding to a simple column phpName + * Uses model alias if it exists + * Warning: restricted to the columns of the main model + * e.g. => 'Title' => 'book.TITLE' + * + * @param string $columnName the Column phpName, without the table name + * + * @return string the fully qualified column name + */ + protected function getRealColumnName($columnName) + { + if (!$this->getTableMap()->hasColumnByPhpName($columnName)) { + throw new PropelException('Unkown column ' . $columnName . ' in model ' . $this->modelName); + } + if ($this->useAliasInSQL) { + return $this->modelAlias . '.' . $this->getTableMap()->getColumnByPhpName($columnName)->getName(); + } else { + return $this->getTableMap()->getColumnByPhpName($columnName)->getFullyQualifiedName(); + } + } + + /** + * Changes the table part of a a fully qualified column name if a true model alias exists + * e.g. => 'book.TITLE' => 'b.TITLE' + * This is for use as first argument of Criteria::add() + * + * @param string $colName the fully qualified column name, e.g 'book.TITLE' or BookPeer::TITLE + * + * @return string the fully qualified column name, using table alias if applicatble + */ + public function getAliasedColName($colName) + { + if ($this->useAliasInSQL) { + return $this->modelAlias . substr($colName, strpos($colName, '.')); + } else { + return $colName; + } + } + + /** + * Overrides Criteria::add() to force the use of a true table alias if it exists + * + * @see Criteria::add() + * @param string $column The colName of column to run the comparison on (e.g. BookPeer::ID) + * @param mixed $value + * @param string $comparison A String. + * + * @return ModelCriteria A modified Criteria object. + */ + public function addUsingAlias($p1, $value = null, $comparison = null) + { + $key = $this->getAliasedColName($p1); + return $this->containsKey($key) ? $this->addAnd($key, $value, $comparison) : $this->add($key, $value, $comparison); + } + + /** + * Get all the parameters to bind to this criteria + * Does part of the job of BasePeer::createSelectSql() for the cache + * + * @return array list of parameters, each parameter being an array like + * array('table' => $realtable, 'column' => $column, 'value' => $value) + */ + public function getParams() + { + $params = array(); + $dbMap = Propel::getDatabaseMap($this->getDbName()); + + foreach ($this->getMap() as $criterion) { + + $table = null; + foreach ($criterion->getAttachedCriterion() as $attachedCriterion) { + $tableName = $attachedCriterion->getTable(); + + $table = $this->getTableForAlias($tableName); + if (null === $table) { + $table = $tableName; + } + + if (($this->isIgnoreCase() || $attachedCriterion->isIgnoreCase()) + && $dbMap->getTable($table)->getColumn($attachedCriterion->getColumn())->isText()) { + $attachedCriterion->setIgnoreCase(true); + } + } + + $sb = ''; + $criterion->appendPsTo($sb, $params); + } + + $having = $this->getHaving(); + if ($having !== null) { + $sb = ''; + $having->appendPsTo($sb, $params); + } + + return $params; + } + + /** + * Handle the magic + * Supports findByXXX(), findOneByXXX(), filterByXXX(), orderByXXX(), and groupByXXX() methods, + * where XXX is a column phpName. + * Supports XXXJoin(), where XXX is a join direction (in 'left', 'right', 'inner') + */ + public function __call($name, $arguments) + { + // Maybe it's a magic call to one of the methods supporting it, e.g. 'findByTitle' + static $methods = array('findBy', 'findOneBy', 'filterBy', 'orderBy', 'groupBy'); + foreach ($methods as $method) + { + if(strpos($name, $method) === 0) + { + $columns = substr($name, strlen($method)); + if(in_array($method, array('findBy', 'findOneBy')) && strpos($columns, 'And') !== false) { + $method = $method . 'Array'; + $columns = explode('And', $columns); + $conditions = array(); + foreach ($columns as $column) { + $conditions[$column] = array_shift($arguments); + } + array_unshift($arguments, $conditions); + } else { + array_unshift($arguments, $columns); + } + return call_user_func_array(array($this, $method), $arguments); + } + } + + // Maybe it's a magic call to a qualified joinWith method, e.g. 'leftJoinWith' or 'joinWithAuthor' + if(($pos = stripos($name, 'joinWith')) !== false) { + $type = substr($name, 0, $pos); + if(in_array($type, array('left', 'right', 'inner'))) { + $joinType = strtoupper($type) . ' JOIN'; + } else { + $joinType = Criteria::INNER_JOIN; + } + if(!$relation = substr($name, $pos + 8)) { + $relation = $arguments[0]; + } + return $this->joinWith($relation, $joinType); + } + + // Maybe it's a magic call to a qualified join method, e.g. 'leftJoin' + if(($pos = strpos($name, 'Join')) > 0) + { + $type = substr($name, 0, $pos); + if(in_array($type, array('left', 'right', 'inner'))) + { + $joinType = strtoupper($type) . ' JOIN'; + // Test if first argument is suplied, else don't provide an alias to joinXXX (default value) + if (!isset($arguments[0])) { + $arguments[0] = ''; + } + array_push($arguments, $joinType); + $method = substr($name, $pos); + // no lcfirst in php<5.3... + $method[0] = strtolower($method[0]); + return call_user_func_array(array($this, $method), $arguments); + } + } + + throw new PropelException(sprintf('Undefined method %s::%s()', __CLASS__, $name)); + } + + /** + * Ensures deep cloning of attached objects + */ + public function __clone() + { + parent::__clone(); + foreach ($this->with as $key => $join) { + $this->with[$key] = clone $join; + } + if (null !== $this->formatter) { + $this->formatter = clone $this->formatter; + } + } +} \ No newline at end of file diff --git a/library/propel/runtime/lib/query/ModelCriterion.php b/library/propel/runtime/lib/query/ModelCriterion.php new file mode 100644 index 000000000..beeeda79a --- /dev/null +++ b/library/propel/runtime/lib/query/ModelCriterion.php @@ -0,0 +1,283 @@ +value = $value; + if ($column instanceof ColumnMap) { + $this->column = $column->getName(); + $this->table = $column->getTable()->getName(); + } else { + $dotPos = strrpos($column,'.'); + if ($dotPos === false) { + // no dot => aliased column + $this->table = null; + $this->column = $column; + } else { + $this->table = substr($column, 0, $dotPos); + $this->column = substr($column, $dotPos+1, strlen($column)); + } + } + $this->comparison = ($comparison === null ? Criteria::EQUAL : $comparison); + $this->clause = $clause; + $this->init($outer); + } + + public function getClause() + { + return $this->clause; + } + + /** + * Figure out which MocelCriterion method to use + * to build the prepared statement and parameters using to the Criterion comparison + * and call it to append the prepared statement and the parameters of the current clause. + * For performance reasons, this method tests the cases of parent::dispatchPsHandling() + * first, and that is not possible through inheritance ; that's why the parent + * code is duplicated here. + * + * @param string &$sb The string that will receive the Prepared Statement + * @param array $params A list to which Prepared Statement parameters will be appended + */ + protected function dispatchPsHandling(&$sb, array &$params) + { + switch ($this->comparison) { + case Criteria::CUSTOM: + // custom expression with no parameter binding + $this->appendCustomToPs($sb, $params); + break; + case Criteria::IN: + case Criteria::NOT_IN: + // table.column IN (?, ?) or table.column NOT IN (?, ?) + $this->appendInToPs($sb, $params); + break; + case Criteria::LIKE: + case Criteria::NOT_LIKE: + case Criteria::ILIKE: + case Criteria::NOT_ILIKE: + // table.column LIKE ? or table.column NOT LIKE ? (or ILIKE for Postgres) + $this->appendLikeToPs($sb, $params); + break; + case ModelCriteria::MODEL_CLAUSE: + // regular model clause, e.g. 'book.TITLE = ?' + $this->appendModelClauseToPs($sb, $params); + break; + case ModelCriteria::MODEL_CLAUSE_LIKE: + // regular model clause, e.g. 'book.TITLE = ?' + $this->appendModelClauseLikeToPs($sb, $params); + break; + case ModelCriteria::MODEL_CLAUSE_SEVERAL: + // Ternary model clause, e.G 'book.ID BETWEEN ? AND ?' + $this->appendModelClauseSeveralToPs($sb, $params); + break; + case ModelCriteria::MODEL_CLAUSE_ARRAY: + // IN or NOT IN model clause, e.g. 'book.TITLE NOT IN ?' + $this->appendModelClauseArrayToPs($sb, $params); + break; + default: + // table.column = ? or table.column >= ? etc. (traditional expressions, the default) + $this->appendBasicToPs($sb, $params); + + } + } + + /** + * Appends a Prepared Statement representation of the ModelCriterion onto the buffer + * For regular model clauses, e.g. 'book.TITLE = ?' + * + * @param string &$sb The string that will receive the Prepared Statement + * @param array $params A list to which Prepared Statement parameters will be appended + */ + public function appendModelClauseToPs(&$sb, array &$params) + { + if ($this->value !== null) { + $params[] = array('table' => $this->realtable, 'column' => $this->column, 'value' => $this->value); + $sb .= str_replace('?', ':p'.count($params), $this->clause); + } else { + $sb .= $this->clause; + } + } + + /** + * Appends a Prepared Statement representation of the ModelCriterion onto the buffer + * For LIKE model clauses, e.g. 'book.TITLE LIKE ?' + * Handles case insensitivity for VARCHAR columns + * + * @param string &$sb The string that will receive the Prepared Statement + * @param array $params A list to which Prepared Statement parameters will be appended + */ + public function appendModelClauseLikeToPs(&$sb, array &$params) + { + // LIKE is case insensitive in mySQL and SQLite, but not in PostGres + // If the column is case insensitive, use ILIKE / NOT ILIKE instead of LIKE / NOT LIKE + if ($this->ignoreStringCase && $this->getDb() instanceof DBPostgres) { + $this->clause = preg_replace('/LIKE \?$/i', 'ILIKE ?', $this->clause); + } + $this->appendModelClauseToPs($sb, $params); + } + + /** + * Appends a Prepared Statement representation of the ModelCriterion onto the buffer + * For ternary model clauses, e.G 'book.ID BETWEEN ? AND ?' + * + * @param string &$sb The string that will receive the Prepared Statement + * @param array $params A list to which Prepared Statement parameters will be appended + */ + public function appendModelClauseSeveralToPs(&$sb, array &$params) + { + $clause = $this->clause; + foreach ((array) $this->value as $value) { + if ($value === null) { + // FIXME we eventually need to translate a BETWEEN to + // something like WHERE (col < :p1 OR :p1 IS NULL) AND (col < :p2 OR :p2 IS NULL) + // in order to support null values + throw new PropelException('Null values are not supported inside BETWEEN clauses'); + } + $params[] = array('table' => $this->realtable, 'column' => $this->column, 'value' => $value); + $clause = self::strReplaceOnce('?', ':p'.count($params), $clause); + } + $sb .= $clause; + } + + /** + * Appends a Prepared Statement representation of the ModelCriterion onto the buffer + * For IN or NOT IN model clauses, e.g. 'book.TITLE NOT IN ?' + * + * @param string &$sb The string that will receive the Prepared Statement + * @param array $params A list to which Prepared Statement parameters will be appended + */ + public function appendModelClauseArrayToPs(&$sb, array &$params) + { + $_bindParams = array(); // the param names used in query building + $_idxstart = count($params); + $valuesLength = 0; + foreach ( (array) $this->value as $value ) { + $valuesLength++; // increment this first to correct for wanting bind params to start with :p1 + $params[] = array('table' => $this->realtable, 'column' => $this->column, 'value' => $value); + $_bindParams[] = ':p'.($_idxstart + $valuesLength); + } + if ($valuesLength !== 0) { + $sb .= str_replace('?', '(' . implode(',', $_bindParams) . ')', $this->clause); + } else { + $sb .= (stripos($this->clause, ' NOT IN ') === false) ? "1<>1" : "1=1"; + } + unset ( $value, $valuesLength ); + } + + /** + * This method checks another Criteria to see if they contain + * the same attributes and hashtable entries. + * @return boolean + */ + public function equals($obj) + { + // TODO: optimize me with early outs + if ($this === $obj) { + return true; + } + + if (($obj === null) || !($obj instanceof ModelCriterion)) { + return false; + } + + $crit = $obj; + + $isEquiv = ( ( ($this->table === null && $crit->getTable() === null) + || ( $this->table !== null && $this->table === $crit->getTable() ) + ) + && $this->clause === $crit->getClause() + && $this->column === $crit->getColumn() + && $this->comparison === $crit->getComparison()); + + // check chained criterion + + $clausesLength = count($this->clauses); + $isEquiv &= (count($crit->getClauses()) == $clausesLength); + $critConjunctions = $crit->getConjunctions(); + $critClauses = $crit->getClauses(); + for ($i=0; $i < $clausesLength && $isEquiv; $i++) { + $isEquiv &= ($this->conjunctions[$i] === $critConjunctions[$i]); + $isEquiv &= ($this->clauses[$i] === $critClauses[$i]); + } + + if ($isEquiv) { + $isEquiv &= $this->value === $crit->getValue(); + } + + return $isEquiv; + } + + /** + * Returns a hash code value for the object. + */ + public function hashCode() + { + $h = crc32(serialize($this->value)) ^ crc32($this->comparison) ^ crc32($this->clause); + + if ($this->table !== null) { + $h ^= crc32($this->table); + } + + if ($this->column !== null) { + $h ^= crc32($this->column); + } + + foreach ( $this->clauses as $clause ) { + // TODO: i KNOW there is a php incompatibility with the following line + // but i dont remember what it is, someone care to look it up and + // replace it if it doesnt bother us? + // $clause->appendPsTo($sb='',$params=array()); + $sb = ''; + $params = array(); + $clause->appendPsTo($sb,$params); + $h ^= crc32(serialize(array($sb,$params))); + unset ( $sb, $params ); + } + + return $h; + } + + /** + * Replace only once + * taken from http://www.php.net/manual/en/function.str-replace.php + * + */ + protected static function strReplaceOnce($search, $replace, $subject) + { + $firstChar = strpos($subject, $search); + if($firstChar !== false) { + $beforeStr = substr($subject,0,$firstChar); + $afterStr = substr($subject, $firstChar + strlen($search)); + return $beforeStr.$replace.$afterStr; + } else { + return $subject; + } + } +} \ No newline at end of file diff --git a/library/propel/runtime/lib/query/ModelJoin.php b/library/propel/runtime/lib/query/ModelJoin.php new file mode 100644 index 000000000..59bbfce5d --- /dev/null +++ b/library/propel/runtime/lib/query/ModelJoin.php @@ -0,0 +1,166 @@ +getLeftColumns(); + $rightCols = $relationMap->getRightColumns(); + $nbColumns = $relationMap->countColumnMappings(); + for ($i=0; $i < $nbColumns; $i++) { + $leftColName = ($leftTableAlias ? $leftTableAlias : $leftCols[$i]->getTableName()) . '.' . $leftCols[$i]->getName(); + $rightColName = ($relationAlias ? $relationAlias : $rightCols[$i]->getTableName()) . '.' . $rightCols[$i]->getName(); + $this->addCondition($leftColName, $rightColName, Criteria::EQUAL); + } + $this->relationMap = $relationMap; + $this->leftTableAlias = $leftTableAlias; + $this->relationAlias = $relationAlias; + + return $this; + } + + public function getRelationMap() + { + return $this->relationMap; + } + + /** + * Sets the right tableMap for this join + * + * @param TableMap $tableMap The table map to use + * + * @return ModelJoin The current join object, for fluid interface + */ + public function setTableMap(TableMap $tableMap) + { + $this->tableMap = $tableMap; + + return $this; + } + + /** + * Gets the right tableMap for this join + * + * @return TableMap The table map + */ + public function getTableMap() + { + if (null === $this->tableMap && null !== $this->relationMap) + { + $this->tableMap = $this->relationMap->getRightTable(); + } + return $this->tableMap; + } + + public function setPreviousJoin(ModelJoin $join) + { + $this->previousJoin = $join; + + return $this; + } + + public function getPreviousJoin() + { + return $this->previousJoin; + } + + public function isPrimary() + { + return null === $this->previousJoin; + } + + public function setLeftTableAlias($leftTableAlias) + { + $this->leftTableAlias = $leftTableAlias; + + return $this; + } + + public function getLeftTableAlias() + { + return $this->leftTableAlias; + } + + public function hasLeftTableAlias() + { + return null !== $this->leftTableAlias; + } + + public function setRelationAlias($relationAlias) + { + $this->relationAlias = $relationAlias; + + return $this; + } + + public function getRelationAlias() + { + return $this->relationAlias; + } + + public function hasRelationAlias() + { + return null !== $this->relationAlias; + } + + /** + * This method returns the last related, but already hydrated object up until this join + * Starting from $startObject and continuously calling the getters to get + * to the base object for the current join. + * + * This method only works if PreviousJoin has been defined, + * which only happens when you provide dotted relations when calling join + * + * @param Object $startObject the start object all joins originate from and which has already hydrated + * @return Object the base Object of this join + */ + public function getObjectToRelate($startObject) + { + if($this->isPrimary()) { + return $startObject; + } else { + $previousJoin = $this->getPreviousJoin(); + $previousObject = $previousJoin->getObjectToRelate($startObject); + $method = 'get' . $previousJoin->getRelationMap()->getName(); + return $previousObject->$method(); + } + } + + public function equals($join) + { + return parent::equals($join) + && $this->relationMap == $join->getRelationMap() + && $this->previousJoin == $join->getPreviousJoin() + && $this->relationAlias == $join->getRelationAlias(); + } + + public function __toString() + { + return parent::toString() + . ' tableMap: ' . ($this->tableMap ? get_class($this->tableMap) : 'null') + . ' relationMap: ' . $this->relationMap->getName() + . ' previousJoin: ' . ($this->previousJoin ? '(' . $this->previousJoin . ')' : 'null') + . ' relationAlias: ' . $this->relationAlias; + } +} + \ No newline at end of file diff --git a/library/propel/runtime/lib/query/PropelQuery.php b/library/propel/runtime/lib/query/PropelQuery.php new file mode 100644 index 000000000..18f93c9b9 --- /dev/null +++ b/library/propel/runtime/lib/query/PropelQuery.php @@ -0,0 +1,33 @@ +setModelAlias($alias); + } + return $query; + } +} diff --git a/library/propel/runtime/lib/util/BasePeer.php b/library/propel/runtime/lib/util/BasePeer.php new file mode 100644 index 000000000..0e6c91254 --- /dev/null +++ b/library/propel/runtime/lib/util/BasePeer.php @@ -0,0 +1,1099 @@ + (Propel) + * @author Kaspars Jaudzems (Propel) + * @author Heltem (Propel) + * @author Frank Y. Kim (Torque) + * @author John D. McNally (Torque) + * @author Brett McLaughlin (Torque) + * @author Stephen Haberman (Torque) + * @version $Revision: 1772 $ + * @package propel.runtime.util + */ +class BasePeer +{ + + /** Array (hash) that contains the cached mapBuilders. */ + private static $mapBuilders = array(); + + /** Array (hash) that contains cached validators */ + private static $validatorMap = array(); + + /** + * phpname type + * e.g. 'AuthorId' + */ + const TYPE_PHPNAME = 'phpName'; + + /** + * studlyphpname type + * e.g. 'authorId' + */ + const TYPE_STUDLYPHPNAME = 'studlyPhpName'; + + /** + * column (peer) name type + * e.g. 'book.AUTHOR_ID' + */ + const TYPE_COLNAME = 'colName'; + + /** + * column part of the column peer name + * e.g. 'AUTHOR_ID' + */ + const TYPE_RAW_COLNAME = 'rawColName'; + + /** + * column fieldname type + * e.g. 'author_id' + */ + const TYPE_FIELDNAME = 'fieldName'; + + /** + * num type + * simply the numerical array index, e.g. 4 + */ + const TYPE_NUM = 'num'; + + static public function getFieldnames ($classname, $type = self::TYPE_PHPNAME) { + + // TODO we should take care of including the peer class here + + $peerclass = 'Base' . $classname . 'Peer'; // TODO is this always true? + $callable = array($peerclass, 'getFieldnames'); + + return call_user_func($callable, $type); + } + + static public function translateFieldname($classname, $fieldname, $fromType, $toType) { + + // TODO we should take care of including the peer class here + + $peerclass = 'Base' . $classname . 'Peer'; // TODO is this always true? + $callable = array($peerclass, 'translateFieldname'); + $args = array($fieldname, $fromType, $toType); + + return call_user_func_array($callable, $args); + } + + /** + * Method to perform deletes based on values and keys in a + * Criteria. + * + * @param Criteria $criteria The criteria to use. + * @param PropelPDO $con A PropelPDO connection object. + * @return int The number of rows affected by last statement execution. For most + * uses there is only one delete statement executed, so this number + * will correspond to the number of rows affected by the call to this + * method. Note that the return value does require that this information + * is returned (supported) by the PDO driver. + * @throws PropelException + */ + public static function doDelete(Criteria $criteria, PropelPDO $con) + { + $db = Propel::getDB($criteria->getDbName()); + $dbMap = Propel::getDatabaseMap($criteria->getDbName()); + + // Set up a list of required tables (one DELETE statement will + // be executed per table) + $tables = $criteria->getTablesColumns(); + if (empty($tables)) { + throw new PropelException("Cannot delete from an empty Criteria"); + } + + $affectedRows = 0; // initialize this in case the next loop has no iterations. + + foreach ($tables as $tableName => $columns) { + + $whereClause = array(); + $params = array(); + $stmt = null; + try { + $sql = 'DELETE '; + if ($queryComment = $criteria->getComment()) { + $sql .= '/* ' . $queryComment . ' */ '; + } + if ($realTableName = $criteria->getTableForAlias($tableName)) { + if ($db->useQuoteIdentifier()) { + $realTableName = $db->quoteIdentifierTable($realTableName); + } + $sql .= $tableName . ' FROM ' . $realTableName . ' AS ' . $tableName; + } else { + if ($db->useQuoteIdentifier()) { + $tableName = $db->quoteIdentifierTable($tableName); + } + $sql .= 'FROM ' . $tableName; + } + + foreach ($columns as $colName) { + $sb = ""; + $criteria->getCriterion($colName)->appendPsTo($sb, $params); + $whereClause[] = $sb; + } + $sql .= " WHERE " . implode(" AND ", $whereClause); + + $stmt = $con->prepare($sql); + self::populateStmtValues($stmt, $params, $dbMap, $db); + $stmt->execute(); + $affectedRows = $stmt->rowCount(); + } catch (Exception $e) { + Propel::log($e->getMessage(), Propel::LOG_ERR); + throw new PropelException(sprintf('Unable to execute DELETE statement [%s]', $sql), $e); + } + + } // for each table + + return $affectedRows; + } + + /** + * Method to deletes all contents of specified table. + * + * This method is invoked from generated Peer classes like this: + * + * public static function doDeleteAll($con = null) + * { + * if ($con === null) $con = Propel::getConnection(self::DATABASE_NAME); + * BasePeer::doDeleteAll(self::TABLE_NAME, $con, self::DATABASE_NAME); + * } + * + * + * @param string $tableName The name of the table to empty. + * @param PropelPDO $con A PropelPDO connection object. + * @param string $databaseName the name of the database. + * @return int The number of rows affected by the statement. Note + * that the return value does require that this information + * is returned (supported) by the Propel db driver. + * @throws PropelException - wrapping SQLException caught from statement execution. + */ + public static function doDeleteAll($tableName, PropelPDO $con, $databaseName = null) + { + try { + $db = Propel::getDB($databaseName); + if ($db->useQuoteIdentifier()) { + $tableName = $db->quoteIdentifierTable($tableName); + } + $sql = "DELETE FROM " . $tableName; + $stmt = $con->prepare($sql); + $stmt->execute(); + return $stmt->rowCount(); + } catch (Exception $e) { + Propel::log($e->getMessage(), Propel::LOG_ERR); + throw new PropelException(sprintf('Unable to execute DELETE ALL statement [%s]', $sql), $e); + } + } + + /** + * Method to perform inserts based on values and keys in a + * Criteria. + *

    + * If the primary key is auto incremented the data in Criteria + * will be inserted and the auto increment value will be returned. + *

    + * If the primary key is included in Criteria then that value will + * be used to insert the row. + *

    + * If no primary key is included in Criteria then we will try to + * figure out the primary key from the database map and insert the + * row with the next available id using util.db.IDBroker. + *

    + * If no primary key is defined for the table the values will be + * inserted as specified in Criteria and null will be returned. + * + * @param Criteria $criteria Object containing values to insert. + * @param PropelPDO $con A PropelPDO connection. + * @return mixed The primary key for the new row if (and only if!) the primary key + * is auto-generated. Otherwise will return null. + * @throws PropelException + */ + public static function doInsert(Criteria $criteria, PropelPDO $con) { + + // the primary key + $id = null; + + $db = Propel::getDB($criteria->getDbName()); + + // Get the table name and method for determining the primary + // key value. + $keys = $criteria->keys(); + if (!empty($keys)) { + $tableName = $criteria->getTableName( $keys[0] ); + } else { + throw new PropelException("Database insert attempted without anything specified to insert"); + } + + $dbMap = Propel::getDatabaseMap($criteria->getDbName()); + $tableMap = $dbMap->getTable($tableName); + $keyInfo = $tableMap->getPrimaryKeyMethodInfo(); + $useIdGen = $tableMap->isUseIdGenerator(); + //$keyGen = $con->getIdGenerator(); + + $pk = self::getPrimaryKey($criteria); + + // only get a new key value if you need to + // the reason is that a primary key might be defined + // but you are still going to set its value. for example: + // a join table where both keys are primary and you are + // setting both columns with your own values + + // pk will be null if there is no primary key defined for the table + // we're inserting into. + if ($pk !== null && $useIdGen && !$criteria->keyContainsValue($pk->getFullyQualifiedName()) && $db->isGetIdBeforeInsert()) { + try { + $id = $db->getId($con, $keyInfo); + } catch (Exception $e) { + throw new PropelException("Unable to get sequence id.", $e); + } + $criteria->add($pk->getFullyQualifiedName(), $id); + } + + try { + $adapter = Propel::getDB($criteria->getDBName()); + + $qualifiedCols = $criteria->keys(); // we need table.column cols when populating values + $columns = array(); // but just 'column' cols for the SQL + foreach ($qualifiedCols as $qualifiedCol) { + $columns[] = substr($qualifiedCol, strrpos($qualifiedCol, '.') + 1); + } + + // add identifiers + if ($adapter->useQuoteIdentifier()) { + $columns = array_map(array($adapter, 'quoteIdentifier'), $columns); + $tableName = $adapter->quoteIdentifierTable($tableName); + } + + $sql = 'INSERT INTO ' . $tableName + . ' (' . implode(',', $columns) . ')' + . ' VALUES ('; + // . substr(str_repeat("?,", count($columns)), 0, -1) . + for($p=1, $cnt=count($columns); $p <= $cnt; $p++) { + $sql .= ':p'.$p; + if ($p !== $cnt) $sql .= ','; + } + $sql .= ')'; + + $stmt = $con->prepare($sql); + self::populateStmtValues($stmt, self::buildParams($qualifiedCols, $criteria), $dbMap, $db); + $stmt->execute(); + + } catch (Exception $e) { + Propel::log($e->getMessage(), Propel::LOG_ERR); + throw new PropelException(sprintf('Unable to execute INSERT statement [%s]', $sql), $e); + } + + // If the primary key column is auto-incremented, get the id now. + if ($pk !== null && $useIdGen && $db->isGetIdAfterInsert()) { + try { + $id = $db->getId($con, $keyInfo); + } catch (Exception $e) { + throw new PropelException("Unable to get autoincrement id.", $e); + } + } + + return $id; + } + + /** + * Method used to update rows in the DB. Rows are selected based + * on selectCriteria and updated using values in updateValues. + *

    + * Use this method for performing an update of the kind: + *

    + * WHERE some_column = some value AND could_have_another_column = + * another value AND so on. + * + * @param $selectCriteria A Criteria object containing values used in where + * clause. + * @param $updateValues A Criteria object containing values used in set + * clause. + * @param PropelPDO $con The PropelPDO connection object to use. + * @return int The number of rows affected by last update statement. For most + * uses there is only one update statement executed, so this number + * will correspond to the number of rows affected by the call to this + * method. Note that the return value does require that this information + * is returned (supported) by the Propel db driver. + * @throws PropelException + */ + public static function doUpdate(Criteria $selectCriteria, Criteria $updateValues, PropelPDO $con) { + + $db = Propel::getDB($selectCriteria->getDbName()); + $dbMap = Propel::getDatabaseMap($selectCriteria->getDbName()); + + // Get list of required tables, containing all columns + $tablesColumns = $selectCriteria->getTablesColumns(); + if (empty($tablesColumns)) { + $tablesColumns = array($selectCriteria->getPrimaryTableName() => array()); + } + + // we also need the columns for the update SQL + $updateTablesColumns = $updateValues->getTablesColumns(); + + $affectedRows = 0; // initialize this in case the next loop has no iterations. + + foreach ($tablesColumns as $tableName => $columns) { + + $whereClause = array(); + $params = array(); + $stmt = null; + try { + $sql = 'UPDATE '; + if ($queryComment = $selectCriteria->getComment()) { + $sql .= '/* ' . $queryComment . ' */ '; + } + // is it a table alias? + if ($tableName2 = $selectCriteria->getTableForAlias($tableName)) { + $udpateTable = $tableName2 . ' ' . $tableName; + $tableName = $tableName2; + } else { + $udpateTable = $tableName; + } + if ($db->useQuoteIdentifier()) { + $sql .= $db->quoteIdentifierTable($udpateTable); + } else { + $sql .= $udpateTable; + } + $sql .= " SET "; + $p = 1; + foreach ($updateTablesColumns[$tableName] as $col) { + $updateColumnName = substr($col, strrpos($col, '.') + 1); + // add identifiers for the actual database? + if ($db->useQuoteIdentifier()) { + $updateColumnName = $db->quoteIdentifier($updateColumnName); + } + if ($updateValues->getComparison($col) != Criteria::CUSTOM_EQUAL) { + $sql .= $updateColumnName . '=:p'.$p++.', '; + } else { + $param = $updateValues->get($col); + $sql .= $updateColumnName . ' = '; + if (is_array($param)) { + if (isset($param['raw'])) { + $raw = $param['raw']; + $rawcvt = ''; + // parse the $params['raw'] for ? chars + for($r=0,$len=strlen($raw); $r < $len; $r++) { + if ($raw{$r} == '?') { + $rawcvt .= ':p'.$p++; + } else { + $rawcvt .= $raw{$r}; + } + } + $sql .= $rawcvt . ', '; + } else { + $sql .= ':p'.$p++.', '; + } + if (isset($param['value'])) { + $updateValues->put($col, $param['value']); + } + } else { + $updateValues->remove($col); + $sql .= $param . ', '; + } + } + } + + $params = self::buildParams($updateTablesColumns[$tableName], $updateValues); + + $sql = substr($sql, 0, -2); + if (!empty($columns)) { + foreach ($columns as $colName) { + $sb = ""; + $selectCriteria->getCriterion($colName)->appendPsTo($sb, $params); + $whereClause[] = $sb; + } + $sql .= " WHERE " . implode(" AND ", $whereClause); + } + + $stmt = $con->prepare($sql); + + // Replace ':p?' with the actual values + self::populateStmtValues($stmt, $params, $dbMap, $db); + + $stmt->execute(); + + $affectedRows = $stmt->rowCount(); + + $stmt = null; // close + + } catch (Exception $e) { + if ($stmt) $stmt = null; // close + Propel::log($e->getMessage(), Propel::LOG_ERR); + throw new PropelException(sprintf('Unable to execute UPDATE statement [%s]', $sql), $e); + } + + } // foreach table in the criteria + + return $affectedRows; + } + + /** + * Executes query build by createSelectSql() and returns the resultset statement. + * + * @param Criteria $criteria A Criteria. + * @param PropelPDO $con A PropelPDO connection to use. + * @return PDOStatement The resultset. + * @throws PropelException + * @see createSelectSql() + */ + public static function doSelect(Criteria $criteria, PropelPDO $con = null) + { + $dbMap = Propel::getDatabaseMap($criteria->getDbName()); + $db = Propel::getDB($criteria->getDbName()); + $stmt = null; + + if ($con === null) { + $con = Propel::getConnection($criteria->getDbName(), Propel::CONNECTION_READ); + } + + if ($criteria->isUseTransaction()) { + $con->beginTransaction(); + } + + try { + + $params = array(); + $sql = self::createSelectSql($criteria, $params); + + $stmt = $con->prepare($sql); + + self::populateStmtValues($stmt, $params, $dbMap, $db); + + $stmt->execute(); + + if ($criteria->isUseTransaction()) { + $con->commit(); + } + + } catch (Exception $e) { + if ($stmt) { + $stmt = null; // close + } + if ($criteria->isUseTransaction()) { + $con->rollBack(); + } + Propel::log($e->getMessage(), Propel::LOG_ERR); + throw new PropelException(sprintf('Unable to execute SELECT statement [%s]', $sql), $e); + } + + return $stmt; + } + + /** + * Executes a COUNT query using either a simple SQL rewrite or, for more complex queries, a + * sub-select of the SQL created by createSelectSql() and returns the statement. + * + * @param Criteria $criteria A Criteria. + * @param PropelPDO $con A PropelPDO connection to use. + * @return PDOStatement The resultset statement. + * @throws PropelException + * @see createSelectSql() + */ + public static function doCount(Criteria $criteria, PropelPDO $con = null) + { + $dbMap = Propel::getDatabaseMap($criteria->getDbName()); + $db = Propel::getDB($criteria->getDbName()); + + if ($con === null) { + $con = Propel::getConnection($criteria->getDbName(), Propel::CONNECTION_READ); + } + + $stmt = null; + + if ($criteria->isUseTransaction()) { + $con->beginTransaction(); + } + + $needsComplexCount = $criteria->getGroupByColumns() + || $criteria->getOffset() + || $criteria->getLimit() + || $criteria->getHaving() + || in_array(Criteria::DISTINCT, $criteria->getSelectModifiers()); + + try { + + $params = array(); + + if ($needsComplexCount) { + if (self::needsSelectAliases($criteria)) { + if ($criteria->getHaving()) { + throw new PropelException('Propel cannot create a COUNT query when using HAVING and duplicate column names in the SELECT part'); + } + self::turnSelectColumnsToAliases($criteria); + } + $selectSql = self::createSelectSql($criteria, $params); + $sql = 'SELECT COUNT(*) FROM (' . $selectSql . ') propelmatch4cnt'; + } else { + // Replace SELECT columns with COUNT(*) + $criteria->clearSelectColumns()->addSelectColumn('COUNT(*)'); + $sql = self::createSelectSql($criteria, $params); + } + + $stmt = $con->prepare($sql); + self::populateStmtValues($stmt, $params, $dbMap, $db); + $stmt->execute(); + + if ($criteria->isUseTransaction()) { + $con->commit(); + } + + } catch (Exception $e) { + if ($stmt !== null) { + $stmt = null; + } + if ($criteria->isUseTransaction()) { + $con->rollBack(); + } + Propel::log($e->getMessage(), Propel::LOG_ERR); + throw new PropelException(sprintf('Unable to execute COUNT statement [%s]', $sql), $e); + } + + return $stmt; + } + + /** + * Populates values in a prepared statement. + * + * This method is designed to work with the createSelectSql() method, which creates + * both the SELECT SQL statement and populates a passed-in array of parameter + * values that should be substituted. + * + * + * $params = array(); + * $sql = BasePeer::createSelectSql($criteria, $params); + * BasePeer::populateStmtValues($stmt, $params, Propel::getDatabaseMap($critera->getDbName()), Propel::getDB($criteria->getDbName())); + * + * + * @param PDOStatement $stmt + * @param array $params array('column' => ..., 'table' => ..., 'value' => ...) + * @param DatabaseMap $dbMap + * @return int The number of params replaced. + * @see createSelectSql() + * @see doSelect() + */ + public static function populateStmtValues(PDOStatement $stmt, array $params, DatabaseMap $dbMap, DBAdapter $db) + { + $i = 1; + foreach ($params as $param) { + $tableName = $param['table']; + $columnName = $param['column']; + $value = $param['value']; + + if (null === $value) { + + $stmt->bindValue(':p'.$i++, null, PDO::PARAM_NULL); + + } elseif (null !== $tableName) { + + $cMap = $dbMap->getTable($tableName)->getColumn($columnName); + $type = $cMap->getType(); + $pdoType = $cMap->getPdoType(); + + // FIXME - This is a temporary hack to get around apparent bugs w/ PDO+MYSQL + // See http://pecl.php.net/bugs/bug.php?id=9919 + if ($pdoType == PDO::PARAM_BOOL && $db instanceof DBMySQL) { + $value = (int) $value; + $pdoType = PDO::PARAM_INT; + } elseif (is_numeric($value) && $cMap->isEpochTemporal()) { // it's a timestamp that needs to be formatted + if ($type == PropelColumnTypes::TIMESTAMP) { + $value = date($db->getTimestampFormatter(), $value); + } else if ($type == PropelColumnTypes::DATE) { + $value = date($db->getDateFormatter(), $value); + } else if ($type == PropelColumnTypes::TIME) { + $value = date($db->getTimeFormatter(), $value); + } + } elseif ($value instanceof DateTime && $cMap->isTemporal()) { // it's a timestamp that needs to be formatted + if ($type == PropelColumnTypes::TIMESTAMP || $type == PropelColumnTypes::BU_TIMESTAMP) { + $value = $value->format($db->getTimestampFormatter()); + } else if ($type == PropelColumnTypes::DATE || $type == PropelColumnTypes::BU_DATE) { + $value = $value->format($db->getDateFormatter()); + } else if ($type == PropelColumnTypes::TIME) { + $value = $value->format($db->getTimeFormatter()); + } + } elseif (is_resource($value) && $cMap->isLob()) { + // we always need to make sure that the stream is rewound, otherwise nothing will + // get written to database. + rewind($value); + } + + $stmt->bindValue(':p'.$i++, $value, $pdoType); + } else { + $stmt->bindValue(':p'.$i++, $value); + } + } // foreach + } + + /** + * Applies any validators that were defined in the schema to the specified columns. + * + * @param string $dbName The name of the database + * @param string $tableName The name of the table + * @param array $columns Array of column names as key and column values as value. + */ + public static function doValidate($dbName, $tableName, $columns) + { + $dbMap = Propel::getDatabaseMap($dbName); + $tableMap = $dbMap->getTable($tableName); + $failureMap = array(); // map of ValidationFailed objects + foreach ($columns as $colName => $colValue) { + if ($tableMap->containsColumn($colName)) { + $col = $tableMap->getColumn($colName); + foreach ($col->getValidators() as $validatorMap) { + $validator = BasePeer::getValidator($validatorMap->getClass()); + if ($validator && ($col->isNotNull() || $colValue !== null) && $validator->isValid($validatorMap, $colValue) === false) { + if (!isset($failureMap[$colName])) { // for now we do one ValidationFailed per column, not per rule + $failureMap[$colName] = new ValidationFailed($colName, $validatorMap->getMessage(), $validator); + } + } + } + } + } + return (!empty($failureMap) ? $failureMap : true); + } + + /** + * Helper method which returns the primary key contained + * in the given Criteria object. + * + * @param Criteria $criteria A Criteria. + * @return ColumnMap If the Criteria object contains a primary + * key, or null if it doesn't. + * @throws PropelException + */ + private static function getPrimaryKey(Criteria $criteria) + { + // Assume all the keys are for the same table. + $keys = $criteria->keys(); + $key = $keys[0]; + $table = $criteria->getTableName($key); + + $pk = null; + + if (!empty($table)) { + + $dbMap = Propel::getDatabaseMap($criteria->getDbName()); + + $pks = $dbMap->getTable($table)->getPrimaryKeys(); + if (!empty($pks)) { + $pk = array_shift($pks); + } + } + return $pk; + } + + /** + * Checks whether the Criteria needs to use column aliasing + * This is implemented in a service class rather than in Criteria itself + * in order to avoid doing the tests when it's not necessary (e.g. for SELECTs) + */ + public static function needsSelectAliases(Criteria $criteria) + { + $columnNames = array(); + foreach ($criteria->getSelectColumns() as $fullyQualifiedColumnName) { + if ($pos = strrpos($fullyQualifiedColumnName, '.')) { + $columnName = substr($fullyQualifiedColumnName, $pos); + if (isset($columnNames[$columnName])) { + // more than one column with the same name, so aliasing is required + return true; + } + $columnNames[$columnName] = true; + } + } + return false; + } + + /** + * Ensures uniqueness of select column names by turning them all into aliases + * This is necessary for queries on more than one table when the tables share a column name + * @see http://propel.phpdb.org/trac/ticket/795 + * + * @param Criteria $criteria + * + * @return Criteria The input, with Select columns replaced by aliases + */ + public static function turnSelectColumnsToAliases(Criteria $criteria) + { + $selectColumns = $criteria->getSelectColumns(); + // clearSelectColumns also clears the aliases, so get them too + $asColumns = $criteria->getAsColumns(); + $criteria->clearSelectColumns(); + $columnAliases = $asColumns; + // add the select columns back + foreach ($selectColumns as $clause) { + // Generate a unique alias + $baseAlias = preg_replace('/\W/', '_', $clause); + $alias = $baseAlias; + // If it already exists, add a unique suffix + $i = 0; + while (isset($columnAliases[$alias])) { + $i++; + $alias = $baseAlias . '_' . $i; + } + // Add it as an alias + $criteria->addAsColumn($alias, $clause); + $columnAliases[$alias] = $clause; + } + // Add the aliases back, don't modify them + foreach ($asColumns as $name => $clause) { + $criteria->addAsColumn($name, $clause); + } + + return $criteria; + } + + /** + * Method to create an SQL query based on values in a Criteria. + * + * This method creates only prepared statement SQL (using ? where values + * will go). The second parameter ($params) stores the values that need + * to be set before the statement is executed. The reason we do it this way + * is to let the PDO layer handle all escaping & value formatting. + * + * @param Criteria $criteria Criteria for the SELECT query. + * @param array &$params Parameters that are to be replaced in prepared statement. + * @return string + * @throws PropelException Trouble creating the query string. + */ + public static function createSelectSql(Criteria $criteria, &$params) + { + $db = Propel::getDB($criteria->getDbName()); + $dbMap = Propel::getDatabaseMap($criteria->getDbName()); + + $fromClause = array(); + $joinClause = array(); + $joinTables = array(); + $whereClause = array(); + $orderByClause = array(); + + $orderBy = $criteria->getOrderByColumns(); + $groupBy = $criteria->getGroupByColumns(); + $ignoreCase = $criteria->isIgnoreCase(); + + // get the first part of the SQL statement, the SELECT part + $selectSql = self::createSelectSqlPart($criteria, $fromClause); + + // add the criteria to WHERE clause + // this will also add the table names to the FROM clause if they are not already + // included via a LEFT JOIN + foreach ($criteria->keys() as $key) { + + $criterion = $criteria->getCriterion($key); + $table = null; + foreach ($criterion->getAttachedCriterion() as $attachedCriterion) { + $tableName = $attachedCriterion->getTable(); + + $table = $criteria->getTableForAlias($tableName); + if ($table !== null) { + $fromClause[] = $table . ' ' . $tableName; + } else { + $fromClause[] = $tableName; + $table = $tableName; + } + + if (($criteria->isIgnoreCase() || $attachedCriterion->isIgnoreCase()) + && $dbMap->getTable($table)->getColumn($attachedCriterion->getColumn())->isText()) { + $attachedCriterion->setIgnoreCase(true); + } + } + + $criterion->setDB($db); + + $sb = ''; + $criterion->appendPsTo($sb, $params); + $whereClause[] = $sb; + } + + // Handle joins + // joins with a null join type will be added to the FROM clause and the condition added to the WHERE clause. + // joins of a specified type: the LEFT side will be added to the fromClause and the RIGHT to the joinClause + foreach ($criteria->getJoins() as $join) { + // The join might have been established using an alias name + $leftTable = $join->getLeftTableName(); + if ($realTable = $criteria->getTableForAlias($leftTable)) { + $leftTableForFrom = $realTable . ' ' . $leftTable; + $leftTable = $realTable; + } else { + $leftTableForFrom = $leftTable; + } + + $rightTable = $join->getRightTableName(); + if ($realTable = $criteria->getTableForAlias($rightTable)) { + $rightTableForFrom = $realTable . ' ' . $rightTable; + $rightTable = $realTable; + } else { + $rightTableForFrom = $rightTable; + } + + // determine if casing is relevant. + if ($ignoreCase = $criteria->isIgnoreCase()) { + $leftColType = $dbMap->getTable($leftTable)->getColumn($join->getLeftColumnName())->getType(); + $rightColType = $dbMap->getTable($rightTable)->getColumn($join->getRightColumnName())->getType(); + $ignoreCase = ($leftColType == 'string' || $rightColType == 'string'); + } + + // build the condition + $condition = ''; + foreach ($join->getConditions() as $index => $conditionDesc) { + if ($ignoreCase) { + $condition .= $db->ignoreCase($conditionDesc['left']) . $conditionDesc['operator'] . $db->ignoreCase($conditionDesc['right']); + } else { + $condition .= implode($conditionDesc); + } + if ($index + 1 < $join->countConditions()) { + $condition .= ' AND '; + } + } + + // add 'em to the queues.. + if ($joinType = $join->getJoinType()) { + // real join + if (!$fromClause) { + $fromClause[] = $leftTableForFrom; + } + $joinTables[] = $rightTableForFrom; + $joinClause[] = $join->getJoinType() . ' ' . $rightTableForFrom . " ON ($condition)"; + } else { + // implicit join, translates to a where + $fromClause[] = $leftTableForFrom; + $fromClause[] = $rightTableForFrom; + $whereClause[] = $condition; + } + } + + // Unique from clause elements + $fromClause = array_unique($fromClause); + $fromClause = array_diff($fromClause, array('')); + + // tables should not exist in both the from and join clauses + if ($joinTables && $fromClause) { + foreach ($fromClause as $fi => $ftable) { + if (in_array($ftable, $joinTables)) { + unset($fromClause[$fi]); + } + } + } + + // Add the GROUP BY columns + $groupByClause = $groupBy; + + $having = $criteria->getHaving(); + $havingString = null; + if ($having !== null) { + $sb = ''; + $having->appendPsTo($sb, $params); + $havingString = $sb; + } + + if (!empty($orderBy)) { + + foreach ($orderBy as $orderByColumn) { + + // Add function expression as-is. + + if (strpos($orderByColumn, '(') !== false) { + $orderByClause[] = $orderByColumn; + continue; + } + + // Split orderByColumn (i.e. "table.column DESC") + + $dotPos = strrpos($orderByColumn, '.'); + + if ($dotPos !== false) { + $tableName = substr($orderByColumn, 0, $dotPos); + $columnName = substr($orderByColumn, $dotPos + 1); + } else { + $tableName = ''; + $columnName = $orderByColumn; + } + + $spacePos = strpos($columnName, ' '); + + if ($spacePos !== false) { + $direction = substr($columnName, $spacePos); + $columnName = substr($columnName, 0, $spacePos); + } else { + $direction = ''; + } + + $tableAlias = $tableName; + if ($aliasTableName = $criteria->getTableForAlias($tableName)) { + $tableName = $aliasTableName; + } + + $columnAlias = $columnName; + if ($asColumnName = $criteria->getColumnForAs($columnName)) { + $columnName = $asColumnName; + } + + $column = $tableName ? $dbMap->getTable($tableName)->getColumn($columnName) : null; + + if ($criteria->isIgnoreCase() && $column && $column->isText()) { + $ignoreCaseColumn = $db->ignoreCaseInOrderBy("$tableAlias.$columnAlias"); + $orderByClause[] = $ignoreCaseColumn . $direction; + $selectSql .= ', ' . $ignoreCaseColumn; + } else { + $orderByClause[] = $orderByColumn; + } + } + } + + if (empty($fromClause) && $criteria->getPrimaryTableName()) { + $fromClause[] = $criteria->getPrimaryTableName(); + } + + // from / join tables quoted if it is necessary + if ($db->useQuoteIdentifier()) { + $fromClause = array_map(array($db, 'quoteIdentifierTable'), $fromClause); + $joinClause = $joinClause ? $joinClause : array_map(array($db, 'quoteIdentifierTable'), $joinClause); + } + + // build from-clause + $from = ''; + if (!empty($joinClause) && count($fromClause) > 1) { + $from .= implode(" CROSS JOIN ", $fromClause); + } else { + $from .= implode(", ", $fromClause); + } + + $from .= $joinClause ? ' ' . implode(' ', $joinClause) : ''; + + // Build the SQL from the arrays we compiled + $sql = $selectSql + ." FROM " . $from + .($whereClause ? " WHERE ".implode(" AND ", $whereClause) : "") + .($groupByClause ? " GROUP BY ".implode(",", $groupByClause) : "") + .($havingString ? " HAVING ".$havingString : "") + .($orderByClause ? " ORDER BY ".implode(",", $orderByClause) : ""); + + // APPLY OFFSET & LIMIT to the query. + if ($criteria->getLimit() || $criteria->getOffset()) { + $db->applyLimit($sql, $criteria->getOffset(), $criteria->getLimit(), $criteria); + } + + return $sql; + } + + /** + * Builds the SELECT part of a SQL statement based on a Criteria + * taking into account select columns and 'as' columns (i.e. columns aliases) + */ + public static function createSelectSqlPart(Criteria $criteria, &$fromClause, $aliasAll = false) + { + $selectClause = array(); + + if ($aliasAll) { + self::turnSelectColumnsToAliases($criteria); + // no select columns after that, they are all aliases + } else { + foreach ($criteria->getSelectColumns() as $columnName) { + + // expect every column to be of "table.column" formation + // it could be a function: e.g. MAX(books.price) + + $tableName = null; + + $selectClause[] = $columnName; // the full column name: e.g. MAX(books.price) + + $parenPos = strrpos($columnName, '('); + $dotPos = strrpos($columnName, '.', ($parenPos !== false ? $parenPos : 0)); + + if ($dotPos !== false) { + if ($parenPos === false) { // table.column + $tableName = substr($columnName, 0, $dotPos); + } else { // FUNC(table.column) + // functions may contain qualifiers so only take the last + // word as the table name. + // COUNT(DISTINCT books.price) + $lastSpace = strpos($tableName, ' '); + if ($lastSpace !== false) { // COUNT(DISTINCT books.price) + $tableName = substr($tableName, $lastSpace + 1); + } else { + $tableName = substr($columnName, $parenPos + 1, $dotPos - ($parenPos + 1)); + } + } + // is it a table alias? + $tableName2 = $criteria->getTableForAlias($tableName); + if ($tableName2 !== null) { + $fromClause[] = $tableName2 . ' ' . $tableName; + } else { + $fromClause[] = $tableName; + } + } // if $dotPost !== false + } + } + + // set the aliases + foreach ($criteria->getAsColumns() as $alias => $col) { + $selectClause[] = $col . ' AS ' . $alias; + } + + $selectModifiers = $criteria->getSelectModifiers(); + $queryComment = $criteria->getComment(); + + // Build the SQL from the arrays we compiled + $sql = "SELECT " + . ($queryComment ? '/* ' . $queryComment . ' */ ' : '') + . ($selectModifiers ? (implode(' ', $selectModifiers) . ' ') : '') + . implode(", ", $selectClause); + + return $sql; + } + + /** + * Builds a params array, like the kind populated by Criterion::appendPsTo(). + * This is useful for building an array even when it is not using the appendPsTo() method. + * @param array $columns + * @param Criteria $values + * @return array params array('column' => ..., 'table' => ..., 'value' => ...) + */ + private static function buildParams($columns, Criteria $values) + { + $params = array(); + foreach ($columns as $key) { + if ($values->containsKey($key)) { + $crit = $values->getCriterion($key); + $params[] = array('column' => $crit->getColumn(), 'table' => $crit->getTable(), 'value' => $crit->getValue()); + } + } + return $params; + } + + /** + * This function searches for the given validator $name under propel/validator/$name.php, + * imports and caches it. + * + * @param string $classname The dot-path name of class (e.g. myapp.propel.MyValidator) + * @return Validator object or null if not able to instantiate validator class (and error will be logged in this case) + */ + public static function getValidator($classname) + { + try { + $v = isset(self::$validatorMap[$classname]) ? self::$validatorMap[$classname] : null; + if ($v === null) { + $cls = Propel::importClass($classname); + $v = new $cls(); + self::$validatorMap[$classname] = $v; + } + return $v; + } catch (Exception $e) { + Propel::log("BasePeer::getValidator(): failed trying to instantiate " . $classname . ": ".$e->getMessage(), Propel::LOG_ERR); + } + } + +} diff --git a/library/propel/runtime/lib/util/NodePeer.php b/library/propel/runtime/lib/util/NodePeer.php new file mode 100644 index 000000000..d40b7642a --- /dev/null +++ b/library/propel/runtime/lib/util/NodePeer.php @@ -0,0 +1,369 @@ + (Propel) + * @version $Revision: 1612 $ + * @package propel.runtime.util + */ +interface NodePeer +{ + /** + * Creates the supplied node as the root node. + * + * @param object $node Propel object for model + * @return object Inserted propel object for model + */ + public static function createRoot(NodeObject $node); + + /** + * Returns the root node for a given scope id + * + * @param int $scopeId Scope id to determine which root node to return + * @param PropelPDO $con Connection to use. + * @return object Propel object for root node + */ + public static function retrieveRoot($scopeId = 1, PropelPDO $con = null); + + /** + * Inserts $child as first child of destination node $parent + * + * @param object $child Propel object for child node + * @param object $parent Propel object for parent node + * @param PropelPDO $con Connection to use. + * @return void + */ + public static function insertAsFirstChildOf(NodeObject $child, NodeObject $parent, PropelPDO $con = null); + + /** + * Inserts $child as last child of destination node $parent + * + * @param object $child Propel object for child node + * @param object $parent Propel object for parent node + * @param PropelPDO $con Connection to use. + * @return void + */ + public static function insertAsLastChildOf(NodeObject $child, NodeObject $parent, PropelPDO $con = null); + + /** + * Inserts $sibling as previous sibling to destination node $node + * + * @param object $node Propel object for destination node + * @param object $sibling Propel object for source node + * @param PropelPDO $con Connection to use. + * @return void + */ + public static function insertAsPrevSiblingOf(NodeObject $node, NodeObject $sibling, PropelPDO $con = null); + + /** + * Inserts $sibling as next sibling to destination node $node + * + * @param object $node Propel object for destination node + * @param object $sibling Propel object for source node + * @param PropelPDO $con Connection to use. + * @return void + */ + public static function insertAsNextSiblingOf(NodeObject $node, NodeObject $sibling, PropelPDO $con = null); + + /** + * Inserts $parent as parent of given $node. + * + * @param object $parent Propel object for given parent node + * @param object $node Propel object for given destination node + * @param PropelPDO $con Connection to use. + * @return void + * @throws Exception When trying to insert node as parent of a root node + */ + public static function insertAsParentOf(NodeObject $parent, NodeObject $node, PropelPDO $con = null); + + /** + * Inserts $node as root node + * + * @param object $node Propel object as root node + * @param PropelPDO $con Connection to use. + * @return void + */ + public static function insertRoot(NodeObject $node, PropelPDO $con = null); + + /** + * Delete root node + * + * @param int $scopeId Scope id to determine which root node to delete + * @param PropelPDO $con Connection to use. + * @return boolean Deletion status + */ + public static function deleteRoot($scopeId = 1, PropelPDO $con = null); + + /** + * Delete $dest node + * + * @param object $dest Propel object node to delete + * @param PropelPDO $con Connection to use. + * @return boolean Deletion status + */ + public static function deleteNode(NodeObject $dest, PropelPDO $con = null); + + /** + * Moves $child to be first child of $parent + * + * @param object $parent Propel object for parent node + * @param object $child Propel object for child node + * @param PropelPDO $con Connection to use. + * @return void + */ + public static function moveToFirstChildOf(NodeObject $parent, NodeObject $child, PropelPDO $con = null); + + /** + * Moves $node to be last child of $dest + * + * @param object $dest Propel object for destination node + * @param object $node Propel object for source node + * @param PropelPDO $con Connection to use. + * @return void + */ + public static function moveToLastChildOf(NodeObject $dest, NodeObject $node, PropelPDO $con = null); + + /** + * Moves $node to be prev sibling to $dest + * + * @param object $dest Propel object for destination node + * @param object $node Propel object for source node + * @param PropelPDO $con Connection to use. + * @return void + */ + public static function moveToPrevSiblingOf(NodeObject $dest, NodeObject $node, PropelPDO $con = null); + + /** + * Moves $node to be next sibling to $dest + * + * @param object $dest Propel object for destination node + * @param object $node Propel object for source node + * @param PropelPDO $con Connection to use. + * @return void + */ + public static function moveToNextSiblingOf(NodeObject $dest, NodeObject $node, PropelPDO $con = null); + + /** + * Gets first child for the given node if it exists + * + * @param object $node Propel object for src node + * @param PropelPDO $con Connection to use. + * @return mixed Propel object if exists else false + */ + public static function retrieveFirstChild(NodeObject $node, PropelPDO $con = null); + + /** + * Gets last child for the given node if it exists + * + * @param object $node Propel object for src node + * @param PropelPDO $con Connection to use. + * @return mixed Propel object if exists else false + */ + public static function retrieveLastChild(NodeObject $node, PropelPDO $con = null); + + /** + * Gets prev sibling for the given node if it exists + * + * @param object $node Propel object for src node + * @param PropelPDO $con Connection to use. + * @return mixed Propel object if exists else false + */ + public static function retrievePrevSibling(NodeObject $node, PropelPDO $con = null); + + /** + * Gets next sibling for the given node if it exists + * + * @param object $node Propel object for src node + * @param PropelPDO $con Connection to use. + * @return mixed Propel object if exists else false + */ + public static function retrieveNextSibling(NodeObject $node, PropelPDO $con = null); + + /** + * Retrieves the entire tree from root + * + * @param int $scopeId Scope id to determine which scope tree to return + * @param PropelPDO $con Connection to use. + */ + public static function retrieveTree($scopeId = 1, PropelPDO $con = null); + + /** + * Retrieves the entire tree from parent $node + * + * @param PropelPDO $con Connection to use. + */ + public static function retrieveBranch(NodeObject $node, PropelPDO $con = null); + + /** + * Gets direct children for the node + * + * @param object $node Propel object for parent node + * @param PropelPDO $con Connection to use. + */ + public static function retrieveChildren(NodeObject $node, PropelPDO $con = null); + + /** + * Gets all descendants for the node + * + * @param object $node Propel object for parent node + * @param PropelPDO $con Connection to use. + */ + public static function retrieveDescendants(NodeObject $node, PropelPDO $con = null); + + /** + * Gets all siblings for the node + * + * @param object $node Propel object for src node + * @param PropelPDO $con Connection to use. + */ + public static function retrieveSiblings(NodeObject $node, PropelPDO $con = null); + + /** + * Gets ancestor for the given node if it exists + * + * @param object $node Propel object for src node + * @param PropelPDO $con Connection to use. + * @return mixed Propel object if exists else false + */ + public static function retrieveParent(NodeObject $node, PropelPDO $con = null); + + /** + * Gets level for the given node + * + * @param object $node Propel object for src node + * @param PropelPDO $con Connection to use. + * @return int Level for the given node + */ + public static function getLevel(NodeObject $node, PropelPDO $con = null); + + /** + * Gets number of direct children for given node + * + * @param object $node Propel object for src node + * @param PropelPDO $con Connection to use. + * @return int Level for the given node + */ + public static function getNumberOfChildren(NodeObject $node, PropelPDO $con = null); + + /** + * Gets number of descendants for given node + * + * @param object $node Propel object for src node + * @param PropelPDO $con Connection to use. + * @return int Level for the given node + */ + public static function getNumberOfDescendants(NodeObject $node, PropelPDO $con = null); + + /** + * Returns path to a specific node as an array, useful to create breadcrumbs + * + * @param object $node Propel object of node to create path to + * @param PropelPDO $con Connection to use. + * @return array Array in order of heirarchy + */ + public static function getPath(NodeObject $node, PropelPDO $con = null); + + /** + * Tests if node is valid + * + * @param object $node Propel object for src node + * @return bool + */ + public static function isValid(NodeObject $node = null); + + /** + * Tests if node is a root + * + * @param object $node Propel object for src node + * @return bool + */ + public static function isRoot(NodeObject $node); + + /** + * Tests if node is a leaf + * + * @param object $node Propel object for src node + * @return bool + */ + public static function isLeaf(NodeObject $node); + + /** + * Tests if $child is a child of $parent + * + * @param object $child Propel object for node + * @param object $parent Propel object for node + * @return bool + */ + public static function isChildOf(NodeObject $child, NodeObject $parent); + + /** + * Tests if $node1 is equal to $node2 + * + * @param object $node1 Propel object for node + * @param object $node2 Propel object for node + * @return bool + */ + public static function isEqualTo(NodeObject $node1, NodeObject $node2); + + /** + * Tests if $node has an ancestor + * + * @param object $node Propel object for node + * @param PropelPDO $con Connection to use. + * @return bool + */ + public static function hasParent(NodeObject $node, PropelPDO $con = null); + + /** + * Tests if $node has prev sibling + * + * @param object $node Propel object for node + * @param PropelPDO $con Connection to use. + * @return bool + */ + public static function hasPrevSibling(NodeObject $node, PropelPDO $con = null); + + /** + * Tests if $node has next sibling + * + * @param object $node Propel object for node + * @param PropelPDO $con Connection to use. + * @return bool + */ + public static function hasNextSibling(NodeObject $node, PropelPDO $con = null); + + /** + * Tests if $node has children + * + * @param object $node Propel object for node + * @return bool + */ + public static function hasChildren(NodeObject $node); + + /** + * Deletes $node and all of its descendants + * + * @param object $node Propel object for source node + * @param PropelPDO $con Connection to use. + */ + public static function deleteDescendants(NodeObject $node, PropelPDO $con = null); + + /** + * Returns a node given its primary key or the node itself + * + * @param int/object $node Primary key/instance of required node + * @param PropelPDO $con Connection to use. + * @return object Propel object for model + */ + public static function getNode($node, PropelPDO $con = null); + +} // NodePeer diff --git a/library/propel/runtime/lib/util/PropelAutoloader.php b/library/propel/runtime/lib/util/PropelAutoloader.php new file mode 100755 index 000000000..1bba65cec --- /dev/null +++ b/library/propel/runtime/lib/util/PropelAutoloader.php @@ -0,0 +1,113 @@ + $classPath + */ + public function addClassPaths($classMap) + { + $this->classes = array_merge($this->classes, $classMap); + } + + /** + * Sets the path for a particular class. + * + * @param string $class A PHP class name + * @param string $path A path (absolute or relative to the include path) + */ + public function addClassPath($class, $path) + { + $this->classes[$class] = $path; + } + + /** + * Returns the path where a particular class can be found. + * + * @param string $class A PHP class name + * + * @return string|null A path (absolute or relative to the include path) + */ + public function getClassPath($class) + { + return isset($this->classes[$class]) ? $this->classes[$class] : null; + } + + /** + * Handles autoloading of classes that have been registered in this instance + * + * @param string $class A class name. + * + * @return boolean Returns true if the class has been loaded + */ + public function autoload($class) + { + if (isset($this->classes[$class])) { + require $this->classes[$class]; + return true; + } + return false; + } +} diff --git a/library/propel/runtime/lib/util/PropelColumnTypes.php b/library/propel/runtime/lib/util/PropelColumnTypes.php new file mode 100644 index 000000000..e23c6f350 --- /dev/null +++ b/library/propel/runtime/lib/util/PropelColumnTypes.php @@ -0,0 +1,88 @@ + (Propel) + * @version $Revision: 1612 $ + * @package propel.runtime.util + */ +class PropelColumnTypes +{ + + const + CHAR = "CHAR", + VARCHAR = "VARCHAR", + LONGVARCHAR = "LONGVARCHAR", + CLOB = "CLOB", + CLOB_EMU = "CLOB_EMU", + NUMERIC = "NUMERIC", + DECIMAL = "DECIMAL", + TINYINT = "TINYINT", + SMALLINT = "SMALLINT", + INTEGER = "INTEGER", + BIGINT = "BIGINT", + REAL = "REAL", + FLOAT = "FLOAT", + DOUBLE = "DOUBLE", + BINARY = "BINARY", + VARBINARY = "VARBINARY", + LONGVARBINARY = "LONGVARBINARY", + BLOB = "BLOB", + DATE = "DATE", + TIME = "TIME", + TIMESTAMP = "TIMESTAMP", + BU_DATE = "BU_DATE", + BU_TIMESTAMP = "BU_TIMESTAMP", + BOOLEAN = "BOOLEAN", + BOOLEAN_EMU = "BOOLEAN_EMU"; + + private static $propelToPdoMap = array( + self::CHAR => PDO::PARAM_STR, + self::VARCHAR => PDO::PARAM_STR, + self::LONGVARCHAR => PDO::PARAM_STR, + self::CLOB => PDO::PARAM_LOB, + self::CLOB_EMU => PDO::PARAM_STR, + self::NUMERIC => PDO::PARAM_STR, + self::DECIMAL => PDO::PARAM_STR, + self::TINYINT => PDO::PARAM_INT, + self::SMALLINT => PDO::PARAM_INT, + self::INTEGER => PDO::PARAM_INT, + self::BIGINT => PDO::PARAM_STR, + self::REAL => PDO::PARAM_STR, + self::FLOAT => PDO::PARAM_STR, + self::DOUBLE => PDO::PARAM_STR, + self::BINARY => PDO::PARAM_STR, + self::VARBINARY => PDO::PARAM_STR, + self::LONGVARBINARY => PDO::PARAM_STR, + self::BLOB => PDO::PARAM_LOB, + self::DATE => PDO::PARAM_STR, + self::TIME => PDO::PARAM_STR, + self::TIMESTAMP => PDO::PARAM_STR, + self::BU_DATE => PDO::PARAM_STR, + self::BU_TIMESTAMP => PDO::PARAM_STR, + self::BOOLEAN => PDO::PARAM_BOOL, + self::BOOLEAN_EMU => PDO::PARAM_INT, + ); + + /** + * Resturns the PDO type (PDO::PARAM_* constant) value for the Propel type provided. + * @param string $propelType + * @return int + */ + public static function getPdoType($propelType) + { + return self::$propelToPdoMap[$propelType]; + } + +} diff --git a/library/propel/runtime/lib/util/PropelConditionalProxy.php b/library/propel/runtime/lib/util/PropelConditionalProxy.php new file mode 100644 index 000000000..e153760fe --- /dev/null +++ b/library/propel/runtime/lib/util/PropelConditionalProxy.php @@ -0,0 +1,71 @@ + + * $c->_if(true) // returns $c + * ->doStuff() // executed + * ->_else() // returns a PropelConditionalProxy instance + * ->doOtherStuff() // not executed + * ->_endif(); // returns $c + * $c->_if(false) // returns a PropelConditionalProxy instance + * ->doStuff() // not executed + * ->_else() // returns $c + * ->doOtherStuff() // executed + * ->_endif(); // returns $c + * @see Criteria + * + * @author Francois Zaninotto + * @version $Revision: 1612 $ + * @package propel.runtime.util + */ +class PropelConditionalProxy +{ + protected $mainObject; + + public function __construct($mainObject) + { + $this->mainObject = $mainObject; + } + + public function _if() + { + throw new PropelException('_if() statements cannot be nested'); + } + + public function _elseif($cond) + { + if($cond) { + return $this->mainObject; + } else { + return $this; + } + } + + public function _else() + { + return $this->mainObject; + } + + public function _endif() + { + return $this->mainObject; + } + + public function __call($name, $arguments) + { + return $this; + } +} \ No newline at end of file diff --git a/library/propel/runtime/lib/util/PropelDateTime.php b/library/propel/runtime/lib/util/PropelDateTime.php new file mode 100644 index 000000000..cfa1276dd --- /dev/null +++ b/library/propel/runtime/lib/util/PropelDateTime.php @@ -0,0 +1,76 @@ +dateString = $this->format('Y-m-d H:i:s'); + $this->tzString = $this->getTimeZone()->getName(); + return array('dateString', 'tzString'); + } + + /** + * PHP "magic" function called when object is restored from serialized state. + * Calls DateTime constructor with previously stored string value of date. + */ + function __wakeup() + { + parent::__construct($this->dateString, new DateTimeZone($this->tzString)); + } + +} diff --git a/library/propel/runtime/lib/util/PropelModelPager.php b/library/propel/runtime/lib/util/PropelModelPager.php new file mode 100644 index 000000000..5236ebbd5 --- /dev/null +++ b/library/propel/runtime/lib/util/PropelModelPager.php @@ -0,0 +1,349 @@ + + * @author François Zaninotto + * @version $Revision: 1665 $ + * @package propel.runtime.query + */ +class PropelModelPager implements IteratorAggregate, Countable +{ + protected + $query = null, + $page = 1, + $maxPerPage = 10, + $lastPage = 1, + $nbResults = 0, + $objects = null, + $parameters = array(), + $currentMaxLink = 1, + $parameterHolder = null, + $maxRecordLimit = false, + $results = null, + $resultsCounter = 0; + + public function __construct(Criteria $query, $maxPerPage = 10) + { + $this->setQuery($query); + $this->setMaxPerPage($maxPerPage); + } + + public function setQuery(Criteria $query) + { + $this->query = $query; + } + + public function getQuery() + { + return $this->query; + } + + public function init() + { + $hasMaxRecordLimit = ($this->getMaxRecordLimit() !== false); + $maxRecordLimit = $this->getMaxRecordLimit(); + + $qForCount = clone $this->getQuery(); + $count = $qForCount + ->offset(0) + ->limit(0) + ->count(); + + $this->setNbResults($hasMaxRecordLimit ? min($count, $maxRecordLimit) : $count); + + $q = $this->getQuery() + ->offset(0) + ->limit(0); + + if (($this->getPage() == 0 || $this->getMaxPerPage() == 0)) { + $this->setLastPage(0); + } else { + $this->setLastPage(ceil($this->getNbResults() / $this->getMaxPerPage())); + + $offset = ($this->getPage() - 1) * $this->getMaxPerPage(); + $q->offset($offset); + + if ($hasMaxRecordLimit) { + $maxRecordLimit = $maxRecordLimit - $offset; + if ($maxRecordLimit > $this->getMaxPerPage()) { + $q->limit($this->getMaxPerPage()); + } else { + $q->limit($maxRecordLimit); + } + } else { + $q->limit($this->getMaxPerPage()); + } + } + } + + /** + * Get the collection of results in the page + * + * @return PropelObjectCollection A collection of results + */ + public function getResults() + { + if (null === $this->results) { + $this->results = $this->getQuery() + ->setFormatter(ModelCriteria::FORMAT_OBJECT) + ->find(); + } + return $this->results; + } + + public function getCurrentMaxLink() + { + return $this->currentMaxLink; + } + + public function getMaxRecordLimit() + { + return $this->maxRecordLimit; + } + + public function setMaxRecordLimit($limit) + { + $this->maxRecordLimit = $limit; + } + + public function getLinks($nb_links = 5) + { + $links = array(); + $tmp = $this->page - floor($nb_links / 2); + $check = $this->lastPage - $nb_links + 1; + $limit = ($check > 0) ? $check : 1; + $begin = ($tmp > 0) ? (($tmp > $limit) ? $limit : $tmp) : 1; + + $i = (int) $begin; + while (($i < $begin + $nb_links) && ($i <= $this->lastPage)) { + $links[] = $i++; + } + + $this->currentMaxLink = count($links) ? $links[count($links) - 1] : 1; + + return $links; + } + + /** + * Test whether the number of results exceeds the max number of results per page + * + * @return boolean true if the pager displays only a subset of the results + */ + public function haveToPaginate() + { + return (($this->getMaxPerPage() != 0) && ($this->getNbResults() > $this->getMaxPerPage())); + } + + /** + * Get the index of the first element in the page + * Returns 1 on the first page, $maxPerPage +1 on the second page, etc + * + * @return int + */ + public function getFirstIndex() + { + if ($this->page == 0) { + return 1; + } else { + return ($this->page - 1) * $this->maxPerPage + 1; + } + } + + /** + * Get the index of the last element in the page + * Always less than or eaqual to $maxPerPage + * + * @return int + */ + public function getLastIndex() + { + if ($this->page == 0) { + return $this->nbResults; + } else { + if (($this->page * $this->maxPerPage) >= $this->nbResults) { + return $this->nbResults; + } else { + return ($this->page * $this->maxPerPage); + } + } + } + + /** + * Get the total number of results of the query + * This can be greater than $maxPerPage + * + * @return int + */ + public function getNbResults() + { + return $this->nbResults; + } + + /** + * Set the total number of results of the query + * + * @param int $nb + */ + protected function setNbResults($nb) + { + $this->nbResults = $nb; + } + + /** + * Check whether the current page is the first page + * + * @return boolean true if the current page is the first page + */ + public function isFirstPage() + { + return $this->getPage() == $this->getFirstPage(); + } + + /** + * Get the number of the first page + * + * @return int Always 1 + */ + public function getFirstPage() + { + return 1; + } + + /** + * Check whether the current page is the last page + * + * @return boolean true if the current page is the last page + */ + public function isLastPage() + { + return $this->getPage() == $this->getLastPage(); + } + + /** + * Get the number of the last page + * + * @return int + */ + public function getLastPage() + { + return $this->lastPage; + } + + /** + * Set the number of the first page + * + * @param int $page + */ + protected function setLastPage($page) + { + $this->lastPage = $page; + if ($this->getPage() > $page) { + $this->setPage($page); + } + } + + /** + * Get the number of the current page + * + * @return int + */ + public function getPage() + { + return $this->page; + } + + /** + * Set the number of the current page + * + * @param int $page + */ + public function setPage($page) + { + $this->page = intval($page); + if ($this->page <= 0) { + // set first page, which depends on a maximum set + $this->page = $this->getMaxPerPage() ? 1 : 0; + } + } + + /** + * Get the number of the next page + * + * @return int + */ + public function getNextPage() + { + return min($this->getPage() + 1, $this->getLastPage()); + } + + /** + * Get the number of the previous page + * + * @return int + */ + public function getPreviousPage() + { + return max($this->getPage() - 1, $this->getFirstPage()); + } + + /** + * Get the maximum number results per page + * + * @return int + */ + public function getMaxPerPage() + { + return $this->maxPerPage; + } + + /** + * Set the maximum number results per page + * + * @param int $max + */ + public function setMaxPerPage($max) + { + if ($max > 0) { + $this->maxPerPage = $max; + if ($this->page == 0) { + $this->page = 1; + } + } else if ($max == 0) { + $this->maxPerPage = 0; + $this->page = 0; + } else { + $this->maxPerPage = 1; + if ($this->page == 0) { + $this->page = 1; + } + } + } + + public function getIterator() + { + return $this->getResults()->getIterator(); + } + + /** + * Returns the total number of results. + * + * @see Countable + * @return int + */ + public function count() + { + return $this->getNbResults(); + } + +} diff --git a/library/propel/runtime/lib/util/PropelPager.php b/library/propel/runtime/lib/util/PropelPager.php new file mode 100644 index 000000000..8fefdbc0a --- /dev/null +++ b/library/propel/runtime/lib/util/PropelPager.php @@ -0,0 +1,597 @@ +addDescendingOrderByColumn(poemPeer::SID); + * + * // with join + * $pager = new PropelPager($c, 'poemPeer', 'doSelectJoinPoemUsers', 1, 50); + * + * // without Join + * + * $pager = new PropelPager($c, 'poemPeer', 'doSelect', 1, 50); + * + * Some template: + * + *

    + * Total Pages: getTotalPages()?> Total Records: getTotalRecordCount()?> + *

    + * + * + * + * + * + * + * + * + * + * + *
    + * getFirstPage):?> + * | + * + * + * getPrev()):?> + * Previous| + * + * + * getPrevLinks() as $link):?> + * | + * + * getPage()?> + * getNextLinks() as $link):?> + * | + * + * + * getNext()):?> + * Last| + * + * + * getLastPage()):?> + * | + * + *
    + * + * + * + * + * + * + * + * getResult() as $poem):?> + * + * + * + * + * + * + * + *
    TitleAuteurDatecomments
    getTitle()?>getPoemUsers()->getUname()?>getTime()?>getComments()?>
    + * + * + * @author Rob Halff + * @author Niklas Närhinen + * @version $Revision: 1612 $ + * @copyright Copyright (c) 2004 Rob Halff: LGPL - See LICENCE + * @package propel.runtime.util + */ +class PropelPager implements Countable, Iterator +{ + + private $recordCount; + private $pages; + private $peerClass; + private $peerSelectMethod; + private $peerCountMethod; + private $criteria; + private $countCriteria; + private $page; + private $rs = null; + + //Iterator vars + private $currentKey = 0; + + /** @var int Start row (offset) */ + protected $start = 0; + + /** @var int Max rows to return (0 means all) */ + protected $max = 0; + + /** + * Create a new Propel Pager. + * @param Criteria $c + * @param string $peerClass The name of the static Peer class. + * @param string $peerSelectMethod The name of the static method for selecting content from the Peer class. + * @param int $page The current page (1-based). + * @param int $rowsPerPage The number of rows that should be displayed per page. + */ + public function __construct($c = null, $peerClass = null, $peerSelectMethod = null, $page = 1, $rowsPerPage = 25) + { + if (!isset($c)) { + $c = new Criteria(); + } + $this->setCriteria($c); + $this->setPeerClass($peerClass); + $this->setPeerSelectMethod($peerSelectMethod); + $this->guessPeerCountMethod(); + $this->setPage($page); + $this->setRowsPerPage($rowsPerPage); + } + + /** + * Set the criteria for this pager. + * @param Criteria $c + * @return void + */ + public function setCriteria(Criteria $c) + { + $this->criteria = $c; + } + + /** + * Return the Criteria object for this pager. + * @return Criteria + */ + public function getCriteria() + { + return $this->criteria; + } + + /** + * Set the Peer Classname + * + * @param string $class + * @return void + */ + public function setPeerClass($class) + { + $this->peerClass = $class; + } + + /** + * Return the Peer Classname. + * @return string + */ + public function getPeerClass() + { + return $this->peerClass; + } + + /** + * Set the Peer select method. + * This exists for legacy support, please use setPeerSelectMethod(). + * @param string $method The name of the static method to call on the Peer class. + * @return void + * @see setPeerSelectMethod() + * @deprecated + */ + public function setPeerMethod($method) + { + $this->setPeerSelectMethod($method); + } + + /** + * Return the Peer select method. + * This exists for legacy support, please use getPeerSelectMethod(). + * @return string + * @see getPeerSelectMethod() + * @deprecated + */ + public function getPeerMethod() + { + return $this->getPeerSelectMethod(); + } + + /** + * Set the Peer select method. + * + * @param string $method The name of the static method to call on the Peer class. + * @return void + */ + public function setPeerSelectMethod($method) + { + $this->peerSelectMethod = $method; + } + + /** + * Return the Peer select method. + * @return string + */ + public function getPeerSelectMethod() + { + return $this->peerSelectMethod; + } + + /** + * Sets the Count method. + * This is set based on the Peer method, for example if Peer method is doSelectJoin*() then the + * count method will be doCountJoin*(). + * @param string $method The name of the static method to call on the Peer class. + */ + public function setPeerCountMethod($method) + { + $this->peerCountMethod = $method; + } + + /** + * Return the Peer count method. + */ + public function getPeerCountMethod() + { + return $this->peerCountMethod; + } + + /** + * Guesses the Peer count method based on the select method. + */ + private function guessPeerCountMethod() + { + $selectMethod = $this->getPeerSelectMethod(); + if ($selectMethod == 'doSelect') { + $countMethod = 'doCount'; + } elseif ( ($pos = stripos($selectMethod, 'doSelectJoin')) === 0) { + $countMethod = 'doCount' . substr($selectMethod, strlen('doSelect')); + } else { + // we will fall back to doCount() if we don't understand the join + // method; however, it probably won't be accurate. Maybe triggering an error would + // be appropriate ... + $countMethod = 'doCount'; + } + $this->setPeerCountMethod($countMethod); + } + + /** + * Get the paged resultset + * + * @return mixed $rs + */ + public function getResult() + { + if (!isset($this->rs)) { + $this->doRs(); + } + + return $this->rs; + } + + /** + * Get the paged resultset + * + * Main method which creates a paged result set based on the criteria + * and the requested peer select method. + * + */ + private function doRs() + { + $this->criteria->setOffset($this->start); + $this->criteria->setLimit($this->max); + $this->rs = call_user_func(array($this->getPeerClass(), $this->getPeerSelectMethod()), $this->criteria); + } + + /** + * Get the first page + * + * For now I can only think of returning 1 always. + * It should probably return 0 if there are no pages + * + * @return int 1 + */ + public function getFirstPage() + { + return '1'; + } + + /** + * Convenience method to indicate whether current page is the first page. + * + * @return boolean + */ + public function atFirstPage() + { + return $this->getPage() == $this->getFirstPage(); + } + + /** + * Get last page + * + * @return int $lastPage + */ + public function getLastPage() + { + $totalPages = $this->getTotalPages(); + if ($totalPages == 0) { + return 1; + } else { + return $totalPages; + } + } + + /** + * Convenience method to indicate whether current page is the last page. + * + * @return boolean + */ + public function atLastPage() + { + return $this->getPage() == $this->getLastPage(); + } + + /** + * get total pages + * + * @return int $this->pages + */ + public function getTotalPages() { + if (!isset($this->pages)) { + $recordCount = $this->getTotalRecordCount(); + if ($this->max > 0) { + $this->pages = ceil($recordCount/$this->max); + } else { + $this->pages = 0; + } + } + return $this->pages; + } + + /** + * get an array of previous id's + * + * @param int $range + * @return array $links + */ + public function getPrevLinks($range = 5) + { + $total = $this->getTotalPages(); + $start = $this->getPage() - 1; + $end = $this->getPage() - $range; + $first = $this->getFirstPage(); + $links = array(); + for ($i=$start; $i>$end; $i--) { + if ($i < $first) { + break; + } + $links[] = $i; + } + + return array_reverse($links); + } + + /** + * get an array of next id's + * + * @param int $range + * @return array $links + */ + public function getNextLinks($range = 5) + { + $total = $this->getTotalPages(); + $start = $this->getPage() + 1; + $end = $this->getPage() + $range; + $last = $this->getLastPage(); + $links = array(); + for ($i=$start; $i<$end; $i++) { + if ($i > $last) { + break; + } + $links[] = $i; + } + + return $links; + } + + /** + * Returns whether last page is complete + * + * @return bool Last page complete or not + */ + public function isLastPageComplete() + { + return !($this->getTotalRecordCount() % $this->max); + } + + /** + * get previous id + * + * @return mixed $prev + */ + public function getPrev() { + if ($this->getPage() != $this->getFirstPage()) { + $prev = $this->getPage() - 1; + } else { + $prev = false; + } + return $prev; + } + + /** + * get next id + * + * @return mixed $next + */ + public function getNext() { + if ($this->getPage() != $this->getLastPage()) { + $next = $this->getPage() + 1; + } else { + $next = false; + } + return $next; + } + + /** + * Set the current page number (First page is 1). + * @param int $page + * @return void + */ + public function setPage($page) + { + $this->page = $page; + // (re-)calculate start rec + $this->calculateStart(); + } + + /** + * Get current page. + * @return int + */ + public function getPage() + { + return $this->page; + } + + /** + * Set the number of rows per page. + * @param int $r + */ + public function setRowsPerPage($r) + { + $this->max = $r; + // (re-)calculate start rec + $this->calculateStart(); + } + + /** + * Get number of rows per page. + * @return int + */ + public function getRowsPerPage() + { + return $this->max; + } + + /** + * Calculate startrow / max rows based on current page and rows-per-page. + * @return void + */ + private function calculateStart() + { + $this->start = ( ($this->page - 1) * $this->max ); + } + + /** + * Gets the total number of (un-LIMITed) records. + * + * This method will perform a query that executes un-LIMITed query. + * + * @return int Total number of records - disregarding page, maxrows, etc. + */ + public function getTotalRecordCount() + { + + if (!isset($this->rs)) { + $this->doRs(); + } + + if (empty($this->recordCount)) { + $this->countCriteria = clone $this->criteria; + $this->countCriteria->setLimit(0); + $this->countCriteria->setOffset(0); + + $this->recordCount = call_user_func( + array( + $this->getPeerClass(), + $this->getPeerCountMethod() + ), + $this->countCriteria + ); + + } + + return $this->recordCount; + + } + + /** + * Sets the start row or offset. + * @param int $v + */ + public function setStart($v) + { + $this->start = $v; + } + + /** + * Sets max rows (limit). + * @param int $v + * @return void + */ + public function setMax($v) + { + $this->max = $v; + } + + /** + * Returns the count of the current page's records + * @return int + */ + public function count() + { + return count($this->getResult()); + } + + /** + * Returns the current element of the iterator + * @return mixed + */ + public function current() + { + if (!isset($this->rs)) { + $this->doRs(); + } + return $this->rs[$this->currentKey]; + } + + /** + * Returns the current key of the iterator + * @return int + */ + public function key() + { + return $this->currentKey; + } + + /** + * Advances the iterator to the next element + * @return void + */ + public function next() + { + $this->currentKey++; + } + + /** + * Resets the iterator to the first element + * @return void + */ + public function rewind() + { + $this->currentKey = 0; + } + + /** + * Checks if the current key exists in the container + * @return boolean + */ + public function valid() + { + if (!isset($this->rs)) { + $this->doRs(); + } + return in_array($this->currentKey, array_keys($this->rs)); + } + +} diff --git a/library/propel/runtime/lib/validator/BasicValidator.php b/library/propel/runtime/lib/validator/BasicValidator.php new file mode 100644 index 000000000..5733e0bf8 --- /dev/null +++ b/library/propel/runtime/lib/validator/BasicValidator.php @@ -0,0 +1,35 @@ + + * @version $Revision: 1612 $ + * @package propel.runtime.validator + */ +interface BasicValidator +{ + + /** + * Determine whether a value meets the criteria specified + * + * @param ValidatorMap $map A column map object for the column to be validated. + * @param string $str a String to be tested + * + * @return mixed TRUE if valid, error message otherwise + */ + public function isValid(ValidatorMap $map, $str); + +} diff --git a/library/propel/runtime/lib/validator/MatchValidator.php b/library/propel/runtime/lib/validator/MatchValidator.php new file mode 100644 index 000000000..a2890e9b5 --- /dev/null +++ b/library/propel/runtime/lib/validator/MatchValidator.php @@ -0,0 +1,68 @@ + + * + * + * + * + * + *
    + * + * @author Michael Aichler + * @author Hans Lellelid + * @version $Revision: 1612 $ + * @package propel.runtime.validator + */ +class MatchValidator implements BasicValidator +{ + /** + * Prepares the regular expression entered in the XML + * for use with preg_match(). + * @param string $exp + * @return string Prepared regular expession. + */ + private function prepareRegexp($exp) + { + // remove surrounding '/' marks so that they don't get escaped in next step + if ($exp{0} !== '/' || $exp{strlen($exp)-1} !== '/' ) { + $exp = '/' . $exp . '/'; + } + + // if they did not escape / chars; we do that for them + $exp = preg_replace('/([^\\\])\/([^$])/', '$1\/$2', $exp); + + return $exp; + } + + /** + * Whether the passed string matches regular expression. + */ + public function isValid (ValidatorMap $map, $str) + { + return (preg_match($this->prepareRegexp($map->getValue()), $str) != 0); + } +} diff --git a/library/propel/runtime/lib/validator/MaxLengthValidator.php b/library/propel/runtime/lib/validator/MaxLengthValidator.php new file mode 100644 index 000000000..286c82c7e --- /dev/null +++ b/library/propel/runtime/lib/validator/MaxLengthValidator.php @@ -0,0 +1,39 @@ + + * + * + * + * + * + * + * + * @author Michael Aichler + * @version $Revision: 1612 $ + * @package propel.runtime.validator + */ +class MaxLengthValidator implements BasicValidator +{ + + public function isValid (ValidatorMap $map, $str) + { + return strlen($str) <= intval($map->getValue()); + } +} diff --git a/library/propel/runtime/lib/validator/MaxValueValidator.php b/library/propel/runtime/lib/validator/MaxValueValidator.php new file mode 100644 index 000000000..b71294043 --- /dev/null +++ b/library/propel/runtime/lib/validator/MaxValueValidator.php @@ -0,0 +1,43 @@ + + * + * + * + * + * + * + * + * + * @author Michael Aichler + * @version $Revision: 1612 $ + * @package propel.runtime.validator + */ +class MaxValueValidator implements BasicValidator +{ + + /** + * @see BasicValidator::isValid() + */ + public function isValid (ValidatorMap $map, $value) + { + if (is_null($value) == false && is_numeric($value) == true) { + return intval($value) <= intval($map->getValue()); + } + + return false; + } +} diff --git a/library/propel/runtime/lib/validator/MinLengthValidator.php b/library/propel/runtime/lib/validator/MinLengthValidator.php new file mode 100644 index 000000000..e5c034596 --- /dev/null +++ b/library/propel/runtime/lib/validator/MinLengthValidator.php @@ -0,0 +1,36 @@ + + * + * + * + * + * + * + * + * @author Michael Aichler + * @version $Revision: 1612 $ + * @package propel.runtime.validator + */ +class MinLengthValidator implements BasicValidator +{ + + /** + * @see BasicValidator::isValid() + */ + public function isValid (ValidatorMap $map, $str) + { + return strlen($str) >= intval($map->getValue()); + } +} diff --git a/library/propel/runtime/lib/validator/MinValueValidator.php b/library/propel/runtime/lib/validator/MinValueValidator.php new file mode 100644 index 000000000..d58894663 --- /dev/null +++ b/library/propel/runtime/lib/validator/MinValueValidator.php @@ -0,0 +1,43 @@ + + * + * + * + * + * + * + * + * + * @author Michael Aichler + * @version $Revision: 1612 $ + * @package propel.runtime.validator + */ +class MinValueValidator implements BasicValidator +{ + + /** + * @see BasicValidator::isValid() + */ + public function isValid (ValidatorMap $map, $value) + { + if (is_null($value) == false && is_numeric($value)) { + return intval($value) >= intval($map->getValue()); + } + + return false; + } +} diff --git a/library/propel/runtime/lib/validator/NotMatchValidator.php b/library/propel/runtime/lib/validator/NotMatchValidator.php new file mode 100644 index 000000000..f0f72998d --- /dev/null +++ b/library/propel/runtime/lib/validator/NotMatchValidator.php @@ -0,0 +1,66 @@ + + * + * + * + * + * + * + * + * @author Michael Aichler + * @author Hans Lellelid + * @version $Revision: 1612 $ + * @package propel.runtime.validator + */ +class NotMatchValidator implements BasicValidator +{ + /** + * Prepares the regular expression entered in the XML + * for use with preg_match(). + * @param string $exp + * @return string Prepared regular expession. + */ + private function prepareRegexp($exp) + { + // remove surrounding '/' marks so that they don't get escaped in next step + if ($exp{0} !== '/' || $exp{strlen($exp)-1} !== '/' ) { + $exp = '/' . $exp . '/'; + } + + // if they did not escape / chars; we do that for them + $exp = preg_replace('/([^\\\])\/([^$])/', '$1\/$2', $exp); + + return $exp; + } + + /** + * Whether the passed string matches regular expression. + */ + public function isValid (ValidatorMap $map, $str) + { + return (preg_match($this->prepareRegexp($map->getValue()), $str) == 0); + } +} diff --git a/library/propel/runtime/lib/validator/RequiredValidator.php b/library/propel/runtime/lib/validator/RequiredValidator.php new file mode 100644 index 000000000..57ab63a6f --- /dev/null +++ b/library/propel/runtime/lib/validator/RequiredValidator.php @@ -0,0 +1,38 @@ + + * + * + * + * + * + * + * + * @author Michael Aichler + * @version $Revision: 1612 $ + * @package propel.runtime.validator + */ +class RequiredValidator implements BasicValidator +{ + + /** + * @see BasicValidator::isValid() + */ + public function isValid (ValidatorMap $map, $str) + { + return ($str !== null && $str !== ""); + } +} diff --git a/library/propel/runtime/lib/validator/TypeValidator.php b/library/propel/runtime/lib/validator/TypeValidator.php new file mode 100644 index 000000000..141760f0a --- /dev/null +++ b/library/propel/runtime/lib/validator/TypeValidator.php @@ -0,0 +1,68 @@ + + * + * + * + * + * + * + * + * @author Hans Lellelid + * @version $Revision: 1612 $ + * @package propel.runtime.validator + */ +class TypeValidator implements BasicValidator +{ + public function isValid(ValidatorMap $map, $value) + { + switch ($map->getValue()) { + case 'array': + return is_array($value); + break; + case 'bool': + case 'boolean': + return is_bool($value); + break; + case 'float': + return is_float($value); + break; + case 'int': + case 'integer': + return is_int($value); + break; + case 'numeric': + return is_numeric($value); + break; + case 'object': + return is_object($value); + break; + case 'resource': + return is_resource($value); + break; + case 'scalar': + return is_scalar($value); + break; + case 'string': + return is_string($value); + break; + case 'function': + return function_exists($value); + break; + default: + throw new PropelException('Unkonwn type ' . $map->getValue()); + break; + } + } +} diff --git a/library/propel/runtime/lib/validator/UniqueValidator.php b/library/propel/runtime/lib/validator/UniqueValidator.php new file mode 100644 index 000000000..49cc91d34 --- /dev/null +++ b/library/propel/runtime/lib/validator/UniqueValidator.php @@ -0,0 +1,48 @@ + + * + * + * + * + * + * + * + * @author Michael Aichler + * @version $Revision: 1612 $ + * @package propel.runtime.validator + */ +class UniqueValidator implements BasicValidator +{ + + /** + * @see BasicValidator::isValid() + */ + public function isValid (ValidatorMap $map, $str) + { + $column = $map->getColumn(); + + $c = new Criteria(); + $c->add($column->getFullyQualifiedName(), $str, Criteria::EQUAL); + + $table = $column->getTable()->getClassName(); + + $clazz = $table . 'Peer'; + $count = call_user_func(array($clazz, 'doCount'), $c); + + $isValid = ($count === 0); + + return $isValid; + } +} diff --git a/library/propel/runtime/lib/validator/ValidValuesValidator.php b/library/propel/runtime/lib/validator/ValidValuesValidator.php new file mode 100644 index 000000000..d680108e9 --- /dev/null +++ b/library/propel/runtime/lib/validator/ValidValuesValidator.php @@ -0,0 +1,33 @@ + + * + * + * + * + * + * + * + * @author Michael Aichler + * @version $Revision: 1612 $ + * @package propel.runtime.validator + */ +class ValidValuesValidator implements BasicValidator +{ + + public function isValid (ValidatorMap $map, $str) + { + return in_array($str, preg_split("/[|,]/", $map->getValue())); + } +} diff --git a/library/propel/runtime/lib/validator/ValidationFailed.php b/library/propel/runtime/lib/validator/ValidationFailed.php new file mode 100644 index 000000000..ed3472074 --- /dev/null +++ b/library/propel/runtime/lib/validator/ValidationFailed.php @@ -0,0 +1,115 @@ + + * @version $Revision: 1612 $ + * @package propel.runtime.validator + * @see BasePeer::doValidate() + */ +class ValidationFailed { + + /** Column name in tablename.COLUMN_NAME format */ + private $colname; + + /** Message to display to user. */ + private $message; + + /** Validator object that caused this to fail. */ + private $validator; + + /** + * Construct a new ValidationFailed object. + * @param string $colname Column name. + * @param string $message Message to display to user. + * @param object $validator The Validator that caused this column to fail. + */ + public function __construct($colname, $message, $validator = null) + { + $this->colname = $colname; + $this->message = $message; + $this->validator = $validator; + } + + /** + * Set the column name. + * @param string $v + */ + public function setColumn($v) + { + $this->colname = $v; + } + + /** + * Gets the column name. + * @return string Qualified column name (tablename.COLUMN_NAME) + */ + public function getColumn() + { + return $this->colname; + } + + /** + * Set the message for the validation failure. + * @param string $v + */ + public function setMessage($v) + { + $this->message = $v; + } + + /** + * Gets the message for the validation failure. + * @return string + */ + public function getMessage() + { + return $this->message; + } + + /** + * Set the validator object that caused this to fail. + * @param object $v + */ + public function setValidator($v) + { + $this->validator = $v; + } + + /** + * Gets the validator object that caused this to fail. + * @return object + */ + public function getValidator() + { + return $this->validator; + } + + /** + * "magic" method to get string represenation of object. + * Maybe someday PHP5 will support the invoking this method automatically + * on (string) cast. Until then it's pretty useless. + * @return string + */ + public function __toString() + { + return $this->getMessage(); + } + +} diff --git a/library/propel/runtime/pear/BuildPropelPEARPackageTask.php b/library/propel/runtime/pear/BuildPropelPEARPackageTask.php new file mode 100644 index 000000000..023820fc4 --- /dev/null +++ b/library/propel/runtime/pear/BuildPropelPEARPackageTask.php @@ -0,0 +1,205 @@ + + * @package phing.tasks.ext + * @version $Revision: 1681 $ + */ +class BuildPropelPEARPackageTask extends MatchingTask +{ + + /** Base directory for reading files. */ + private $dir; + + private $version; + private $state = 'stable'; + private $notes; + + private $filesets = array(); + + /** Package file */ + private $packageFile; + + public function init() + { + include_once 'PEAR/PackageFileManager2.php'; + if (!class_exists('PEAR_PackageFileManager2')) { + throw new BuildException("You must have installed PEAR_PackageFileManager2 (PEAR_PackageFileManager >= 1.6.0) in order to create a PEAR package.xml file."); + } + } + + private function setOptions($pkg) + { + $options['baseinstalldir'] = 'propel'; + $options['packagedirectory'] = $this->dir->getAbsolutePath(); + + if (empty($this->filesets)) { + throw new BuildException("You must use a tag to specify the files to include in the package.xml"); + } + + $options['filelistgenerator'] = 'Fileset'; + + // Some PHING-specific options needed by our Fileset reader + $options['phing_project'] = $this->getProject(); + $options['phing_filesets'] = $this->filesets; + + if ($this->packageFile !== null) { + // create one w/ full path + $f = new PhingFile($this->packageFile->getAbsolutePath()); + $options['packagefile'] = $f->getName(); + // must end in trailing slash + $options['outputdirectory'] = $f->getParent() . DIRECTORY_SEPARATOR; + $this->log("Creating package file: " . $f->getPath(), Project::MSG_INFO); + } else { + $this->log("Creating [default] package.xml file in base directory.", Project::MSG_INFO); + } + + $pkg->setOptions($options); + + } + + /** + * Main entry point. + * @return void + */ + public function main() + { + if ($this->dir === null) { + throw new BuildException("You must specify the \"dir\" attribute for PEAR package task."); + } + + if ($this->version === null) { + throw new BuildException("You must specify the \"version\" attribute for PEAR package task."); + } + + $package = new PEAR_PackageFileManager2(); + + $this->setOptions($package); + + // the hard-coded stuff + $package->setPackage('propel_runtime'); + $package->setSummary('Runtime component of the Propel PHP object persistence layer'); + $package->setDescription('Propel is an object persistence layer for PHP5 based on Apache Torque. This package provides the runtime engine that transparently handles object persistence and retrieval.'); + $package->setChannel('pear.propelorm.org'); + $package->setPackageType('php'); + + $package->setReleaseVersion($this->version); + $package->setAPIVersion($this->version); + + $package->setReleaseStability($this->state); + $package->setAPIStability($this->state); + + $package->setNotes($this->notes); + + $package->setLicense('MIT', 'http://www.opensource.org/licenses/mit-license.php'); + + // Add package maintainers + $package->addMaintainer('lead', 'hans', 'Hans Lellelid', 'hans@xmpl.org'); + $package->addMaintainer('lead', 'david', 'David Zuelke', 'dz@bitxtender.com'); + $package->addMaintainer('lead', 'francois', 'Francois Zaninotto', 'fzaninotto@[gmail].com'); + + // "core" dependencies + $package->setPhpDep('5.2.0'); + $package->setPearinstallerDep('1.4.0'); + + // "package" dependencies + $package->addExtensionDep('required', 'pdo'); + $package->addExtensionDep('required', 'spl'); + + // now we run this weird generateContents() method that apparently + // is necessary before we can add replacements ... ? + $package->generateContents(); + + $e = $package->writePackageFile(); + + if (PEAR::isError($e)) { + throw new BuildException("Unable to write package file.", new Exception($e->getMessage())); + } + + } + + /** + * Used by the PEAR_PackageFileManager_PhingFileSet lister. + * @return array FileSet[] + */ + public function getFileSets() + { + return $this->filesets; + } + + // ------------------------------- + // Set properties from XML + // ------------------------------- + + /** + * Nested creator, creates a FileSet for this task + * + * @return FileSet The created fileset object + */ + function createFileSet() + { + $num = array_push($this->filesets, new FileSet()); + return $this->filesets[$num-1]; + } + + /** + * Set the version we are building. + * @param string $v + * @return void + */ + public function setVersion($v) + { + $this->version = $v; + } + + /** + * Set the state we are building. + * @param string $v + * @return void + */ + public function setState($v) + { + $this->state = $v; + } + + /** + * Sets release notes field. + * @param string $v + * @return void + */ + public function setNotes($v) + { + $this->notes = $v; + } + /** + * Sets "dir" property from XML. + * @param PhingFile $f + * @return void + */ + public function setDir(PhingFile $f) + { + $this->dir = $f; + } + + /** + * Sets the file to use for generated package.xml + */ + public function setDestFile(PhingFile $f) + { + $this->packageFile = $f; + } + +} diff --git a/library/propel/runtime/pear/build-pear-package.xml b/library/propel/runtime/pear/build-pear-package.xml new file mode 100644 index 000000000..31a741530 --- /dev/null +++ b/library/propel/runtime/pear/build-pear-package.xml @@ -0,0 +1,121 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Propel version for package + + + + + + + + + ----------------------------- + | Creating directory layout | + ----------------------------- + + + + + + + + + + + + + + ----------------------------- + | Creating PEAR package.xml | + ----------------------------- + + + + + + + + + + + + + + + ----------------------------- + | Creating tar.gz package | + ----------------------------- + + + + + + \ No newline at end of file diff --git a/library/propel/test/README b/library/propel/test/README new file mode 100644 index 000000000..eb0e1934b --- /dev/null +++ b/library/propel/test/README @@ -0,0 +1,139 @@ += Running The Propel Unit Tests = + +== Background == + +Propel uses [http://www.phpunit.de PHPUnit 3.9] to test the build and runtime frameworks. + +You can find the unit test classes and support files in the [browser:branches/1.4/test/testsuite] directory. + +== Install PHPUnit == + +In order to run the tests, you must install PHPUnit, PEAR:Log, and Phing: +{{{ +> pear channel-discover pear.phpunit.de +> pear install phpunit/PHPUnit-3.3.9 +}}} + +{{{ +> pear channel-discover pear.phing.info +> pear install phing/phing-2.3.3 +}}} + +{{{ +> pear install log +}}} + +Tip: The latest release of PHPUnit (3.4) is not totally BC with the 3.3, and doesn't have a Phing adapter yet. That's why the Propel unit tests still use PHPUnit version 3.3. + +== Configure the Database to be Used in the Tests == + +You must configure both the generator and the runtime connection settings. +{{{ +// in test/fixtures/bookstore/build.properties +propel.database = mysql +propel.database.url = mysql:dbname=test +propel.mysqlTableType = InnoDB +propel.disableIdentifierQuoting=true +# For MySQL or Oracle, you also need to specify username & password +propel.database.user = myusername +propel.database.password = p@ssw0rd +}}} + +{{{ +// in test/fixtures/bookstore/runtime-conf.xml + + + mysql + + + DebugPDO + mysql:dbname=test + myusername + p@ssw0rd + + + + + + + + + + utf8 + + + +}}} + +== Build the Propel Model and Initialize the Database == + +{{{ +> cd /path/to/propel/test +> ../generator/bin/propel-gen fixtures/bookstore main +> mysqladmin create test +> ../generator/bin/propel-gen fixtures/bookstore insert-sql +}}} + +**Tip**: To run the unit tests for the namespace support in PHP 5.3, you must also build the `fixtures/namespaced` project. + +== Run the Unit Tests == + +Run all the unit tests at once using Phing: +{{{ +> cd /path/to/propel/test +> phing -f test.xml -verbose +}}} + +'''Tip''': The `-verbose` option will force the display of PHP notices, which are hidden by default. + +To run a single test, specify the classname (minus 'Test' ending) on the commandline, using the `test` property. For example to run only GeneratedObjectTest: + +{{{ +> phing -f test.xml -verbose -Dtest=GeneratedObject +}}} + +Tip: If you want to set up custom Phing properties for your unit tests, create a `test.properties` file inside the main `test/` directory. Phing will automatically try to load it if it exists. + +== How the Tests Work == + +Every method in the test classes that begins with 'test' is run as a test case by PHPUnit. All tests are run in isolation; the `setUp()` method is called at the beginning of ''each'' test and the `tearDown()` method is called at the end. + +The [browser:branches/1.4/test/tools/helpers/bookstore/BookstoreTestBase.php BookstoreTestBase] class specifies `setUp()` and `tearDown()` methods which populate and depopulate, respectively, the database. This means that every unit test is run with a cleanly populated database. To see the sample data that is populated, take a look at the [browser:branches/1.4/test/tools/helpers/bookstore/BookstoreDataPopulator.php BookstoreDataPopulator] class. You can also add data to this class, if needed by your tests; however, proceed cautiously when changing existing data in there as there may be unit tests that depend on it. More typically, you can simply create the data you need from within your test method. It will be deleted by the `tearDown()` method, so no need to clean up after yourself. + +== Writing Tests == + +If you've made a change to a template or to Propel behavior, the right thing to do is write a unit test that ensures that it works properly -- and continues to work in the future. + +Writing a unit test often means adding a method to one of the existing test classes. For example, let's test a feature in the Propel templates that supports saving of objects when only default values have been specified. Just add a `testSaveWithDefaultValues()` method to the [browser:branches/1.4/test/testsuite/generator/engine/builder/om/php5/GeneratedObjectTest.php GeneratedObjectTest] class, as follows: + +{{{ +#!php +setName('Penguin'); + // in the past this wouldn't have marked object as modified + // since 'Penguin' is the value that's already set for that attrib + $pub->save(); + + // if getId() returns the new ID, then we know save() worked. + $this->assertTrue($pub->getId() !== null, "Expect Publisher->save() to work with only default values."); +} +?> +}}} + +Run the test again using the command line to check that it passes: + +{{{ +> phing -f test.xml -Dtest=GeneratedObject +}}} + +You can also write additional unit test classes to any of the directories in `test/testsuite/` (or add new directories if needed). The Phing task will find these files automatically and run them. \ No newline at end of file diff --git a/library/propel/test/bookstore-packaged-test.php b/library/propel/test/bookstore-packaged-test.php new file mode 100644 index 000000000..7455e9011 --- /dev/null +++ b/library/propel/test/bookstore-packaged-test.php @@ -0,0 +1,811 @@ + + * @version $Revision: 1612 $ + */ + +// Setup configuration. It is expected that the bookstore-conf.php file exists in ../build/conf +// + +error_reporting(E_ALL); + +$conf_path = realpath(dirname(__FILE__) . '/../projects/bookstore-packaged/build/conf/bookstore-packaged-conf.php'); +if (!file_exists($conf_path)) { + print "Make sure that you specify properties in conf/bookstore-packaged.properties and " + ."build propel before running this script."; + exit; +} + +// Add PHP_CLASSPATH, if set +if (getenv("PHP_CLASSPATH")) { + set_include_path(getenv("PHP_CLASSPATH") . PATH_SEPARATOR . get_include_path()); +} + + // Add build/classes/ and classes/ to path +set_include_path( + realpath(dirname(__FILE__) . '/../projects/bookstore-packaged/build/classes') . PATH_SEPARATOR . + dirname(__FILE__) . '/../../runtime/classes' . PATH_SEPARATOR . + get_include_path() +); + + + // Require classes. + require_once 'propel/Propel.php'; + require_once 'author/Author.php'; + require_once 'publisher/Publisher.php'; + require_once 'book/Book.php'; + require_once 'review/Review.php'; + include_once 'media/Media.php'; + include_once 'log/BookstoreLog.php'; + include_once 'book_club_list/BookClubList.php'; + include_once 'book_club_list/BookListRel.php'; + + include_once 'Benchmark/Timer.php'; + + $timer = new Benchmark_Timer; + + $timer->start(); + + // Some utility functions + function boolTest($cond) { + if ($cond) { + return "[OK]\n"; + } else { + return "[FAILED]\n"; + } + } + + try { + // Initialize Propel + Propel::init($conf_path); + } catch (Exception $e) { + die("Error initializing propel: ". $e->__toString()); + } + +function check_tables_empty() { + try { + + print "\nChecking to see that tables are empty\n"; + print "-------------------------------------\n\n"; + + print "Ensuring that there are no records in [author] table: "; + $res = AuthorPeer::doSelect(new Criteria()); + print boolTest(empty($res)); + + print "Ensuring that there are no records in [publisher] table: "; + $res2 = PublisherPeer::doSelect(new Criteria()); + print boolTest(empty($res2)); + + print "Ensuring that there are no records in [book] table: "; + $res3 = AuthorPeer::doSelect(new Criteria()); + print boolTest(empty($res3)); + + print "Ensuring that there are no records in [review] table: "; + $res4 = ReviewPeer::doSelect(new Criteria()); + print boolTest(empty($res4)); + + print "Ensuring that there are no records in [media] table: "; + $res5 = MediaPeer::doSelect(new Criteria()); + print boolTest(empty($res5)); + + print "Ensuring that there are no records in [book_club_list] table: "; + $res6 = BookClubListPeer::doSelect(new Criteria()); + print boolTest(empty($res6)); + + print "Ensuring that there are no records in [book_x_list] table: "; + $res7 = BookListRelPeer::doSelect(new Criteria()); + print boolTest(empty($res7)); + + return (empty($res) && empty($res2) && empty($res3) && empty($res4) && empty($res5)); + + } catch (Exception $e) { + die("Error ensuring tables were empty: " . $e->__toString()); + } +} + +// Check to see if records already exist in any of the three tables. If so, display an error +// and exit. + +if (!check_tables_empty()) { + die("Tables must be empty to perform these tests."); +} + +// Add publisher records +// --------------------- + +try { + print "\nAdding some new publishers to the list\n"; + print "--------------------------------------\n\n"; + + $scholastic = new Publisher(); + $scholastic->setName("Scholastic"); + // do not save, will do later to test cascade + print "Added publisher \"Scholastic\" [not saved yet].\n"; + + $morrow = new Publisher(); + $morrow->setName("William Morrow"); + $morrow->save(); + $morrow_id = $morrow->getId(); + print "Added publisher \"William Morrow\" [id = $morrow_id].\n"; + + $penguin = new Publisher(); + $penguin->setName("Penguin"); + $penguin->save(); + $penguin_id = $penguin->getId(); + print "Added publisher \"Penguin\" [id = $penguin_id].\n"; + + $vintage = new Publisher(); + $vintage->setName("Vintage"); + $vintage->save(); + $vintage_id = $vintage->getId(); + print "Added publisher \"Vintage\" [id = $vintage_id].\n"; + +} catch (Exception $e) { + die("Error adding publisher: " . $e->__toString()); +} + +// Add author records +// ------------------ + +try { + print "\nAdding some new authors to the list\n"; + print "--------------------------------------\n\n"; + + $rowling = new Author(); + $rowling->setFirstName("J.K."); + $rowling->setLastName("Rowling"); + // no save() + print "Added author \"J.K. Rowling\" [not saved yet].\n"; + + $stephenson = new Author(); + $stephenson->setFirstName("Neal"); + $stephenson->setLastName("Stephenson"); + $stephenson->save(); + $stephenson_id = $stephenson->getId(); + print "Added author \"Neal Stephenson\" [id = $stephenson_id].\n"; + + $byron = new Author(); + $byron->setFirstName("George"); + $byron->setLastName("Byron"); + $byron->save(); + $byron_id = $byron->getId(); + print "Added author \"George Byron\" [id = $byron_id].\n"; + + + $grass = new Author(); + $grass->setFirstName("Gunter"); + $grass->setLastName("Grass"); + $grass->save(); + $grass_id = $grass->getId(); + print "Added author \"Gunter Grass\" [id = $grass_id].\n"; + +} catch (Exception $e) { + die("Error adding author: " . $e->__toString()); +} + +// Add book records +// ---------------- + +try { + + print "\nAdding some new books to the list\n"; + print "-------------------------------------\n\n"; + + $phoenix = new Book(); + $phoenix->setTitle("Harry Potter and the Order of the Phoenix"); + $phoenix->setISBN("043935806X"); + + print "Trying cascading save (Harry Potter): "; + $phoenix->setAuthor($rowling); + $phoenix->setPublisher($scholastic); + $phoenix->save(); + $phoenix_id = $phoenix->getId(); + print boolTest(true); + print "Added book \"Harry Potter and the Order of the Phoenix\" [id = $phoenix_id].\n"; + + $qs = new Book(); + $qs->setISBN("0380977427"); + $qs->setTitle("Quicksilver"); + $qs->setAuthor($stephenson); + $qs->setPublisher($morrow); + $qs->save(); + $qs_id = $qs->getId(); + print "Added book \"Quicksilver\" [id = $qs_id].\n"; + + $dj = new Book(); + $dj->setISBN("0140422161"); + $dj->setTitle("Don Juan"); + $dj->setAuthor($byron); + $dj->setPublisher($penguin); + $dj->save(); + $dj_id = $qs->getId(); + print "Added book \"Don Juan\" [id = $dj_id].\n"; + + $td = new Book(); + $td->setISBN("067972575X"); + $td->setTitle("The Tin Drum"); + $td->setAuthor($grass); + $td->setPublisher($vintage); + $td->save(); + $td_id = $td->getId(); + print "Added book \"The Tin Drum\" [id = $dj_id].\n"; + +} catch (Exception $e) { + die("Error saving book: " . $e->__toString()); +} + +// Add review records +// ------------------ + +try { + + print "\nAdding some book reviews to the list\n"; + print "------------------------------------\n\n"; + + $r1 = new Review(); + $r1->setBook($phoenix); + $r1->setReviewedBy("Washington Post"); + $r1->setRecommended(true); + $r1->setReviewDate(time()); + $r1->save(); + $r1_id = $r1->getId(); + print "Added Washington Post book review [id = $r1_id].\n"; + + $r2 = new Review(); + $r2->setBook($phoenix); + $r2->setReviewedBy("New York Times"); + $r2->setRecommended(false); + $r2->setReviewDate(time()); + $r2->save(); + $r2_id = $r2->getId(); + print "Added New York Times book review [id = $r2_id].\n"; + +} catch (Exception $e) { + die("Error saving book review: " . $e->__toString()); +} + +// Perform a "complex" search +// -------------------------- + +try { + + print "\nDoing complex search on books\n"; + print "-----------------------------\n\n"; + + $crit = new Criteria(); + $crit->add(BookPeer::TITLE, 'Harry%', Criteria::LIKE); + + print "Looking for \"Harry%\": "; + $results = BookPeer::doSelect($crit); + print boolTest(count($results) === 1); + + + $crit2 = new Criteria(); + $crit2->add(BookPeer::ISBN, array("0380977427", "0140422161"), Criteria::IN); + $results = BookPeer::doSelect($crit2); + print "Looking for ISBN IN (\"0380977427\", \"0140422161\"): "; + print boolTest(count($results) === 2); + +} catch (Exception $e) { + die("Error while performing complex query: " . $e->__toString()); +} + + +// Perform a "limit" search +// ------------------------ + +try { + + print "\nDoing LIMITed search on books\n"; + print "-----------------------------\n\n"; + + $crit = new Criteria(); + $crit->setLimit(2); + $crit->setOffset(1); + $crit->addAscendingOrderByColumn(BookPeer::TITLE); + + print "Checking to make sure correct number returned: "; + $results = BookPeer::doSelect($crit); + print boolTest(count($results) === 2); + + print "Checking to make sure correct books returned: "; + // we ordered on book title, so we expect to get + print boolTest( $results[0]->getTitle() == "Harry Potter and the Order of the Phoenix" && $results[1]->getTitle() == "Quicksilver" ); + + +} catch (Exception $e) { + die("Error while performing LIMIT query: " . $e->__toString()); +} + + + +// Perform a lookup & update! +// -------------------------- + +try { + + print "\nUpdating just-created book title\n"; + print "--------------------------------\n\n"; + + print "First finding book by PK (=$qs_id) .... "; + + try { + $qs_lookup = BookPeer::retrieveByPk($qs_id); + } catch (Exception $e) { + print "ERROR!\n"; + die("Error retrieving by pk: " . $e->__toString()); + } + + if ($qs_lookup) { + print "FOUND!\n"; + } else { + print "NOT FOUND :(\n"; + die("Couldn't find just-created book: book_id = $qs_id"); + } + + try { + $new_title = "Quicksilver (".crc32(uniqid(rand())).")"; + print "Attempting to update found object (".$qs_lookup->getTitle()." -> ".$new_title."): "; + $qs_lookup->setTitle($new_title); + $qs_lookup->save(); + print boolTest(true); + } catch (Exception $e) { + die("Error saving (updating) book: " . $e->__toString()); + } + + print "Making sure object was correctly updated: "; + $qs_lookup2 = BookPeer::retrieveByPk($qs_id); + print boolTest($qs_lookup2->getTitle() == $new_title); + +} catch (Exception $e) { + die("Error updating book: " . $e->__toString()); +} + + +// Test some basic DATE / TIME stuff +// --------------------------------- + +try { + print "\nTesting the DATE/TIME columns\n"; + print "-----------------------------\n\n"; + + // that's the control timestamp. + $control = strtotime('2004-02-29 00:00:00'); + + // should be two in the db + $r = ReviewPeer::doSelectOne(new Criteria()); + $r_id = $r->getId(); + $r->setReviewDate($control); + $r->save(); + + $r2 = ReviewPeer::retrieveByPk($r_id); + + print "Checking ability to fetch native unix timestamp: "; + print boolTest($r2->getReviewDate(null) === $control); + + print "Checking ability to use date() formatter: "; + print boolTest($r2->getReviewDate('n-j-Y') === '2-29-2004'); + + print "[FYI] Here's the strftime() formatter for current locale: " . $r2->getReviewDate('%x') . "\n"; + +} catch (Exception $e) { + die("Error test date/time: " . $e->__toString()); +} + +// Handle BLOB/CLOB Columns +// ------------------------ + +try { + print "\nTesting the BLOB/CLOB columns\n"; + print "-------------------------------\n\n"; + + $blob_path = dirname(__FILE__) . '/etc/lob/tin_drum.gif'; + $blob2_path = dirname(__FILE__) . '/etc/lob/propel.gif'; + $clob_path = dirname(__FILE__) . '/etc/lob/tin_drum.txt'; + + $m1 = new Media(); + $m1->setBook($phoenix); + $m1->setCoverImage(file_get_contents($blob_path)); + $m1->setExcerpt(file_get_contents($clob_path)); + $m1->save(); + $m1_id = $m1->getId(); + print "Added Media collection [id = $m1_id].\n"; + + print "Looking for just-created mediat by PK (=$m1_id) .... "; + + try { + $m1_lookup = MediaPeer::retrieveByPk($m1_id); + } catch (Exception $e) { + print "ERROR!\n"; + die("Error retrieving media by pk: " . $e->__toString()); + } + + if ($m1_lookup) { + print "FOUND!\n"; + } else { + print "NOT FOUND :(\n"; + die("Couldn't find just-created media item: media_id = $m1_id"); + } + + print "Making sure BLOB was correctly updated: "; + print boolTest( $m1_lookup->getCoverImage()->getContents() === file_get_contents($blob_path)); + print "Making sure CLOB was correctly updated: "; + print boolTest((string) $m1_lookup->getExcerpt()->getContents() === file_get_contents($clob_path)); + + + // now update the BLOB column and save it & check the results + + $b = $m1_lookup->getCoverImage(); + $b->setContents(file_get_contents($blob2_path)); + $m1_lookup->setCoverImage($b); + $m1_lookup->save(); + + try { + $m2_lookup = MediaPeer::retrieveByPk($m1_id); + } catch (Exception $e) { + print "ERROR!\n"; + die("Error retrieving media by pk: " . $e->__toString()); + } + + print "Making sure BLOB was correctly overwritten: "; + print boolTest($m2_lookup->getCoverImage()->getContents() === file_get_contents($blob2_path)); + +} catch (Exception $e) { + die("Error doing blob/clob updates: " . $e->__toString()); +} + +// Test Validators +// --------------- + +try { + + print "\nTesting the column validators\n"; + print "-----------------------------\n\n"; + + $bk1 = new Book(); + $bk1->setTitle("12345"); // min length is 10 + $ret = $bk1->validate(); + + print "Making sure validation failed: "; + print boolTest($ret !== true); + + print "Making sure 1 validation message was returned: "; + print boolTest(count($ret) === 1); + + print "Making sure expected validation message was returned: "; + $el = array_shift($ret); + print boolTest(stripos($el->getMessage(), "must be more than") !== false); + + print "\n(Unique validator)\n"; + + $bk2 = new Book(); + $bk2->setTitle("Don Juan"); + $ret = $bk2->validate(); + + print "Making sure validation failed: "; + print boolTest($ret !== true); + + print "Making sure 1 validation message was returned: "; + print boolTest(count($ret) === 1); + + print "Making sure expected validation message was returned: "; + $el = array_shift($ret); + print boolTest(stripos($el->getMessage(), "Book title already in database.") !== false); + + print "\n(Now trying some more complex validation.)\n"; + $auth1 = new Author(); + $auth1->setFirstName("Hans"); + // last name required; will fail + + $bk1->setAuthor($auth1); + + $rev1 = new Review(); + $rev1->setReviewDate("08/09/2001"); + // will fail: reviewed_by column required + + $bk1->addReview($rev1); + + $ret2 = $bk1->validate(); + + print "Making sure 6 validation messages were returned: "; + print boolTest(count($ret2) === 6); + + print "Making sure correct columns failed: "; + print boolTest(array_keys($ret2) === array( + AuthorPeer::LAST_NAME, + AuthorPeer::EMAIL, + AuthorPeer::AGE, + BookPeer::TITLE, + ReviewPeer::REVIEWED_BY, + ReviewPeer::STATUS + )); + + + $bk2 = new Book(); + $bk2->setTitle("12345678901"); // passes + + $auth2 = new Author(); + $auth2->setLastName("Blah"); //passes + $auth2->setEmail("some@body.com"); //passes + $auth2->setAge(50); //passes + $bk2->setAuthor($auth2); + + $rev2 = new Review(); + $rev2->setReviewedBy("Me!"); // passes + $rev2->setStatus("new"); // passes + $bk2->addReview($rev2); + + $ret3 = $bk2->validate(); + + print "Making sure complex validation can pass: "; + print boolTest($ret3 === true); + +} catch (Exception $e) { + die("Error doing validation tests: " . $e->__toString()); +} + + +// Test doCount() +// +try { + + print "\nTesting doCount() functionality\n"; + print "-------------------------------\n\n"; + + $c = new Criteria(); + $records = BookPeer::doSelect($c); + $count = BookPeer::doCount($c); + + print "Making sure correct number of results: "; + print boolTest(count($records) === $count); + +} catch (Exception $e) { + die("Error deleting book: " . $e->__toString()); +} + +// Test many-to-many relationships +// --------------- + +try { + + print "\nTesting many-to-many relationships\n"; + print "-----------------------------\n\n"; + + // init book club list 1 with 2 books + + $blc1 = new BookClubList(); + $blc1->setGroupLeader("Crazyleggs"); + $blc1->setTheme("Happiness"); + + $brel1 = new BookListRel(); + $brel1->setBook($phoenix); + + $brel2 = new BookListRel(); + $brel2->setBook($dj); + + $blc1->addBookListRel($brel1); + $blc1->addBookListRel($brel2); + + $blc1->save(); + + print "Making sure BookClubList 1 was saved: "; + print boolTest(!is_null($blc1->getId())); + + // init book club list 2 with 1 book + + $blc2 = new BookClubList(); + $blc2->setGroupLeader("John Foo"); + $blc2->setTheme("Default"); + + $brel3 = new BookListRel(); + $brel3->setBook($phoenix); + + $blc2->addBookListRel($brel3); + + $blc2->save(); + + print "Making sure BookClubList 2 was saved: "; + print boolTest(!is_null($blc2->getId())); + + // re-fetch books and lists from db to be sure that nothing is cached + + $crit = new Criteria(); + $crit->add(BookPeer::ID, $phoenix->getId()); + $phoenix = BookPeer::doSelectOne($crit); + print "Making sure book 'phoenix' has been re-fetched from db: "; + print boolTest(!empty($phoenix)); + + $crit = new Criteria(); + $crit->add(BookClubListPeer::ID, $blc1->getId()); + $blc1 = BookClubListPeer::doSelectOne($crit); + print "Making sure BookClubList 1 has been re-fetched from db: "; + print boolTest(!empty($blc1)); + + $crit = new Criteria(); + $crit->add(BookClubListPeer::ID, $blc2->getId()); + $blc2 = BookClubListPeer::doSelectOne($crit); + print "Making sure BookClubList 2 has been re-fetched from db: "; + print boolTest(!empty($blc2)); + + $relCount = $phoenix->countBookListRels(); + print "Making sure book 'phoenix' has 2 BookListRels: "; + print boolTest($relCount == 2); + + $relCount = $blc1->countBookListRels(); + print "Making sure BookClubList 1 has 2 BookListRels: "; + print boolTest($relCount == 2); + + $relCount = $blc2->countBookListRels(); + print "Making sure BookClubList 2 has 1 BookListRel: "; + print boolTest($relCount == 1); + + +} catch (Exception $e) { + die("Error doing many-to-many relationships tests: " . $e->__toString()); +} + +// Test multiple databases +// --------------- + +try { + + print "\nTesting multiple databases\n"; + print "-----------------------------\n\n"; + + $line = new BookstoreLog(); + $line->setIdent('bookstore-packaged-test'); + $line->setTime(time()); + $line->setMessage('We are testing to write something to the log database ...'); + $line->setPriority('debug'); + $line->save(); + + $line_id = $line->getId(); + print "Making sure BookstoreLog was saved: "; + print boolTest(!empty($line_id)); + +} catch (Exception $e) { + die("Error doing multiple databases tests: " . $e->__toString()); +} + +// Cleanup (tests DELETE) +// ---------------------- + +try { + + print "\nRemoving books that were just created\n"; + print "-------------------------------------\n\n"; + + print "First finding book by PK (=$phoenix_id) .... "; + try { + $hp = BookPeer::retrieveByPk($phoenix_id); + } catch (Exception $e) { + print "ERROR!\n"; + die("Error retrieving by pk: " . $e->__toString()); + } + + if ($hp) { + print "FOUND!\n"; + } else { + print "NOT FOUND :(\n"; + die("Couldn't find just-created book: book_id = $phoenix_id"); + } + + print "Attempting to delete [multi-table] by found pk: "; + $c = new Criteria(); + $c->add(BookPeer::ID, $hp->getId()); + // The only way for cascading to work currently + // is to specify the author_id and publisher_id (i.e. the fkeys + // have to be in the criteria). + $c->add(AuthorPeer::ID, $hp->getId()); + $c->add(PublisherPeer::ID, $hp->getId()); + $c->setSingleRecord(true); + BookPeer::doDelete($c); + print boolTest(true); + + print "Checking to make sure correct records were removed.\n"; + print "\tFrom author table: "; + $res = AuthorPeer::doSelect(new Criteria()); + print boolTest(count($res) === 3); + print "\tFrom publisher table: "; + $res2 = PublisherPeer::doSelect(new Criteria()); + print boolTest(count($res2) === 3); + print "\tFrom book table: "; + $res3 = BookPeer::doSelect(new Criteria()); + print boolTest(count($res3) === 3); + + print "Attempting to delete books by complex criteria: "; + $c = new Criteria(); + $cn = $c->getNewCriterion(BookPeer::ISBN, "043935806X"); + $cn->addOr($c->getNewCriterion(BookPeer::ISBN, "0380977427")); + $cn->addOr($c->getNewCriterion(BookPeer::ISBN, "0140422161")); + $c->add($cn); + BookPeer::doDelete($c); + print boolTest(true); + + print "Attempting to delete book [id = $td_id]: "; + $td->delete(); + print boolTest(true); + + print "Attempting to delete author [id = $stephenson_id]: "; + AuthorPeer::doDelete($stephenson_id); + print boolTest(true); + + print "Attempting to delete author [id = $byron_id]: "; + AuthorPeer::doDelete($byron_id); + print boolTest(true); + + print "Attempting to delete author [id = $grass_id]: "; + $grass->delete(); + print boolTest(true); + + print "Attempting to delete publisher [id = $morrow_id]: "; + PublisherPeer::doDelete($morrow_id); + print boolTest(true); + + print "Attempting to delete publisher [id = $penguin_id]: "; + PublisherPeer::doDelete($penguin_id); + print boolTest(true); + + print "Attempting to delete publisher [id = $vintage_id]: "; + $vintage->delete(); + print boolTest(true); + + // These have to be deleted manually also since we have onDelete + // set to SETNULL in the foreign keys in book. Is this correct? + print "Attempting to delete author [lastname = 'Rowling']: "; + $rowling->delete(); + print boolTest(true); + + print "Attempting to delete publisher [lastname = 'Scholastic']: "; + $scholastic->delete(); + print boolTest(true); + + print "Attempting to delete BookClubList 1: "; + $blc1->delete(); + print boolTest(true); + + print "Attempting to delete BookClubList 2: "; + $blc2->delete(); + print boolTest(true); + +} catch (Exception $e) { + die("Error deleting book: " . $e->__toString()); +} + + +// Check again to make sure that tables are empty +// ---------------------------------------------- + +check_tables_empty(); + + + + + +$timer->stop(); +print $timer->display(); diff --git a/library/propel/test/etc/lob/propel.gif b/library/propel/test/etc/lob/propel.gif new file mode 100644 index 000000000..48e881ef5 Binary files /dev/null and b/library/propel/test/etc/lob/propel.gif differ diff --git a/library/propel/test/etc/lob/tin_drum.gif b/library/propel/test/etc/lob/tin_drum.gif new file mode 100644 index 000000000..a0de847bb Binary files /dev/null and b/library/propel/test/etc/lob/tin_drum.gif differ diff --git a/library/propel/test/etc/lob/tin_drum.txt b/library/propel/test/etc/lob/tin_drum.txt new file mode 100644 index 000000000..09dacac0f --- /dev/null +++ b/library/propel/test/etc/lob/tin_drum.txt @@ -0,0 +1,76 @@ +Granted: I am an inmate of a mental hospital; my keeper is watching me, he never lets me out of his sight; there's a peephole in the door, and my keeper's eye is the shade of brown that can never see through a blue-eyed type like me. + +So you see, my keeper can't be an enemy. I've come to be very fond of him; when he stops looking at me from behind the door and comes into the room, I tell him incidents from my life, so he can get to know me in spite of the peephole between us. He seems to treasure my stories, because every time I tell him some fairy tale, he shows his gratitude by bringing out his latest knot construction. I wouldn't swear that he's an artist. But I am certain that an exhibition of his creations would be well received by the press and attract a few purchasers. He picks up common pieces of string in the patients' rooms after visiting hours, disentangles them, and works them up into elaborate contorted spooks; then he dips them in plaster, lets them harden, and mounts them on knitting needles that he fastens to little wooden pedestals. + +He often plays with the idea of coloring his works. l advise him against it, taking my white enamel bed as an example and bidding him try to imagine how this most perfect of all beds would look if painted in many colors. He raises his hands in horror, tries to give his rather expressionless face an expression of extreme disgust, and abandons his polychrome projects. + +So you see, my white-enameled, metal hospital bed has become a norm and standard. To me it is still more: my bed is a goal attained at last, it is my consolation and might become my faith if the management allowed me to make a few changes: I should like, for instance, to have the bars built up higher, to prevent anyone from coming too close to me. + + +Once a week a visiting day breaks in on the stillness that I plait between the white metal bars. This is the time for the people who want to save me, whom it amuses to love me, who try to esteem and respect themselves, to get to know themselves, through me. How blind, how nervous and ill-bred they are! They scratch the white enamel of my bedstead with their fingernail scissors, they scribble obscene little men on it with their ballpoint pens and blue pencils. No sooner has my lawyer blasted the room with his hello than he slaps his nylon hat down over the lower left-hand bedpost--an act of violence that shatters my peace of mind for the duration of his visit, and lawyers find a good deal to talk about. + +After my visitors have deposited their gifts beneath the water color of the anemones, on the little white table covered with oilcloth, after they have submitted their current projects for my salvation, and convinced me, whom they are working indefatigably to save, of the high quality of their charity, they recover their relish in their own existence, and leave me. Then my keeper comes in to air the room and collect the strings from the gift packages. Often after airing he finds time to sit by my bed for a while, disentangling his strings, and spreading silence until I call the silence Bruno and Bruno silence. + +Bruno Munsterberg--this time I mean my keeper, I've stopped playing with words--has bought me five hundred sheets of writing paper. + +Should this supply prove insufficient, Bruno, who is unmarried and childless and hails from the Sauerland, will go to the little stationery store that also sells toys, and get me some more of the unlined space I need for the recording of my memories--I only hope they are accurate. I could never have asked such a service of my visitors, the lawyer for instance, or Klepp. The solicitous affection prescribed in my case would surely have deterred my friends from bringing me anything so dangerous as blank paper and making it available to this mind of mine which persists in excreting syllables. + +"Oh, Bruno," I said, "would you buy me a ream of virgin paper?" And Bruno, looking up at the ceiling and pointing his index finger in the same direction by way of inviting a comparison, replied: "You mean white paper, Herr Oskar?" + +I stuck to "virgin" and asked Bruno to say just that in the store. When he came back late in the afternoon with the package, he gave the impression of a Bruno shaken by thought. Several times he looked fixedly up at the ceiling from which he derived all his inspiration. And a little later he spoke: "That was the right word you told me. I asked for virgin paper and the salesgirl blushed like mad before getting it." + +Fearing an interminable conversation about salesgirls in stationery stores, I regretted having spoken of virgin paper and said nothing, waiting for Bruno to leave the room. Only then did I open the package with the five hundred sheets of writing paper. + +For a time I weighed the hard, flexible ream in my hands; then I counted out ten sheets and stowed the rest in my bedside table. I found my fountain pen in the drawer beside the photograph album: it's full, ink is no problem, how shall I begin? + +You can begin a story in the middle and create confusion by striking out boldly, backward and forward. You can be modern, put aside all mention of time and distance and, when the whole thing is done, proclaim, or let someone else proclaim, that you have finally, at the last moment, solved the space-time problem. Or you can declare at the very start that it's impossible to write a novel nowadays, but then, behind your own back so to speak, give birth to a whopper, a novel to end all novels. I have also been told that it makes a good impression, an impression of modesty so to speak, if you begin by saying that a novel can't have a hero any more because there are no more individualists, because individuality is a thing of the past, because man--each man and all men together--is alone in his loneliness and no one is entitled to individual loneliness, and all men lumped together make up a "lonely mass" without names and without heroes. All this may be true. But as far as I and Bruno my keeper are concerned, I beg leave to say that we are both heroes, very different heroes, he on his side of the peephole, and I on my side; and even, when he opens the door, the two of us, with all our friendship and loneliness, are still far from being a nameless, heroless mass. + +I shall begin far away from me; for no one ought to tell the story of his life who hasn't the patience to say a word or two about at least half of his grandparents before plunging into his own existence. And so to you personally, dear reader, who are no doubt leading a muddled kind of life outside this institution, to you my friends and weekly visitors who suspect nothing of my paper supply, I introduce Oskar's maternal grandmother. + +Late one October afternoon my grandmother Anna Bronski was sitting in her skirts at the edge of a potato field. In the morning you might have seen how expert my grandmother was at making the limp potato plants into neat piles; at noon she had eaten a chunk of bread smeared with lard and syrup; then she had dug over the field a last time, and now she sat in her skirts between two nearly full baskets. The soles of her boots rose up at right angles to the ground, converging slightly at the toes, and in front of them smoldered a fire of potato plants, flaring up asthmatically from time to time, sending a queasy film of smoke out over the scarcely inclined crust of the earth. The year was 1899; she was sitting in the heart of Kashubia, not far from Bissau but still closer to the brickworks between Ramkau and Viereck, in front of her the Brenntau highway at a point between Dirschau and Karthaus, behind her the black forest of Goldkrug; there she sat, pushing potatoes about beneath the hot ashes with the charred tip of a hazel branch. + +If I have made a special point of my grandmother's skirt, leaving no doubt, I hope, that she was sitting in her skirts; if indeed I have gone so far as to call the whole chapter "The Wide Skirt," it is because I know how much I owe to this article of apparel. My grandmother had on not just one skirt, but four, one over the other. It should not be supposed that she wore one skirt and three petticoats; no, she wore four skirts; one supported the next, and she wore the lot of them in accordance with a definite system, that is, the order of the skirts was changed from day to day. The one that was on top yesterday was today in second place; the second became the third. The one that was third yesterday was next to her skin today. The one that was closest to her yesterday clearly disclosed its pattern today, or rather its lack of pattern: all my grandmother Anna Bronski's skirts favored the same potato color. It must have been becoming to her. + +Aside from the color, my grandmother's skirts were distinguished by a lavish expanse of material. They puffed and billowed when the wind came, crackled as it passed, and sagged when it was gone, and all four of them flew out ahead of her when she had the wind in her stern. When she sat down, she gathered her skirts about her. + +In addition to the four skirts, billowing, sagging, hanging down in folds, or standing stiff and empty beside her bed, my grandmother possessed a fifth. It differed in no way from the other four potato-coloured garments. And actually the fifth skirt was not always fifth. Like its brothers-for skirts are masculine by nature-it was subject to change, it was worn like the other four, and like them when its time had come, took its turn in the wash trough every fifth Friday, then Saturday on the line by the kitchen window, and when dry on the ironing board. + +When, after one of these Saturdays spent in housecleaning, baking, washing and ironing, after milking and feeding the cow, my grandmother immersed herself from top to toe in the tub, when after leaving a little of herself in the soapsuds and letting the water in the tub sink back to its normal level, she sat down on the edge of the bed swathed in a great flowery towel, the four worn skirts and the freshly washed skirt lay spread out before her on the floor. She pondered, propping the lower lid of her right eye with her right index finger, and since she consulted no one, not even her brother Vincent, she quickly made up her mind. She stood up and with her bare toes pushed aside the skirt whose potato color had lost the most bloom. The freshly laundered one took its place. + +On Sunday morning she went to church in Ramkau and inaugurated the new order of skirts in honor of ]esus, about whom she had very set ideas. Where did my grandmother wear the laundered skirt? She was not only a cleanly woman, but also a rather vain one; she wore the best piece where it could be seen in the sunlight when the weather was good. + +But now it was a Monday afternoon and my grandmother was sitting by the potato fire. Today her Sunday skirt was one layer closer to her person, while the one that had basked in the warmth of her skin on Sunday swathed her hips in Monday gloom. Whistling with no particular tune in mind, she coaxed the first cooked potato out of the ashes with her hazel branch and pushed it away from the smoldering mound to cool in the breeze. Then she spitted the charred and crusty tuber on a pointed stick and held it close to her mouth; she had stopped whistling and instead pursed her cracked, wind-parched lips to blow the earth and ashes off the potato skin. + +In blowing, my grandmother closed her eyes. When she thought she had blown enough, she opened first one eye, then the other, bit into the potato with her widely spaced but otherwise perfect front teeth, removed half the potato, cradled the other half, mealy, steaming, and still too hot to chew, in her open mouth, and, snifflng at the smoke and the October air, gazed wide-eyed across the field toward the nearby horizon, sectioned by telegraph poles and the upper third of the brickworks chimney. + +Something was moving between the telegraph poles. My grandmother closed her mouth. Something was jumping about. Three men were darting between the poles, three men made for the chimney, then round in front, then one doubled back. Short and wide he seemed, he took a fresh start and made it across the brickyard, the other two, sort of long and thin, just behind him. They were out of the brickyard, back between the telegraph poles, but Short and Wide twisted and turned and seemed to be in more of a hurry than Long and Thin, who had to double back to the chimney, because he was already rolling over it when they two hands' breadths away, were still taking a start, and suddenly they were gone as though they had given up, and the little one disappeared too, behind the horizon, in the middle of his jump from the chimney. + +Out of sight they remained, it was intermission, they were changing their costumes, or making bricks and getting paid for it. + +Taking advantage of the intermission, my grandmother tried to spit another potato, but missed it. Because the one who seemed to be short and wide, who hadn't changed his clothes after all climbed up over the horizon as if it were a fence and he had left his pursuers behind it, in among the bricks or on the road to Brenntau. But he was still in a hurry; trying to go faster than the telegraph poles, he took long slow leaps across the field; the mud flew from his boots as he leapt over the soggy ground, but leap as he might, he seemed to be crawling. Sometimes he seemed to stick in the ground and then to stick in mid-air, short and wide time enough to wipe his face before his foot came down again in the freshly plowed field, which bordered the five acres of potatoes and narrowed into a sunken lane. + +He made it to the lane; short and wide, he had barely disappeared into the lane, when the two others, long and thin, who had probably been searching the brickyard in the meantime, climbed over the horizon and came plodding through the mud, so long and thin, but not really skinny, that my grandmother missed her potato again; because it's not every day that you see this kind of thing, three full-grown men, though they hadn't grown in exactly the same directions, hopping around telegraph poles, nearly breaking the chimney off the brickworks, and then at intervals, first short and wide, then long and thin, but all with the same difficulty, picking up more and more mud on the soles of their boots, leaping through the field that Vincent had plowed two days before, and disappearing down the sunken lane. + +Then all three of them were gone and my grandmother ventured to spit another potato, which by this time was almost cold. She hastily blew the earth and ashes off the skin, popped the whole potato straight into her mouth. They must be from the brickworks, she thought if she thought anything, and she was still chewing with a circular motion when one of them jumped out of the lane, wild eyes over a black mustache, reached the fire in two jumps, stood before, behind, and beside the fire all at once, cursing, scared, not knowing which way to go, unable to turn back, for behind him Long and Thin were running down the lane. He hit his knees, the eyes in his head were like to pop out, and sweat poured from his forehead. Panting, his whole face atremble, he ventured to crawl closer, toward the soles of my grandmother's boots, peering up at her like a squat little animal. Heaving a great sigh, which made her stop chewing on her potato, my grandmother let her feet tilt over, stopped thinking about bricks and brickmakers, and lifted high her skirt, no, all four skirts, high enough so that Short and Wide, who was not from the brickworks, could crawl underneath. Gone was his black mustache; he didn't look like an animal any more, he was neither from Ramkau nor from Viereck, at any rate he had vanished with his fright, he had ceased to be wide or short but he took up room just the same, he forgot to pant or tremble and he had stopped hitting his knees; all was as still as on the first day of Creation or the last; a bit of wind hummed in the potato fire, the telegraph poles counted themselves in silence, the chimney of the brickworks stood at attention, and my grandmother smoothed down her uppermost skirt neatly and sensibly over the second one; she scarcely felt him under her fourth skirt, and her third skirt wasn't even aware that there was anything new and unusual next to her skin. Yes, unusual it was, but the top was nicely smoothed out and the second and third layers didn't know a thing; and so she scraped two or three potatoes out of the ashes, took four raw ones from the basket beneath her right elbow, pushed the raw spuds one after another into the hot ashes, covered them over with more ashes, and poked the fire till the smoke rose in clouds--what else could she have done? + +My grandmother's skirts had barely settled down; the sticky smudge of the potato fire, which had lost its direction with all the poking and thrashing about, had barely had time to adjust itself to the wind and resume its low yellow course across the field to southwestward, when Long and Thin popped out of the lane, hot in pursuit of Short and Wide, who by now had set up housekeeping beneath my grandmother's skirts; they were indeed long and thin and they wore the uniform of the rural constabulary. + +They nearly ran past my grandmother. One of them even jumped over the fire. But suddenly they remembered they had heels and used them to brake with, about-faced, stood booted and uniformed in the smudge, coughed, pulled their uniforms out of the smudge, taking some of it along with them, and, still coughing, turned to my grandmother, asked her if she had seen Koljaiczek, 'cause she must have seen him 'cause she was sitting here by the lane and that was the way he had come. + +My grandmother hadn't seen any Koljaiczek because she didn't know any Koljaiczek. Was he from the brickworks, she asked, 'cause the only ones she knew were the ones from the brickworks. But according to the uniforms, this Koljaiczek had nothing to do with bricks, but was short and stocky. My grandmother remembered she had seen somebody like that running and pointed her stick with the steaming potato on the end toward Bissau, which, to judge by the potato, must have been between the sixth and seventh telegraph poles if you counted westward from the chimney. But whether this fellow that was running was a Koljaiczek, my grandmother couldn't say; she'd been having enough trouble with this fire,: she explained, it was burning poorly, how could she worry her head about all the people that ran by or stood in the smoke, and anyway she never worried her head about people she didn't know, she only knew the people in Bissau, Ramkau, Viereck, and the brickworks--and that was plenty for her. + +After saying all this, my grandmother heaved a gentle sigh, but lt was enough of a sigh to make the uniforms ask what there was to sigh about. She nodded toward the fire, meaning to say that she had sighed because the fire was doing poorly and maybe a little on account of the people standing in the smoke; then she bit off half her potato with her widely spaced incisors, and gave her undivided attention to the business of chewing, while her eyeballs rolled heavenward. + +My grandmother's absent gaze told the uniforms nothing; unable to make up their minds whether to look for Bissau behind the telegraph poles, they poked their bayonets into all the piles of potato tops that hadn't been set on fire. Responding to a sudden inspiration, they upset the two baskets under my grandmother's elbows almost simultaneously and were quite bewildered when nothing but potatoes came rolling out, and no Koljaiczek. Full of suspicion, they crept round the stack of potatoes, as though Koljaiczek had somehow got into it, thrust in their bayonets as though deliberately taking aim, and were disappointed to hear no cry. Their suspicions were aroused by every bush, however abject, by every mousehole, by a colony of molehills, and most of all by my grandmother, who sat there as if rooted to the spot, sighing, rolling her eyes so that the whites showed, listing the Kashubian names of all the saints--all of which seemed to have been brought on by the poor performance of the fire and the overturning of her potato baskets. + +The uniforms stayed on for a good half-hour. They took up positions at varying distances from the fire, they took an azimuth on the chimney, contemplated an offensive against Bissau but postponed it, and held out their purple hands over the fire until my grandmother, though without interrupting her sighs, gave each of them a charred potato. But in the midst of chewing, the uniforms remembered their uniforms, dashed a little way out into the field along the furze bordering the lane, and scared up a hare which, however, turned out not to be Koljaiczek. Returning to the fire, they recovered the mealy, steaming spuds and then, wearied and rather mellowed by their battles, decided to pick up the raw potatoes and put them back into the baskets which they had overturned in line of duty. + +Only when evening began to squeeze a fine slanting rain and an inky twilight from the October sky did they briefly and without enthusiasm attack a dark boulder at the other end of the field, but once this enemy had been disposed of they decided to let well enough alone. After flexing their legs for another moment or two and holding out their hands in blessing over the rather dampened fire, they coughed a last cough and dropped a last tear in the green and yellow smudge, and plodded off coughing and weeping in the direction of Bissau. If Koljaiczek wasn't here, he must be in Bissau. Rural constables never envisage more than two possibilities. + +The smoke of the slowly dying fire enveloped my grandmother like a spacious fifth skirt, so that she too with her four skirts, her sighs, and her holy names, was under a skirt. Only when the uniforms had become staggering dots, vanishing in the dusk between the telegraph poles, did my grandmother arise, slowly and painfully as though she had struck root and now, drawing earth and fibers along with her, were tearing herself out of the ground. + +Suddenly Koljaiczek found himself short, wide, and coverless in the rain, and he was cold. Quickly he buttoned his pants, which fear and a boundless need for shelter had bidden him open during his stay beneath the skirts. Hurriedly he manipulated the buttons, fearing to let his piston cool too quickly, for there was a threat of dire chills in the autumn air. + +My grandmother found four more hot potatoes under the ashes. She gave Koljaiczek three of them and took one for herself; before biting into it she asked if he was from the brickworks, though she knew perfectly well that Koljaiczek came from somewhere else and had no connection with bricks. Without waiting for an answer, she lifted the lighter basket to his back, took the heavier one for herself, and still had a hand free for her rake and hoe. Then with her basket, her potatoes, her rake, and her hoe, she set off, like a sail billowing in the breeze, in the direction of Bissau Quarry. + +That wasn't the same as Bissau itself. It lay more in the direction of Ramkau. Passing to the right of the brickworks, they headed for the black forest with Goldkrug in it and Brenntau behind it. But in a hollow, before you come to the forest, lay Bissau Quarry. Thither Joseph Koljaiczek, unable to tear himself away from her skirts, followed my grandmother. \ No newline at end of file diff --git a/library/propel/test/etc/schema/tabletest-schema.xml b/library/propel/test/etc/schema/tabletest-schema.xml new file mode 100644 index 000000000..7710370e3 --- /dev/null +++ b/library/propel/test/etc/schema/tabletest-schema.xml @@ -0,0 +1,25 @@ + + + + + + + + + +
    + + + + +
    + + +
    + diff --git a/library/propel/test/etc/xsl/coverage-frames.xsl b/library/propel/test/etc/xsl/coverage-frames.xsl new file mode 100644 index 000000000..edfcf2983 --- /dev/null +++ b/library/propel/test/etc/xsl/coverage-frames.xsl @@ -0,0 +1,636 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Coverage Results. + + + + + + + + + <h2>Frame Alert</h2> + <p> + This document is designed to be viewed using the frames feature. If you see this message, you are using a non-frame-capable web client. + </p> + + + + + + + + .bannercell { + border: 0px; + padding: 0px; + } + body { + margin-left: 10; + margin-right: 10; + background-color:#FFFFFF; + font-family: verdana,arial,sanserif; + color:#000000; + } + a { + color: #003399; + } + a:hover { + color: #888888; + } + .a td { + background: #efefef; + } + .b td { + background: #fff; + } + th, td { + text-align: left; + vertical-align: top; + } + th { + font-weight:bold; + background: #ccc; + color: black; + } + table, th, td { + font-size: 12px; + border: none + } + table.log tr td, tr th { + } + h2 { + font-weight:bold; + font-size: 12px; + margin-bottom: 5; + } + h3 { + font-size:100%; + font-weight: 12px; + background: #DFDFDF + color: white; + text-decoration: none; + padding: 5px; + margin-right: 2px; + margin-left: 2px; + margin-bottom: 0; + } + .small { + font-size: 9px; + } +TD.empty { + FONT-SIZE: 2px; BACKGROUND: #c0c0c0; BORDER:#9c9c9c 1px solid; + color: #c0c0c0; +} +TD.fullcover { + FONT-SIZE: 2px; BACKGROUND: #00df00; BORDER:#9c9c9c 1px solid; + color: #00df00; +} +TD.covered { + FONT-SIZE: 2px; BACKGROUND: #00df00; BORDER-LEFT:#9c9c9c 1px solid;BORDER-TOP:#9c9c9c 1px solid;BORDER-BOTTOM:#9c9c9c 1px solid; + color: #00df00; +} +TD.uncovered { + FONT-SIZE: 2px; BACKGROUND: #df0000; BORDER:#9c9c9c 1px solid; + color: #df0000; +} +PRE.srcLine { + BACKGROUND: #ffffff; MARGIN-TOP: 0px; MARGIN-BOTTOM: 0px; +} +td.lineCount, td.coverageCount { + BACKGROUND: #F0F0F0; PADDING-RIGHT: 3px; + text-align: right; +} +td.lineCountHighlight { + background: #C8C8F0; PADDING-RIGHT: 3px; + text-align: right; +} +td.coverageCountHighlight { + background: #F0C8C8; PADDING-RIGHT: 3px; + text-align: right; +} +span.srcLineHighlight { + background: #F0C8C8; +} +span.srcLine { + background: #C8C8F0; +} +TD.srcLineClassStart { + WIDTH: 100%; BORDER-TOP:#dcdcdc 1px solid; FONT-WEIGHT: bold; +} +.srcLine , .srcLine ol, .srcLine ol li {margin: 0;} +.srcLine .de1, .srcLine .de2 {font-family: 'Courier New', Courier, monospace; font-weight: normal;} +.srcLine .imp {font-weight: bold; color: red;} +.srcLine .kw1 {color: #b1b100;} +.srcLine .kw2 {color: #000000; font-weight: bold;} +.srcLine .kw3 {color: #000066;} +.srcLine .co1 {color: #808080; font-style: italic;} +.srcLine .co2 {color: #808080; font-style: italic;} +.srcLine .coMULTI {color: #808080; font-style: italic;} +.srcLine .es0 {color: #000099; font-weight: bold;} +.srcLine .br0 {color: #66cc66;} +.srcLine .st0 {color: #ff0000;} +.srcLine .nu0 {color: #cc66cc;} +.srcLine .me1 {color: #006600;} +.srcLine .me2 {color: #006600;} +.srcLine .re0 {color: #0000ff;} + + + + + + + + + +

    All Classes

    + + + + + + + / + .html + + + + + +
    + + + + (-) + + + () + + +
    + + +
    + + + + + + + + +

    Overview

    +

    All Packages

    + + + + + + + +
    + + + +
    + + +
    + + + + + + + + + + + + + + + + +
    Packages: Classes: Methods: LOC:
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Methods covered
    Total coverage

    PackagesMethods covered
    + + + +
    + + + + + + . + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +

    +
    + +

    Classes

    + + + + + + + +
    + + + + (-) + + + () + + +
    + + +
    + + + + + + + + + + + + + + + + + + +
    Classes: Methods: LOC:
    +
    + + + + + + + + + + + + + + + + + + + + +
    PackageMethods covered

    ClassesMethods covered
    + + + +
    + + + + + + + + + + + + + + + + + +
    Methods: LOC:
    +
    + + + + + + + + + + + + +
    Source fileMethods covered
    + + +
    +
    + + + + +
    + + + + + + + + + + + + +
    + + http://phing.info/ + +

    Source Code Coverage

    Designed for use with PHPUnit2, Xdebug and Phing.
    +
    +
    + + + + + + +

    Report generated at
    +
    + + + + + + + + + + + + + + + + + + + + + - + + + + + +
     
    + +
    + + + + + + + + + + + + + + + + + + + + + + + +
       
    + +
    +
    +
    + + + + + + + + + + 0 + + + + + + + + srcLineClassStart + + + +
    +
    +
    + + +
    +
    +
    + +
    +
    + + +
    + + + + + + ../ + + + + + + ../ + + + + + + + + stylesheet.css + + + + + + a + b + + + +
    + + diff --git a/library/propel/test/etc/xsl/log.xsl b/library/propel/test/etc/xsl/log.xsl new file mode 100644 index 000000000..a460b667c --- /dev/null +++ b/library/propel/test/etc/xsl/log.xsl @@ -0,0 +1,216 @@ + + + + + + + + + + + Phing Build Log + + + + + + + + + +
    + + http://phing.info/ + + + Phing +
    + + + +

    + + + + + + + +

    +
    + Phing +
    +
    + + +
    + + + + + + failed + complete + + + + + + + + + + + + + +
    Build FailedBuild CompleteTotal Time:
    + +
    + See the stacktrace. +
    +
    + + + +
    phing.file
    phing.version
    + +

    Build events

    + + + + + + + +
    targettaskmessage
    +

    + + + +

    Error details

    + + +
    +
    +
    + +

    +
    + + + + + + + a + b + + + [ ] + + + + + + +
    diff --git a/library/propel/test/etc/xsl/phpunit2-noframes.xsl b/library/propel/test/etc/xsl/phpunit2-noframes.xsl new file mode 100644 index 000000000..20b96a707 --- /dev/null +++ b/library/propel/test/etc/xsl/phpunit2-noframes.xsl @@ -0,0 +1,445 @@ + + + + + + + + + + + Unit Test Results + + + + + + + + + + + + +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Failure + Error + + + + + + + + + + +
    + + + +
    +
    + + + + + + + + +

    Packages

    + + + + +

    Package

    + + + + + + +
    +

    + + + + +

    Test Cases

    + + + + +

    TestCase

    + + + + + + + + + + +
    +

    + + + + +

    Summary

    + + + + + + + + + + + + + + + + + + + Failure + Error + + + + + + + + + + +
    AssertionsTestsFailuresErrorsSuccess rateTime
    + + + + + + + +
    + + + + +
    + Note: failures are anticipated and checked for with assertions while errors are unanticipated. +
    +
    + + + +

    Unit Test Results

    + + + + + +
    Designed for use with PHPUnit2 and Phing.
    +
    +
    + + + + + + +

    Report generated at
    +
    + + + + Name + Assertions + Tests + Errors + Failures + Time(s) + + + + + + + Name + Assertions + Tests + Errors + Failures + Time(s) + + + + + + + Name + Status + Type + Assertions + Time (s) + + + + + + + + + + + Failure + Error + + + + + + + + + + + + + + + + + + + + + + Error + + + + + + Failure + + + + Error + + + + Success + + + + + + + + + + + + + + + + + + + + + + + + + N/A + + + + + + +

    + + + + +
    + + +
    + + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + diff --git a/library/propel/test/etc/xsl/str.replace.function.xsl b/library/propel/test/etc/xsl/str.replace.function.xsl new file mode 100644 index 000000000..626e5498c --- /dev/null +++ b/library/propel/test/etc/xsl/str.replace.function.xsl @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ERROR: function implementation of str:replace() relies on exsl:node-set(). + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/library/propel/test/fixtures/bookstore-packaged/book.schema.xml b/library/propel/test/fixtures/bookstore-packaged/book.schema.xml new file mode 100644 index 000000000..27810c5ec --- /dev/null +++ b/library/propel/test/fixtures/bookstore-packaged/book.schema.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    diff --git a/library/propel/test/fixtures/bookstore-packaged/book_club_list.schema.xml b/library/propel/test/fixtures/bookstore-packaged/book_club_list.schema.xml new file mode 100644 index 000000000..6b6cb5fcd --- /dev/null +++ b/library/propel/test/fixtures/bookstore-packaged/book_club_list.schema.xml @@ -0,0 +1,42 @@ + + + + + + + + + + +
    + + + + + + + + + + +
    + +
    diff --git a/library/propel/test/fixtures/bookstore-packaged/build.properties b/library/propel/test/fixtures/bookstore-packaged/build.properties new file mode 100644 index 000000000..3113ded43 --- /dev/null +++ b/library/propel/test/fixtures/bookstore-packaged/build.properties @@ -0,0 +1,19 @@ +# $Id: build.properties 1756 2010-05-10 08:54:06Z francois $ +# +# This is a project-specific build.properties file. The properties +# in this file override anything set in Propel's top-level build.properties +# file when *this* project is being built. +# +# See top-level build.properties-sample for explanation of configuration +# options. +# +# Because this file is included before the top-level build.properties file, +# you cannot refer to any properties set therein. + +propel.project = bookstore-packaged +propel.database = sqlite +propel.database.url = sqlite://localhost/./test/@DB@.db +# propel.database.createUrl = (doesn't aply for SQLite, since db is auto-created) + +propel.targetPackage = bookstore-packaged +propel.packageObjectModel = true \ No newline at end of file diff --git a/library/propel/test/fixtures/bookstore-packaged/external/author.schema.xml b/library/propel/test/fixtures/bookstore-packaged/external/author.schema.xml new file mode 100644 index 000000000..14ce34fba --- /dev/null +++ b/library/propel/test/fixtures/bookstore-packaged/external/author.schema.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + +
    + +
    diff --git a/library/propel/test/fixtures/bookstore-packaged/log.schema.xml b/library/propel/test/fixtures/bookstore-packaged/log.schema.xml new file mode 100644 index 000000000..96590489c --- /dev/null +++ b/library/propel/test/fixtures/bookstore-packaged/log.schema.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + +
    + +
    diff --git a/library/propel/test/fixtures/bookstore-packaged/media.schema.xml b/library/propel/test/fixtures/bookstore-packaged/media.schema.xml new file mode 100644 index 000000000..0aa3a2f9b --- /dev/null +++ b/library/propel/test/fixtures/bookstore-packaged/media.schema.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + +
    + +
    diff --git a/library/propel/test/fixtures/bookstore-packaged/publisher.schema.xml b/library/propel/test/fixtures/bookstore-packaged/publisher.schema.xml new file mode 100644 index 000000000..9f6408210 --- /dev/null +++ b/library/propel/test/fixtures/bookstore-packaged/publisher.schema.xml @@ -0,0 +1,17 @@ + + + + + + + + +
    + +
    diff --git a/library/propel/test/fixtures/bookstore-packaged/review.schema.xml b/library/propel/test/fixtures/bookstore-packaged/review.schema.xml new file mode 100644 index 000000000..f486c8014 --- /dev/null +++ b/library/propel/test/fixtures/bookstore-packaged/review.schema.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + +
    + +
    diff --git a/library/propel/test/fixtures/bookstore-packaged/runtime-conf.xml b/library/propel/test/fixtures/bookstore-packaged/runtime-conf.xml new file mode 100644 index 000000000..be7a2bd42 --- /dev/null +++ b/library/propel/test/fixtures/bookstore-packaged/runtime-conf.xml @@ -0,0 +1,66 @@ + + + + + + + propel-bookstore-packaged + 7 + + + + + + + sqlite + + + sqlite + localhost + ./bookstore.db + + + + + + + sqlite + + + sqlite + localhost + ./bookstore-log.db + + + + + + + \ No newline at end of file diff --git a/library/propel/test/fixtures/bookstore/behavior-aggregate-schema.xml b/library/propel/test/fixtures/bookstore/behavior-aggregate-schema.xml new file mode 100644 index 000000000..8ad697d03 --- /dev/null +++ b/library/propel/test/fixtures/bookstore/behavior-aggregate-schema.xml @@ -0,0 +1,39 @@ + + + + + + + + + + +
    + + + + + + + +
    + + + + + + + + +
    + + + + + + + + +
    + +
    diff --git a/library/propel/test/fixtures/bookstore/behavior-auto-add-pk-schema.xml b/library/propel/test/fixtures/bookstore/behavior-auto-add-pk-schema.xml new file mode 100644 index 000000000..881b4d536 --- /dev/null +++ b/library/propel/test/fixtures/bookstore/behavior-auto-add-pk-schema.xml @@ -0,0 +1,28 @@ + + + + + + +
    + + + + + +
    + + + + + + + + + + + + +
    + +
    diff --git a/library/propel/test/fixtures/bookstore/behavior-concrete-inheritance-schema.xml b/library/propel/test/fixtures/bookstore/behavior-concrete-inheritance-schema.xml new file mode 100644 index 000000000..e54760333 --- /dev/null +++ b/library/propel/test/fixtures/bookstore/behavior-concrete-inheritance-schema.xml @@ -0,0 +1,69 @@ + + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + + + + + + +
    + + + + +
    + + + + + + +
    + + + + + + + +
    + + + + + + + + + + + + +
    + +
    diff --git a/library/propel/test/fixtures/bookstore/behavior-nested-set-schema.xml b/library/propel/test/fixtures/bookstore/behavior-nested-set-schema.xml new file mode 100644 index 000000000..c89acd437 --- /dev/null +++ b/library/propel/test/fixtures/bookstore/behavior-nested-set-schema.xml @@ -0,0 +1,27 @@ + + + + + + + +
    + + + + + + + + + + + + + + + + +
    + +
    diff --git a/library/propel/test/fixtures/bookstore/behavior-schema.xml b/library/propel/test/fixtures/bookstore/behavior-schema.xml new file mode 100644 index 000000000..191b54bd1 --- /dev/null +++ b/library/propel/test/fixtures/bookstore/behavior-schema.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + +
    + +
    diff --git a/library/propel/test/fixtures/bookstore/behavior-sluggable-schema.xml b/library/propel/test/fixtures/bookstore/behavior-sluggable-schema.xml new file mode 100644 index 000000000..29f962670 --- /dev/null +++ b/library/propel/test/fixtures/bookstore/behavior-sluggable-schema.xml @@ -0,0 +1,23 @@ + + + + + + + +
    + + + + + + + + + + + + +
    + +
    diff --git a/library/propel/test/fixtures/bookstore/behavior-soft-delete-schema.xml b/library/propel/test/fixtures/bookstore/behavior-soft-delete-schema.xml new file mode 100644 index 000000000..3b9f464bf --- /dev/null +++ b/library/propel/test/fixtures/bookstore/behavior-soft-delete-schema.xml @@ -0,0 +1,19 @@ + + + + + + + +
    + + + + + + + + +
    + +
    diff --git a/library/propel/test/fixtures/bookstore/behavior-sortable-schema.xml b/library/propel/test/fixtures/bookstore/behavior-sortable-schema.xml new file mode 100644 index 000000000..1f08c7172 --- /dev/null +++ b/library/propel/test/fixtures/bookstore/behavior-sortable-schema.xml @@ -0,0 +1,21 @@ + + + + + + + +
    + + + + + + + + + + +
    + +
    diff --git a/library/propel/test/fixtures/bookstore/behavior-timestampable-schema.xml b/library/propel/test/fixtures/bookstore/behavior-timestampable-schema.xml new file mode 100644 index 000000000..850466305 --- /dev/null +++ b/library/propel/test/fixtures/bookstore/behavior-timestampable-schema.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + +
    + + + + + +
    + +
    diff --git a/library/propel/test/fixtures/bookstore/build.properties b/library/propel/test/fixtures/bookstore/build.properties new file mode 100644 index 000000000..1981cc4b6 --- /dev/null +++ b/library/propel/test/fixtures/bookstore/build.properties @@ -0,0 +1,35 @@ +# $Id: build.properties 1688 2010-04-19 20:23:27Z francois $ +# +# This is a project-specific build.properties file. The properties +# in this file override anything set in Propel's top-level build.properties +# file when *this* project is being built. +# +# See top-level build.properties-sample for explanation of configuration +# options. +# +# Because this file is included before the top-level build.properties file, +# you cannot refer to any properties set therein. + +propel.project = bookstore +propel.database = mysql +propel.database.url = mysql:dbname=test +propel.mysqlTableType = InnoDB +propel.disableIdentifierQuoting=true + +# For MySQL or Oracle, you also need to specify username & password +#propel.database.user = [db username] +#propel.database.password = [db password] + +# Note that if you do not wish to specify the database (e.g. if you +# are using multiple databses) you can use the @DB@ token which +# will be replaced with a database at runtime. +# E.g.: propel.database.url = sqlite://localhost/./test/@DB@.db +# This will work for the datadump and the insert-sql tasks. + +# propel.database.createUrl = (doesn't apply for SQLite, since db is auto-created) + +propel.targetPackage = bookstore + +# We need to test behavior hooks +propel.behavior.test_all_hooks.class = ../test.tools.helpers.bookstore.behavior.Testallhooksbehavior +propel.behavior.do_nothing.class = ../test.tools.helpers.bookstore.behavior.DonothingBehavior \ No newline at end of file diff --git a/library/propel/test/fixtures/bookstore/cms-schema.xml b/library/propel/test/fixtures/bookstore/cms-schema.xml new file mode 100644 index 000000000..bf85a0df0 --- /dev/null +++ b/library/propel/test/fixtures/bookstore/cms-schema.xml @@ -0,0 +1,19 @@ + + + + + + + + + +
    + + + + + + +
    + +
    diff --git a/library/propel/test/fixtures/bookstore/runtime-conf.xml b/library/propel/test/fixtures/bookstore/runtime-conf.xml new file mode 100644 index 000000000..e38b7bf9f --- /dev/null +++ b/library/propel/test/fixtures/bookstore/runtime-conf.xml @@ -0,0 +1,125 @@ + + + + + propel-bookstore + propel.log + 7 + + + + + + + + mysql + + + DebugPDO + mysql:dbname=test + + + + + + + + + + + + + utf8 + + + + + + + mysql + + DebugPDO + mysql:dbname=test + + + + + + + + + utf8 + + + + + + mysql + + DebugPDO + mysql:dbname=test + + + + + + + + + utf8 + + + + + + + diff --git a/library/propel/test/fixtures/bookstore/schema.xml b/library/propel/test/fixtures/bookstore/schema.xml new file mode 100644 index 000000000..c55066ceb --- /dev/null +++ b/library/propel/test/fixtures/bookstore/schema.xml @@ -0,0 +1,320 @@ + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + +
    + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + +
    + + + + + + + + + +
    + + + + + + +
    + + + + + + + + + + +
    + + + + + + +
    + + + + + + + + + + +
    + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + +
    + + + + +
    + + + + + +
    + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + +
    + + + + + +
    + + + + +
    + + + + + +
    + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + +
    + +
    diff --git a/library/propel/test/fixtures/namespaced/build.properties b/library/propel/test/fixtures/namespaced/build.properties new file mode 100644 index 000000000..9dc98b564 --- /dev/null +++ b/library/propel/test/fixtures/namespaced/build.properties @@ -0,0 +1,31 @@ +# $Id: build.properties 1688 2010-04-19 20:23:27Z francois $ +# +# This is a project-specific build.properties file. The properties +# in this file override anything set in Propel's top-level build.properties +# file when *this* project is being built. +# +# See top-level build.properties-sample for explanation of configuration +# options. +# +# Because this file is included before the top-level build.properties file, +# you cannot refer to any properties set therein. + +propel.project = bookstore_namespaced +propel.database = mysql +propel.database.url = mysql:dbname=test +propel.mysqlTableType = InnoDB +propel.disableIdentifierQuoting=true + +# For MySQL or Oracle, you also need to specify username & password +#propel.database.user = [db username] +#propel.database.password = [db password] + +# Note that if you do not wish to specify the database (e.g. if you +# are using multiple databses) you can use the @DB@ token which +# will be replaced with a database at runtime. +# E.g.: propel.database.url = sqlite://localhost/./test/@DB@.db +# This will work for the datadump and the insert-sql tasks. + +# propel.database.createUrl = (doesn't apply for SQLite, since db is auto-created) + +propel.targetPackage = bookstore \ No newline at end of file diff --git a/library/propel/test/fixtures/namespaced/runtime-conf.xml b/library/propel/test/fixtures/namespaced/runtime-conf.xml new file mode 100644 index 000000000..ccc5a5c87 --- /dev/null +++ b/library/propel/test/fixtures/namespaced/runtime-conf.xml @@ -0,0 +1,81 @@ + + + + + propel-bookstore + propel.log + 7 + + + + + + + + mysql + + + DebugPDO + mysql:dbname=test + + + + + + + + + + + + + utf8 + + + + + + + + diff --git a/library/propel/test/fixtures/namespaced/schema.xml b/library/propel/test/fixtures/namespaced/schema.xml new file mode 100644 index 000000000..0951482aa --- /dev/null +++ b/library/propel/test/fixtures/namespaced/schema.xml @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + +
    + + + + + + + + + + + + + + + + + +
    + + + + + + +
    + + + + + + + + + + +
    + + + + + + + + + + + + + + + + +
    + +
    diff --git a/library/propel/test/fixtures/nestedset/build.properties b/library/propel/test/fixtures/nestedset/build.properties new file mode 100644 index 000000000..7105e18ab --- /dev/null +++ b/library/propel/test/fixtures/nestedset/build.properties @@ -0,0 +1,35 @@ +# $Id: build.properties 1260 2009-10-26 20:43:51Z francois $ +# +# This is a project-specific build.properties file. The properties +# in this file override anything set in Propel's top-level build.properties +# file when *this* project is being built. +# +# See top-level build.properties-sample for explanation of configuration +# options. +# +# Because this file is included before the top-level build.properties file, +# you cannot refer to any properties set therein. + +propel.project = nestedset +propel.database = sqlite +propel.database.url = sqlite:/var/tmp/nestedset.db +# For MySQL or Oracle, you also need to specify username & password +# propel.database.user = [db username] +# propel.database.password = [db password] + +# Note that if you do not wish to specify the database (e.g. if you +# are using multiple databses) you can use the @DB@ token which +# will be replaced with a database at runtime. +# E.g.: propel.database.url = sqlite://localhost/./test/@DB@.db +# This will work for the datadump and the insert-sql tasks. + +# propel.database.createUrl = (doesn't aply for SQLite, since db is auto-created) + +propel.targetPackage = nestedset + +# The unit tests need to test this stuff +propel.addGenericAccessors = true +propel.addGenericMutators = true + +# Use the new PHP 5.2 DateTime class +propel.useDateTimeClass = true diff --git a/library/propel/test/fixtures/nestedset/nestedset-schema.xml b/library/propel/test/fixtures/nestedset/nestedset-schema.xml new file mode 100644 index 000000000..9a49d5103 --- /dev/null +++ b/library/propel/test/fixtures/nestedset/nestedset-schema.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + +
    +
    diff --git a/library/propel/test/fixtures/nestedset/runtime-conf.xml b/library/propel/test/fixtures/nestedset/runtime-conf.xml new file mode 100644 index 000000000..8cc8fca13 --- /dev/null +++ b/library/propel/test/fixtures/nestedset/runtime-conf.xml @@ -0,0 +1,53 @@ + + + + + + + propel-nestedset + 7 + + + + + + + sqlite + + + sqlite + sqlite:/var/tmp/nestedset.db + + + + + + + diff --git a/library/propel/test/fixtures/treetest/build.properties b/library/propel/test/fixtures/treetest/build.properties new file mode 100644 index 000000000..eca0fdfcf --- /dev/null +++ b/library/propel/test/fixtures/treetest/build.properties @@ -0,0 +1,26 @@ +# $Id: build.properties 1260 2009-10-26 20:43:51Z francois $ +# +# This is a project-specific build.properties file. The properties +# in this file override anything set in Propel's top-level build.properties +# file when *this* project is being built. +# +# See top-level build.properties-sample for explanation of configuration +# options. +# +# Because this file is included before the top-level build.properties file, +# you cannot refer to any properties set therein. + +propel.targetPackage = treetest +propel.project = treetest + +propel.database = sqlite +propel.database.url = sqlite:/var/tmp/treetest.db + +#propel.database = mysql +#propel.database.url = mysql://localhost/test + +#propel.database = codebase +#propel.database.url = odbc://localhost/Driver=CodeBaseOdbcStand;DBQ=test;?adapter=CodeBase + + + diff --git a/library/propel/test/fixtures/treetest/runtime-conf.xml b/library/propel/test/fixtures/treetest/runtime-conf.xml new file mode 100644 index 000000000..c20f28ae4 --- /dev/null +++ b/library/propel/test/fixtures/treetest/runtime-conf.xml @@ -0,0 +1,53 @@ + + + + + + + propel-treetest + 7 + + + + + + + sqlite + + + sqlite + sqlite:/var/tmp/treetest.db + + + + + + + diff --git a/library/propel/test/fixtures/treetest/treetest-schema.xml b/library/propel/test/fixtures/treetest/treetest-schema.xml new file mode 100644 index 000000000..a2f11e889 --- /dev/null +++ b/library/propel/test/fixtures/treetest/treetest-schema.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + +
    + +
    diff --git a/library/propel/test/fixtures/unique-column/column-schema.xml b/library/propel/test/fixtures/unique-column/column-schema.xml new file mode 100644 index 000000000..6c0dcf725 --- /dev/null +++ b/library/propel/test/fixtures/unique-column/column-schema.xml @@ -0,0 +1,9 @@ + + + + + + + +
    +
    diff --git a/library/propel/test/fixtures/unique-column/table-schema.xml b/library/propel/test/fixtures/unique-column/table-schema.xml new file mode 100644 index 000000000..0fb6b26f2 --- /dev/null +++ b/library/propel/test/fixtures/unique-column/table-schema.xml @@ -0,0 +1,13 @@ + + + + + + +
    + + + + +
    +
    diff --git a/library/propel/test/speed.php b/library/propel/test/speed.php new file mode 100644 index 000000000..c013f18f5 --- /dev/null +++ b/library/propel/test/speed.php @@ -0,0 +1,401 @@ +iterations = $iterations; + } + + public function run() + { + $timers = array(); + fwrite(STDOUT, "Running scenario"); + // perform tests + for ($i=0; $i < $this->iterations; $i++) { + fwrite(STDOUT, '.'); + $this->setUp(); + $t = microtime(true); + $this->testSpeed(); + $timers[]= microtime(true) - $t; + $this->tearDown(); + } + fwrite(STDOUT, " done\n"); + // sort tests + sort($timers); + + // eliminate first and last + array_shift($timers); + array_pop($timers); + + return array_sum($timers) / count($timers); + } + + protected function emptyTables() + { + $res1 = AuthorPeer::doDeleteAll(); + $res2 = PublisherPeer::doDeleteAll(); + $res3 = AuthorPeer::doDeleteAll(); + $res4 = ReviewPeer::doDeleteAll(); + $res5 = MediaPeer::doDeleteAll(); + $res6 = BookClubListPeer::doDeleteAll(); + $res7 = BookListRelPeer::doDeleteAll(); + } + + public function setUp() + { + $this->con = Propel::getConnection(BookPeer::DATABASE_NAME); + $this->con->beginTransaction(); + $this->emptyTables(); + } + + public function tearDown() + { + $this->emptyTables(); + $this->con->commit(); + } + + public function testSpeed() + { + // Add publisher records + // --------------------- + + $scholastic = new Publisher(); + $scholastic->setName("Scholastic"); + // do not save, will do later to test cascade + + $morrow = new Publisher(); + $morrow->setName("William Morrow"); + $morrow->save(); + $morrow_id = $morrow->getId(); + + $penguin = new Publisher(); + $penguin->setName("Penguin"); + $penguin->save(); + $penguin_id = $penguin->getId(); + + $vintage = new Publisher(); + $vintage->setName("Vintage"); + $vintage->save(); + $vintage_id = $vintage->getId(); + + // Add author records + // ------------------ + + $rowling = new Author(); + $rowling->setFirstName("J.K."); + $rowling->setLastName("Rowling"); + // no save() + + $stephenson = new Author(); + $stephenson->setFirstName("Neal"); + $stephenson->setLastName("Stephenson"); + $stephenson->save(); + $stephenson_id = $stephenson->getId(); + + $byron = new Author(); + $byron->setFirstName("George"); + $byron->setLastName("Byron"); + $byron->save(); + $byron_id = $byron->getId(); + + $grass = new Author(); + $grass->setFirstName("Gunter"); + $grass->setLastName("Grass"); + $grass->save(); + $grass_id = $grass->getId(); + + // Add book records + // ---------------- + + $phoenix = new Book(); + $phoenix->setTitle("Harry Potter and the Order of the Phoenix"); + $phoenix->setISBN("043935806X"); + + // cascading save (Harry Potter) + $phoenix->setAuthor($rowling); + $phoenix->setPublisher($scholastic); + $phoenix->save(); + $phoenix_id = $phoenix->getId(); + + $qs = new Book(); + $qs->setISBN("0380977427"); + $qs->setTitle("Quicksilver"); + $qs->setAuthor($stephenson); + $qs->setPublisher($morrow); + $qs->save(); + $qs_id = $qs->getId(); + + $dj = new Book(); + $dj->setISBN("0140422161"); + $dj->setTitle("Don Juan"); + $dj->setAuthor($byron); + $dj->setPublisher($penguin); + $dj->save(); + $dj_id = $qs->getId(); + + $td = new Book(); + $td->setISBN("067972575X"); + $td->setTitle("The Tin Drum"); + $td->setAuthor($grass); + $td->setPublisher($vintage); + $td->save(); + $td_id = $td->getId(); + + // Add review records + // ------------------ + + $r1 = new Review(); + $r1->setBook($phoenix); + $r1->setReviewedBy("Washington Post"); + $r1->setRecommended(true); + $r1->setReviewDate(time()); + $r1->save(); + $r1_id = $r1->getId(); + + $r2 = new Review(); + $r2->setBook($phoenix); + $r2->setReviewedBy("New York Times"); + $r2->setRecommended(false); + $r2->setReviewDate(time()); + $r2->save(); + $r2_id = $r2->getId(); + + // Perform a "complex" search + // -------------------------- + + $results = BookQuery::create() + ->filterByTitle('Harry%') + ->find(); + + $results = BookQuery::create() + ->where('Book.ISBN IN ?', array("0380977427", "0140422161")) + ->find(); + + // Perform a "limit" search + // ------------------------ + + $results = BookQuery::create() + ->limit(2) + ->offset(1) + ->orderByTitle() + ->find(); + + // Perform a lookup & update! + // -------------------------- + + $qs_lookup = BookQuery::create()->findPk($qs_id); + $new_title = "Quicksilver (".crc32(uniqid(rand())).")"; + $qs_lookup->setTitle($new_title); + $qs_lookup->save(); + + $qs_lookup2 = BookQuery::create()->findPk($qs_id); + + // Test some basic DATE / TIME stuff + // --------------------------------- + + // that's the control timestamp. + $control = strtotime('2004-02-29 00:00:00'); + + // should be two in the db + $r = ReviewQuery::create()->findOne(); + $r_id = $r->getId(); + $r->setReviewDate($control); + $r->save(); + + $r2 = ReviewQuery::create()->findPk($r_id); + + // Testing the DATE/TIME columns + // ----------------------------- + + // that's the control timestamp. + $control = strtotime('2004-02-29 00:00:00'); + + // should be two in the db + $r = ReviewQuery::create()->findOne(); + $r_id = $r->getId(); + $r->setReviewDate($control); + $r->save(); + + $r2 = ReviewQuery::create()->findPk($r_id); + + // Testing the column validators + // ----------------------------- + + $bk1 = new Book(); + $bk1->setTitle("12345"); // min length is 10 + $ret = $bk1->validate(); + + // Unique validator + $bk2 = new Book(); + $bk2->setTitle("Don Juan"); + $ret = $bk2->validate(); + + // Now trying some more complex validation. + $auth1 = new Author(); + $auth1->setFirstName("Hans"); + // last name required; will fail + + $bk1->setAuthor($auth1); + + $rev1 = new Review(); + $rev1->setReviewDate("08/09/2001"); + // will fail: reviewed_by column required + + $bk1->addReview($rev1); + + $ret2 = $bk1->validate(); + + $bk2 = new Book(); + $bk2->setTitle("12345678901"); // passes + + $auth2 = new Author(); + $auth2->setLastName("Blah"); //passes + $auth2->setEmail("some@body.com"); //passes + $auth2->setAge(50); //passes + $bk2->setAuthor($auth2); + + $rev2 = new Review(); + $rev2->setReviewedBy("Me!"); // passes + $rev2->setStatus("new"); // passes + $bk2->addReview($rev2); + + $ret3 = $bk2->validate(); + + // Testing doCount() functionality + // ------------------------------- + + $count = BookQuery::create()->count(); + + // Testing many-to-many relationships + // ---------------------------------- + + // init book club list 1 with 2 books + + $blc1 = new BookClubList(); + $blc1->setGroupLeader("Crazyleggs"); + $blc1->setTheme("Happiness"); + + $brel1 = new BookListRel(); + $brel1->setBook($phoenix); + + $brel2 = new BookListRel(); + $brel2->setBook($dj); + + $blc1->addBookListRel($brel1); + $blc1->addBookListRel($brel2); + + $blc1->save(); + + // init book club list 2 with 1 book + + $blc2 = new BookClubList(); + $blc2->setGroupLeader("John Foo"); + $blc2->setTheme("Default"); + + $brel3 = new BookListRel(); + $brel3->setBook($phoenix); + + $blc2->addBookListRel($brel3); + + $blc2->save(); + + // re-fetch books and lists from db to be sure that nothing is cached + + $phoenix = BookQuery::create() + ->filterById($phoenix->getId()) + ->findOne(); + + $blc1 = BookClubListQuery::create() + ->filterById($blc1->getId()) + ->findOne(); + + $blc2 = BookClubListQuery::create() + ->filterbyId($blc2->getId()) + ->findOne(); + + $relCount = $phoenix->countBookListRels(); + + $relCount = $blc1->countBookListRels(); + + $relCount = $blc2->countBookListRels(); + + // Removing books that were just created + // ------------------------------------- + + $hp = BookQuery::create()->findPk($phoenix_id); + $c = new Criteria(); + $c->add(BookPeer::ID, $hp->getId()); + // The only way for cascading to work currently + // is to specify the author_id and publisher_id (i.e. the fkeys + // have to be in the criteria). + $c->add(AuthorPeer::ID, $hp->getId()); + $c->add(PublisherPeer::ID, $hp->getId()); + $c->setSingleRecord(true); + BookPeer::doDelete($c); + + // Attempting to delete books by complex criteria + BookQuery::create() + ->filterByISBN("043935806X") + ->orWhere('Book.ISBN = ?', "0380977427") + ->orWhere('Book.ISBN = ?', "0140422161") + ->delete(); + + $td->delete(); + + AuthorQuery::create()->filterById($stephenson_id)->delete(); + + AuthorQuery::create()->filterById($byron_id)->delete(); + + $grass->delete(); + + PublisherQuery::create()->filterById($morrow_id)->delete(); + + PublisherQuery::create()->filterById($penguin_id)->delete(); + + $vintage->delete(); + + // These have to be deleted manually also since we have onDelete + // set to SETNULL in the foreign keys in book. Is this correct? + $rowling->delete(); + + $scholastic->delete(); + + $blc1->delete(); + + $blc2->delete(); + } +} + +$test = new PropelSpeedTest(100); +echo "Test speed: {$test->run()} ({$test->iterations} iterations)\n"; diff --git a/library/propel/test/test.xml b/library/propel/test/test.xml new file mode 100644 index 000000000..e97215e7f --- /dev/null +++ b/library/propel/test/test.xml @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ------------------------------------------------- + +++++ Running Propel unit tests + ------------------------------------------------- + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/library/propel/test/testsuite/generator/behavior/AutoAddPkBehaviorTest.php b/library/propel/test/testsuite/generator/behavior/AutoAddPkBehaviorTest.php new file mode 100644 index 000000000..a59f84575 --- /dev/null +++ b/library/propel/test/testsuite/generator/behavior/AutoAddPkBehaviorTest.php @@ -0,0 +1,70 @@ +assertEquals(count($table6->getColumns()), 2, 'auto_add_pk adds one column by default'); + $pks = $table6->getPrimaryKeys(); + $this->assertEquals(count($pks), 1, 'auto_add_pk adds a simple primary key by default'); + $pk = array_pop($pks); + $this->assertEquals($pk->getName(), 'ID', 'auto_add_pk adds an id column by default'); + $this->assertEquals($pk->getType(), 'INTEGER', 'auto_add_pk adds an integer column by default'); + $this->assertTrue($pk->isPrimaryKey(), 'auto_add_pk adds a primary key column by default'); + $this->assertTrue($table6->isUseIdGenerator(), 'auto_add_pk adds an autoIncrement column by default'); + } + + public function testNoTrigger() + { + $table7 = Table7Peer::getTableMap(); + $this->assertEquals(count($table7->getColumns()), 2, 'auto_add_pk does not add a column when the table already has a primary key'); + $this->assertFalse(method_exists('Table7', 'getId'), 'auto_add_pk does not add an id column when the table already has a primary key'); + $pks = $table7->getPrimaryKeys(); + $pk = array_pop($pks); + $this->assertEquals($pk->getName(), 'FOO', 'auto_add_pk does not change an existing primary key'); + } + + public function testParameters() + { + $table8 = Table8Peer::getTableMap(); + $this->assertEquals(count($table8->getColumns()), 3, 'auto_add_pk adds one column with custom parameters'); + $pks = $table8->getPrimaryKeys(); + $pk = array_pop($pks); + $this->assertEquals($pk->getName(), 'IDENTIFIER', 'auto_add_pk accepts customization of pk column name'); + $this->assertEquals($pk->getType(), 'BIGINT', 'auto_add_pk accepts customization of pk column type'); + $this->assertTrue($pk->isPrimaryKey(), 'auto_add_pk adds a primary key column with custom parameters'); + $this->assertFalse($table8->isUseIdGenerator(), 'auto_add_pk accepts customization of pk column autoIncrement'); + } + + public function testForeignKey() + { + $t6 = new Table6(); + $t6->setTitle('foo'); + $t6->save(); + $t8 = new Table8(); + $t8->setIdentifier(1); + $t8->setTable6($t6); + $t8->save(); + $this->assertEquals($t8->getFooId(), $t6->getId(), 'Auto added pkeys can be used in relations'); + $t8->delete(); + $t6->delete(); + } +} \ No newline at end of file diff --git a/library/propel/test/testsuite/generator/behavior/ObjectBehaviorTest.php b/library/propel/test/testsuite/generator/behavior/ObjectBehaviorTest.php new file mode 100644 index 000000000..f42c8e9c2 --- /dev/null +++ b/library/propel/test/testsuite/generator/behavior/ObjectBehaviorTest.php @@ -0,0 +1,152 @@ +assertEquals($t->customAttribute, 1, 'objectAttributes hook is called when adding attributes'); + } + + public function testPreSave() + { + $t = new Table3(); + $t->preSave = 0; + $t->save(); + $this->assertEquals($t->preSave, 1, 'preSave hook is called on object insertion'); + $this->assertEquals($t->preSaveBuilder, 'PHP5ObjectBuilder', 'preSave hook is called with the object builder as parameter'); + $this->assertFalse($t->preSaveIsAfterSave, 'preSave hook is called before save'); + $t->preSave = 0; + $t->setTitle('foo'); + $t->save(); + $this->assertEquals($t->preSave, 1, 'preSave hook is called on object modification'); + } + + public function testPostSave() + { + $t = new Table3(); + $t->postSave = 0; + $t->save(); + $this->assertEquals($t->postSave, 1, 'postSave hook is called on object insertion'); + $this->assertEquals($t->postSaveBuilder, 'PHP5ObjectBuilder', 'postSave hook is called with the object builder as parameter'); + $this->assertTrue($t->postSaveIsAfterSave, 'postSave hook is called after save'); + $t->postSave = 0; + $t->setTitle('foo'); + $t->save(); + $this->assertEquals($t->postSave, 1, 'postSave hook is called on object modification'); + } + + public function testPreInsert() + { + $t = new Table3(); + $t->preInsert = 0; + $t->save(); + $this->assertEquals($t->preInsert, 1, 'preInsert hook is called on object insertion'); + $this->assertEquals($t->preInsertBuilder, 'PHP5ObjectBuilder', 'preInsert hook is called with the object builder as parameter'); + $this->assertFalse($t->preInsertIsAfterSave, 'preInsert hook is called before save'); + $t->preInsert = 0; + $t->setTitle('foo'); + $t->save(); + $this->assertEquals($t->preInsert, 0, 'preInsert hook is not called on object modification'); + } + + public function testPostInsert() + { + $t = new Table3(); + $t->postInsert = 0; + $t->save(); + $this->assertEquals($t->postInsert, 1, 'postInsert hook is called on object insertion'); + $this->assertEquals($t->postInsertBuilder, 'PHP5ObjectBuilder', 'postInsert hook is called with the object builder as parameter'); + $this->assertTrue($t->postInsertIsAfterSave, 'postInsert hook is called after save'); + $t->postInsert = 0; + $t->setTitle('foo'); + $t->save(); + $this->assertEquals($t->postInsert, 0, 'postInsert hook is not called on object modification'); + } + + public function testPreUpdate() + { + $t = new Table3(); + $t->preUpdate = 0; + $t->save(); + $this->assertEquals($t->preUpdate, 0, 'preUpdate hook is not called on object insertion'); + $t->preUpdate = 0; + $t->setTitle('foo'); + $t->save(); + $this->assertEquals($t->preUpdate, 1, 'preUpdate hook is called on object modification'); + $this->assertEquals($t->preUpdateBuilder, 'PHP5ObjectBuilder', 'preUpdate hook is called with the object builder as parameter'); + $this->assertFalse($t->preUpdateIsAfterSave, 'preUpdate hook is called before save'); + } + + public function testPostUpdate() + { + $t = new Table3(); + $t->postUpdate = 0; + $t->save(); + $this->assertEquals($t->postUpdate, 0, 'postUpdate hook is not called on object insertion'); + $t->postUpdate = 0; + $t->setTitle('foo'); + $t->save(); + $this->assertEquals($t->postUpdate, 1, 'postUpdate hook is called on object modification'); + $this->assertEquals($t->postUpdateBuilder, 'PHP5ObjectBuilder', 'postUpdate hook is called with the object builder as parameter'); + $this->assertTrue($t->postUpdateIsAfterSave, 'postUpdate hook is called after save'); + } + + public function testPreDelete() + { + $t = new Table3(); + $t->save(); + $this->preDelete = 0; + $t->delete(); + $this->assertEquals($t->preDelete, 1, 'preDelete hook is called on object deletion'); + $this->assertEquals($t->preDeleteBuilder, 'PHP5ObjectBuilder', 'preDelete hook is called with the object builder as parameter'); + $this->assertTrue($t->preDeleteIsBeforeDelete, 'preDelete hook is called before deletion'); + } + + public function testPostDelete() + { + $t = new Table3(); + $t->save(); + $this->postDelete = 0; + $t->delete(); + $this->assertEquals($t->postDelete, 1, 'postDelete hook is called on object deletion'); + $this->assertEquals($t->postDeleteBuilder, 'PHP5ObjectBuilder', 'postDelete hook is called with the object builder as parameter'); + $this->assertFalse($t->postDeleteIsBeforeDelete, 'postDelete hook is called before deletion'); + } + + public function testObjectMethods() + { + $t = new Table3(); + $this->assertTrue(method_exists($t, 'hello'), 'objectMethods hook is called when adding methods'); + $this->assertEquals($t->hello(), 'PHP5ObjectBuilder', 'objectMethods hook is called with the object builder as parameter'); + } + + public function testObjectCall() + { + $t = new Table3(); + $this->assertEquals('bar', $t->foo(), 'objectCall hook is called when building the magic __call()'); + } + + public function testObjectFilter() + { + $t = new Table3(); + $this->assertTrue(class_exists('testObjectFilter'), 'objectFilter hook allows complete manipulation of the generated script'); + $this->assertEquals(testObjectFilter::FOO, 'PHP5ObjectBuilder', 'objectFilter hook is called with the object builder as parameter'); + } +} diff --git a/library/propel/test/testsuite/generator/behavior/PeerBehaviorTest.php b/library/propel/test/testsuite/generator/behavior/PeerBehaviorTest.php new file mode 100644 index 000000000..5e968f2c4 --- /dev/null +++ b/library/propel/test/testsuite/generator/behavior/PeerBehaviorTest.php @@ -0,0 +1,61 @@ +assertEquals(Table3Peer::$customStaticAttribute, 1, 'staticAttributes hook is called when adding attributes'); + $this->assertEquals(Table3Peer::$staticAttributeBuilder, 'PHP5PeerBuilder', 'staticAttributes hook is called with the peer builder as parameter'); + } + + public function testStaticMethods() + { + $this->assertTrue(method_exists('Table3Peer', 'hello'), 'staticMethods hook is called when adding methods'); + $this->assertEquals(Table3Peer::hello(), 'PHP5PeerBuilder', 'staticMethods hook is called with the peer builder as parameter'); + } + + public function testPreSelect() + { + $con = Propel::getConnection(Table3Peer::DATABASE_NAME, Propel::CONNECTION_READ); + $con->preSelect = 0; + Table3Peer::doSelect(new Criteria, $con); + $this->assertNotEquals($con->preSelect, 0, 'preSelect hook is called in doSelect()'); + $con->preSelect = 0; + Table3Peer::doSelectOne(new Criteria, $con); + $this->assertNotEquals($con->preSelect, 0, 'preSelect hook is called in doSelectOne()'); + $con->preSelect = 0; + Table3Peer::doCount(new Criteria, $con); + $this->assertNotEquals($con->preSelect, 0, 'preSelect hook is called in doCount()'); + $con->preSelect = 0; + Table3Peer::doSelectStmt(new Criteria, $con); + $this->assertNotEquals($con->preSelect, 0, 'preSelect hook is called in doSelectStmt()'); + // and for the doSelectJoin and doCountJoin methods, well just believe my word + + $con->preSelect = 0; + Table3Peer::doSelect(new Criteria, $con); + $this->assertEquals($con->preSelect, 'PHP5PeerBuilder', 'preSelect hook is called with the peer builder as parameter'); + } + + public function testPeerFilter() + { + Table3Peer::TABLE_NAME; + $this->assertTrue(class_exists('testPeerFilter'), 'peerFilter hook allows complete manipulation of the generated script'); + $this->assertEquals(testPeerFilter::FOO, 'PHP5PeerBuilder', 'peerFilter hook is called with the peer builder as parameter'); + } +} diff --git a/library/propel/test/testsuite/generator/behavior/SoftDeleteBehaviorTest.php b/library/propel/test/testsuite/generator/behavior/SoftDeleteBehaviorTest.php new file mode 100644 index 000000000..2f3fecb97 --- /dev/null +++ b/library/propel/test/testsuite/generator/behavior/SoftDeleteBehaviorTest.php @@ -0,0 +1,360 @@ +assertEquals(count($table2->getColumns()), 3, 'SoftDelete adds one columns by default'); + $this->assertTrue(method_exists('Table4', 'getDeletedAt'), 'SoftDelete adds an updated_at column by default'); + $table1 = Table5Peer::getTableMap(); + $this->assertEquals(count($table1->getColumns()), 3, 'SoftDelete does not add a column when it already exists'); + $this->assertTrue(method_exists('Table5', 'getDeletedOn'), 'SoftDelete allows customization of deleted_column name'); + } + + public function testStaticSoftDeleteStatus() + { + $this->assertTrue(Table4Peer::isSoftDeleteEnabled(), 'The static soft delete is enabled by default'); + Table4Peer::disableSoftDelete(); + $this->assertFalse(Table4Peer::isSoftDeleteEnabled(), 'disableSoftDelete() disables the static soft delete'); + Table4Peer::enableSoftDelete(); + $this->assertTrue(Table4Peer::isSoftDeleteEnabled(), 'enableSoftDelete() enables the static soft delete'); + } + + public function testInstancePoolingAndSoftDelete() + { + Table4Peer::doForceDeleteAll($this->con); + $t = new Table4(); + $t->save($this->con); + Table4Peer::enableSoftDelete(); + $t->delete($this->con); + $t2 = Table4Peer::retrieveByPk($t->getPrimaryKey(), $this->con); + $this->assertNull($t2, 'An object is removed from the instance pool on soft deletion'); + Table4Peer::disableSoftDelete(); + $t2 = Table4Peer::retrieveByPk($t->getPrimaryKey(), $this->con); + $this->assertNotNull($t2); + Table4Peer::enableSoftDelete(); + $t2 = Table4Peer::retrieveByPk($t->getPrimaryKey(), $this->con); + $this->assertNull($t2, 'A soft deleted object is removed from the instance pool when the soft delete behavior is enabled'); + } + + public function testStaticDoForceDelete() + { + $t1 = new Table4(); + $t1->save(); + Table4Peer::doForceDelete($t1); + Table4Peer::disableSoftDelete(); + $this->assertEquals(0, Table4Peer::doCount(new Criteria()), 'doForceDelete() actually deletes records'); + } + + public function testStaticDoSoftDelete() + { + $t1 = new Table4(); + $t1->save(); + $t2 = new Table4(); + $t2->save(); + $t3 = new Table4(); + $t3->save(); + // softDelete with a criteria + $c = new Criteria(); + $c->add(Table4Peer::ID, $t1->getId()); + Table4Peer::doSoftDelete($c); + Table4Peer::disableSoftDelete(); + $this->assertEquals(3, Table4Peer::doCount(new Criteria()), 'doSoftDelete() keeps deleted record in the database'); + Table4Peer::enableSoftDelete(); + $this->assertEquals(2, Table4Peer::doCount(new Criteria()), 'doSoftDelete() marks deleted record as deleted'); + // softDelete with a value + Table4Peer::doSoftDelete(array($t2->getId())); + Table4Peer::disableSoftDelete(); + $this->assertEquals(3, Table4Peer::doCount(new Criteria()), 'doSoftDelete() keeps deleted record in the database'); + Table4Peer::enableSoftDelete(); + $this->assertEquals(1, Table4Peer::doCount(new Criteria()), 'doSoftDelete() marks deleted record as deleted'); + // softDelete with an object + Table4Peer::doSoftDelete($t3); + Table4Peer::disableSoftDelete(); + $this->assertEquals(3, Table4Peer::doCount(new Criteria()), 'doSoftDelete() keeps deleted record in the database'); + Table4Peer::enableSoftDelete(); + $this->assertEquals(0, Table4Peer::doCount(new Criteria()), 'doSoftDelete() marks deleted record as deleted'); + } + + public function testStaticDoDelete() + { + $t1 = new Table4(); + $t1->save(); + $t2 = new Table4(); + $t2->save(); + Table4Peer::disableSoftDelete(); + Table4Peer::doDelete($t1); + Table4Peer::disableSoftDelete(); + $this->assertEquals(1, Table4Peer::doCount(new Criteria()), 'doDelete() calls doForceDelete() when soft delete is disabled'); + Table4Peer::enableSoftDelete(); + Table4Peer::doDelete($t2); + Table4Peer::disableSoftDelete(); + $this->assertEquals(1, Table4Peer::doCount(new Criteria()), 'doDelete() calls doSoftDelete() when soft delete is enabled'); + Table4Peer::enableSoftDelete(); + $this->assertEquals(0, Table4Peer::doCount(new Criteria()), 'doDelete() calls doSoftDelete() when soft delete is enabled'); + } + + public function testStaticDoForceDeleteAll() + { + $t1 = new Table4(); + $t1->save(); + Table4Peer::doForceDeleteAll(); + Table4Peer::disableSoftDelete(); + $this->assertEquals(0, Table4Peer::doCount(new Criteria()), 'doForceDeleteAll() actually deletes records'); + } + + public function testStaticDoSoftDeleteAll() + { + $t1 = new Table4(); + $t1->save(); + $t2 = new Table4(); + $t2->save(); + Table4Peer::enableSoftDelete(); + Table4Peer::doSoftDeleteAll(); + Table4Peer::disableSoftDelete(); + $this->assertEquals(2, Table4Peer::doCount(new Criteria()), 'doSoftDeleteAll() keeps deleted record in the database'); + Table4Peer::enableSoftDelete(); + $this->assertEquals(0, Table4Peer::doCount(new Criteria()), 'doSoftDeleteAll() marks deleted record as deleted'); + } + + public function testStaticDoDeleteAll() + { + $t1 = new Table4(); + $t1->save(); + $t2 = new Table4(); + $t2->save(); + Table4Peer::disableSoftDelete(); + Table4Peer::doDeleteAll(); + Table4Peer::disableSoftDelete(); + $this->assertEquals(0, Table4Peer::doCount(new Criteria()), 'doDeleteAll() calls doForceDeleteAll() when soft delete is disabled'); + $t1 = new Table4(); + $t1->save(); + $t2 = new Table4(); + $t2->save(); + Table4Peer::enableSoftDelete(); + Table4Peer::doDeleteAll(); + Table4Peer::disableSoftDelete(); + $this->assertEquals(2, Table4Peer::doCount(new Criteria()), 'doDeleteAll() calls doSoftDeleteAll() when soft delete is disabled'); + Table4Peer::enableSoftDelete(); + $this->assertEquals(0, Table4Peer::doCount(new Criteria()), 'doDeleteAll() calls doSoftDeleteAll() when soft delete is disabled'); + } + + public function testSelect() + { + $t = new Table4(); + $t->setDeletedAt(123); + $t->save(); + Table4Peer::enableSoftDelete(); + $this->assertEquals(0, Table4Peer::doCount(new Criteria), 'rows with a deleted_at date are hidden for select queries'); + Table4Peer::disableSoftDelete(); + $this->assertEquals(1, Table4Peer::doCount(new Criteria), 'rows with a deleted_at date are visible for select queries once the static soft_delete is enabled'); + $this->assertTrue(Table4Peer::isSoftDeleteEnabled(), 'Executing a select query enables the static soft delete again'); + } + + public function testDelete() + { + $t = new Table4(); + $t->save(); + $this->assertNull($t->getDeletedAt(), 'deleted_column is null by default'); + $t->delete(); + $this->assertNotNull($t->getDeletedAt(), 'deleted_column is not null after a soft delete'); + $this->assertEquals(0, Table4Peer::doCount(new Criteria), 'soft deleted rows are hidden for select queries'); + Table4Peer::disableSoftDelete(); + $this->assertEquals(1, Table4Peer::doCount(new Criteria), 'soft deleted rows are still present in the database'); + } + + public function testDeleteUndeletable() + { + $t = new UndeletableTable4(); + $t->save(); + $t->delete(); + $this->assertNull($t->getDeletedAt(), 'soft_delete is not triggered for objects wit ha preDelete hook returning false'); + $this->assertEquals(1, Table4Peer::doCount(new Criteria), 'soft_delete is not triggered for objects wit ha preDelete hook returning false'); + } + + public function testUnDelete() + { + $t = new Table4(); + $t->save(); + $t->delete(); + $t->undelete(); + $this->assertNull($t->getDeletedAt(), 'deleted_column is null again after an undelete'); + $this->assertEquals(1, Table4Peer::doCount(new Criteria), 'undeleted rows are visible for select queries'); + } + + public function testForceDelete() + { + $t = new Table4(); + $t->save(); + $t->forceDelete(); + $this->assertTrue($t->isDeleted(), 'forceDelete() actually deletes a row'); + Table4Peer::disableSoftDelete(); + $this->assertEquals(0, Table4Peer::doCount(new Criteria), 'forced deleted rows are not present in the database'); + } + + public function testQueryIncludeDeleted() + { + $t = new Table4(); + $t->setDeletedAt(123); + $t->save(); + Table4Peer::enableSoftDelete(); + $this->assertEquals(0, Table4Query::create()->count(), 'rows with a deleted_at date are hidden for select queries'); + $this->assertEquals(1, Table4Query::create()->includeDeleted()->count(), 'rows with a deleted_at date are visible for select queries using includeDeleted()'); + } + + public function testQueryForceDelete() + { + $t1 = new Table4(); + $t1->save(); + Table4Query::create()->filterById($t1->getId())->forceDelete(); + Table4Peer::disableSoftDelete(); + $this->assertEquals(0, Table4Query::create()->count(), 'forceDelete() actually deletes records'); + } + + public function testQuerySoftDelete() + { + $t1 = new Table4(); + $t1->save(); + $t2 = new Table4(); + $t2->save(); + $t3 = new Table4(); + $t3->save(); + + Table4Query::create() + ->filterById($t1->getId()) + ->softDelete(); + Table4Peer::disableSoftDelete(); + $this->assertEquals(3, Table4Query::create()->count(), 'softDelete() keeps deleted record in the database'); + Table4Peer::enableSoftDelete(); + $this->assertEquals(2, Table4Query::create()->count(), 'softDelete() marks deleted record as deleted'); + } + + public function testQueryDelete() + { + $t1 = new Table4(); + $t1->save(); + $t2 = new Table4(); + $t2->save(); + + Table4Peer::disableSoftDelete(); + Table4Query::create()->filterById($t1->getId())->delete(); + Table4Peer::disableSoftDelete(); + $this->assertEquals(1, Table4Query::create()->count(), 'delete() calls forceDelete() when soft delete is disabled'); + Table4Peer::enableSoftDelete(); + Table4Query::create()->filterById($t2->getId())->delete(); + Table4Peer::disableSoftDelete(); + $this->assertEquals(1, Table4Query::create()->count(), 'delete() calls softDelete() when soft delete is enabled'); + Table4Peer::enableSoftDelete(); + $this->assertEquals(0, Table4Query::create()->count(), 'delete() calls softDelete() when soft delete is enabled'); + } + + public function testQueryForceDeleteAll() + { + $t1 = new Table4(); + $t1->save(); + Table4Query::create()->forceDeleteAll(); + Table4Peer::disableSoftDelete(); + $this->assertEquals(0, Table4Query::create()->count(), 'forceDeleteAll() actually deletes records'); + } + + public function testQuerySoftDeleteAll() + { + $t1 = new Table4(); + $t1->save(); + $t2 = new Table4(); + $t2->save(); + Table4Peer::enableSoftDelete(); + Table4Query::create()->softDelete(); + Table4Peer::disableSoftDelete(); + $this->assertEquals(2, Table4Query::create()->count(), 'softDelete() keeps deleted record in the database'); + Table4Peer::enableSoftDelete(); + $this->assertEquals(0, Table4Query::create()->count(), 'softDelete() marks deleted record as deleted'); + } + + public function testQueryDeleteAll() + { + $t1 = new Table4(); + $t1->save(); + $t2 = new Table4(); + $t2->save(); + Table4Peer::disableSoftDelete(); + Table4Query::create()->deleteAll(); + Table4Peer::disableSoftDelete(); + $this->assertEquals(0, Table4Query::create()->count(), 'deleteAll() calls forceDeleteAll() when soft delete is disabled'); + + $t1 = new Table4(); + $t1->save(); + $t2 = new Table4(); + $t2->save(); + Table4Peer::enableSoftDelete(); + Table4Query::create()->deleteAll(); + Table4Peer::disableSoftDelete(); + $this->assertEquals(2, Table4Query::create()->count(), 'deleteAll() calls softDeleteAll() when soft delete is disabled'); + Table4Peer::enableSoftDelete(); + $this->assertEquals(0, Table4Query::create()->count(), 'deleteAll() calls softDeleteAll() when soft delete is disabled'); + } + + public function testQuerySelect() + { + $t = new Table4(); + $t->setDeletedAt(123); + $t->save(); + Table4Peer::enableSoftDelete(); + $this->assertEquals(0, Table4Query::create()->count(), 'rows with a deleted_at date are hidden for select queries'); + Table4Peer::disableSoftDelete(); + $this->assertEquals(1, Table4Query::create()->count(), 'rows with a deleted_at date are visible for select queries once the static soft_delete is enabled'); + $this->assertTrue(Table4Peer::isSoftDeleteEnabled(), 'Executing a select query enables the static soft delete again'); + } + + public function testCustomization() + { + Table5Peer::disableSoftDelete(); + Table5Peer::doDeleteAll(); + Table5Peer::enableSoftDelete(); + $t = new Table5(); + $t->save(); + $this->assertNull($t->getDeletedOn(), 'deleted_column is null by default'); + $t->delete(); + $this->assertNotNull($t->getDeletedOn(), 'deleted_column is not null after a soft delete'); + $this->assertEquals(0, Table5Peer::doCount(new Criteria), 'soft deleted rows are hidden for select queries'); + Table5Peer::disableSoftDelete(); + $this->assertEquals(1, Table5Peer::doCount(new Criteria), 'soft deleted rows are still present in the database'); + } +} + +class UndeletableTable4 extends Table4 +{ + public function preDelete(PropelPDO $con = null) + { + parent::preDelete($con); + $this->setTitle('foo'); + return false; + } +} \ No newline at end of file diff --git a/library/propel/test/testsuite/generator/behavior/TableBehaviorTest.php b/library/propel/test/testsuite/generator/behavior/TableBehaviorTest.php new file mode 100644 index 000000000..25e534f7e --- /dev/null +++ b/library/propel/test/testsuite/generator/behavior/TableBehaviorTest.php @@ -0,0 +1,34 @@ +assertTrue($t->hasColumn('test'), 'modifyTable hook is called when building the model structure'); + } +} diff --git a/library/propel/test/testsuite/generator/behavior/TimestampableBehaviorTest.php b/library/propel/test/testsuite/generator/behavior/TimestampableBehaviorTest.php new file mode 100644 index 000000000..ba80d64f8 --- /dev/null +++ b/library/propel/test/testsuite/generator/behavior/TimestampableBehaviorTest.php @@ -0,0 +1,215 @@ +assertEquals(count($table2->getColumns()), 4, 'Timestampable adds two columns by default'); + $this->assertTrue(method_exists('Table2', 'getCreatedAt'), 'Timestamplable adds a created_at column by default'); + $this->assertTrue(method_exists('Table2', 'getUpdatedAt'), 'Timestamplable adds an updated_at column by default'); + $table1 = Table1Peer::getTableMap(); + $this->assertEquals(count($table1->getColumns()), 4, 'Timestampable does not add two columns when they already exist'); + $this->assertTrue(method_exists('Table1', 'getCreatedOn'), 'Timestamplable allows customization of create_column name'); + $this->assertTrue(method_exists('Table1', 'getUpdatedOn'), 'Timestamplable allows customization of update_column name'); + } + + public function testPreSave() + { + $t1 = new Table2(); + $this->assertNull($t1->getUpdatedAt()); + $tsave = time(); + $t1->save(); + $this->assertEquals($t1->getUpdatedAt('U'), $tsave, 'Timestampable sets updated_column to time() on creation'); + sleep(1); + $t1->setTitle('foo'); + $tupdate = time(); + $t1->save(); + $this->assertEquals($t1->getUpdatedAt('U'), $tupdate, 'Timestampable changes updated_column to time() on update'); + } + + public function testPreSaveNoChange() + { + $t1 = new Table2(); + $this->assertNull($t1->getUpdatedAt()); + $tsave = time(); + $t1->save(); + $this->assertEquals($t1->getUpdatedAt('U'), $tsave, 'Timestampable sets updated_column to time() on creation'); + sleep(1); + $tupdate = time(); + $t1->save(); + $this->assertEquals($t1->getUpdatedAt('U'), $tsave, 'Timestampable only changes updated_column if the object was modified'); + } + + public function testPreSaveManuallyUpdated() + { + $t1 = new Table2(); + $t1->setUpdatedAt(time() - 10); + $tsave = time(); + $t1->save(); + $this->assertNotEquals($t1->getUpdatedAt('U'), $tsave, 'Timestampable does not set updated_column to time() on creation when it is set by the user'); + // tip: if I set it to time()-10 a second time, the object sees that I want to change it to the same value + // and skips the update, therefore the updated_at is not in the list of modified columns, + // and the behavior changes it to the current date... let's say it's an edge case + $t1->setUpdatedAt(time() - 15); + $tupdate = time(); + $t1->save(); + $this->assertNotEquals($t1->getUpdatedAt('U'), $tupdate, 'Timestampable does not change updated_column to time() on update when it is set by the user'); + } + + public function testPreInsert() + { + $t1 = new Table2(); + $this->assertNull($t1->getCreatedAt()); + $tsave = time(); + $t1->save(); + $this->assertEquals($t1->getCreatedAt('U'), $tsave, 'Timestampable sets created_column to time() on creation'); + sleep(1); + $t1->setTitle('foo'); + $tupdate = time(); + $t1->save(); + $this->assertEquals($t1->getCreatedAt('U'), $tsave, 'Timestampable does not update created_column on update'); + } + + public function testPreInsertManuallyUpdated() + { + $t1 = new Table2(); + $t1->setCreatedAt(time() - 10); + $tsave = time(); + $t1->save(); + $this->assertNotEquals($t1->getCreatedAt('U'), $tsave, 'Timestampable does not set created_column to time() on creation when it is set by the user'); + } + + public function testObjectKeepUpdateDateUnchanged() + { + $t1 = new Table2(); + $t1->setUpdatedAt(time() - 10); + $tsave = time(); + $t1->save(); + $this->assertNotEquals($t1->getUpdatedAt('U'), $tsave); + // let's save it a second time; the updated_at should be changed + $t1->setTitle('foo'); + $tsave = time(); + $t1->save(); + $this->assertEquals($t1->getUpdatedAt('U'), $tsave); + + // now let's do this a second time + $t1 = new Table2(); + $t1->setUpdatedAt(time() - 10); + $tsave = time(); + $t1->save(); + $this->assertNotEquals($t1->getUpdatedAt('U'), $tsave); + // let's save it a second time; the updated_at should be changed + $t1->keepUpdateDateUnchanged(); + $t1->setTitle('foo'); + $tsave = time(); + $t1->save(); + $this->assertNotEquals($t1->getUpdatedAt('U'), $tsave, 'keepUpdateDateUnchanged() prevents the behavior from updating the update date'); + + } + + protected function populateUpdatedAt() + { + Table2Query::create()->deleteAll(); + $ts = new PropelObjectCollection(); + $ts->setModel('Table2'); + for ($i=0; $i < 10; $i++) { + $t = new Table2(); + $t->setTitle('UpdatedAt' . $i); + $t->setUpdatedAt(time() - $i * 24 * 60 * 60); + $ts[]= $t; + } + $ts->save(); + } + + protected function populateCreatedAt() + { + Table2Query::create()->deleteAll(); + $ts = new PropelObjectCollection(); + $ts->setModel('Table2'); + for ($i=0; $i < 10; $i++) { + $t = new Table2(); + $t->setTitle('CreatedAt' . $i); + $t->setCreatedAt(time() - $i * 24 * 60 * 60); + $ts[]= $t; + } + $ts->save(); + } + + public function testQueryRecentlyUpdated() + { + $q = Table2Query::create()->recentlyUpdated(); + $this->assertTrue($q instanceof Table2Query, 'recentlyUpdated() returns the current Query object'); + $this->populateUpdatedAt(); + $ts = Table2Query::create()->recentlyUpdated()->count(); + $this->assertEquals(8, $ts, 'recentlyUpdated() returns the elements updated in the last 7 days by default'); + $ts = Table2Query::create()->recentlyUpdated(5)->count(); + $this->assertEquals(6, $ts, 'recentlyUpdated() accepts a number of days as parameter'); + } + + public function testQueryRecentlyCreated() + { + $q = Table2Query::create()->recentlyCreated(); + $this->assertTrue($q instanceof Table2Query, 'recentlyCreated() returns the current Query object'); + $this->populateCreatedAt(); + $ts = Table2Query::create()->recentlyCreated()->count(); + $this->assertEquals(8, $ts, 'recentlyCreated() returns the elements created in the last 7 days by default'); + $ts = Table2Query::create()->recentlyCreated(5)->count(); + $this->assertEquals(6, $ts, 'recentlyCreated() accepts a number of days as parameter'); + } + + public function testQueryLastUpdatedFirst() + { + $q = Table2Query::create()->lastUpdatedFirst(); + $this->assertTrue($q instanceof Table2Query, 'lastUpdatedFirst() returns the current Query object'); + $this->populateUpdatedAt(); + $t = Table2Query::create()->lastUpdatedFirst()->findOne(); + $this->assertEquals('UpdatedAt0', $t->getTitle(), 'lastUpdatedFirst() returns element with most recent update date first'); + } + + public function testQueryFirstUpdatedFirst() + { + $q = Table2Query::create()->firstUpdatedFirst(); + $this->assertTrue($q instanceof Table2Query, 'firstUpdatedFirst() returns the current Query object'); + $this->populateUpdatedAt(); + $t = Table2Query::create()->firstUpdatedFirst()->findOne(); + $this->assertEquals('UpdatedAt9', $t->getTitle(), 'firstUpdatedFirst() returns the element with oldest updated date first'); + } + + public function testQueryLastCreatedFirst() + { + $q = Table2Query::create()->lastCreatedFirst(); + $this->assertTrue($q instanceof Table2Query, 'lastCreatedFirst() returns the current Query object'); + $this->populateCreatedAt(); + $t = Table2Query::create()->lastCreatedFirst()->findOne(); + $this->assertEquals('CreatedAt0', $t->getTitle(), 'lastCreatedFirst() returns element with most recent create date first'); + } + + public function testQueryFirstCreatedFirst() + { + $q = Table2Query::create()->firstCreatedFirst(); + $this->assertTrue($q instanceof Table2Query, 'firstCreatedFirst() returns the current Query object'); + $this->populateCreatedAt(); + $t = Table2Query::create()->firstCreatedFirst()->findOne(); + $this->assertEquals('CreatedAt9', $t->getTitle(), 'firstCreatedFirst() returns the element with oldest create date first'); + } + +} diff --git a/library/propel/test/testsuite/generator/behavior/aggregate_column/AggregateColumnBehaviorTest.php b/library/propel/test/testsuite/generator/behavior/aggregate_column/AggregateColumnBehaviorTest.php new file mode 100644 index 000000000..3019727c5 --- /dev/null +++ b/library/propel/test/testsuite/generator/behavior/aggregate_column/AggregateColumnBehaviorTest.php @@ -0,0 +1,254 @@ +assertEquals(count($postTable->getColumns()), 2, 'AggregateColumn adds one column by default'); + $this->assertTrue(method_exists('AggregatePost', 'getNbComments')); + } + + public function testCompute() + { + AggregateCommentQuery::create()->deleteAll($this->con); + AggregatePostQuery::create()->deleteAll($this->con); + $post = new AggregatePost(); + $post->save($this->con); + $this->assertEquals(0, $post->computeNbComments($this->con), 'The compute method returns 0 for objects with no related objects'); + $comment1 = new AggregateComment(); + $comment1->setAggregatePost($post); + $comment1->save($this->con); + $this->assertEquals(1, $post->computeNbComments($this->con), 'The compute method computes the aggregate function on related objects'); + $comment2 = new AggregateComment(); + $comment2->setAggregatePost($post); + $comment2->save($this->con); + $this->assertEquals(2, $post->computeNbComments($this->con), 'The compute method computes the aggregate function on related objects'); + $comment1->delete($this->con); + $this->assertEquals(1, $post->computeNbComments($this->con), 'The compute method computes the aggregate function on related objects'); + } + + public function testUpdate() + { + AggregateCommentQuery::create()->deleteAll($this->con); + AggregatePostQuery::create()->deleteAll($this->con); + $post = new AggregatePost(); + $post->save($this->con); + $comment = new TestableComment(); + $comment->setAggregatePost($post); + $comment->save($this->con); + $this->assertNull($post->getNbComments()); + $post->updateNbComments($this->con); + $this->assertEquals(1, $post->getNbComments(), 'The update method updates the aggregate column'); + $comment->delete($this->con); + $this->assertEquals(1, $post->getNbComments()); + $post->updateNbComments($this->con); + $this->assertEquals(0, $post->getNbComments(), 'The update method updates the aggregate column'); + } + + public function testCreateRelated() + { + AggregateCommentQuery::create()->deleteAll($this->con); + AggregatePostQuery::create()->deleteAll($this->con); + $post = new AggregatePost(); + $post->save($this->con); + $comment1 = new AggregateComment(); + $comment1->save($this->con); + $this->assertNull($post->getNbComments(), 'Adding a new foreign object does not update the aggregate column'); + $comment2 = new AggregateComment(); + $comment2->setAggregatePost($post); + $comment2->save($this->con); + $this->assertEquals(1, $post->getNbComments(), 'Adding a new related object updates the aggregate column'); + $comment3 = new AggregateComment(); + $comment3->setAggregatePost($post); + $comment3->save($this->con); + $this->assertEquals(2, $post->getNbComments(), 'Adding a new related object updates the aggregate column'); + } + + public function testUpdateRelated() + { + list($poll, $item1, $item2) = $this->populatePoll(); + $this->assertEquals(19, $poll->getTotalScore()); + $item1->setScore(10); + $item1->save($this->con); + $this->assertEquals(17, $poll->getTotalScore(), 'Updating a related object updates the aggregate column'); + } + + public function testDeleteRelated() + { + list($poll, $item1, $item2) = $this->populatePoll(); + $this->assertEquals(19, $poll->getTotalScore()); + $item1->delete($this->con); + $this->assertEquals(7, $poll->getTotalScore(), 'Deleting a related object updates the aggregate column'); + $item2->delete($this->con); + $this->assertNull($poll->getTotalScore(), 'Deleting a related object updates the aggregate column'); + } + + public function testUpdateRelatedWithQuery() + { + list($poll, $item1, $item2) = $this->populatePoll(); + $this->assertEquals(19, $poll->getTotalScore()); + AggregateItemQuery::create() + ->update(array('Score' => 4), $this->con); + $this->assertEquals(8, $poll->getTotalScore(), 'Updating related objects with a query updates the aggregate column'); + } + + public function testUpdateRelatedWithQueryUsingAlias() + { + list($poll, $item1, $item2) = $this->populatePoll(); + $this->assertEquals(19, $poll->getTotalScore()); + AggregateItemQuery::create() + ->setModelAlias('foo', true) + ->update(array('Score' => 4), $this->con); + $this->assertEquals(8, $poll->getTotalScore(), 'Updating related objects with a query using alias updates the aggregate column'); + } + + public function testDeleteRelatedWithQuery() + { + list($poll, $item1, $item2) = $this->populatePoll(); + $this->assertEquals(19, $poll->getTotalScore()); + AggregateItemQuery::create() + ->deleteAll($this->con); + $this->assertNull($poll->getTotalScore(), 'Deleting related objects with a query updates the aggregate column'); + } + + public function testDeleteRelatedWithQueryUsingAlias() + { + list($poll, $item1, $item2) = $this->populatePoll(); + $this->assertEquals(19, $poll->getTotalScore()); + AggregateItemQuery::create() + ->setModelAlias('foo', true) + ->filterById($item1->getId()) + ->delete($this->con); + $this->assertEquals(7, $poll->getTotalScore(), 'Deleting related objects with a query using alias updates the aggregate column'); + } + + public function testRemoveRelation() + { + AggregateCommentQuery::create()->deleteAll($this->con); + AggregatePostQuery::create()->deleteAll($this->con); + $post = new AggregatePost(); + $post->save($this->con); + $comment1 = new AggregateComment(); + $comment1->setAggregatePost($post); + $comment1->save($this->con); + $comment2 = new AggregateComment(); + $comment2->setAggregatePost($post); + $comment2->save($this->con); + $this->assertEquals(2, $post->getNbComments()); + $comment2->setAggregatePost(null); + $comment2->save($this->con); + $this->assertEquals(1, $post->getNbComments(), 'Removing a relation changes the related object aggregate column'); + } + + public function testReplaceRelation() + { + AggregateCommentQuery::create()->deleteAll($this->con); + AggregatePostQuery::create()->deleteAll($this->con); + $post1 = new AggregatePost(); + $post1->save($this->con); + $post2 = new AggregatePost(); + $post2->save($this->con); + $comment = new AggregateComment(); + $comment->setAggregatePost($post1); + $comment->save($this->con); + $this->assertEquals(1, $post1->getNbComments()); + $this->assertNull($post2->getNbComments()); + $comment->setAggregatePost($post2); + $comment->save($this->con); + $this->assertEquals(0, $post1->getNbComments(), 'Replacing a relation changes the related object aggregate column'); + $this->assertEquals(1, $post2->getNbComments(), 'Replacing a relation changes the related object aggregate column'); + } + + protected function populatePoll() + { + AggregateItemQuery::create()->deleteAll($this->con); + AggregatePollQuery::create()->deleteAll($this->con); + $poll = new AggregatePoll(); + $poll->save($this->con); + $item1 = new AggregateItem(); + $item1->setScore(12); + $item1->setAggregatePoll($poll); + $item1->save($this->con); + $item2 = new AggregateItem(); + $item2->setScore(7); + $item2->setAggregatePoll($poll); + $item2->save($this->con); + return array($poll, $item1, $item2); + } + +} + +class TestableComment extends AggregateComment +{ + // overrides the parent save() to bypass behavior hooks + public function save(PropelPDO $con = null) + { + $con->beginTransaction(); + try { + $affectedRows = $this->doSave($con); + AggregateCommentPeer::addInstanceToPool($this); + $con->commit(); + return $affectedRows; + } catch (PropelException $e) { + $con->rollBack(); + throw $e; + } + } + + // overrides the parent delete() to bypass behavior hooks + public function delete(PropelPDO $con = null) + { + $con->beginTransaction(); + try { + TestableAggregateCommentQuery::create() + ->filterByPrimaryKey($this->getPrimaryKey()) + ->delete($con); + $con->commit(); + $this->setDeleted(true); + } catch (PropelException $e) { + $con->rollBack(); + throw $e; + } + } + +} + +class TestableAggregateCommentQuery extends AggregateCommentQuery +{ + public static function create($modelAlias = null, $criteria = null) + { + return new TestableAggregateCommentQuery(); + } + + // overrides the parent basePreDelete() to bypass behavior hooks + protected function basePreDelete(PropelPDO $con) + { + return $this->preDelete($con); + } + + // overrides the parent basePostDelete() to bypass behavior hooks + protected function basePostDelete($affectedRows, PropelPDO $con) + { + return $this->postDelete($affectedRows, $con); + } + +} \ No newline at end of file diff --git a/library/propel/test/testsuite/generator/behavior/concrete_inheritance/ConcreteInheritanceBehaviorTest.php b/library/propel/test/testsuite/generator/behavior/concrete_inheritance/ConcreteInheritanceBehaviorTest.php new file mode 100644 index 000000000..8c86fa122 --- /dev/null +++ b/library/propel/test/testsuite/generator/behavior/concrete_inheritance/ConcreteInheritanceBehaviorTest.php @@ -0,0 +1,202 @@ +getBehaviors(); + $this->assertTrue(array_key_exists('concrete_inheritance_parent', $behaviors), 'modifyTable() gives the parent table the concrete_inheritance_parent behavior'); + $this->assertEquals('descendant_class', $behaviors['concrete_inheritance_parent']['descendant_column'], 'modifyTable() passed the descendent_column parameter to the parent behavior'); + } + + public function testModifyTableAddsParentColumn() + { + $contentColumns = array('id', 'title', 'category_id'); + $article = ConcreteArticlePeer::getTableMap(); + foreach ($contentColumns as $column) { + $this->assertTrue($article->containsColumn($column), 'modifyTable() adds the columns of the parent table'); + } + $quizz = ConcreteQuizzPeer::getTableMap(); + $this->assertEquals(3, count($quizz->getColumns()), 'modifyTable() does not add a column of the parent table if a similar column exists'); + } + + public function testModifyTableCopyDataAddsOneToOneRelationships() + { + $article = ConcreteArticlePeer::getTableMap(); + $this->assertTrue($article->hasRelation('ConcreteContent'), 'modifyTable() adds a relationship to the parent'); + $relation = $article->getRelation('ConcreteContent'); + $this->assertEquals(RelationMap::MANY_TO_ONE, $relation->getType(), 'modifyTable adds a one-to-one relationship'); + $content = ConcreteContentPeer::getTableMap(); + $relation = $content->getRelation('ConcreteArticle'); + $this->assertEquals(RelationMap::ONE_TO_ONE, $relation->getType(), 'modifyTable adds a one-to-one relationship'); + } + + public function testModifyTableNoCopyDataNoParentRelationship() + { + $quizz = ConcreteQuizzPeer::getTableMap(); + $this->assertFalse($quizz->hasRelation('ConcreteContent'), 'modifyTable() does not add a relationship to the parent when copy_data is false'); + } + + public function testModifyTableCopyDataRemovesAutoIncrement() + { + $content = new ConcreteContent(); + $content->save(); + $c = new Criteria; + $c->add(ConcreteArticlePeer::ID, $content->getId()); + try { + ConcreteArticlePeer::doInsert($c); + $this->assertTrue(true, 'modifyTable() removed autoIncrement from copied Primary keys'); + } catch (PropelException $e) { + $this->fail('modifyTable() removed autoIncrement from copied Primary keys'); + } + } + + /** + * @expectedException PropelException + */ + public function testModifyTableNoCopyDataKeepsAutoIncrement() + { + $content = new ConcreteContent(); + $content->save(); + $c = new Criteria; + $c->add(ConcreteQuizzPeer::ID, $content->getId()); + ConcreteQuizzPeer::doInsert($c); + } + + public function testModifyTableAddsForeignKeys() + { + $article = ConcreteArticlePeer::getTableMap(); + $this->assertTrue($article->hasRelation('ConcreteCategory'), 'modifyTable() copies relationships from parent table'); + } + + public function testModifyTableAddsForeignKeysWithoutDuplicates() + { + $article = ConcreteAuthorPeer::getTableMap(); + $this->assertTrue($article->hasRelation('ConcreteNews'), 'modifyTable() copies relationships from parent table and removes hardcoded refPhpName'); + } + + public function testModifyTableAddsValidators() + { + $article = ConcreteArticlePeer::getTableMap(); + $this->assertTrue($article->getColumn('title')->hasValidators(), 'modifyTable() copies validators from parent table'); + } + + // no way to test copying of indices and uniques, except by reverse engineering the db... + + public function testParentObjectClass() + { + $article = new ConcreteArticle(); // to autoload the BaseConcreteArticle class + $r = new ReflectionClass('BaseConcreteArticle'); + $this->assertEquals('ConcreteContent', $r->getParentClass()->getName(), 'concrete_inheritance changes the parent class of the Model Object to the parent object class'); + $quizz = new ConcreteQuizz(); // to autoload the BaseConcreteQuizz class + $r = new ReflectionClass('BaseConcreteQuizz'); + $this->assertEquals('ConcreteContent', $r->getParentClass()->getName(), 'concrete_inheritance changes the parent class of the Model Object to the parent object class'); + } + + public function testParentQueryClass() + { + $q = new ConcreteArticleQuery(); // to autoload the BaseConcreteArticleQuery class + $r = new ReflectionClass('BaseConcreteArticleQuery'); + $this->assertEquals('ConcreteContentQuery', $r->getParentClass()->getName(), 'concrete_inheritance changes the parent class of the Query Object to the parent object class'); + $q = new ConcreteQuizzQuery(); // to autoload the BaseConcreteQuizzQuery class + $r = new ReflectionClass('BaseConcreteQuizzQuery'); + $this->assertEquals('ConcreteContentQuery', $r->getParentClass()->getName(), 'concrete_inheritance changes the parent class of the Query Object to the parent object class'); + } + + public function testPreSaveCopyData() + { + ConcreteArticleQuery::create()->deleteAll(); + ConcreteQuizzQuery::create()->deleteAll(); + ConcreteContentQuery::create()->deleteAll(); + ConcreteCategoryQuery::create()->deleteAll(); + $category = new ConcreteCategory(); + $category->setName('main'); + $article = new ConcreteArticle(); + $article->setConcreteCategory($category); + $article->save(); + $this->assertNotNull($article->getId()); + $this->assertNotNull($category->getId()); + $content = ConcreteContentQuery::create()->findPk($article->getId()); + $this->assertNotNull($content); + $this->assertEquals($category->getId(), $content->getCategoryId()); + } + + public function testPreSaveNoCopyData() + { + ConcreteArticleQuery::create()->deleteAll(); + ConcreteQuizzQuery::create()->deleteAll(); + ConcreteContentQuery::create()->deleteAll(); + $quizz = new ConcreteQuizz(); + $quizz->save(); + $this->assertNotNull($quizz->getId()); + $content = ConcreteContentQuery::create()->findPk($quizz->getId()); + $this->assertNull($content); + } + + public function testGetParentOrCreateNew() + { + $article = new ConcreteArticle(); + $content = $article->getParentOrCreate(); + $this->assertTrue($content instanceof ConcreteContent, 'getParentOrCreate() returns an instance of the parent class'); + $this->assertTrue($content->isNew(), 'getParentOrCreate() returns a new instance of the parent class if the object is new'); + $this->assertEquals('ConcreteArticle', $content->getDescendantClass(), 'getParentOrCreate() correctly sets the descendant_class of the parent object'); + } + + public function testGetParentOrCreateExisting() + { + $article = new ConcreteArticle(); + $article->save(); + ConcreteContentPeer::clearInstancePool(); + $content = $article->getParentOrCreate(); + $this->assertTrue($content instanceof ConcreteContent, 'getParentOrCreate() returns an instance of the parent class'); + $this->assertFalse($content->isNew(), 'getParentOrCreate() returns an existing instance of the parent class if the object is persisted'); + $this->assertEquals($article->getId(), $content->getId(), 'getParentOrCreate() returns the parent object related to the current object'); + } + + public function testGetSyncParent() + { + $category = new ConcreteCategory(); + $category->setName('main'); + $article = new ConcreteArticle(); + $article->setTitle('FooBar'); + $article->setConcreteCategory($category); + $content = $article->getSyncParent(); + $this->assertEquals('FooBar', $content->getTitle(), 'getSyncParent() returns a synchronized parent object'); + $this->assertEquals($category, $content->getConcreteCategory(), 'getSyncParent() returns a synchronized parent object'); + } + + public function testPostDeleteCopyData() + { + ConcreteArticleQuery::create()->deleteAll(); + ConcreteQuizzQuery::create()->deleteAll(); + ConcreteContentQuery::create()->deleteAll(); + ConcreteCategoryQuery::create()->deleteAll(); + $category = new ConcreteCategory(); + $category->setName('main'); + $article = new ConcreteArticle(); + $article->setConcreteCategory($category); + $article->save(); + $id = $article->getId(); + $article->delete(); + $this->assertNull(ConcreteContentQuery::create()->findPk($id), 'delete() removes the parent record as well'); + } + +} diff --git a/library/propel/test/testsuite/generator/behavior/concrete_inheritance/ConcreteInheritanceParentBehaviorTest.php b/library/propel/test/testsuite/generator/behavior/concrete_inheritance/ConcreteInheritanceParentBehaviorTest.php new file mode 100644 index 000000000..b62cd3d00 --- /dev/null +++ b/library/propel/test/testsuite/generator/behavior/concrete_inheritance/ConcreteInheritanceParentBehaviorTest.php @@ -0,0 +1,53 @@ +deleteAll(); + ConcreteQuizzQuery::create()->deleteAll(); + ConcreteContentQuery::create()->deleteAll(); + $content = new ConcreteContent(); + $content->save(); + $this->assertFalse($content->hasChildObject()); + + $article = new ConcreteArticle(); + $article->save(); + $content = $article->getConcreteContent(); + $this->assertTrue($content->hasChildObject()); + } + + public function testGetChildObject() + { + ConcreteArticleQuery::create()->deleteAll(); + ConcreteQuizzQuery::create()->deleteAll(); + ConcreteContentQuery::create()->deleteAll(); + $content = new ConcreteContent(); + $content->save(); + $this->assertNull($content->getChildObject()); + + $article = new ConcreteArticle(); + $article->save(); + $content = $article->getConcreteContent(); + $this->assertEquals($article, $content->getChildObject()); + } + +} diff --git a/library/propel/test/testsuite/generator/behavior/nestedset/NestedSetBehaviorObjectBuilderModifierTest.php b/library/propel/test/testsuite/generator/behavior/nestedset/NestedSetBehaviorObjectBuilderModifierTest.php new file mode 100644 index 000000000..2abb542d1 --- /dev/null +++ b/library/propel/test/testsuite/generator/behavior/nestedset/NestedSetBehaviorObjectBuilderModifierTest.php @@ -0,0 +1,1351 @@ +setTreeLeft('123'); + $this->assertEquals($t->getLeftValue(), '123', 'nested_set adds a getLeftValue() method'); + $t->setTreeRight('456'); + $this->assertEquals($t->getRightValue(), '456', 'nested_set adds a getRightValue() method'); + $t->setLevel('789'); + $this->assertEquals($t->getLevel(), '789', 'nested_set adds a getLevel() method'); + } + + public function testParameters() + { + $t = new Table10(); + $t->setMyLeftColumn('123'); + $this->assertEquals($t->getLeftValue(), '123', 'nested_set adds a getLeftValue() method'); + $t->setMyRightColumn('456'); + $this->assertEquals($t->getRightValue(), '456', 'nested_set adds a getRightValue() method'); + $t->setMyLevelColumn('789'); + $this->assertEquals($t->getLevel(), '789', 'nested_set adds a getLevel() method'); + $t->setMyScopeColumn('012'); + $this->assertEquals($t->getScopeValue(), '012', 'nested_set adds a getScopeValue() method'); + } + + public function testObjectAttributes() + { + $expectedAttributes = array('nestedSetQueries'); + foreach ($expectedAttributes as $attribute) { + $this->assertClassHasAttribute($attribute, 'Table9'); + } + } + + public function testSaveOutOfTree() + { + Table9Peer::doDeleteAll(); + $t1 = new Table9(); + $t1->setTitle('t1'); + try { + $t1->save(); + $this->assertTrue(true, 'A node can be saved without valid tree information'); + } catch (Exception $e) { + $this->fail('A node can be saved without valid tree information'); + } + try { + $t1->makeRoot(); + $this->assertTrue(true, 'A saved node can be turned into root'); + } catch (Exception $e) { + $this->fail('A saved node can be turned into root'); + } + $t1->save(); + $t2 = new Table9(); + $t2->setTitle('t1'); + $t2->save(); + try { + $t2->insertAsFirstChildOf($t1); + $this->assertTrue(true, 'A saved node can be inserted into the tree'); + } catch (Exception $e) { + $this->fail('A saved node can be inserted into the tree'); + } + try { + $t2->save(); + $this->assertTrue(true, 'A saved node can be inserted into the tree'); + } catch (Exception $e) { + $this->fail('A saved node can be inserted into the tree'); + } + } + + public function testPreUpdate() + { + list($t1, $t2, $t3, $t4, $t5, $t6, $t7) = $this->initTree(); + $t3->setLeftValue(null); + try { + $t3->save(); + $this->fail('Trying to save a node incorrectly updated throws an exception'); + } catch (Exception $e) { + $this->assertTrue(true, 'Trying to save a node incorrectly updated throws an exception'); + } + } + + public function testDelete() + { + list($t1, $t2, $t3, $t4, $t5, $t6, $t7) = $this->initTree(); + /* Tree used for tests + t1 + | \ + t2 t3 + | \ + t4 t5 + | \ + t6 t7 + */ + $t5->delete(); + $this->assertEquals(13, $t3->getRightValue(), 'delete() does not update existing nodes (because delete() clears the instance cache)'); + $expected = array( + 't1' => array(1, 8, 0), + 't2' => array(2, 3, 1), + 't3' => array(4, 7, 1), + 't4' => array(5, 6, 2), + ); + $this->assertEquals($expected, $this->dumpTree(), 'delete() deletes all descendants and shifts the entire subtree correctly'); + list($t1, $t2, $t3, $t4, $t5, $t6, $t7) = $this->initTree(); + try { + $t1->delete(); + $this->fail('delete() throws an exception when called on a root node'); + } catch (PropelException $e) { + $this->assertTrue(true, 'delete() throws an exception when called on a root node'); + } + $this->assertNotEquals(array(), Table9Peer::doSelect(new Criteria()), 'delete() called on the root node does not delete the whole tree'); + } + + public function testMakeRoot() + { + $t = new Table9(); + $t->makeRoot(); + $this->assertEquals($t->getLeftValue(), 1, 'makeRoot() initializes left_column to 1'); + $this->assertEquals($t->getRightValue(), 2, 'makeRoot() initializes right_column to 2'); + $this->assertEquals($t->getLevel(), 0, 'makeRoot() initializes right_column to 0'); + $t = new Table9(); + $t->setLeftValue(12); + try { + $t->makeRoot(); + $this->fail('makeRoot() throws an exception when called on an object with a left_column value'); + } catch (PropelException $e) { + $this->assertTrue(true, 'makeRoot() throws an exception when called on an object with a left_column value'); + } + } + + public function testIsInTree() + { + $t1 = new Table9(); + $this->assertFalse($t1->isInTree(), 'inInTree() returns false for nodes with no left and right value'); + $t1->save(); + $this->assertFalse($t1->isInTree(), 'inInTree() returns false for saved nodes with no left and right value'); + $t1->setLeftValue(1)->setRightValue(0); + $this->assertFalse($t1->isInTree(), 'inInTree() returns false for nodes with zero left value'); + $t1->setLeftValue(0)->setRightValue(1); + $this->assertFalse($t1->isInTree(), 'inInTree() returns false for nodes with zero right value'); + $t1->setLeftValue(1)->setRightValue(1); + $this->assertFalse($t1->isInTree(), 'inInTree() returns false for nodes with equal left and right value'); + $t1->setLeftValue(1)->setRightValue(2); + $this->assertTrue($t1->isInTree(), 'inInTree() returns true for nodes with left < right value'); + $t1->setLeftValue(2)->setRightValue(1); + $this->assertFalse($t1->isInTree(), 'inInTree() returns false for nodes with left > right value'); + } + + public function testIsRoot() + { + list($t1, $t2, $t3, $t4, $t5, $t6, $t7) = $this->initTree(); + /* Tree used for tests + t1 + | \ + t2 t3 + | \ + t4 t5 + | \ + t6 t7 + */ + $this->assertTrue($t1->isRoot(), 'root is seen as root'); + $this->assertFalse($t2->isRoot(), 'leaf is not seen as root'); + $this->assertFalse($t3->isRoot(), 'node is not seen as root'); + } + + public function testIsLeaf() + { + list($t1, $t2, $t3, $t4, $t5, $t6, $t7) = $this->initTree(); + /* Tree used for tests + t1 + | \ + t2 t3 + | \ + t4 t5 + | \ + t6 t7 + */ + $this->assertFalse($t1->isLeaf(), 'root is not seen as leaf'); + $this->assertTrue($t2->isLeaf(), 'leaf is seen as leaf'); + $this->assertFalse($t3->isLeaf(), 'node is not seen as leaf'); + } + + public function testIsDescendantOf() + { + list($t1, $t2, $t3, $t4, $t5, $t6, $t7) = $this->initTree(); + /* Tree used for tests + t1 + | \ + t2 t3 + | \ + t4 t5 + | \ + t6 t7 + */ + $this->assertFalse($t1->isDescendantOf($t1), 'root is not seen as a descendant of root'); + $this->assertTrue($t2->isDescendantOf($t1), 'direct child is seen as a descendant of root'); + $this->assertFalse($t1->isDescendantOf($t2), 'root is not seen as a descendant of leaf'); + $this->assertTrue($t5->isDescendantOf($t1), 'grandchild is seen as a descendant of root'); + $this->assertTrue($t5->isDescendantOf($t3), 'direct child is seen as a descendant of node'); + $this->assertFalse($t3->isDescendantOf($t5), 'node is not seen as a descendant of its parent'); + } + + public function testIsAncestorOf() + { + list($t1, $t2, $t3, $t4, $t5, $t6, $t7) = $this->initTree(); + /* Tree used for tests + t1 + | \ + t2 t3 + | \ + t4 t5 + | \ + t6 t7 + */ + $this->assertFalse($t1->isAncestorOf($t1), 'root is not seen as an ancestor of root'); + $this->assertTrue($t1->isAncestorOf($t2), 'root is seen as an ancestor of direct child'); + $this->assertFalse($t2->isAncestorOf($t1), 'direct child is not seen as an ancestor of root'); + $this->assertTrue($t1->isAncestorOf($t5), 'root is seen as an ancestor of grandchild'); + $this->assertTrue($t3->isAncestorOf($t5), 'parent is seen as an ancestor of node'); + $this->assertFalse($t5->isAncestorOf($t3), 'child is not seen as an ancestor of its parent'); + } + + public function testHasParent() + { + Table9Peer::doDeleteAll(); + $t0 = new Table9(); + $t1 = new Table9(); + $t1->setTitle('t1')->setLeftValue(1)->setRightValue(6)->setLevel(0)->save(); + $t2 = new Table9(); + $t2->setTitle('t2')->setLeftValue(2)->setRightValue(5)->setLevel(1)->save(); + $t3 = new Table9(); + $t3->setTitle('t3')->setLeftValue(3)->setRightValue(4)->setLevel(2)->save(); + $this->assertFalse($t0->hasParent(), 'empty node has no parent'); + $this->assertFalse($t1->hasParent(), 'root node has no parent'); + $this->assertTrue($t2->hasParent(), 'not root node has a parent'); + $this->assertTrue($t3->hasParent(), 'leaf node has a parent'); + } + + public function testGetParent() + { + Table9Peer::doDeleteAll(); + $t0 = new Table9(); + $this->assertFalse($t0->hasParent(), 'empty node has no parent'); + $t1 = new Table9(); + $t1->setTitle('t1')->setLeftValue(1)->setRightValue(8)->setLevel(0)->save(); + $t2 = new Table9(); + $t2->setTitle('t2')->setLeftValue(2)->setRightValue(7)->setLevel(1)->save(); + $t3 = new Table9(); + $t3->setTitle('t3')->setLeftValue(3)->setRightValue(4)->setLevel(2)->save(); + $t4 = new Table9(); + $t4->setTitle('t4')->setLeftValue(5)->setRightValue(6)->setLevel(2)->save(); + $this->assertNull($t1->getParent($this->con), 'getParent() return null for root nodes'); + $this->assertEquals($t2->getParent($this->con), $t1, 'getParent() correctly retrieves parent for nodes'); + $this->assertEquals($t3->getParent($this->con), $t2, 'getParent() correctly retrieves parent for leafs'); + $this->assertEquals($t4->getParent($this->con), $t2, 'getParent() retrieves the same parent for two siblings'); + } + + public function testGetParentCache() + { + list($t1, $t2, $t3, $t4, $t5, $t6, $t7) = $this->initTree(); + /* Tree used for tests + t1 + | \ + t2 t3 + | \ + t4 t5 + | \ + t6 t7 + */ + $con = Propel::getConnection(); + $count = $con->getQueryCount(); + $parent = $t5->getParent($con); + $parent = $t5->getParent($con); + $this->assertEquals($count + 1, $con->getQueryCount(), 'getParent() only issues a query once'); + $this->assertEquals('t3', $parent->getTitle(), 'getParent() returns the parent Node'); + } + + public function testHasPrevSibling() + { + Table9Peer::doDeleteAll(); + $t0 = new Table9(); + $t1 = new Table9(); + $t1->setTitle('t1')->setLeftValue(1)->setRightValue(6)->save(); + $t2 = new Table9(); + $t2->setTitle('t2')->setLeftValue(2)->setRightValue(3)->save(); + $t3 = new Table9(); + $t3->setTitle('t3')->setLeftValue(4)->setRightValue(5)->save(); + $this->assertFalse($t0->hasPrevSibling(), 'empty node has no previous sibling'); + $this->assertFalse($t1->hasPrevSibling(), 'root node has no previous sibling'); + $this->assertFalse($t2->hasPrevSibling(), 'first sibling has no previous sibling'); + $this->assertTrue($t3->hasPrevSibling(), 'not first sibling has a previous siblingt'); + } + + public function testGetPrevSibling() + { + list($t1, $t2, $t3, $t4, $t5, $t6, $t7) = $this->initTree(); + /* Tree used for tests + t1 + | \ + t2 t3 + | \ + t4 t5 + | \ + t6 t7 + */ + $this->assertNull($t1->getPrevSibling($this->con), 'getPrevSibling() returns null for root nodes'); + $this->assertNull($t2->getPrevSibling($this->con), 'getPrevSibling() returns null for first siblings'); + $this->assertEquals($t3->getPrevSibling($this->con), $t2, 'getPrevSibling() correctly retrieves prev sibling'); + $this->assertNull($t6->getPrevSibling($this->con), 'getPrevSibling() returns null for first siblings'); + $this->assertEquals($t7->getPrevSibling($this->con), $t6, 'getPrevSibling() correctly retrieves prev sibling'); + } + + public function testHasNextSibling() + { + Table9Peer::doDeleteAll(); + $t0 = new Table9(); + $t1 = new Table9(); + $t1->setTitle('t1')->setLeftValue(1)->setRightValue(6)->save(); + $t2 = new Table9(); + $t2->setTitle('t2')->setLeftValue(2)->setRightValue(3)->save(); + $t3 = new Table9(); + $t3->setTitle('t3')->setLeftValue(4)->setRightValue(5)->save(); + $this->assertFalse($t0->hasNextSibling(), 'empty node has no next sibling'); + $this->assertFalse($t1->hasNextSibling(), 'root node has no next sibling'); + $this->assertTrue($t2->hasNextSibling(), 'not last sibling has a next sibling'); + $this->assertFalse($t3->hasNextSibling(), 'last sibling has no next sibling'); + } + + public function testGetNextSibling() + { + list($t1, $t2, $t3, $t4, $t5, $t6, $t7) = $this->initTree(); + /* Tree used for tests + t1 + | \ + t2 t3 + | \ + t4 t5 + | \ + t6 t7 + */ + $this->assertNull($t1->getNextSibling($this->con), 'getNextSibling() returns null for root nodes'); + $this->assertEquals($t2->getNextSibling($this->con), $t3, 'getNextSibling() correctly retrieves next sibling'); + $this->assertNull($t3->getNextSibling($this->con), 'getNextSibling() returns null for last siblings'); + $this->assertEquals($t6->getNextSibling($this->con), $t7, 'getNextSibling() correctly retrieves next sibling'); + $this->assertNull($t7->getNextSibling($this->con), 'getNextSibling() returns null for last siblings'); + } + + public function testAddNestedSetChildren() + { + $t0 = new Table9(); + $t1 = new Table9(); + $t2 = new Table9(); + $t0->addNestedSetChild($t1); + $t0->addNestedSetChild($t2); + $this->assertEquals(2, $t0->countChildren(), 'addNestedSetChild() adds the object to the internal children collection'); + $this->assertEquals($t0, $t1->getParent(), 'addNestedSetChild() sets the object as th parent of the parameter'); + $this->assertEquals($t0, $t2->getParent(), 'addNestedSetChild() sets the object as th parent of the parameter'); + } + + public function testHasChildren() + { + list($t1, $t2, $t3, $t4, $t5, $t6, $t7) = $this->initTree(); + /* Tree used for tests + t1 + | \ + t2 t3 + | \ + t4 t5 + | \ + t6 t7 + */ + $this->assertTrue($t1->hasChildren(), 'root has children'); + $this->assertFalse($t2->hasChildren(), 'leaf has no children'); + $this->assertTrue($t3->hasChildren(), 'node has children'); + } + + public function testGetChildren() + { + list($t1, $t2, $t3, $t4, $t5, $t6, $t7) = $this->initTree(); + /* Tree used for tests + t1 + | \ + t2 t3 + | \ + t4 t5 + | \ + t6 t7 + */ + $this->assertTrue($t2->getChildren() instanceof PropelObjectCollection, 'getChildren() returns a collection'); + $this->assertEquals(0, count($t2->getChildren()), 'getChildren() returns an empty collection for leafs'); + $children = $t3->getChildren(); + $expected = array( + 't4' => array(5, 6, 2), + 't5' => array(7, 12, 2), + ); + $this->assertEquals($expected, $this->dumpNodes($children, true), 'getChildren() returns a collection of children'); + $c = new Criteria(); + $c->add(Table9Peer::TITLE, 't5'); + $children = $t3->getChildren($c); + $expected = array( + 't5' => array(7, 12, 2), + ); + $this->assertEquals($expected, $this->dumpNodes($children, true), 'getChildren() accepts a criteria as parameter'); + } + + public function testGetChildrenCache() + { + list($t1, $t2, $t3, $t4, $t5, $t6, $t7) = $this->initTree(); + $con = Propel::getConnection(); + $count = $con->getQueryCount(); + $children = $t3->getChildren(null, $con); + $children = $t3->getChildren(null, $con); + $this->assertEquals($count + 1, $con->getQueryCount(), 'getChildren() only issues a query once'); + $expected = array( + 't4' => array(5, 6, 2), + 't5' => array(7, 12, 2), + ); + $this->assertEquals($expected, $this->dumpNodes($children, true), 'getChildren() returns a collection of children'); + // when using criteria, cache is not used + $c = new Criteria(); + $c->add(Table9Peer::TITLE, 't5'); + $children = $t3->getChildren($c, $con); + $this->assertEquals($count + 2, $con->getQueryCount(), 'getChildren() issues a new query when âssed a non-null Criteria'); + $expected = array( + 't5' => array(7, 12, 2), + ); + $this->assertEquals($expected, $this->dumpNodes($children, true), 'getChildren() accepts a criteria as parameter'); + // but not erased either + $children = $t3->getChildren(null, $con); + $this->assertEquals($count + 2, $con->getQueryCount(), 'getChildren() keeps its internal cache after being called with a Criteria'); + $expected = array( + 't4' => array(5, 6, 2), + 't5' => array(7, 12, 2), + ); + $this->assertEquals($expected, $this->dumpNodes($children, true), 'getChildren() returns a collection of children'); + } + + public function testCountChildren() + { + list($t1, $t2, $t3, $t4, $t5, $t6, $t7) = $this->initTree(); + /* Tree used for tests + t1 + | \ + t2 t3 + | \ + t4 t5 + | \ + t6 t7 + */ + $this->assertEquals(0, $t2->countChildren(), 'countChildren() returns 0 for leafs'); + $this->assertEquals(2, $t3->countChildren(), 'countChildren() returns the number of children'); + $c = new Criteria(); + $c->add(Table9Peer::TITLE, 't5'); + $this->assertEquals(1, $t3->countChildren($c), 'countChildren() accepts a criteria as parameter'); + } + + public function testCountChildrenCache() + { + list($t1, $t2, $t3, $t4, $t5, $t6, $t7) = $this->initTree(); + /* Tree used for tests + t1 + | \ + t2 t3 + | \ + t4 t5 + | \ + t6 t7 + */ + $con = Propel::getConnection(); + $count = $con->getQueryCount(); + $children = $t3->getChildren(null, $con); + $nbChildren = $t3->countChildren(null, $con); + $this->assertEquals($count + 1, $con->getQueryCount(), 'countChildren() uses the internal collection when passed no Criteria'); + $nbChildren = $t3->countChildren(new Criteria(), $con); + $this->assertEquals($count + 2, $con->getQueryCount(), 'countChildren() issues a new query when passed a Criteria'); + } + + public function testGetFirstChild() + { + list($t1, $t2, $t3, $t4, $t5, $t6, $t7) = $this->initTree(); + $t5->moveToNextSiblingOf($t3); + /* Results in + t1 + | \ \ + t2 t3 t5 + | | \ + t4 t6 t7 + */ + $this->assertEquals($t2, $t1->getFirstChild(), 'getFirstChild() returns the first child'); + } + + public function testGetLastChild() + { + list($t1, $t2, $t3, $t4, $t5, $t6, $t7) = $this->initTree(); + $t5->moveToNextSiblingOf($t3); + /* Results in + t1 + | \ \ + t2 t3 t5 + | | \ + t4 t6 t7 + */ + $this->assertEquals($t5, $t1->getLastChild(), 'getLastChild() returns the last child'); + } + + public function testGetSiblings() + { + list($t1, $t2, $t3, $t4, $t5, $t6, $t7) = $this->initTree(); + /* Tree used for tests + t1 + | \ + t2 t3 + | \ + t4 t5 + | \ + t6 t7 + */ + $this->assertEquals(array(), $t1->getSiblings(), 'getSiblings() returns an empty array for root'); + $descendants = $t5->getSiblings(); + $expected = array( + 't4' => array(5, 6, 2), + ); + $this->assertEquals($expected, $this->dumpNodes($descendants), 'getSiblings() returns an array of siblings'); + $descendants = $t5->getSiblings(true); + $expected = array( + 't4' => array(5, 6, 2), + 't5' => array(7, 12, 2), + ); + $this->assertEquals($expected, $this->dumpNodes($descendants), 'getSiblings(true) includes the current node'); + $t5->moveToNextSiblingOf($t3); + /* Results in + t1 + | \ \ + t2 t3 t5 + | | \ + t4 t6 t7 + */ + $this->assertEquals(0, count($t4->getSiblings()), 'getSiblings() returns an empty colleciton for lone children'); + $descendants = $t3->getSiblings(); + $expected = array( + 't2' => array(2, 3, 1), + 't5' => array(8, 13, 1), + ); + $this->assertEquals($expected, $this->dumpNodes($descendants), 'getSiblings() returns all siblings'); + } + + public function testGetDescendants() + { + list($t1, $t2, $t3, $t4, $t5, $t6, $t7) = $this->initTree(); + /* Tree used for tests + t1 + | \ + t2 t3 + | \ + t4 t5 + | \ + t6 t7 + */ + $this->assertEquals(array(), $t2->getDescendants(), 'getDescendants() returns an empty array for leafs'); + $descendants = $t3->getDescendants(); + $expected = array( + 't4' => array(5, 6, 2), + 't5' => array(7, 12, 2), + 't6' => array(8, 9, 3), + 't7' => array(10, 11, 3), + ); + $this->assertEquals($expected, $this->dumpNodes($descendants), 'getDescendants() returns an array of descendants'); + $c = new Criteria(); + $c->add(Table9Peer::TITLE, 't5'); + $descendants = $t3->getDescendants($c); + $expected = array( + 't5' => array(7, 12, 2), + ); + $this->assertEquals($expected, $this->dumpNodes($descendants), 'getDescendants() accepts a criteria as parameter'); + } + + public function testCountDescendants() + { + list($t1, $t2, $t3, $t4, $t5, $t6, $t7) = $this->initTree(); + /* Tree used for tests + t1 + | \ + t2 t3 + | \ + t4 t5 + | \ + t6 t7 + */ + $this->assertEquals(0, $t2->countDescendants(), 'countDescendants() returns 0 for leafs'); + $this->assertEquals(4, $t3->countDescendants(), 'countDescendants() returns the number of descendants'); + $c = new Criteria(); + $c->add(Table9Peer::TITLE, 't5'); + $this->assertEquals(1, $t3->countDescendants($c), 'countDescendants() accepts a criteria as parameter'); + } + + public function testGetBranch() + { + list($t1, $t2, $t3, $t4, $t5, $t6, $t7) = $this->initTree(); + /* Tree used for tests + t1 + | \ + t2 t3 + | \ + t4 t5 + | \ + t6 t7 + */ + $this->assertEquals(array($t2), $t2->getBranch()->getArrayCopy(), 'getBranch() returns the current node for leafs'); + $descendants = $t3->getBranch(); + $expected = array( + 't3' => array(4, 13, 1), + 't4' => array(5, 6, 2), + 't5' => array(7, 12, 2), + 't6' => array(8, 9, 3), + 't7' => array(10, 11, 3), + ); + $this->assertEquals($expected, $this->dumpNodes($descendants), 'getBranch() returns an array of descendants, uncluding the current node'); + $c = new Criteria(); + $c->add(Table9Peer::TITLE, 't3', Criteria::NOT_EQUAL); + $descendants = $t3->getBranch($c); + unset($expected['t3']); + $this->assertEquals($expected, $this->dumpNodes($descendants), 'getBranch() accepts a criteria as first parameter'); + } + + public function testGetAncestors() + { + list($t1, $t2, $t3, $t4, $t5, $t6, $t7) = $this->initTree(); + /* Tree used for tests + t1 + | \ + t2 t3 + | \ + t4 t5 + | \ + t6 t7 + */ + $this->assertEquals(array(), $t1->getAncestors(), 'getAncestors() returns an empty array for roots'); + $ancestors = $t5->getAncestors(); + $expected = array( + 't1' => array(1, 14, 0), + 't3' => array(4, 13, 1), + ); + $this->assertEquals($expected, $this->dumpNodes($ancestors), 'getAncestors() returns an array of ancestors'); + $c = new Criteria(); + $c->add(Table9Peer::TITLE, 't3'); + $ancestors = $t5->getAncestors($c); + $expected = array( + 't3' => array(4, 13, 1), + ); + $this->assertEquals($expected, $this->dumpNodes($ancestors), 'getAncestors() accepts a criteria as parameter'); + } + + public function testAddChild() + { + Table9Peer::doDeleteAll(); + $t1 = new Table9(); + $t1->setTitle('t1'); + $t1->makeRoot(); + $t1->save(); + $t2 = new Table9(); + $t2->setTitle('t2'); + $t1->addChild($t2); + $t2->save(); + $t3 = new Table9(); + $t3->setTitle('t3'); + $t1->addChild($t3); + $t3->save(); + $t4 = new Table9(); + $t4->setTitle('t4'); + $t2->addChild($t4); + $t4->save(); + $expected = array( + 't1' => array(1, 8, 0), + 't2' => array(4, 7, 1), + 't3' => array(2, 3, 1), + 't4' => array(5, 6, 2), + ); + $this->assertEquals($expected, $this->dumpTree(), 'addChild() adds the child and saves it'); + } + + public function testInsertAsFirstChildOf() + { + $this->assertTrue(method_exists('Table9', 'insertAsFirstChildOf'), 'nested_set adds a insertAsFirstChildOf() method'); + list($t1, $t2, $t3, $t4, $t5, $t6, $t7) = $this->initTree(); + /* Tree used for tests + t1 + | \ + t2 t3 + | \ + t4 t5 + | \ + t6 t7 + */ + $t8 = new PublicTable9(); + $t8->setTitle('t8'); + $t = $t8->insertAsFirstChildOf($t3); + $this->assertEquals($t8, $t, 'insertAsFirstChildOf() returns the object it was called on'); + $this->assertEquals(5, $t4->getLeftValue(), 'insertAsFirstChildOf() does not modify the tree until the object is saved'); + $t8->save(); + $this->assertEquals(5, $t8->getLeftValue(), 'insertAsFirstChildOf() sets the left value correctly'); + $this->assertEquals(6, $t8->getRightValue(), 'insertAsFirstChildOf() sets the right value correctly'); + $this->assertEquals(2, $t8->getLevel(), 'insertAsFirstChildOf() sets the level correctly'); + $expected = array( + 't1' => array(1, 16, 0), + 't2' => array(2, 3, 1), + 't3' => array(4, 15, 1), + 't4' => array(7, 8, 2), + 't5' => array(9, 14, 2), + 't6' => array(10, 11, 3), + 't7' => array(12, 13, 3), + 't8' => array(5, 6, 2) + ); + $this->assertEquals($expected, $this->dumpTree(), 'insertAsFirstChildOf() shifts the other nodes correctly'); + try { + $t8->insertAsFirstChildOf($t4); + $this->fail('insertAsFirstChildOf() throws an exception when called on a saved object'); + } catch (PropelException $e) { + $this->assertTrue(true, 'insertAsFirstChildOf() throws an exception when called on a saved object'); + } + } + + public function testInsertAsLastChildOf() + { + $this->assertTrue(method_exists('Table9', 'insertAsLastChildOf'), 'nested_set adds a insertAsLastChildOf() method'); + list($t1, $t2, $t3, $t4, $t5, $t6, $t7) = $this->initTree(); + /* Tree used for tests + t1 + | \ + t2 t3 + | \ + t4 t5 + | \ + t6 t7 + */ + $t8 = new PublicTable9(); + $t8->setTitle('t8'); + $t = $t8->insertAsLastChildOf($t3); + $this->assertEquals($t8, $t, 'insertAsLastChildOf() returns the object it was called on'); + $this->assertEquals(13, $t3->getRightValue(), 'insertAsLastChildOf() does not modify the tree until the object is saved'); + $t8->save(); + $this->assertEquals(13, $t8->getLeftValue(), 'insertAsLastChildOf() sets the left value correctly'); + $this->assertEquals(14, $t8->getRightValue(), 'insertAsLastChildOf() sets the right value correctly'); + $this->assertEquals(2, $t8->getLevel(), 'insertAsLastChildOf() sets the level correctly'); + $expected = array( + 't1' => array(1, 16, 0), + 't2' => array(2, 3, 1), + 't3' => array(4, 15, 1), + 't4' => array(5, 6, 2), + 't5' => array(7, 12, 2), + 't6' => array(8, 9, 3), + 't7' => array(10, 11, 3), + 't8' => array(13, 14, 2) + ); + $this->assertEquals($expected, $this->dumpTree(), 'insertAsLastChildOf() shifts the other nodes correctly'); + try { + $t8->insertAsLastChildOf($t4); + $this->fail('insertAsLastChildOf() throws an exception when called on a saved object'); + } catch (PropelException $e) { + $this->assertTrue(true, 'insertAsLastChildOf() throws an exception when called on a saved object'); + } + } + + public function testInsertAsPrevSiblingOf() + { + $this->assertTrue(method_exists('Table9', 'insertAsPrevSiblingOf'), 'nested_set adds a insertAsPrevSiblingOf() method'); + list($t1, $t2, $t3, $t4, $t5, $t6, $t7) = $this->initTree(); + /* Tree used for tests + t1 + | \ + t2 t3 + | \ + t4 t5 + | \ + t6 t7 + */ + $t8 = new PublicTable9(); + $t8->setTitle('t8'); + $t = $t8->insertAsPrevSiblingOf($t3); + $this->assertEquals($t8, $t, 'insertAsPrevSiblingOf() returns the object it was called on'); + $this->assertEquals(4, $t3->getLeftValue(), 'insertAsPrevSiblingOf() does not modify the tree until the object is saved'); + $t8->save(); + $this->assertEquals(4, $t8->getLeftValue(), 'insertAsPrevSiblingOf() sets the left value correctly'); + $this->assertEquals(5, $t8->getRightValue(), 'insertAsPrevSiblingOf() sets the right value correctly'); + $this->assertEquals(1, $t8->getLevel(), 'insertAsPrevSiblingOf() sets the level correctly'); + $expected = array( + 't1' => array(1, 16, 0), + 't2' => array(2, 3, 1), + 't3' => array(6, 15, 1), + 't4' => array(7, 8, 2), + 't5' => array(9, 14, 2), + 't6' => array(10, 11, 3), + 't7' => array(12, 13, 3), + 't8' => array(4, 5, 1) + ); + $this->assertEquals($expected, $this->dumpTree(), 'insertAsPrevSiblingOf() shifts the other nodes correctly'); + try { + $t8->insertAsPrevSiblingOf($t4); + $this->fail('insertAsPrevSiblingOf() throws an exception when called on a saved object'); + } catch (PropelException $e) { + $this->assertTrue(true, 'insertAsPrevSiblingOf() throws an exception when called on a saved object'); + } + } + + public function testInsertAsNextSiblingOf() + { + $this->assertTrue(method_exists('Table9', 'insertAsNextSiblingOf'), 'nested_set adds a insertAsNextSiblingOf() method'); + list($t1, $t2, $t3, $t4, $t5, $t6, $t7) = $this->initTree(); + /* Tree used for tests + t1 + | \ + t2 t3 + | \ + t4 t5 + | \ + t6 t7 + */ + $t8 = new PublicTable9(); + $t8->setTitle('t8'); + $t = $t8->insertAsNextSiblingOf($t3); + $this->assertEquals($t8, $t, 'insertAsNextSiblingOf() returns the object it was called on'); + $this->assertEquals(14, $t1->getRightValue(), 'insertAsNextSiblingOf() does not modify the tree until the object is saved'); + $t8->save(); + $this->assertEquals(14, $t8->getLeftValue(), 'insertAsNextSiblingOf() sets the left value correctly'); + $this->assertEquals(15, $t8->getRightValue(), 'insertAsNextSiblingOf() sets the right value correctly'); + $this->assertEquals(1, $t8->getLevel(), 'insertAsNextSiblingOf() sets the level correctly'); + $expected = array( + 't1' => array(1, 16, 0), + 't2' => array(2, 3, 1), + 't3' => array(4, 13, 1), + 't4' => array(5, 6, 2), + 't5' => array(7, 12, 2), + 't6' => array(8, 9, 3), + 't7' => array(10, 11, 3), + 't8' => array(14, 15, 1) + ); + $this->assertEquals($expected, $this->dumpTree(), 'insertAsNextSiblingOf() shifts the other nodes correctly'); + try { + $t8->insertAsNextSiblingOf($t4); + $this->fail('insertAsNextSiblingOf() throws an exception when called on a saved object'); + } catch (PropelException $e) { + $this->assertTrue(true, 'insertAsNextSiblingOf() throws an exception when called on a saved object'); + } + } + + public function testMoveToFirstChildOf() + { + $this->assertTrue(method_exists('Table9', 'moveToFirstChildOf'), 'nested_set adds a moveToFirstChildOf() method'); + list($t1, $t2, $t3, $t4, $t5, $t6, $t7) = $this->initTree(); + /* Tree used for tests + t1 + | \ + t2 t3 + | \ + t4 t5 + | \ + t6 t7 + */ + try { + $t3->moveToFirstChildOf($t5); + $this->fail('moveToFirstChildOf() throws an exception when the target is a child node'); + } catch (PropelException $e) { + $this->assertTrue(true, 'moveToFirstChildOf() throws an exception when the target is a child node'); + } + // moving down + $t = $t3->moveToFirstChildOf($t2); + $this->assertEquals($t3, $t, 'moveToFirstChildOf() returns the object it was called on'); + $expected = array( + 't1' => array(1, 14, 0), + 't2' => array(2, 13, 1), + 't3' => array(3, 12, 2), + 't4' => array(4, 5, 3), + 't5' => array(6, 11, 3), + 't6' => array(7, 8, 4), + 't7' => array(9, 10, 4), + ); + $this->assertEquals($expected, $this->dumpTree(), 'moveToFirstChildOf() moves the entire subtree down correctly'); + // moving up + list($t1, $t2, $t3, $t4, $t5, $t6, $t7) = $this->initTree(); + $t5->moveToFirstChildOf($t1); + $expected = array( + 't1' => array(1, 14, 0), + 't2' => array(8, 9, 1), + 't3' => array(10, 13, 1), + 't4' => array(11, 12, 2), + 't5' => array(2, 7, 1), + 't6' => array(3, 4, 2), + 't7' => array(5, 6, 2), + ); + $this->assertEquals($expected, $this->dumpTree(), 'moveToFirstChildOf() moves the entire subtree up correctly'); + // moving to the same level + list($t1, $t2, $t3, $t4, $t5, $t6, $t7) = $this->initTree(); + $t5->moveToFirstChildOf($t3); + $expected = array( + 't1' => array(1, 14, 0), + 't2' => array(2, 3, 1), + 't3' => array(4, 13, 1), + 't4' => array(11, 12, 2), + 't5' => array(5, 10, 2), + 't6' => array(6, 7, 3), + 't7' => array(8, 9, 3), + ); + $this->assertEquals($expected, $this->dumpTree(), 'moveToFirstChildOf() moves the entire subtree to the same level correctly'); + } + + public function testMoveToFirstChildOfAndChildrenCache() + { + list($t1, $t2, $t3, $t4, $t5, $t6, $t7) = $this->initTree(); + /* Tree used for tests + t1 + | \ + t2 t3 + | \ + t4 t5 + | \ + t6 t7 + */ + // fill children cache + $t3->getChildren(); + $t1->getChildren(); + // move + $t5->moveToFirstChildOf($t1); + $children = $t3->getChildren(); + $expected = array( + 't4' => array(11, 12, 2), + ); + $this->assertEquals($expected, $this->dumpNodes($children, true), 'moveToFirstChildOf() reinitializes the child collection of all concerned nodes'); + $children = $t1->getChildren(); + $expected = array( + 't5' => array(2, 7, 1), + 't2' => array(8, 9, 1), + 't3' => array(10, 13, 1), + ); + $this->assertEquals($expected, $this->dumpNodes($children, true), 'moveToFirstChildOf() reinitializes the child collection of all concerned nodes'); + } + + public function testMoveToLastChildOf() + { + $this->assertTrue(method_exists('Table9', 'moveToLastChildOf'), 'nested_set adds a moveToLastChildOf() method'); + list($t1, $t2, $t3, $t4, $t5, $t6, $t7) = $this->initTree(); + /* Tree used for tests + t1 + | \ + t2 t3 + | \ + t4 t5 + | \ + t6 t7 + */ + try { + $t3->moveToLastChildOf($t5); + $this->fail('moveToLastChildOf() throws an exception when the target is a child node'); + } catch (PropelException $e) { + $this->assertTrue(true, 'moveToLastChildOf() throws an exception when the target is a child node'); + } + // moving up + $t = $t5->moveToLastChildOf($t1); + $this->assertEquals($t5, $t, 'moveToLastChildOf() returns the object it was called on'); + $expected = array( + 't1' => array(1, 14, 0), + 't2' => array(2, 3, 1), + 't3' => array(4, 7, 1), + 't4' => array(5, 6, 2), + 't5' => array(8, 13, 1), + 't6' => array(9, 10, 2), + 't7' => array(11, 12, 2), + ); + $this->assertEquals($expected, $this->dumpTree(), 'moveToLastChildOf() moves the entire subtree up correctly'); + // moving down + list($t1, $t2, $t3, $t4, $t5, $t6, $t7) = $this->initTree(); + $t3->moveToLastChildOf($t2); + $expected = array( + 't1' => array(1, 14, 0), + 't2' => array(2, 13, 1), + 't3' => array(3, 12, 2), + 't4' => array(4, 5, 3), + 't5' => array(6, 11, 3), + 't6' => array(7, 8, 4), + 't7' => array(9, 10, 4), + ); + $this->assertEquals($expected, $this->dumpTree(), 'moveToLastChildOf() moves the entire subtree down correctly'); + // moving to the same level + list($t1, $t2, $t3, $t4, $t5, $t6, $t7) = $this->initTree(); + $t4->moveToLastChildOf($t3); + $expected = array( + 't1' => array(1, 14, 0), + 't2' => array(2, 3, 1), + 't3' => array(4, 13, 1), + 't4' => array(11, 12, 2), + 't5' => array(5, 10, 2), + 't6' => array(6, 7, 3), + 't7' => array(8, 9, 3), + ); + $this->assertEquals($expected, $this->dumpTree(), 'moveToLastChildOf() moves the entire subtree to the same level correctly'); + } + + public function testMoveToLastChildOfAndChildrenCache() + { + list($t1, $t2, $t3, $t4, $t5, $t6, $t7) = $this->initTree(); + /* Tree used for tests + t1 + | \ + t2 t3 + | \ + t4 t5 + | \ + t6 t7 + */ + // fill children cache + $t3->getChildren(); + $t1->getChildren(); + // move + $t5->moveToLastChildOf($t1); + $children = $t3->getChildren(); + $expected = array( + 't4' => array(5, 6, 2), + ); + $this->assertEquals($expected, $this->dumpNodes($children, true), 'moveToLastChildOf() reinitializes the child collection of all concerned nodes'); + $children = $t1->getChildren(); + $expected = array( + 't2' => array(2, 3, 1), + 't3' => array(4, 7, 1), + 't5' => array(8, 13, 1), + ); + $this->assertEquals($expected, $this->dumpNodes($children, true), 'moveToLastChildOf() reinitializes the child collection of all concerned nodes'); + } + + public function testMoveToPrevSiblingOf() + { + $this->assertTrue(method_exists('Table9', 'moveToPrevSiblingOf'), 'nested_set adds a moveToPrevSiblingOf() method'); + list($t1, $t2, $t3, $t4, $t5, $t6, $t7) = $this->initTree(); + /* Tree used for tests + t1 + | \ + t2 t3 + | \ + t4 t5 + | \ + t6 t7 + */ + try { + $t5->moveToPrevSiblingOf($t1); + $this->fail('moveToPrevSiblingOf() throws an exception when the target is a root node'); + } catch (PropelException $e) { + $this->assertTrue(true, 'moveToPrevSiblingOf() throws an exception when the target is a root node'); + } + try { + $t5->moveToPrevSiblingOf($t6); + $this->fail('moveToPrevSiblingOf() throws an exception when the target is a child node'); + } catch (PropelException $e) { + $this->assertTrue(true, 'moveToPrevSiblingOf() throws an exception when the target is a child node'); + } + // moving up + $t = $t5->moveToPrevSiblingOf($t3); + /* Results in + t1 + | \ \ + t2 t5 t3 + | \ | + t6 t7 t4 + */ + $this->assertEquals($t5, $t, 'moveToPrevSiblingOf() returns the object it was called on'); + $expected = array( + 't1' => array(1, 14, 0), + 't2' => array(2, 3, 1), + 't3' => array(10, 13, 1), + 't4' => array(11, 12, 2), + 't5' => array(4, 9, 1), + 't6' => array(5, 6, 2), + 't7' => array(7, 8, 2), + ); + $this->assertEquals($expected, $this->dumpTree(), 'moveToPrevSiblingOf() moves the entire subtree up correctly'); + // moving down + $t5->moveToPrevSiblingOf($t4); + /* Results in + t1 + | \ + t2 t3 + | \ + t5 t4 + | \ + t6 t7 + */ + $expected = array( + 't1' => array(1, 14, 0), + 't2' => array(2, 3, 1), + 't3' => array(4, 13, 1), + 't4' => array(11, 12, 2), + 't5' => array(5, 10, 2), + 't6' => array(6, 7, 3), + 't7' => array(8, 9, 3), + ); + $this->assertEquals($expected, $this->dumpTree(), 'moveToPrevSiblingOf() moves the entire subtree down correctly'); + // moving at the same level + $t4->moveToPrevSiblingOf($t5); + /* Results in + t1 + | \ + t2 t3 + | \ + t4 t5 + | \ + t6 t7 + */ + $expected = array( + 't1' => array(1, 14, 0), + 't2' => array(2, 3, 1), + 't3' => array(4, 13, 1), + 't4' => array(5, 6, 2), + 't5' => array(7, 12, 2), + 't6' => array(8, 9, 3), + 't7' => array(10, 11, 3), + ); + $this->assertEquals($expected, $this->dumpTree(), 'moveToPrevSiblingOf() moves the entire subtree at the same level correctly'); + } + + public function testMoveToPrevSiblingOfAndChildrenCache() + { + list($t1, $t2, $t3, $t4, $t5, $t6, $t7) = $this->initTree(); + /* Tree used for tests + t1 + | \ + t2 t3 + | \ + t4 t5 + | \ + t6 t7 + */ + // fill children cache + $t3->getChildren(); + $t1->getChildren(); + // move + $t5->moveToPrevSiblingOf($t2); + $children = $t3->getChildren(); + $expected = array( + 't4' => array(11, 12, 2), + ); + $this->assertEquals($expected, $this->dumpNodes($children, true), 'moveToPrevSiblingOf() reinitializes the child collection of all concerned nodes'); + $children = $t1->getChildren(); + $expected = array( + 't5' => array(2, 7, 1), + 't2' => array(8, 9, 1), + 't3' => array(10, 13, 1), + ); + $this->assertEquals($expected, $this->dumpNodes($children, true), 'moveToPrevSiblingOf() reinitializes the child collection of all concerned nodes'); + } + + public function testMoveToNextSiblingOfAndChildrenCache() + { + list($t1, $t2, $t3, $t4, $t5, $t6, $t7) = $this->initTree(); + /* Tree used for tests + t1 + | \ + t2 t3 + | \ + t4 t5 + | \ + t6 t7 + */ + // fill children cache + $t3->getChildren(); + $t1->getChildren(); + // move + $t5->moveToNextSiblingOf($t3); + $children = $t3->getChildren(); + $expected = array( + 't4' => array(5, 6, 2), + ); + $this->assertEquals($expected, $this->dumpNodes($children, true), 'moveToNextSiblingOf() reinitializes the child collection of all concerned nodes'); + $children = $t1->getChildren(); + $expected = array( + 't2' => array(2, 3, 1), + 't3' => array(4, 7, 1), + 't5' => array(8, 13, 1), + ); + $this->assertEquals($expected, $this->dumpNodes($children, true), 'moveToNextSiblingOf() reinitializes the child collection of all concerned nodes'); + } + + public function testMoveToNextSiblingOf() + { + $this->assertTrue(method_exists('Table9', 'moveToNextSiblingOf'), 'nested_set adds a moveToNextSiblingOf() method'); + list($t1, $t2, $t3, $t4, $t5, $t6, $t7) = $this->initTree(); + /* Tree used for tests + t1 + | \ + t2 t3 + | \ + t4 t5 + | \ + t6 t7 + */ + try { + $t5->moveToNextSiblingOf($t1); + $this->fail('moveToNextSiblingOf() throws an exception when the target is a root node'); + } catch (PropelException $e) { + $this->assertTrue(true, 'moveToNextSiblingOf() throws an exception when the target is a root node'); + } + try { + $t5->moveToNextSiblingOf($t6); + $this->fail('moveToNextSiblingOf() throws an exception when the target is a child node'); + } catch (PropelException $e) { + $this->assertTrue(true, 'moveToNextSiblingOf() throws an exception when the target is a child node'); + } + // moving up + $t = $t5->moveToNextSiblingOf($t3); + /* Results in + t1 + | \ \ + t2 t3 t5 + | | \ + t4 t6 t7 + */ + $this->assertEquals($t5, $t, 'moveToPrevSiblingOf() returns the object it was called on'); + $expected = array( + 't1' => array(1, 14, 0), + 't2' => array(2, 3, 1), + 't3' => array(4, 7, 1), + 't4' => array(5, 6, 2), + 't5' => array(8, 13, 1), + 't6' => array(9, 10, 2), + 't7' => array(11, 12, 2), + ); + $this->assertEquals($expected, $this->dumpTree(), 'moveToNextSiblingOf() moves the entire subtree up correctly'); + // moving down + $t = $t5->moveToNextSiblingOf($t4); + /* Results in + t1 + | \ + t2 t3 + | \ + t4 t5 + | \ + t6 t7 + */ + $expected = array( + 't1' => array(1, 14, 0), + 't2' => array(2, 3, 1), + 't3' => array(4, 13, 1), + 't4' => array(5, 6, 2), + 't5' => array(7, 12, 2), + 't6' => array(8, 9, 3), + 't7' => array(10, 11, 3), + ); + $this->assertEquals($expected, $this->dumpTree(), 'moveToNextSiblingOf() moves the entire subtree down correctly'); + // moving at the same level + $t = $t4->moveToNextSiblingOf($t5); + /* Results in + t1 + | \ + t2 t3 + | \ + t5 t4 + | \ + t6 t7 + */ + $expected = array( + 't1' => array(1, 14, 0), + 't2' => array(2, 3, 1), + 't3' => array(4, 13, 1), + 't4' => array(11, 12, 2), + 't5' => array(5, 10, 2), + 't6' => array(6, 7, 3), + 't7' => array(8, 9, 3), + ); + $this->assertEquals($expected, $this->dumpTree(), 'moveToNextSiblingOf() moves the entire subtree at the same level correctly'); + } + + public function testDeleteDescendants() + { + list($t1, $t2, $t3, $t4, $t5, $t6, $t7) = $this->initTree(); + /* Tree used for tests + t1 + | \ + t2 t3 + | \ + t4 t5 + | \ + t6 t7 + */ + $this->assertNull($t2->deleteDescendants(), 'deleteDescendants() returns null leafs'); + $this->assertEquals(4, $t3->deleteDescendants(), 'deleteDescendants() returns the number of deleted nodes'); + $this->assertEquals(5, $t3->getRightValue(), 'deleteDescendants() updates the current node'); + $this->assertEquals(5, $t4->getLeftValue(), 'deleteDescendants() does not update existing nodes (because delete() clears the instance cache)'); + $expected = array( + 't1' => array(1, 6, 0), + 't2' => array(2, 3, 1), + 't3' => array(4, 5, 1), + ); + $this->assertEquals($expected, $this->dumpTree(), 'deleteDescendants() shifts the entire subtree correctly'); + list($t1, $t2, $t3, $t4, $t5, $t6, $t7) = $this->initTree(); + /* Tree used for tests + t1 + | \ + t2 t3 + | \ + t4 t5 + | \ + t6 t7 + */ + $this->assertEquals(6, $t1->deleteDescendants(), 'deleteDescendants() can be called on the root node'); + $expected = array( + 't1' => array(1, 2, 0), + ); + $this->assertEquals($expected, $this->dumpTree(), 'deleteDescendants() can delete all descendants of the root node'); + } + + public function testGetIterator() + { + $fixtures = $this->initTree(); + $this->assertTrue(method_exists('Table9', 'getIterator'), 'nested_set adds a getIterator() method'); + $root = Table9Peer::retrieveRoot(); + $iterator = $root->getIterator(); + $this->assertTrue($iterator instanceof NestedSetRecursiveIterator, 'getIterator() returns a NestedSetRecursiveIterator'); + foreach ($iterator as $node) { + $expected = array_shift($fixtures); + $this->assertEquals($expected, $node, 'getIterator returns an iterator parsing the tree order by left column'); + } + } + + public function testCompatibilityProxies() + { + $proxies = array('createRoot', 'retrieveParent', 'setParentNode', 'getNumberOfDescendants', 'getNumberOfChildren', 'retrievePrevSibling', 'retrieveNextSibling', 'retrieveFirstChild', 'retrieveLastChild', 'getPath'); + foreach ($proxies as $method) { + $this->assertFalse(method_exists('Table9', $method), 'proxies are not enabled by default'); + $this->assertTrue(method_exists('Table10', $method), 'setting method_proxies to true adds compatibility proxies'); + } + $t = new Table10(); + $t->createRoot(); + $this->assertEquals($t->getLeftValue(), 1, 'createRoot() is an alias for makeRoot()'); + $this->assertEquals($t->getRightValue(), 2, 'createRoot() is an alias for makeRoot()'); + $this->assertEquals($t->getLevel(), 0, 'createRoot() is an alias for makeRoot()'); + } +} \ No newline at end of file diff --git a/library/propel/test/testsuite/generator/behavior/nestedset/NestedSetBehaviorObjectBuilderModifierWithScopeTest.php b/library/propel/test/testsuite/generator/behavior/nestedset/NestedSetBehaviorObjectBuilderModifierWithScopeTest.php new file mode 100644 index 000000000..439ff588f --- /dev/null +++ b/library/propel/test/testsuite/generator/behavior/nestedset/NestedSetBehaviorObjectBuilderModifierWithScopeTest.php @@ -0,0 +1,551 @@ +add(Table10Peer::TITLE, $title); + return Table10Peer::doSelectOne($c); + } + + public function testDelete() + { + list($t1, $t2, $t3, $t4, $t5, $t6, $t7, $t8, $t9, $t10) = $this->initTreeWithScope(); + /* Tree used for tests + Scope 1 + t1 + | \ + t2 t3 + | \ + t4 t5 + | \ + t6 t7 + Scope 2 + t8 + | \ + t9 t10 + */ + $t5->delete(); + $expected = array( + 't1' => array(1, 8, 0), + 't2' => array(2, 3, 1), + 't3' => array(4, 7, 1), + 't4' => array(5, 6, 2), + ); + $this->assertEquals($expected, $this->dumpTreeWithScope(1), 'delete() deletes all descendants and shifts the entire subtree correctly'); + $expected = array( + 't8' => array(1, 6, 0), + 't9' => array(2, 3, 1), + 't10' => array(4, 5, 1), + ); + $this->assertEquals($expected, $this->dumpTreeWithScope(2), 'delete() does not delete anything out of the scope'); + } + + public function testIsDescendantOf() + { + list($t1, $t2, $t3, $t4, $t5, $t6, $t7, $t8, $t9, $t10) = $this->initTreeWithScope(); + /* Tree used for tests + Scope 1 + t1 + | \ + t2 t3 + | \ + t4 t5 + | \ + t6 t7 + Scope 2 + t8 + | \ + t9 t10 + */ + $this->assertFalse($t8->isDescendantOf($t9), 'root is not seen as a child of root'); + $this->assertTrue($t9->isDescendantOf($t8), 'direct child is seen as a child of root'); + try { + $t2->isDescendantOf($t8); + $this->fail('isDescendantOf() throws an exception when comparing two nodes of different trees'); + } catch (PropelException $e) { + $this->assertTrue(true, 'isDescendantOf() throws an exception when comparing two nodes of different trees'); + } + } + + public function testGetParent() + { + $this->initTreeWithScope(); + $t1 = $this->getByTitle('t1'); + $this->assertNull($t1->getParent($this->con), 'getParent() return null for root nodes'); + $t2 = $this->getByTitle('t2'); + $this->assertEquals($t2->getParent($this->con), $t1, 'getParent() correctly retrieves parent for leafs'); + $t3 = $this->getByTitle('t3'); + $this->assertEquals($t3->getParent($this->con), $t1, 'getParent() correctly retrieves parent for nodes'); + $t4 = $this->getByTitle('t4'); + $this->assertEquals($t4->getParent($this->con), $t3, 'getParent() retrieves the same parent for nodes'); + } + + public function testGetPrevSibling() + { + list($t1, $t2, $t3, $t4, $t5, $t6, $t7, $t8, $t9, $t10) = $this->initTreeWithScope(); + /* Tree used for tests + Scope 1 + t1 + | \ + t2 t3 + | \ + t4 t5 + | \ + t6 t7 + Scope 2 + t8 + | \ + t9 t10 + */ + $this->assertNull($t1->getPrevSibling($this->con), 'getPrevSibling() returns null for root nodes'); + $this->assertNull($t2->getPrevSibling($this->con), 'getPrevSibling() returns null for first siblings'); + $this->assertEquals($t3->getPrevSibling($this->con), $t2, 'getPrevSibling() correctly retrieves prev sibling'); + $this->assertNull($t6->getPrevSibling($this->con), 'getPrevSibling() returns null for first siblings'); + $this->assertEquals($t7->getPrevSibling($this->con), $t6, 'getPrevSibling() correctly retrieves prev sibling'); + } + + public function testGetNextSibling() + { + list($t1, $t2, $t3, $t4, $t5, $t6, $t7, $t8, $t9, $t10) = $this->initTreeWithScope(); + /* Tree used for tests + Scope 1 + t1 + | \ + t2 t3 + | \ + t4 t5 + | \ + t6 t7 + Scope 2 + t8 + | \ + t9 t10 + */ + $this->assertNull($t1->getNextSibling($this->con), 'getNextSibling() returns null for root nodes'); + $this->assertEquals($t2->getNextSibling($this->con), $t3, 'getNextSibling() correctly retrieves next sibling'); + $this->assertNull($t3->getNextSibling($this->con), 'getNextSibling() returns null for last siblings'); + $this->assertEquals($t6->getNextSibling($this->con), $t7, 'getNextSibling() correctly retrieves next sibling'); + $this->assertNull($t7->getNextSibling($this->con), 'getNextSibling() returns null for last siblings'); + } + + public function testGetDescendants() + { + list($t1, $t2, $t3, $t4, $t5, $t6, $t7, $t8, $t9, $t10) = $this->initTreeWithScope(); + /* Tree used for tests + Scope 1 + t1 + | \ + t2 t3 + | \ + t4 t5 + | \ + t6 t7 + Scope 2 + t8 + | \ + t9 t10 + */ + $descendants = $t3->getDescendants(); + $expected = array( + 't4' => array(5, 6, 2), + 't5' => array(7, 12, 2), + 't6' => array(8, 9, 3), + 't7' => array(10, 11, 3), + ); + $this->assertEquals($expected, $this->dumpNodes($descendants), 'getDescendants() returns descendants from the current scope only'); + } + + public function testGetAncestors() + { + list($t1, $t2, $t3, $t4, $t5, $t6, $t7, $t8, $t9, $t10) = $this->initTreeWithScope(); + /* Tree used for tests + Scope 1 + t1 + | \ + t2 t3 + | \ + t4 t5 + | \ + t6 t7 + Scope 2 + t8 + | \ + t9 t10 + */ + $this->assertEquals(array(), $t1->getAncestors(), 'getAncestors() returns an empty array for roots'); + $ancestors = $t5->getAncestors(); + $expected = array( + 't1' => array(1, 14, 0), + 't3' => array(4, 13, 1), + ); + $this->assertEquals($expected, $this->dumpNodes($ancestors), 'getAncestors() returns ancestors from the current scope only'); + } + + public function testInsertAsFirstChildOf() + { + $this->assertTrue(method_exists('Table10', 'insertAsFirstChildOf'), 'nested_set adds a insertAsFirstChildOf() method'); + $fixtures = $this->initTreeWithScope(); + /* Tree used for tests + Scope 1 + t1 + | \ + t2 t3 + | \ + t4 t5 + | \ + t6 t7 + Scope 2 + t8 + | \ + t9 t10 + */ + $t11 = new PublicTable10(); + $t11->setTitle('t11'); + $t11->insertAsFirstChildOf($fixtures[2]); // first child of t3 + $this->assertEquals(1, $t11->getScopeValue(), 'insertAsFirstChildOf() sets the scope value correctly'); + $t11->save(); + $expected = array( + 't1' => array(1, 16, 0), + 't2' => array(2, 3, 1), + 't3' => array(4, 15, 1), + 't4' => array(7, 8, 2), + 't5' => array(9, 14, 2), + 't6' => array(10, 11, 3), + 't7' => array(12, 13, 3), + 't11' => array(5, 6, 2) + ); + $this->assertEquals($expected, $this->dumpTreeWithScope(1), 'insertAsFirstChildOf() shifts the other nodes correctly'); + $expected = array( + 't8' => array(1, 6, 0), + 't9' => array(2, 3, 1), + 't10' => array(4, 5, 1), + ); + $this->assertEquals($expected, $this->dumpTreeWithScope(2), 'insertAsFirstChildOf() does not shift anything out of the scope'); + } + + public function testInsertAsLastChildOf() + { + $this->assertTrue(method_exists('Table10', 'insertAsLastChildOf'), 'nested_set adds a insertAsLastChildOf() method'); + $fixtures = $this->initTreeWithScope(); + /* Tree used for tests + Scope 1 + t1 + | \ + t2 t3 + | \ + t4 t5 + | \ + t6 t7 + Scope 2 + t8 + | \ + t9 t10 + */ + $t11 = new PublicTable10(); + $t11->setTitle('t11'); + $t11->insertAsLastChildOf($fixtures[2]); // last child of t3 + $this->assertEquals(1, $t11->getScopeValue(), 'insertAsLastChildOf() sets the scope value correctly'); + $t11->save(); + $expected = array( + 't1' => array(1, 16, 0), + 't2' => array(2, 3, 1), + 't3' => array(4, 15, 1), + 't4' => array(5, 6, 2), + 't5' => array(7, 12, 2), + 't6' => array(8, 9, 3), + 't7' => array(10, 11, 3), + 't11' => array(13, 14, 2) + ); + $this->assertEquals($expected, $this->dumpTreeWithScope(1), 'insertAsLastChildOf() shifts the other nodes correctly'); + $expected = array( + 't8' => array(1, 6, 0), + 't9' => array(2, 3, 1), + 't10' => array(4, 5, 1), + ); + $this->assertEquals($expected, $this->dumpTreeWithScope(2), 'insertAsLastChildOf() does not shift anything out of the scope'); + } + + public function testInsertAsPrevSiblingOf() + { + $this->assertTrue(method_exists('Table10', 'insertAsPrevSiblingOf'), 'nested_set adds a insertAsPrevSiblingOf() method'); + $fixtures = $this->initTreeWithScope(); + /* Tree used for tests + Scope 1 + t1 + | \ + t2 t3 + | \ + t4 t5 + | \ + t6 t7 + Scope 2 + t8 + | \ + t9 t10 + */ + $t11 = new PublicTable10(); + $t11->setTitle('t11'); + $t11->insertAsPrevSiblingOf($fixtures[2]); // prev sibling of t3 + $this->assertEquals(1, $t11->getScopeValue(), 'insertAsPrevSiblingOf() sets the scope value correctly'); + $t11->save(); + $expected = array( + 't1' => array(1, 16, 0), + 't2' => array(2, 3, 1), + 't3' => array(6, 15, 1), + 't4' => array(7, 8, 2), + 't5' => array(9, 14, 2), + 't6' => array(10, 11, 3), + 't7' => array(12, 13, 3), + 't11' => array(4, 5, 1) + ); + $this->assertEquals($expected, $this->dumpTreeWithScope(1), 'insertAsPrevSiblingOf() shifts the other nodes correctly'); + $expected = array( + 't8' => array(1, 6, 0), + 't9' => array(2, 3, 1), + 't10' => array(4, 5, 1), + ); + $this->assertEquals($expected, $this->dumpTreeWithScope(2), 'insertAsPrevSiblingOf() does not shift anything out of the scope'); + } + + public function testInsertAsNextSiblingOf() + { + $this->assertTrue(method_exists('Table10', 'insertAsNextSiblingOf'), 'nested_set adds a insertAsNextSiblingOf() method'); + $fixtures = $this->initTreeWithScope(); + /* Tree used for tests + Scope 1 + t1 + | \ + t2 t3 + | \ + t4 t5 + | \ + t6 t7 + Scope 2 + t8 + | \ + t9 t10 + */ + $t11 = new PublicTable10(); + $t11->setTitle('t11'); + $t11->insertAsNextSiblingOf($fixtures[2]); // next sibling of t3 + $this->assertEquals(1, $t11->getScopeValue(), 'insertAsNextSiblingOf() sets the scope value correctly'); + $t11->save(); + $expected = array( + 't1' => array(1, 16, 0), + 't2' => array(2, 3, 1), + 't3' => array(4, 13, 1), + 't4' => array(5, 6, 2), + 't5' => array(7, 12, 2), + 't6' => array(8, 9, 3), + 't7' => array(10, 11, 3), + 't11' => array(14, 15, 1) + ); + $this->assertEquals($expected, $this->dumpTreeWithScope(1), 'insertAsNextSiblingOf() shifts the other nodes correctly'); + $expected = array( + 't8' => array(1, 6, 0), + 't9' => array(2, 3, 1), + 't10' => array(4, 5, 1), + ); + $this->assertEquals($expected, $this->dumpTreeWithScope(2), 'insertAsNextSiblingOf() does not shift anything out of the scope'); + } + + public function testMoveToFirstChildOf() + { + list($t1, $t2, $t3, $t4, $t5, $t6, $t7, $t8, $t9, $t10) = $this->initTreeWithScope(); + /* Tree used for tests + Scope 1 + t1 + | \ + t2 t3 + | \ + t4 t5 + | \ + t6 t7 + Scope 2 + t8 + | \ + t9 t10 + */ + try { + $t8->moveToFirstChildOf($t3); + $this->fail('moveToFirstChildOf() throws an exception when the target is in a different tree'); + } catch (PropelException $e) { + $this->assertTrue(true, 'moveToFirstChildOf() throws an exception when the target is in a different tree'); + } + try { + $t5->moveToLastChildOf($t2); + $this->assertTrue(true, 'moveToFirstChildOf() does not throw an exception when the target is in the same tree'); + } catch (PropelException $e) { + $this->fail('moveToFirstChildOf() does not throw an exception when the target is in the same tree'); + } + $expected = array( + 't8' => array(1, 6, 0), + 't9' => array(2, 3, 1), + 't10' => array(4, 5, 1), + ); + $this->assertEquals($expected, $this->dumpTreeWithScope(2), 'moveToFirstChildOf() does not shift anything out of the scope'); + } + + public function testMoveToLastChildOf() + { + list($t1, $t2, $t3, $t4, $t5, $t6, $t7, $t8, $t9, $t10) = $this->initTreeWithScope(); + /* Tree used for tests + Scope 1 + t1 + | \ + t2 t3 + | \ + t4 t5 + | \ + t6 t7 + Scope 2 + t8 + | \ + t9 t10 + */ + try { + $t8->moveToLastChildOf($t3); + $this->fail('moveToLastChildOf() throws an exception when the target is in a different tree'); + } catch (PropelException $e) { + $this->assertTrue(true, 'moveToLastChildOf() throws an exception when the target is in a different tree'); + } + try { + $t5->moveToLastChildOf($t2); + $this->assertTrue(true, 'moveToLastChildOf() does not throw an exception when the target is in the same tree'); + } catch (PropelException $e) { + $this->fail('moveToLastChildOf() does not throw an exception when the target is in the same tree'); + } + $expected = array( + 't8' => array(1, 6, 0), + 't9' => array(2, 3, 1), + 't10' => array(4, 5, 1), + ); + $this->assertEquals($expected, $this->dumpTreeWithScope(2), 'moveToLastChildOf() does not shift anything out of the scope'); + } + + public function testMoveToPrevSiblingOf() + { + list($t1, $t2, $t3, $t4, $t5, $t6, $t7, $t8, $t9, $t10) = $this->initTreeWithScope(); + /* Tree used for tests + Scope 1 + t1 + | \ + t2 t3 + | \ + t4 t5 + | \ + t6 t7 + Scope 2 + t8 + | \ + t9 t10 + */ + try { + $t8->moveToPrevSiblingOf($t3); + $this->fail('moveToPrevSiblingOf() throws an exception when the target is in a different tree'); + } catch (PropelException $e) { + $this->assertTrue(true, 'moveToPrevSiblingOf() throws an exception when the target is in a different tree'); + } + try { + $t5->moveToPrevSiblingOf($t2); + $this->assertTrue(true, 'moveToPrevSiblingOf() does not throw an exception when the target is in the same tree'); + } catch (PropelException $e) { + $this->fail('moveToPrevSiblingOf() does not throw an exception when the target is in the same tree'); + } + $expected = array( + 't8' => array(1, 6, 0), + 't9' => array(2, 3, 1), + 't10' => array(4, 5, 1), + ); + $this->assertEquals($expected, $this->dumpTreeWithScope(2), 'moveToPrevSiblingOf() does not shift anything out of the scope'); + } + + public function testMoveToNextSiblingOf() + { + list($t1, $t2, $t3, $t4, $t5, $t6, $t7, $t8, $t9, $t10) = $this->initTreeWithScope(); + /* Tree used for tests + Scope 1 + t1 + | \ + t2 t3 + | \ + t4 t5 + | \ + t6 t7 + Scope 2 + t8 + | \ + t9 t10 + */ + try { + $t8->moveToNextSiblingOf($t3); + $this->fail('moveToNextSiblingOf() throws an exception when the target is in a different tree'); + } catch (PropelException $e) { + $this->assertTrue(true, 'moveToNextSiblingOf() throws an exception when the target is in a different tree'); + } + try { + $t5->moveToNextSiblingOf($t2); + $this->assertTrue(true, 'moveToNextSiblingOf() does not throw an exception when the target is in the same tree'); + } catch (PropelException $e) { + $this->fail('moveToNextSiblingOf() does not throw an exception when the target is in the same tree'); + } + $expected = array( + 't8' => array(1, 6, 0), + 't9' => array(2, 3, 1), + 't10' => array(4, 5, 1), + ); + $this->assertEquals($expected, $this->dumpTreeWithScope(2), 'moveToNextSiblingOf() does not shift anything out of the scope'); + } + + public function testDeleteDescendants() + { + list($t1, $t2, $t3, $t4, $t5, $t6, $t7, $t8, $t9, $t10) = $this->initTreeWithScope(); + /* Tree used for tests + Scope 1 + t1 + | \ + t2 t3 + | \ + t4 t5 + | \ + t6 t7 + Scope 2 + t8 + | \ + t9 t10 + */ + $this->assertEquals(4, $t3->deleteDescendants(), 'deleteDescendants() returns the number of deleted nodes'); + $expected = array( + 't1' => array(1, 6, 0), + 't2' => array(2, 3, 1), + 't3' => array(4, 5, 1), + ); + $this->assertEquals($expected, $this->dumpTreeWithScope(1), 'deleteDescendants() shifts the entire subtree correctly'); + $expected = array( + 't8' => array(1, 6, 0), + 't9' => array(2, 3, 1), + 't10' => array(4, 5, 1), + ); + $this->assertEquals($expected, $this->dumpTreeWithScope(2), 'deleteDescendants() does not delete anything out of the scope'); + } +} \ No newline at end of file diff --git a/library/propel/test/testsuite/generator/behavior/nestedset/NestedSetBehaviorPeerBuilderModifierTest.php b/library/propel/test/testsuite/generator/behavior/nestedset/NestedSetBehaviorPeerBuilderModifierTest.php new file mode 100644 index 000000000..24555b3cd --- /dev/null +++ b/library/propel/test/testsuite/generator/behavior/nestedset/NestedSetBehaviorPeerBuilderModifierTest.php @@ -0,0 +1,350 @@ +assertEquals(Table9Peer::LEFT_COL, 'table9.TREE_LEFT', 'nested_set adds a LEFT_COL constant'); + $this->assertEquals(Table9Peer::RIGHT_COL, 'table9.TREE_RIGHT', 'nested_set adds a RIGHT_COL constant'); + $this->assertEquals(Table9Peer::LEVEL_COL, 'table9.TREE_LEVEL', 'nested_set adds a LEVEL_COL constant'); + } + + public function testRetrieveRoot() + { + $this->assertTrue(method_exists('Table9Peer', 'retrieveRoot'), 'nested_set adds a retrieveRoot() method'); + Table9Peer::doDeleteAll(); + $this->assertNull(Table9Peer::retrieveRoot(), 'retrieveRoot() returns null as long as no root node is defined'); + $t1 = new Table9(); + $t1->setLeftValue(123); + $t1->setRightValue(456); + $t1->save(); + $this->assertNull(Table9Peer::retrieveRoot(), 'retrieveRoot() returns null as long as no root node is defined'); + $t2 = new Table9(); + $t2->setLeftValue(1); + $t2->setRightValue(2); + $t2->save(); + $this->assertEquals(Table9Peer::retrieveRoot(), $t2, 'retrieveRoot() retrieves the root node'); + } + + public function testRetrieveTree() + { + list($t1, $t2, $t3, $t4, $t5, $t6, $t7) = $this->initTree(); + $tree = Table9Peer::retrieveTree(); + $this->assertEquals(array($t1, $t2, $t3, $t4, $t5, $t6, $t7), $tree, 'retrieveTree() retrieves the whole tree'); + $c = new Criteria(); + $c->add(Table9Peer::LEFT_COL, 4, Criteria::GREATER_EQUAL); + $tree = Table9Peer::retrieveTree($c); + $this->assertEquals(array($t3, $t4, $t5, $t6, $t7), $tree, 'retrieveTree() accepts a Criteria as first parameter'); + } + + public function testIsValid() + { + $this->assertTrue(method_exists('Table9Peer', 'isValid'), 'nested_set adds an isValid() method'); + $this->assertFalse(Table9Peer::isValid(null), 'isValid() returns false when passed null '); + $t1 = new Table9(); + $this->assertFalse(Table9Peer::isValid($t1), 'isValid() returns false when passed an empty node object'); + $t2 = new Table9(); + $t2->setLeftValue(5)->setRightValue(2); + $this->assertFalse(Table9Peer::isValid($t2), 'isValid() returns false when passed a node object with left > right'); + $t3 = new Table9(); + $t3->setLeftValue(5)->setRightValue(5); + $this->assertFalse(Table9Peer::isValid($t3), 'isValid() returns false when passed a node object with left = right'); + $t4 = new Table9(); + $t4->setLeftValue(2)->setRightValue(5); + $this->assertTrue(Table9Peer::isValid($t4), 'isValid() returns true when passed a node object with left < right'); + } + + public function testDeleteTree() + { + $this->initTree(); + Table9Peer::deleteTree(); + $this->assertEquals(array(), Table9Peer::doSelect(new Criteria()), 'deleteTree() deletes the whole tree'); + } + + public function testShiftRLValuesDelta() + { + $this->initTree(); + Table9Peer::shiftRLValues($delta = 1, $left = 1); + Table9Peer::clearInstancePool(); + $expected = array( + 't1' => array(2, 15, 0), + 't2' => array(3, 4, 1), + 't3' => array(5, 14, 1), + 't4' => array(6, 7, 2), + 't5' => array(8, 13, 2), + 't6' => array(9, 10, 3), + 't7' => array(11, 12, 3), + ); + $this->assertEquals($this->dumpTree(), $expected, 'shiftRLValues shifts all nodes with a positive amount'); + $this->initTree(); + Table9Peer::shiftRLValues($delta = -1, $left = 1); + Table9Peer::clearInstancePool(); + $expected = array( + 't1' => array(0, 13, 0), + 't2' => array(1, 2, 1), + 't3' => array(3, 12, 1), + 't4' => array(4, 5, 2), + 't5' => array(6, 11, 2), + 't6' => array(7, 8, 3), + 't7' => array(9, 10, 3), + ); + $this->assertEquals($this->dumpTree(), $expected, 'shiftRLValues can shift all nodes with a negative amount'); + $this->initTree(); + Table9Peer::shiftRLValues($delta = 3, $left = 1); + Table9Peer::clearInstancePool(); + $expected = array( + 't1'=> array(4, 17, 0), + 't2' => array(5, 6, 1), + 't3' => array(7, 16, 1), + 't4' => array(8, 9, 2), + 't5' => array(10, 15, 2), + 't6' => array(11, 12, 3), + 't7' => array(13, 14, 3), + ); + $this->assertEquals($this->dumpTree(), $expected, 'shiftRLValues shifts all nodes several units to the right'); + Table9Peer::shiftRLValues($delta = -3, $left = 1); + Table9Peer::clearInstancePool(); + $expected = array( + 't1' => array(1, 14, 0), + 't2' => array(2, 3, 1), + 't3' => array(4, 13, 1), + 't4' => array(5, 6, 2), + 't5' => array(7, 12, 2), + 't6' => array(8, 9, 3), + 't7' => array(10, 11, 3), + ); + $this->assertEquals($this->dumpTree(), $expected, 'shiftRLValues shifts all nodes several units to the left'); + } + + public function testShiftRLValuesLeftLimit() + { + $this->initTree(); + Table9Peer::shiftRLValues($delta = 1, $left = 15); + Table9Peer::clearInstancePool(); + $expected = array( + 't1' => array(1, 14, 0), + 't2' => array(2, 3, 1), + 't3' => array(4, 13, 1), + 't4' => array(5, 6, 2), + 't5' => array(7, 12, 2), + 't6' => array(8, 9, 3), + 't7' => array(10, 11, 3), + ); + $this->assertEquals($this->dumpTree(), $expected, 'shiftRLValues does not shift anything when the left parameter is higher than the highest right value'); + $this->initTree(); + Table9Peer::shiftRLValues($delta = 1, $left = 5); + Table9Peer::clearInstancePool(); + $expected = array( + 't1' => array(1, 15, 0), + 't2' => array(2, 3, 1), + 't3' => array(4, 14, 1), + 't4' => array(6, 7, 2), + 't5' => array(8, 13, 2), + 't6' => array(9, 10, 3), + 't7' => array(11, 12, 3), + ); + $this->assertEquals($this->dumpTree(), $expected, 'shiftRLValues shifts only the nodes having a LR value higher than the given left parameter'); + $this->initTree(); + Table9Peer::shiftRLValues($delta = 1, $left = 1); + Table9Peer::clearInstancePool(); + $expected = array( + 't1'=> array(2, 15, 0), + 't2' => array(3, 4, 1), + 't3' => array(5, 14, 1), + 't4' => array(6, 7, 2), + 't5' => array(8, 13, 2), + 't6' => array(9, 10, 3), + 't7' => array(11, 12, 3), + ); + $this->assertEquals($this->dumpTree(), $expected, 'shiftRLValues shifts all nodes when the left parameter is 1'); + } + + public function testShiftRLValuesRightLimit() + { + $this->initTree(); + Table9Peer::shiftRLValues($delta = 1, $left = 1, $right = 0); + Table9Peer::clearInstancePool(); + $expected = array( + 't1' => array(1, 14, 0), + 't2' => array(2, 3, 1), + 't3' => array(4, 13, 1), + 't4' => array(5, 6, 2), + 't5' => array(7, 12, 2), + 't6' => array(8, 9, 3), + 't7' => array(10, 11, 3), + ); + $this->assertEquals($this->dumpTree(), $expected, 'shiftRLValues does not shift anything when the right parameter is 0'); + $this->initTree(); + Table9Peer::shiftRLValues($delta = 1, $left = 1, $right = 5); + Table9Peer::clearInstancePool(); + $expected = array( + 't1' => array(2, 14, 0), + 't2' => array(3, 4, 1), + 't3' => array(5, 13, 1), + 't4' => array(6, 6, 2), + 't5' => array(7, 12, 2), + 't6' => array(8, 9, 3), + 't7' => array(10, 11, 3), + ); + $this->assertEquals($this->dumpTree(), $expected, 'shiftRLValues shiftRLValues shifts only the nodes having a LR value lower than the given right parameter'); + $this->initTree(); + Table9Peer::shiftRLValues($delta = 1, $left = 1, $right = 15); + Table9Peer::clearInstancePool(); + $expected = array( + 't1'=> array(2, 15, 0), + 't2' => array(3, 4, 1), + 't3' => array(5, 14, 1), + 't4' => array(6, 7, 2), + 't5' => array(8, 13, 2), + 't6' => array(9, 10, 3), + 't7' => array(11, 12, 3), + ); + $this->assertEquals($this->dumpTree(), $expected, 'shiftRLValues shifts all nodes when the right parameter is higher than the highest right value'); + } + + public function testShiftLevel() + { + /* Tree used for tests + t1 + | \ + t2 t3 + | \ + t4 t5 + | \ + t6 t7 + */ + $this->initTree(); + Table9Peer::shiftLevel($delta = 1, $first = 7, $last = 12); + Table9Peer::clearInstancePool(); + $expected = array( + 't1' => array(1, 14, 0), + 't2' => array(2, 3, 1), + 't3' => array(4, 13, 1), + 't4' => array(5, 6, 2), + 't5' => array(7, 12, 3), + 't6' => array(8, 9, 4), + 't7' => array(10, 11, 4), + ); + $this->assertEquals($this->dumpTree(), $expected, 'shiftLevel shifts all nodes with a left value between the first and last'); + $this->initTree(); + Table9Peer::shiftLevel($delta = -1, $first = 7, $last = 12); + Table9Peer::clearInstancePool(); + $expected = array( + 't1' => array(1, 14, 0), + 't2' => array(2, 3, 1), + 't3' => array(4, 13, 1), + 't4' => array(5, 6, 2), + 't5' => array(7, 12, 1), + 't6' => array(8, 9, 2), + 't7' => array(10, 11, 2), + ); + $this->assertEquals($this->dumpTree(), $expected, 'shiftLevel shifts all nodes wit ha negative amount'); + } + + public function testUpdateLoadedNodes() + { + $this->assertTrue(method_exists('Table9Peer', 'updateLoadedNodes'), 'nested_set adds a updateLoadedNodes() method'); + $fixtures = $this->initTree(); + Table9Peer::shiftRLValues(1, 5); + $expected = array( + 't1' => array(1, 14), + 't2' => array(2, 3), + 't3' => array(4, 13), + 't4' => array(5, 6), + 't5' => array(7, 12), + 't6' => array(8, 9), + 't7' => array(10, 11), + ); + $actual = array(); + foreach ($fixtures as $t) { + $actual[$t->getTitle()] = array($t->getLeftValue(), $t->getRightValue()); + } + $this->assertEquals($actual, $expected, 'Loaded nodes are not in sync before calling updateLoadedNodes()'); + Table9Peer::updateLoadedNodes(); + $expected = array( + 't1' => array(1, 15), + 't2' => array(2, 3), + 't3' => array(4, 14), + 't4' => array(6, 7), + 't5' => array(8, 13), + 't6' => array(9, 10), + 't7' => array(11, 12), + ); + $actual = array(); + foreach ($fixtures as $t) { + $actual[$t->getTitle()] = array($t->getLeftValue(), $t->getRightValue()); + } + $this->assertEquals($actual, $expected, 'Loaded nodes are in sync after calling updateLoadedNodes()'); + } + + public function testMakeRoomForLeaf() + { + $this->assertTrue(method_exists('Table9Peer', 'makeRoomForLeaf'), 'nested_set adds a makeRoomForLeaf() method'); + list($t1, $t2, $t3, $t4, $t5, $t6, $t7) = $this->initTree(); + /* Tree used for tests + t1 + | \ + t2 t3 + | \ + t4 t5 + | \ + t6 t7 + */ + $t = Table9Peer::makeRoomForLeaf(5); // first child of t3 + $expected = array( + 't1' => array(1, 16, 0), + 't2' => array(2, 3, 1), + 't3' => array(4, 15, 1), + 't4' => array(7, 8, 2), + 't5' => array(9, 14, 2), + 't6' => array(10, 11, 3), + 't7' => array(12, 13, 3), + ); + $this->assertEquals($expected, $this->dumpTree(), 'makeRoomForLeaf() shifts the other nodes correctly'); + foreach ($expected as $key => $values) + { + $this->assertEquals($values, array($$key->getLeftValue(), $$key->getRightValue(), $$key->getLevel()), 'makeRoomForLeaf() updates nodes already in memory'); + } + } + + public function testFixLevels() + { + $fixtures = $this->initTree(); + // reset the levels + foreach ($fixtures as $node) { + $node->setLevel(null)->save(); + } + // fix the levels + Table9Peer::fixLevels(); + $expected = array( + 't1' => array(1, 14, 0), + 't2' => array(2, 3, 1), + 't3' => array(4, 13, 1), + 't4' => array(5, 6, 2), + 't5' => array(7, 12, 2), + 't6' => array(8, 9, 3), + 't7' => array(10, 11, 3), + ); + $this->assertEquals($expected, $this->dumpTree(), 'fixLevels() fixes the levels correctly'); + Table9Peer::fixLevels(); + $this->assertEquals($expected, $this->dumpTree(), 'fixLevels() can be called several times'); + } +} diff --git a/library/propel/test/testsuite/generator/behavior/nestedset/NestedSetBehaviorPeerBuilderModifierWithScopeTest.php b/library/propel/test/testsuite/generator/behavior/nestedset/NestedSetBehaviorPeerBuilderModifierWithScopeTest.php new file mode 100644 index 000000000..87026d348 --- /dev/null +++ b/library/propel/test/testsuite/generator/behavior/nestedset/NestedSetBehaviorPeerBuilderModifierWithScopeTest.php @@ -0,0 +1,253 @@ +assertEquals(Table10Peer::LEFT_COL, 'table10.MY_LEFT_COLUMN', 'nested_set adds a LEFT_COL constant using the custom left_column parameter'); + $this->assertEquals(Table10Peer::RIGHT_COL, 'table10.MY_RIGHT_COLUMN', 'nested_set adds a RIGHT_COL constant using the custom right_column parameter'); + $this->assertEquals(Table10Peer::LEVEL_COL, 'table10.MY_LEVEL_COLUMN', 'nested_set adds a LEVEL_COL constant using the custom level_column parameter'); + $this->assertEquals(Table10Peer::SCOPE_COL, 'table10.MY_SCOPE_COLUMN', 'nested_set adds a SCOPE_COL constant when the use_scope parameter is true'); + } + + public function testRetrieveRoots() + { + $this->assertTrue(method_exists('Table10Peer', 'retrieveRoots'), 'nested_set adds a retrieveRoots() method for trees that use scope'); + $this->assertFalse(method_exists('Table9Peer', 'retrieveRoots'), 'nested_set does not add a retrieveRoots() method for trees that don\'t use scope'); + list($t1, $t2, $t3, $t4, $t5, $t6, $t7, $t8, $t9, $t10) = $this->initTreeWithScope(); + /* Tree used for tests + Scope 1 + t1 + | \ + t2 t3 + | \ + t4 t5 + | \ + t6 t7 + Scope 2 + t8 + | \ + t9 t10 + */ + $this->assertEquals(array($t1, $t8), Table10Peer::retrieveRoots(), 'retrieveRoots() returns the tree roots'); + $c = new Criteria(); + $c->add(Table10Peer::TITLE, 't1'); + $this->assertEquals(array($t1), Table10Peer::retrieveRoots($c), 'retrieveRoots() accepts a Criteria as first parameter'); + } + + public function testRetrieveRoot() + { + $this->assertTrue(method_exists('Table10Peer', 'retrieveRoot'), 'nested_set adds a retrieveRoot() method'); + Table10Peer::doDeleteAll(); + $t1 = new Table10(); + $t1->setLeftValue(1); + $t1->setRightValue(2); + $t1->setScopeValue(2); + $t1->save(); + $this->assertNull(Table10Peer::retrieveRoot(1), 'retrieveRoot() returns null as long as no root node is defined in the required scope'); + $t2 = new Table10(); + $t2->setLeftValue(1); + $t2->setRightValue(2); + $t2->setScopeValue(1); + $t2->save(); + $this->assertEquals(Table10Peer::retrieveRoot(1), $t2, 'retrieveRoot() retrieves the root node in the required scope'); + } + + public function testRetrieveTree() + { + list($t1, $t2, $t3, $t4, $t5, $t6, $t7, $t8, $t9, $t10) = $this->initTreeWithScope(); + /* Tree used for tests + Scope 1 + t1 + | \ + t2 t3 + | \ + t4 t5 + | \ + t6 t7 + Scope 2 + t8 + | \ + t9 t10 + */ + $tree = Table10Peer::retrieveTree(1); + $this->assertEquals(array($t1, $t2, $t3, $t4, $t5, $t6, $t7), $tree, 'retrieveTree() retrieves the scoped tree'); + $tree = Table10Peer::retrieveTree(2); + $this->assertEquals(array($t8, $t9, $t10), $tree, 'retrieveTree() retrieves the scoped tree'); + $c = new Criteria(); + $c->add(Table10Peer::LEFT_COL, 4, Criteria::GREATER_EQUAL); + $tree = Table10Peer::retrieveTree(1, $c); + $this->assertEquals(array($t3, $t4, $t5, $t6, $t7), $tree, 'retrieveTree() accepts a Criteria as first parameter'); + } + + public function testDeleteTree() + { + $this->initTreeWithScope(); + Table10Peer::deleteTree(1); + $expected = array( + 't8' => array(1, 6, 0), + 't9' => array(2, 3, 1), + 't10' => array(4, 5, 1), + ); + $this->assertEquals($this->dumpTreeWithScope(2), $expected, 'deleteTree() does not delete anything out of the scope'); + } + + public function testShiftRLValues() + { + $this->assertTrue(method_exists('Table10Peer', 'shiftRLValues'), 'nested_set adds a shiftRLValues() method'); + $this->initTreeWithScope(); + Table10Peer::shiftRLValues(1, 100, null, 1); + Table10Peer::clearInstancePool(); + $expected = array( + 't1' => array(1, 14, 0), + 't2' => array(2, 3, 1), + 't3' => array(4, 13, 1), + 't4' => array(5, 6, 2), + 't5' => array(7, 12, 2), + 't6' => array(8, 9, 3), + 't7' => array(10, 11, 3), + ); + $this->assertEquals($this->dumpTreeWithScope(1), $expected, 'shiftRLValues does not shift anything when the first parameter is higher than the highest right value'); + $expected = array( + 't8' => array(1, 6, 0), + 't9' => array(2, 3, 1), + 't10' => array(4, 5, 1), + ); + $this->assertEquals($this->dumpTreeWithScope(2), $expected, 'shiftRLValues does not shift anything out of the scope'); + $this->initTreeWithScope(); + Table10Peer::shiftRLValues(1, 1, null, 1); + Table10Peer::clearInstancePool(); + $expected = array( + 't1' => array(2, 15, 0), + 't2' => array(3, 4, 1), + 't3' => array(5, 14, 1), + 't4' => array(6, 7, 2), + 't5' => array(8, 13, 2), + 't6' => array(9, 10, 3), + 't7' => array(11, 12, 3), + ); + $this->assertEquals($this->dumpTreeWithScope(1), $expected, 'shiftRLValues can shift all nodes to the right'); + $expected = array( + 't8' => array(1, 6, 0), + 't9' => array(2, 3, 1), + 't10' => array(4, 5, 1), + ); + $this->assertEquals($this->dumpTreeWithScope(2), $expected, 'shiftRLValues does not shift anything out of the scope'); + $this->initTreeWithScope(); + Table10Peer::shiftRLValues(-1, 1, null, 1); + Table10Peer::clearInstancePool(); + $expected = array( + 't1' => array(0, 13, 0), + 't2' => array(1, 2, 1), + 't3' => array(3, 12, 1), + 't4' => array(4, 5, 2), + 't5' => array(6, 11, 2), + 't6' => array(7, 8, 3), + 't7' => array(9, 10, 3), + ); + $this->assertEquals($this->dumpTreeWithScope(1), $expected, 'shiftRLValues can shift all nodes to the left'); + $expected = array( + 't8' => array(1, 6, 0), + 't9' => array(2, 3, 1), + 't10' => array(4, 5, 1), + ); + $this->assertEquals($this->dumpTreeWithScope(2), $expected, 'shiftRLValues does not shift anything out of the scope'); + $this->initTreeWithScope(); + Table10Peer::shiftRLValues(1, 5, null, 1); + Table10Peer::clearInstancePool(); + $expected = array( + 't1' => array(1, 15, 0), + 't2' => array(2, 3, 1), + 't3' => array(4, 14, 1), + 't4' => array(6, 7, 2), + 't5' => array(8, 13, 2), + 't6' => array(9, 10, 3), + 't7' => array(11, 12, 3), + ); + $this->assertEquals($this->dumpTreeWithScope(1), $expected, 'shiftRLValues can shift some nodes to the right'); + $expected = array( + 't8' => array(1, 6, 0), + 't9' => array(2, 3, 1), + 't10' => array(4, 5, 1), + ); + $this->assertEquals($this->dumpTreeWithScope(2), $expected, 'shiftRLValues does not shift anything out of the scope'); + } + + public function testShiftLevel() + { + $this->initTreeWithScope(); + Table10Peer::shiftLevel($delta = 1, $first = 7, $last = 12, $scope = 1); + Table10Peer::clearInstancePool(); + $expected = array( + 't1' => array(1, 14, 0), + 't2' => array(2, 3, 1), + 't3' => array(4, 13, 1), + 't4' => array(5, 6, 2), + 't5' => array(7, 12, 3), + 't6' => array(8, 9, 4), + 't7' => array(10, 11, 4), + ); + $this->assertEquals($this->dumpTreeWithScope(1), $expected, 'shiftLevel can shift level whith a scope'); + $expected = array( + 't8' => array(1, 6, 0), + 't9' => array(2, 3, 1), + 't10' => array(4, 5, 1), + ); + $this->assertEquals($this->dumpTreeWithScope(2), $expected, 'shiftLevel does not shift anything out of the scope'); + } + + public function testMakeRoomForLeaf() + { + $this->assertTrue(method_exists('Table10Peer', 'makeRoomForLeaf'), 'nested_set adds a makeRoomForLeaf() method'); + $fixtures = $this->initTreeWithScope(); + /* Tree used for tests + Scope 1 + t1 + | \ + t2 t3 + | \ + t4 t5 + | \ + t6 t7 + Scope 2 + t8 + | \ + t9 t10 + */ + $t = Table10Peer::makeRoomForLeaf(5, 1); // first child of t3 + $expected = array( + 't1' => array(1, 16, 0), + 't2' => array(2, 3, 1), + 't3' => array(4, 15, 1), + 't4' => array(7, 8, 2), + 't5' => array(9, 14, 2), + 't6' => array(10, 11, 3), + 't7' => array(12, 13, 3), + ); + $this->assertEquals($expected, $this->dumpTreeWithScope(1), 'makeRoomForLeaf() shifts the other nodes correctly'); + $expected = array( + 't8' => array(1, 6, 0), + 't9' => array(2, 3, 1), + 't10' => array(4, 5, 1), + ); + $this->assertEquals($expected, $this->dumpTreeWithScope(2), 'makeRoomForLeaf() does not shift anything out of the scope'); + } +} \ No newline at end of file diff --git a/library/propel/test/testsuite/generator/behavior/nestedset/NestedSetBehaviorQueryBuilderModifierTest.php b/library/propel/test/testsuite/generator/behavior/nestedset/NestedSetBehaviorQueryBuilderModifierTest.php new file mode 100644 index 000000000..8a6f5729b --- /dev/null +++ b/library/propel/test/testsuite/generator/behavior/nestedset/NestedSetBehaviorQueryBuilderModifierTest.php @@ -0,0 +1,283 @@ +initTree(); + /* Tree used for tests + t1 + | \ + t2 t3 + | \ + t4 t5 + | \ + t6 t7 + */ + $objs = Table9Query::create() + ->descendantsOf($t7) + ->orderByBranch() + ->find(); + $coll = $this->buildCollection(array()); + $this->assertEquals($coll, $objs, 'decendantsOf() filters by descendants'); + $objs = Table9Query::create() + ->descendantsOf($t3) + ->orderByBranch() + ->find(); + $coll = $this->buildCollection(array($t4, $t5, $t6, $t7)); + $this->assertEquals($coll, $objs, 'decendantsOf() filters by descendants'); + } + + public function testBranchOf() + { + list($t1, $t2, $t3, $t4, $t5, $t6, $t7) = $this->initTree(); + /* Tree used for tests + t1 + | \ + t2 t3 + | \ + t4 t5 + | \ + t6 t7 + */ + $objs = Table9Query::create() + ->branchOf($t7) + ->orderByBranch() + ->find(); + $coll = $this->buildCollection(array($t7)); + $this->assertEquals($coll, $objs, 'branchOf() filters by descendants and includes object passed as parameter'); + $objs = Table9Query::create() + ->branchOf($t3) + ->orderByBranch() + ->find(); + $coll = $this->buildCollection(array($t3, $t4, $t5, $t6, $t7)); + $this->assertEquals($coll, $objs, 'branchOf() filters by descendants and includes object passed as parameter'); + $objs = Table9Query::create() + ->branchOf($t1) + ->orderByBranch() + ->find(); + $coll = $this->buildCollection(array($t1, $t2, $t3, $t4, $t5, $t6, $t7)); + $this->assertEquals($coll, $objs, 'branchOf() returns the whole tree for the root node'); + } + + public function testChildrenOf() + { + list($t1, $t2, $t3, $t4, $t5, $t6, $t7) = $this->initTree(); + /* Tree used for tests + t1 + | \ + t2 t3 + | \ + t4 t5 + | \ + t6 t7 + */ + $objs = Table9Query::create() + ->childrenOf($t6) + ->orderByBranch() + ->find(); + $coll = $this->buildCollection(array()); + $this->assertEquals($coll, $objs, 'childrenOf() returns empty collection for leaf nodes'); + $objs = Table9Query::create() + ->childrenOf($t5) + ->orderByBranch() + ->find(); + $coll = $this->buildCollection(array($t6, $t7)); + $this->assertEquals($coll, $objs, 'childrenOf() filters by children'); + $objs = Table9Query::create() + ->childrenOf($t3) + ->orderByBranch() + ->find(); + $coll = $this->buildCollection(array($t4, $t5)); + $this->assertEquals($coll, $objs, 'childrenOf() filters by children and not by descendants'); + } + + public function testSiblingsOf() + { + list($t1, $t2, $t3, $t4, $t5, $t6, $t7) = $this->initTree(); + /* Tree used for tests + t1 + | \ + t2 t3 + | \ + t4 t5 + | \ + t6 t7 + */ + $desc = Table9Query::create() + ->siblingsOf($t1) + ->orderByBranch() + ->find(); + $coll = $this->buildCollection(array()); + $this->assertEquals($coll, $desc, 'siblingsOf() returns empty collection for the root node'); + $desc = Table9Query::create() + ->siblingsOf($t3) + ->orderByBranch() + ->find(); + $coll = $this->buildCollection(array($t2)); + $this->assertEquals($coll, $desc, 'siblingsOf() filters by siblings'); + } + + public function testAncestorsOf() + { + list($t1, $t2, $t3, $t4, $t5, $t6, $t7) = $this->initTree(); + /* Tree used for tests + t1 + | \ + t2 t3 + | \ + t4 t5 + | \ + t6 t7 + */ + $objs = Table9Query::create() + ->ancestorsOf($t1) + ->orderByBranch() + ->find(); + $coll = $this->buildCollection(array()); + $this->assertEquals($coll, $objs, 'ancestorsOf() returns empty collection for root node'); + $objs = Table9Query::create() + ->ancestorsOf($t3) + ->orderByBranch() + ->find(); + $coll = $this->buildCollection(array($t1)); + $this->assertEquals($coll, $objs, 'ancestorsOf() filters by ancestors'); + $objs = Table9Query::create() + ->ancestorsOf($t7) + ->orderByBranch() + ->find(); + $coll = $this->buildCollection(array($t1, $t3, $t5)); + $this->assertEquals($coll, $objs, 'childrenOf() filters by ancestors'); + } + + public function testRootsOf() + { + list($t1, $t2, $t3, $t4, $t5, $t6, $t7) = $this->initTree(); + /* Tree used for tests + t1 + | \ + t2 t3 + | \ + t4 t5 + | \ + t6 t7 + */ + $objs = Table9Query::create() + ->rootsOf($t1) + ->orderByBranch() + ->find(); + $coll = $this->buildCollection(array($t1)); + $this->assertEquals($coll, $objs, 'rootsOf() returns the root node for root node'); + $objs = Table9Query::create() + ->rootsOf($t3) + ->orderByBranch() + ->find(); + $coll = $this->buildCollection(array($t1, $t3)); + $this->assertEquals($coll, $objs, 'rootsOf() filters by ancestors and includes the node passed as parameter'); + $objs = Table9Query::create() + ->rootsOf($t7) + ->orderByBranch() + ->find(); + $coll = $this->buildCollection(array($t1, $t3, $t5, $t7)); + $this->assertEquals($coll, $objs, 'rootsOf() filters by ancestors and includes the node passed as parameter'); + } + + public function testOrderByBranch() + { + list($t1, $t2, $t3, $t4, $t5, $t6, $t7) = $this->initTree(); + $t5->moveToPrevSiblingOf($t4); + /* Results in + t1 + | \ + t2 t3 + | \ + t5 t4 + | \ + t6 t7 + */ + $objs = Table9Query::create() + ->orderByBranch() + ->find(); + $coll = $this->buildCollection(array($t1, $t2, $t3, $t5, $t6, $t7, $t4), 'orderByBranch() orders by branch left to right'); + $objs = Table9Query::create() + ->orderByBranch(true) + ->find(); + $coll = $this->buildCollection(array($t4, $t7, $t6, $t5, $t3, $t2, $t1), 'orderByBranch(true) orders by branch right to left'); + } + + public function testOrderByLevel() + { + list($t1, $t2, $t3, $t4, $t5, $t6, $t7) = $this->initTree(); + $t5->moveToPrevSiblingOf($t4); + /* Results in + t1 + | \ + t2 t3 + | \ + t5 t4 + | \ + t6 t7 + */ + $objs = Table9Query::create() + ->orderByLevel() + ->find(); + $coll = $this->buildCollection(array($t1, $t2, $t5, $t4, $t6, $t7), 'orderByLevel() orders by level, from the root to the leaf'); + $objs = Table9Query::create() + ->orderByLevel(true) + ->find(); + $coll = $this->buildCollection(array($t7, $t6, $t4, $t5, $t2, $t1), 'orderByLevel(true) orders by level, from the leaf to the root'); + } + + public function testFindRoot() + { + $this->assertTrue(method_exists('Table9Query', 'findRoot'), 'nested_set adds a findRoot() method'); + Table9Query::create()->deleteAll(); + $this->assertNull(Table9Query::create()->findRoot(), 'findRoot() returns null as long as no root node is defined'); + $t1 = new Table9(); + $t1->setLeftValue(123); + $t1->setRightValue(456); + $t1->save(); + $this->assertNull(Table9Query::create()->findRoot(), 'findRoot() returns null as long as no root node is defined'); + $t2 = new Table9(); + $t2->setLeftValue(1); + $t2->setRightValue(2); + $t2->save(); + $this->assertEquals(Table9Query::create()->findRoot(), $t2, 'findRoot() retrieves the root node'); + } + + public function testfindTree() + { + list($t1, $t2, $t3, $t4, $t5, $t6, $t7) = $this->initTree(); + $tree = Table9Query::create()->findTree(); + $coll = $this->buildCollection(array($t1, $t2, $t3, $t4, $t5, $t6, $t7)); + $this->assertEquals($coll, $tree, 'findTree() retrieves the whole tree, ordered by branch'); + } + + protected function buildCollection($arr) + { + $coll = new PropelObjectCollection(); + $coll->setData($arr); + $coll->setModel('Table9'); + + return $coll; + } + +} diff --git a/library/propel/test/testsuite/generator/behavior/nestedset/NestedSetBehaviorQueryBuilderModifierWithScopeTest.php b/library/propel/test/testsuite/generator/behavior/nestedset/NestedSetBehaviorQueryBuilderModifierWithScopeTest.php new file mode 100644 index 000000000..a7c283628 --- /dev/null +++ b/library/propel/test/testsuite/generator/behavior/nestedset/NestedSetBehaviorQueryBuilderModifierWithScopeTest.php @@ -0,0 +1,285 @@ +initTreeWithScope(); + /* Tree used for tests + Scope 1 + t1 + | \ + t2 t3 + | \ + t4 t5 + | \ + t6 t7 + Scope 2 + t8 + | \ + t9 t10 + */ + $objs = Table10Query::create() + ->treeRoots() + ->find(); + $coll = $this->buildCollection(array($t1, $t8)); + $this->assertEquals($coll, $objs, 'treeRoots() filters by roots'); + } + + public function testInTree() + { + list($t1, $t2, $t3, $t4, $t5, $t6, $t7, $t8, $t9, $t10) = $this->initTreeWithScope(); + /* Tree used for tests + Scope 1 + t1 + | \ + t2 t3 + | \ + t4 t5 + | \ + t6 t7 + Scope 2 + t8 + | \ + t9 t10 + */ + $tree = Table10Query::create() + ->inTree(1) + ->orderByBranch() + ->find(); + $coll = $this->buildCollection(array($t1, $t2, $t3, $t4, $t5, $t6, $t7)); + $this->assertEquals($coll, $tree, 'inTree() filters by node'); + $tree = Table10Query::create() + ->inTree(2) + ->orderByBranch() + ->find(); + $coll = $this->buildCollection(array($t8, $t9, $t10)); + $this->assertEquals($coll, $tree, 'inTree() filters by node'); + } + + public function testDescendantsOf() + { + list($t1, $t2, $t3, $t4, $t5, $t6, $t7, $t8, $t9, $t10) = $this->initTreeWithScope(); + /* Tree used for tests + Scope 1 + t1 + | \ + t2 t3 + | \ + t4 t5 + | \ + t6 t7 + Scope 2 + t8 + | \ + t9 t10 + */ + $objs = Table10Query::create() + ->descendantsOf($t1) + ->orderByBranch() + ->find(); + $coll = $this->buildCollection(array($t2, $t3, $t4, $t5, $t6, $t7)); + $this->assertEquals($coll, $objs, 'decendantsOf() filters by descendants of the same scope'); + } + + public function testBranchOf() + { + list($t1, $t2, $t3, $t4, $t5, $t6, $t7, $t8, $t9, $t10) = $this->initTreeWithScope(); + /* Tree used for tests + Scope 1 + t1 + | \ + t2 t3 + | \ + t4 t5 + | \ + t6 t7 + Scope 2 + t8 + | \ + t9 t10 + */ + $objs = Table10Query::create() + ->branchOf($t1) + ->orderByBranch() + ->find(); + $coll = $this->buildCollection(array($t1, $t2, $t3, $t4, $t5, $t6, $t7)); + $this->assertEquals($coll, $objs, 'branchOf() filters by branch of the same scope'); + + } + + public function testChildrenOf() + { + list($t1, $t2, $t3, $t4, $t5, $t6, $t7, $t8, $t9, $t10) = $this->initTreeWithScope(); + /* Tree used for tests + Scope 1 + t1 + | \ + t2 t3 + | \ + t4 t5 + | \ + t6 t7 + Scope 2 + t8 + | \ + t9 t10 + */ + $objs = Table10Query::create() + ->childrenOf($t1) + ->orderByBranch() + ->find(); + $coll = $this->buildCollection(array($t2, $t3)); + $this->assertEquals($coll, $objs, 'childrenOf() filters by children of the same scope'); + } + + public function testSiblingsOf() + { + list($t1, $t2, $t3, $t4, $t5, $t6, $t7, $t8, $t9, $t10) = $this->initTreeWithScope(); + /* Tree used for tests + Scope 1 + t1 + | \ + t2 t3 + | \ + t4 t5 + | \ + t6 t7 + Scope 2 + t8 + | \ + t9 t10 + */ + $desc = Table10Query::create() + ->siblingsOf($t3) + ->orderByBranch() + ->find(); + $coll = $this->buildCollection(array($t2)); + $this->assertEquals($coll, $desc, 'siblingsOf() returns filters by siblings of the same scope'); + } + + public function testAncestorsOf() + { + list($t1, $t2, $t3, $t4, $t5, $t6, $t7, $t8, $t9, $t10) = $this->initTreeWithScope(); + /* Tree used for tests + Scope 1 + t1 + | \ + t2 t3 + | \ + t4 t5 + | \ + t6 t7 + Scope 2 + t8 + | \ + t9 t10 + */ + $objs = Table10Query::create() + ->ancestorsOf($t5) + ->orderByBranch() + ->find(); + $coll = $this->buildCollection(array($t1, $t3), 'ancestorsOf() filters by ancestors of the same scope'); + } + + public function testRootsOf() + { + list($t1, $t2, $t3, $t4, $t5, $t6, $t7, $t8, $t9, $t10) = $this->initTreeWithScope(); + /* Tree used for tests + Scope 1 + t1 + | \ + t2 t3 + | \ + t4 t5 + | \ + t6 t7 + Scope 2 + t8 + | \ + t9 t10 + */ + $objs = Table10Query::create() + ->rootsOf($t5) + ->orderByBranch() + ->find(); + $coll = $this->buildCollection(array($t1, $t3, $t5), 'rootsOf() filters by ancestors of the same scope'); + } + + public function testFindRoot() + { + $this->assertTrue(method_exists('Table10Query', 'findRoot'), 'nested_set adds a findRoot() method'); + Table10Query::create()->deleteAll(); + $this->assertNull(Table10Query::create()->findRoot(1), 'findRoot() returns null as long as no root node is defined'); + list($t1, $t2, $t3, $t4, $t5, $t6, $t7, $t8, $t9, $t10) = $this->initTreeWithScope(); + /* Tree used for tests + Scope 1 + t1 + | \ + t2 t3 + | \ + t4 t5 + | \ + t6 t7 + Scope 2 + t8 + | \ + t9 t10 + */ + $this->assertEquals($t1, Table10Query::create()->findRoot(1), 'findRoot() returns a tree root'); + $this->assertEquals($t8, Table10Query::create()->findRoot(2), 'findRoot() returns a tree root'); + } + + public function testFindTree() + { + list($t1, $t2, $t3, $t4, $t5, $t6, $t7, $t8, $t9, $t10) = $this->initTreeWithScope(); + /* Tree used for tests + Scope 1 + t1 + | \ + t2 t3 + | \ + t4 t5 + | \ + t6 t7 + Scope 2 + t8 + | \ + t9 t10 + */ + $tree = Table10Query::create()->findTree(1); + $coll = $this->buildCollection(array($t1, $t2, $t3, $t4, $t5, $t6, $t7)); + $this->assertEquals($coll, $tree, 'findTree() retrieves the tree of a scope, ordered by branch'); + $tree = Table10Query::create()->findTree(2); + $coll = $this->buildCollection(array($t8, $t9, $t10)); + $this->assertEquals($coll, $tree, 'findTree() retrieves the tree of a scope, ordered by branch'); + } + + protected function buildCollection($arr) + { + $coll = new PropelObjectCollection(); + $coll->setData($arr); + $coll->setModel('Table10'); + + return $coll; + } + +} diff --git a/library/propel/test/testsuite/generator/behavior/nestedset/NestedSetBehaviorTest.php b/library/propel/test/testsuite/generator/behavior/nestedset/NestedSetBehaviorTest.php new file mode 100644 index 000000000..06594aac8 --- /dev/null +++ b/library/propel/test/testsuite/generator/behavior/nestedset/NestedSetBehaviorTest.php @@ -0,0 +1,48 @@ +assertEquals(count($table9->getColumns()), 5, 'nested_set adds three column by default'); + $this->assertTrue(method_exists('Table9', 'getTreeLeft'), 'nested_set adds a tree_left column by default'); + $this->assertTrue(method_exists('Table9', 'getLeftValue'), 'nested_set maps the left_value getter with the tree_left column'); + $this->assertTrue(method_exists('Table9', 'getTreeRight'), 'nested_set adds a tree_right column by default'); + $this->assertTrue(method_exists('Table9', 'getRightValue'), 'nested_set maps the right_value getter with the tree_right column'); + $this->assertTrue(method_exists('Table9', 'getTreeLevel'), 'nested_set adds a tree_level column by default'); + $this->assertTrue(method_exists('Table9', 'getLevel'), 'nested_set maps the level getter with the tree_level column'); + $this->assertFalse(method_exists('Table9', 'getTreeScope'), 'nested_set does not add a tree_scope column by default'); + $this->assertFalse(method_exists('Table9', 'getScopeValue'), 'nested_set does not map the scope_value getter with the tree_scope column by default'); + + } + + public function testParameters() + { + $table10 = Table10Peer::getTableMap(); + $this->assertEquals(count($table10->getColumns()), 6, 'nested_set does not add columns when they already exist'); + $this->assertTrue(method_exists('Table10', 'getLeftValue'), 'nested_set maps the left_value getter with the tree_left column'); + $this->assertTrue(method_exists('Table10', 'getRightValue'), 'nested_set maps the right_value getter with the tree_right column'); + $this->assertTrue(method_exists('Table10', 'getLevel'), 'nested_set maps the level getter with the tree_level column'); + $this->assertTrue(method_exists('Table10', 'getScopeValue'), 'nested_set maps the scope_value getter with the tree_scope column when the use_scope parameter is true'); + } + +} \ No newline at end of file diff --git a/library/propel/test/testsuite/generator/behavior/sluggable/SluggableBehaviorTest.php b/library/propel/test/testsuite/generator/behavior/sluggable/SluggableBehaviorTest.php new file mode 100644 index 000000000..a65bf3ea8 --- /dev/null +++ b/library/propel/test/testsuite/generator/behavior/sluggable/SluggableBehaviorTest.php @@ -0,0 +1,299 @@ +assertEquals(count($table13->getColumns()), 3, 'Sluggable adds one columns by default'); + $this->assertTrue(method_exists('Table13', 'getSlug'), 'Sluggable adds a slug column by default'); + $table14 = Table14Peer::getTableMap(); + $this->assertEquals(count($table14->getColumns()), 3, 'Sluggable does not add a column when it already exists'); + $this->assertTrue(method_exists('Table14', 'getUrl'), 'Sluggable allows customization of slug_column name'); + $this->assertTrue(method_exists('Table14', 'getSlug'), 'Sluggable adds a standard getter for the slug column'); + } + + public function testObjectGetter() + { + $this->assertTrue(method_exists('Table13', 'getSlug'), 'Sluggable adds a getter for the slug column'); + $t = new Table13(); + $t->setSlug('foo'); + $this->assertEquals('foo', $t->getSlug(), 'getSlug() returns the object slug'); + $this->assertTrue(method_exists('Table14', 'getSlug'), 'Sluggable adds a getter for the slug column, even if the column does not have the default name'); + $t = new Table14(); + $t->setUrl('foo'); + $this->assertEquals('foo', $t->getSlug(), 'getSlug() returns the object slug'); + } + + public function testObjectSetter() + { + $this->assertTrue(method_exists('Table13', 'setSlug'), 'Sluggable adds a setter for the slug column'); + $t = new Table13(); + $t->setSlug('foo'); + $this->assertEquals('foo', $t->getSlug(), 'setSlug() sets the object slug'); + $this->assertTrue(method_exists('Table14', 'setSlug'), 'Sluggable adds a setter for the slug column, even if the column does not have the default name'); + $t = new Table14(); + $t->setSlug('foo'); + $this->assertEquals('foo', $t->getUrl(), 'setSlug() sets the object slug'); + } + + public function testObjectCreateRawSlug() + { + $t = new TestableTable13(); + $this->assertEquals('n-a', $t->createRawSlug(), 'createRawSlug() returns an empty string for an empty object with no pattern'); + $t->setTitle('Hello, World'); + $this->assertEquals('hello-world', $t->createRawSlug(), 'createRawSlug() returns the cleaned up object string representation by default'); + + $t = new TestableTable14(); + $this->assertEquals('/foo/n-a/bar', $t->createRawSlug(), 'createRawSlug() returns a slug for an empty object with a pattern'); + $t->setTitle('Hello, World'); + $this->assertEquals('/foo/hello-world/bar', $t->createRawSlug(), 'createRawSlug() returns a slug based on a pattern'); + } + + public static function cleanupSlugProvider() + { + return array( + array('', 'n-a'), + array('foo', 'foo'), + array('foo bar', 'foo-bar'), + array('foo bar', 'foo-bar'), + array('FoO', 'foo'), + array('fôo', 'foo'), + array(' foo ', 'foo'), + array('f/o:o', 'f-o-o'), + array('foo1', 'foo1'), + ); + } + + /** + * @dataProvider cleanupSlugProvider + */ + public function testObjectCleanupSlugPart($in, $out) + { + $t = new TestableTable13(); + $this->assertEquals($out, $t->cleanupSlugPart($in), 'cleanupSlugPart() cleans up the slug part'); + } + + public static function limitSlugSizeProvider() + { + return array( + array('123', '123'), + array(str_repeat('*', 80), str_repeat('*', 80)), + array(str_repeat('*', 97), str_repeat('*', 97)), + array(str_repeat('*', 98), str_repeat('*', 97)), + array(str_repeat('*', 99), str_repeat('*', 97)), + array(str_repeat('*', 100), str_repeat('*', 97)), + array(str_repeat('*', 150), str_repeat('*', 97)), + ); + } + + /** + * @dataProvider limitSlugSizeProvider + */ + public function testObjectLimitSlugSize($in, $out) + { + $t = new TestableTable14(); + $this->assertEquals($out, $t->limitSlugSize($in), 'limitSlugsize() limits the slug size'); + } + + public function testObjectMakeSlugUnique() + { + Table13Query::create()->deleteAll(); + $t = new TestableTable13(); + $this->assertEquals('', $t->makeSlugUnique(''), 'makeSlugUnique() returns the input slug when the input is empty'); + $this->assertEquals('foo', $t->makeSlugUnique('foo'), 'makeSlugUnique() returns the input slug when the table is empty'); + $t->setSlug('foo'); + $t->save(); + $t = new TestableTable13(); + $this->assertEquals('bar', $t->makeSlugUnique('bar'), 'makeSlugUnique() returns the input slug when the table does not contain a similar slug'); + $t->save(); + $t = new TestableTable13(); + $this->assertEquals('foo-1', $t->makeSlugUnique('foo'), 'makeSlugUnique() returns an incremented input when it already exists'); + $t->setSlug('foo-1'); + $t->save(); + $t = new TestableTable13(); + $this->assertEquals('foo-2', $t->makeSlugUnique('foo'), 'makeSlugUnique() returns an incremented input when it already exists'); + } + + public function testObjectCreateSlug() + { + Table13Query::create()->deleteAll(); + $t = new TestableTable13(); + $this->assertEquals('n-a', $t->createSlug(), 'createSlug() returns n-a for an empty object'); + $t->setTitle('Hello, World!'); + $this->assertEquals('hello-world', $t->createSlug(), 'createSlug() returns a cleaned up slug'); + $t->setSlug('hello-world'); + $t->save(); + $t = new TestableTable13(); + $t->setTitle('Hello; wOrld'); + $this->assertEquals('hello-world-1', $t->createSlug(), 'createSlug() returns a unique slug'); + + Table14Query::create()->deleteAll(); + $t = new TestableTable14(); + $this->assertEquals('/foo/n-a/bar', $t->createSlug(), 'createSlug() returns a slug for an empty object with a pattern'); + $t->setTitle('Hello, World!'); + $this->assertEquals('/foo/hello-world/bar', $t->createSlug(), 'createSlug() returns a cleaned up slug'); + $t->setSlug('/foo/hello-world/bar'); + $t->save(); + $t = new TestableTable14(); + $t->setTitle('Hello; wOrld:'); + $this->assertEquals('/foo/hello-world/bar/1', $t->createSlug(), 'createSlug() returns a unique slug'); + } + + public function testObjectPreSave() + { + Table14Query::create()->deleteAll(); + $t = new Table14(); + $t->save(); + $this->assertEquals('/foo/n-a/bar', $t->getSlug(), 'preSave() sets a default slug for empty objects'); + $t = new Table14(); + $t->setTitle('Hello, World'); + $t->save(); + $this->assertEquals('/foo/hello-world/bar', $t->getSlug(), 'preSave() sets a cleanued up slug for objects'); + $t = new Table14(); + $t->setTitle('Hello, World'); + $t->save(); + $this->assertEquals('/foo/hello-world/bar/1', $t->getSlug(), 'preSave() sets a unique slug for objects'); + $t = new Table14(); + $t->setTitle('Hello, World'); + $t->setSlug('/foo/custom/bar'); + $t->save(); + $this->assertEquals('/foo/custom/bar', $t->getSlug(), 'preSave() uses the given slug if it exists'); + $t = new Table14(); + $t->setTitle('Hello, World'); + $t->setSlug('/foo/custom/bar'); + $t->save(); + $this->assertEquals('/foo/custom/bar/1', $t->getSlug(), 'preSave() uses the given slug if it exists and makes it unique'); + } + + public function testObjectSlugLifecycle() + { + Table13Query::create()->deleteAll(); + $t = new Table13(); + $t->setTitle('Hello, World'); + $t->save(); + $this->assertEquals('hello-world', $t->getSlug(), 'preSave() creates a slug for new objects'); + $t->setSlug('hello-bar'); + $t->save(); + $this->assertEquals('hello-bar', $t->getSlug(), 'setSlug() allows to override default slug'); + $t->setSlug(''); + $t->save(); + $this->assertEquals('hello-world', $t->getSlug(), 'setSlug(null) relaunches the slug generation'); + + Table14Query::create()->deleteAll(); + $t = new Table14(); + $t->setTitle('Hello, World2'); + $t->setSlug('hello-bar2'); + $t->save(); + $this->assertEquals('hello-bar2', $t->getSlug(), 'setSlug() allows to override default slug, even before save'); + $t->setSlug(''); + $t->save(); + $this->assertEquals('/foo/hello-world2/bar', $t->getSlug(), 'setSlug(null) relaunches the slug generation'); + } + + public function testObjectSlugAutoUpdate() + { + Table13Query::create()->deleteAll(); + $t = new Table13(); + $t->setTitle('Hello, World'); + $t->save(); + $this->assertEquals('hello-world', $t->getSlug(), 'preSave() creates a slug for new objects'); + $t->setTitle('Hello, My World'); + $t->save(); + $this->assertEquals('hello-my-world', $t->getSlug(), 'preSave() autoupdates slug on object change'); + $t->setTitle('Hello, My Whole New World'); + $t->setSlug('hello-bar'); + $t->save(); + $this->assertEquals('hello-bar', $t->getSlug(), 'preSave() does not autoupdate slug when it was set by the user'); + } + + public function testObjectSlugAutoUpdatePermanent() + { + Table14Query::create()->deleteAll(); + $t = new Table14(); + $t->setTitle('Hello, World'); + $t->save(); + $this->assertEquals('/foo/hello-world/bar', $t->getSlug(), 'preSave() creates a slug for new objects'); + $t->setTitle('Hello, My World'); + $t->save(); + $this->assertEquals('/foo/hello-world/bar', $t->getSlug(), 'preSave() does not autoupdate slug on object change for permanent slugs'); + $t->setSlug('hello-bar'); + $t->save(); + $this->assertEquals('hello-bar', $t->getSlug(), 'setSlug() still works for permanent slugs'); + } + + public function testQueryFindOneBySlug() + { + $this->assertTrue(method_exists('Table13Query', 'findOneBySlug'), 'The generated query provides a findOneBySlug() method'); + $this->assertTrue(method_exists('Table14Query', 'findOneBySlug'), 'The generated query provides a findOneBySlug() method even if the slug column doesnt have the default name'); + + Table14Query::create()->deleteAll(); + $t1 = new Table14(); + $t1->setTitle('Hello, World'); + $t1->save(); + $t2 = new Table14(); + $t2->setTitle('Hello, Cruel World'); + $t2->save(); + $t = Table14Query::create()->findOneBySlug('/foo/hello-world/bar'); + $this->assertEquals($t1, $t, 'findOneBySlug() returns a single object matching the slug'); + } +} + +class TestableTable13 extends Table13 +{ + public function createSlug() + { + return parent::createSlug(); + } + + public function createRawSlug() + { + return parent::createRawSlug(); + } + + public static function cleanupSlugPart($slug, $separator = '-') + { + return parent::cleanupSlugPart($slug, $separator); + } + + public function makeSlugUnique($slug, $separator = '-', $increment = 0) + { + return parent::makeSlugUnique($slug, $separator, $increment); + } +} + +class TestableTable14 extends Table14 +{ + public function createSlug() + { + return parent::createSlug(); + } + + public function createRawSlug() + { + return parent::createRawSlug(); + } + + public static function limitSlugSize($slug, $incrementReservedSpace = 3) + { + return parent::limitSlugSize($slug, $incrementReservedSpace); + } +} \ No newline at end of file diff --git a/library/propel/test/testsuite/generator/behavior/sortable/SortableBehaviorObjectBuilderModifierTest.php b/library/propel/test/testsuite/generator/behavior/sortable/SortableBehaviorObjectBuilderModifierTest.php new file mode 100644 index 000000000..125e03a7f --- /dev/null +++ b/library/propel/test/testsuite/generator/behavior/sortable/SortableBehaviorObjectBuilderModifierTest.php @@ -0,0 +1,279 @@ +populateTable11(); + } + + public function testPreInsert() + { + Table11Peer::doDeleteAll(); + $t1 = new Table11(); + $t1->save(); + $this->assertEquals($t1->getRank(), 1, 'Sortable inserts new line in first position if no row present'); + $t2 = new Table11(); + $t2->setTitle('row2'); + $t2->save(); + $this->assertEquals($t2->getRank(), 2, 'Sortable inserts new line in last position'); + } + + public function testPreDelete() + { + $max = Table11Peer::getMaxRank(); + $t3 = Table11Peer::retrieveByRank(3); + $t3->delete(); + $this->assertEquals($max - 1, Table11Peer::getMaxRank(), 'Sortable rearrange subsequent rows on delete'); + $c = new Criteria(); + $c->add(Table11Peer::TITLE, 'row4'); + $t4 = Table11Peer::doSelectOne($c); + $this->assertEquals(3, $t4->getRank(), 'Sortable rearrange subsequent rows on delete'); + } + + public function testIsFirst() + { + $first = Table11Peer::retrieveByRank(1); + $middle = Table11Peer::retrieveByRank(2); + $last = Table11Peer::retrieveByRank(4); + $this->assertTrue($first->isFirst(), 'isFirst() returns true for the first in the rank'); + $this->assertFalse($middle->isFirst(), 'isFirst() returns false for a middle rank'); + $this->assertFalse($last->isFirst(), 'isFirst() returns false for the last in the rank'); + } + + public function testIsLast() + { + $first = Table11Peer::retrieveByRank(1); + $middle = Table11Peer::retrieveByRank(2); + $last = Table11Peer::retrieveByRank(4); + $this->assertFalse($first->isLast(), 'isLast() returns false for the first in the rank'); + $this->assertFalse($middle->isLast(), 'isLast() returns false for a middle rank'); + $this->assertTrue($last->isLast(), 'isLast() returns true for the last in the rank'); + } + + public function testGetNext() + { + $t = Table11Peer::retrieveByRank(3); + $this->assertEquals(4, $t->getNext()->getRank(), 'getNext() returns the next object in rank'); + + $t = Table11Peer::retrieveByRank(4); + $this->assertNull($t->getNext(), 'getNext() returns null for the last object'); + } + + public function testGetPrevious() + { + $t = Table11Peer::retrieveByRank(3); + $this->assertEquals(2, $t->getPrevious()->getRank(), 'getPrevious() returns the previous object in rank'); + + $t = Table11Peer::retrieveByRank(1); + $this->assertNull($t->getPrevious(), 'getPrevious() returns null for the first object'); + } + + public function testInsertAtRank() + { + $t = new Table11(); + $t->setTitle('new'); + $t->insertAtRank(2); + $this->assertEquals(2, $t->getRank(), 'insertAtRank() sets the position'); + $this->assertTrue($t->isNew(), 'insertAtRank() doesn\'t save the object'); + $t->save(); + $expected = array(1 => 'row1', 2 => 'new', 3 => 'row2', 4 => 'row3', 5 => 'row4'); + $this->assertEquals($expected, $this->getFixturesArray(), 'insertAtRank() shifts the entire suite'); + } + + public function testInsertAtMaxRankPlusOne() + { + $t = new Table11(); + $t->setTitle('new'); + $t->insertAtRank(5); + $this->assertEquals(5, $t->getRank(), 'insertAtRank() sets the position'); + $t->save(); + $expected = array(1 => 'row1', 2 => 'row2', 3 => 'row3', 4 => 'row4', 5 => 'new'); + $this->assertEquals($expected, $this->getFixturesArray(), 'insertAtRank() can insert an object at the end of the list'); + } + + /** + * @expectedException PropelException + */ + public function testInsertAtNegativeRank() + { + $t = new Table11(); + $t->insertAtRank(0); + } + + /** + * @expectedException PropelException + */ + public function testInsertAtOverMaxRank() + { + $t = new Table11(); + $t->insertAtRank(6); + } + + public function testInsertAtBottom() + { + $t = new Table11(); + $t->setTitle('new'); + $t->insertAtBottom(); + $this->assertEquals(5, $t->getRank(), 'insertAtBottom() sets the position to the last'); + $this->assertTrue($t->isNew(), 'insertAtBottom() doesn\'t save the object'); + $t->save(); + $expected = array(1 => 'row1', 2 => 'row2', 3 => 'row3', 4 => 'row4', 5 => 'new'); + $this->assertEquals($expected, $this->getFixturesArray(), 'insertAtBottom() does not shift the entire suite'); + } + + public function testInsertAtTop() + { + $t = new Table11(); + $t->setTitle('new'); + $t->insertAtTop(); + $this->assertEquals(1, $t->getRank(), 'insertAtTop() sets the position to 1'); + $this->assertTrue($t->isNew(), 'insertAtTop() doesn\'t save the object'); + $t->save(); + $expected = array(1 => 'new', 2 => 'row1', 3 => 'row2', 4 => 'row3', 5 => 'row4'); + $this->assertEquals($expected, $this->getFixturesArray(), 'insertAtTop() shifts the entire suite'); + } + + public function testMoveToRank() + { + $t2 = Table11Peer::retrieveByRank(2); + $t2->moveToRank(3); + $expected = array(1 => 'row1', 2 => 'row3', 3 => 'row2', 4 => 'row4'); + $this->assertEquals($expected, $this->getFixturesArray(), 'moveToRank() can move up'); + $t2->moveToRank(1); + $expected = array(1 => 'row2', 2 => 'row1', 3 => 'row3', 4 => 'row4'); + $this->assertEquals($expected, $this->getFixturesArray(), 'moveToRank() can move to the first rank'); + $t2->moveToRank(4); + $expected = array(1 => 'row1', 2 => 'row3', 3 => 'row4', 4 => 'row2'); + $this->assertEquals($expected, $this->getFixturesArray(), 'moveToRank() can move to the last rank'); + $t2->moveToRank(2); + $expected = array(1 => 'row1', 2 => 'row2', 3 => 'row3', 4 => 'row4'); + $this->assertEquals($expected, $this->getFixturesArray(), 'moveToRank() can move down'); + } + + /** + * @expectedException PropelException + */ + public function testMoveToNewObject() + { + $t = new Table11(); + $t->moveToRank(2); + } + + /** + * @expectedException PropelException + */ + public function testMoveToNegativeRank() + { + $t = Table11Peer::retrieveByRank(2); + $t->moveToRank(0); + } + + /** + * @expectedException PropelException + */ + public function testMoveToOverMaxRank() + { + $t = Table11Peer::retrieveByRank(2); + $t->moveToRank(5); + } + + public function testSwapWith() + { + $t2 = Table11Peer::retrieveByRank(2); + $t4 = Table11Peer::retrieveByRank(4); + $t2->swapWith($t4); + $expected = array(1 => 'row1', 2 => 'row4', 3 => 'row3', 4 => 'row2'); + $this->assertEquals($expected, $this->getFixturesArray(), 'swapWith() swaps ranks of the two objects and leaves the other ranks unchanged'); + } + + public function testMoveUp() + { + $t3 = Table11Peer::retrieveByRank(3); + $res = $t3->moveUp(); + $this->assertEquals($t3, $res, 'moveUp() returns the current object'); + $expected = array(1 => 'row1', 2 => 'row3', 3 => 'row2', 4 => 'row4'); + $this->assertEquals($expected, $this->getFixturesArray(), 'moveUp() swaps ranks with the object of higher rank'); + $t3->moveUp(); + $expected = array(1 => 'row3', 2 => 'row1', 3 => 'row2', 4 => 'row4'); + $this->assertEquals($expected, $this->getFixturesArray(), 'moveUp() swaps ranks with the object of higher rank'); + $res = $t3->moveUp(); + $expected = array(1 => 'row3', 2 => 'row1', 3 => 'row2', 4 => 'row4'); + $this->assertEquals($expected, $this->getFixturesArray(), 'moveUp() changes nothing when called on the object at the top'); + } + + public function testMoveDown() + { + $t2 = Table11Peer::retrieveByRank(2); + $res = $t2->moveDown(); + $this->assertEquals($t2, $res, 'moveDown() returns the current object'); + $expected = array(1 => 'row1', 2 => 'row3', 3 => 'row2', 4 => 'row4'); + $this->assertEquals($expected, $this->getFixturesArray(), 'moveDown() swaps ranks with the object of lower rank'); + $t2->moveDown(); + $expected = array(1 => 'row1', 2 => 'row3', 3 => 'row4', 4 => 'row2'); + $this->assertEquals($expected, $this->getFixturesArray(), 'moveDown() swaps ranks with the object of lower rank'); + $res = $t2->moveDown(); + $expected = array(1 => 'row1', 2 => 'row3', 3 => 'row4', 4 => 'row2'); + $this->assertEquals($expected, $this->getFixturesArray(), 'moveDown() changes nothing when called on the object at the bottom'); + } + + public function testMoveToTop() + { + $t3 = Table11Peer::retrieveByRank(3); + $res = $t3->moveToTop(); + $this->assertEquals($t3, $res, 'moveToTop() returns the current oobject'); + $expected = array(1 => 'row3', 2 => 'row1', 3 => 'row2', 4 => 'row4'); + $this->assertEquals($expected, $this->getFixturesArray(), 'moveToTop() moves to the top'); + $res = $t3->moveToTop(); + $expected = array(1 => 'row3', 2 => 'row1', 3 => 'row2', 4 => 'row4'); + $this->assertEquals($expected, $this->getFixturesArray(), 'moveToTop() changes nothing when called on the top node'); + } + + public function testMoveToBottom() + { + $t2 = Table11Peer::retrieveByRank(2); + $res = $t2->moveToBottom(); + $this->assertEquals($t2, $res, 'moveToBottom() returns the current object'); + $expected = array(1 => 'row1', 2 => 'row3', 3 => 'row4', 4 => 'row2'); + $this->assertEquals($expected, $this->getFixturesArray(), 'moveToBottom() moves to the bottom'); + $res = $t2->moveToBottom(); + $this->assertFalse($res, 'moveToBottom() returns false when called on the bottom node'); + $expected = array(1 => 'row1', 2 => 'row3', 3 => 'row4', 4 => 'row2'); + $this->assertEquals($expected, $this->getFixturesArray(), 'moveToBottom() changes nothing when called on the bottom node'); + } + + public function testRemoveFromList() + { + $t2 = Table11Peer::retrieveByRank(2); + $res = $t2->removeFromList(); + $this->assertTrue($res instanceof Table11, 'removeFromList() returns the current object'); + $this->assertNull($res->getRank(), 'removeFromList() resets the object\'s rank'); + Table11Peer::clearInstancePool(); + $expected = array(1 => 'row1', 2 => 'row2', 3 => 'row3', 4 => 'row4'); + $this->assertEquals($expected, $this->getFixturesArray(), 'removeFromList() does not change the list until the object is saved'); + $t2->save(); + Table11Peer::clearInstancePool(); + $expected = array(null => 'row2', 1 => 'row1', 2 => 'row3', 3 => 'row4'); + $this->assertEquals($expected, $this->getFixturesArray(), 'removeFromList() changes the list once the object is saved'); + } + +} \ No newline at end of file diff --git a/library/propel/test/testsuite/generator/behavior/sortable/SortableBehaviorObjectBuilderModifierWithScopeTest.php b/library/propel/test/testsuite/generator/behavior/sortable/SortableBehaviorObjectBuilderModifierWithScopeTest.php new file mode 100644 index 000000000..22261880a --- /dev/null +++ b/library/propel/test/testsuite/generator/behavior/sortable/SortableBehaviorObjectBuilderModifierWithScopeTest.php @@ -0,0 +1,335 @@ +populateTable12(); + } + + public function testPreInsert() + { + Table12Peer::doDeleteAll(); + $t1 = new Table12(); + $t1->setScopeValue(1); + $t1->save(); + $this->assertEquals($t1->getRank(), 1, 'Sortable inserts new line in first position if no row present'); + $t2 = new Table12(); + $t2->setScopeValue(1); + $t2->save(); + $this->assertEquals($t2->getRank(), 2, 'Sortable inserts new line in last position'); + $t2 = new Table12(); + $t2->setScopeValue(2); + $t2->save(); + $this->assertEquals($t2->getRank(), 1, 'Sortable inserts new line in last position'); + } + + public function testPreDelete() + { + $max = Table12Peer::getMaxRank(1); + $t3 = Table12Peer::retrieveByRank(3, 1); + $t3->delete(); + $this->assertEquals($max - 1, Table12Peer::getMaxRank(1), 'Sortable rearrange subsequent rows on delete'); + $c = new Criteria(); + $c->add(Table12Peer::TITLE, 'row4'); + $t4 = Table12Peer::doSelectOne($c); + $this->assertEquals(3, $t4->getRank(), 'Sortable rearrange subsequent rows on delete'); + $expected = array(1 => 'row5', 2 => 'row6'); + $this->assertEquals($expected, $this->getFixturesArrayWithScope(2), 'delete() leaves other suites unchanged'); + } + + public function testIsFirst() + { + $first = Table12Peer::retrieveByRank(1, 1); + $middle = Table12Peer::retrieveByRank(2, 1); + $last = Table12Peer::retrieveByRank(4, 1); + $this->assertTrue($first->isFirst(), 'isFirst() returns true for the first in the rank'); + $this->assertFalse($middle->isFirst(), 'isFirst() returns false for a middle rank'); + $this->assertFalse($last->isFirst(), 'isFirst() returns false for the last in the rank'); + $first = Table12Peer::retrieveByRank(1, 2); + $last = Table12Peer::retrieveByRank(2, 2); + $this->assertTrue($first->isFirst(), 'isFirst() returns true for the first in the rank'); + $this->assertFalse($last->isFirst(), 'isFirst() returns false for the last in the rank'); + } + + public function testIsLast() + { + $first = Table12Peer::retrieveByRank(1, 1); + $middle = Table12Peer::retrieveByRank(2, 1); + $last = Table12Peer::retrieveByRank(4, 1); + $this->assertFalse($first->isLast(), 'isLast() returns false for the first in the rank'); + $this->assertFalse($middle->isLast(), 'isLast() returns false for a middle rank'); + $this->assertTrue($last->isLast(), 'isLast() returns true for the last in the rank'); + $first = Table12Peer::retrieveByRank(1, 2); + $last = Table12Peer::retrieveByRank(2, 2); + $this->assertFalse($first->isLast(), 'isLast() returns false for the first in the rank'); + $this->assertTrue($last->isLast(), 'isLast() returns true for the last in the rank'); + } + + public function testGetNext() + { + $t = Table12Peer::retrieveByRank(1, 1); + $this->assertEquals('row2', $t->getNext()->getTitle(), 'getNext() returns the next object in rank in the same suite'); + $t = Table12Peer::retrieveByRank(1, 2); + $this->assertEquals('row6', $t->getNext()->getTitle(), 'getNext() returns the next object in rank in the same suite'); + + $t = Table12Peer::retrieveByRank(3, 1); + $this->assertEquals(4, $t->getNext()->getRank(), 'getNext() returns the next object in rank'); + + $t = Table12Peer::retrieveByRank(4, 1); + $this->assertNull($t->getNext(), 'getNext() returns null for the last object'); + } + + public function testGetPrevious() + { + $t = Table12Peer::retrieveByRank(2, 1); + $this->assertEquals('row1', $t->getPrevious()->getTitle(), 'getPrevious() returns the previous object in rank in the same suite'); + $t = Table12Peer::retrieveByRank(2, 2); + $this->assertEquals('row5', $t->getPrevious()->getTitle(), 'getPrevious() returns the previous object in rank in the same suite'); + + $t = Table12Peer::retrieveByRank(3, 1); + $this->assertEquals(2, $t->getPrevious()->getRank(), 'getPrevious() returns the previous object in rank'); + + $t = Table12Peer::retrieveByRank(1, 1); + $this->assertNull($t->getPrevious(), 'getPrevious() returns null for the first object'); + } + + public function testInsertAtRank() + { + $t = new Table12(); + $t->setTitle('new'); + $t->setScopeValue(1); + $t->insertAtRank(2); + $this->assertEquals(2, $t->getRank(), 'insertAtRank() sets the position'); + $this->assertTrue($t->isNew(), 'insertAtTop() doesn\'t save the object'); + $t->save(); + $expected = array(1 => 'row1', 2 => 'new', 3 => 'row2', 4 => 'row3', 5 => 'row4'); + $this->assertEquals($expected, $this->getFixturesArrayWithScope(1), 'insertAtRank() shifts the entire suite'); + $expected = array(1 => 'row5', 2 => 'row6'); + $this->assertEquals($expected, $this->getFixturesArrayWithScope(2), 'insertAtRank() leaves other suites unchanged'); + } + + /** + * @expectedException PropelException + */ + public function testInsertAtNegativeRank() + { + $t = new Table12(); + $t->setScopeValue(1); + $t->insertAtRank(0); + } + + /** + * @expectedException PropelException + */ + public function testInsertAtOverMaxRank() + { + $t = new Table12(); + $t->setScopeValue(1); + $t->insertAtRank(6); + } + + /** + * @expectedException PropelException + */ + public function testInsertAtNoScope() + { + $t = new Table12(); + $t->insertAtRank(3); + } + + public function testInsertAtBottom() + { + $t = new Table12(); + $t->setTitle('new'); + $t->setScopeValue(1); + $t->insertAtBottom(); + $this->assertEquals(5, $t->getRank(), 'insertAtBottom() sets the position to the last'); + $this->assertTrue($t->isNew(), 'insertAtTop() doesn\'t save the object'); + $t->save(); + $expected = array(1 => 'row1', 2 => 'row2', 3 => 'row3', 4 => 'row4', 5 => 'new'); + $this->assertEquals($expected, $this->getFixturesArrayWithScope(1), 'insertAtBottom() does not shift the entire suite'); + $expected = array(1 => 'row5', 2 => 'row6'); + $this->assertEquals($expected, $this->getFixturesArrayWithScope(2), 'insertAtBottom() leaves other suites unchanged'); + } + + /** + * @expectedException PropelException + */ + public function testInsertAtBottomNoScope() + { + $t = new Table12(); + $t->insertAtBottom(); + } + + public function testInsertAtTop() + { + $t = new Table12(); + $t->setTitle('new'); + $t->setScopeValue(1); + $t->insertAtTop(); + $this->assertEquals(1, $t->getRank(), 'insertAtTop() sets the position to 1'); + $this->assertTrue($t->isNew(), 'insertAtTop() doesn\'t save the object'); + $t->save(); + $expected = array(1 => 'new', 2 => 'row1', 3 => 'row2', 4 => 'row3', 5 => 'row4'); + $this->assertEquals($expected, $this->getFixturesArrayWithScope(1), 'insertAtTop() shifts the entire suite'); + $expected = array(1 => 'row5', 2 => 'row6'); + $this->assertEquals($expected, $this->getFixturesArrayWithScope(2), 'insertAtTop() leaves other suites unchanged'); + } + + public function testMoveToRank() + { + $t2 = Table12Peer::retrieveByRank(2, 1); + $t2->moveToRank(3); + $expected = array(1 => 'row1', 2 => 'row3', 3 => 'row2', 4 => 'row4'); + $this->assertEquals($expected, $this->getFixturesArrayWithScope(1), 'moveToRank() can move up'); + $expected = array(1 => 'row5', 2 => 'row6'); + $this->assertEquals($expected, $this->getFixturesArrayWithScope(2), 'moveToRank() leaves other suites unchanged'); + $t2->moveToRank(1); + $expected = array(1 => 'row2', 2 => 'row1', 3 => 'row3', 4 => 'row4'); + $this->assertEquals($expected, $this->getFixturesArrayWithScope(1), 'moveToRank() can move to the first rank'); + $t2->moveToRank(4); + $expected = array(1 => 'row1', 2 => 'row3', 3 => 'row4', 4 => 'row2'); + $this->assertEquals($expected, $this->getFixturesArrayWithScope(1), 'moveToRank() can move to the last rank'); + $t2->moveToRank(2); + $expected = array(1 => 'row1', 2 => 'row2', 3 => 'row3', 4 => 'row4'); + $this->assertEquals($expected, $this->getFixturesArrayWithScope(1), 'moveToRank() can move down'); + } + + /** + * @expectedException PropelException + */ + public function testMoveToNewObject() + { + $t = new Table12(); + $t->moveToRank(2); + } + + /** + * @expectedException PropelException + */ + public function testMoveToNegativeRank() + { + $t = Table12Peer::retrieveByRank(2, 1); + $t->moveToRank(0); + } + + /** + * @expectedException PropelException + */ + public function testMoveToOverMaxRank() + { + $t = Table12Peer::retrieveByRank(2, 1); + $t->moveToRank(5); + } + + public function testSwapWith() + { + $t2 = Table12Peer::retrieveByRank(2, 1); + $t4 = Table12Peer::retrieveByRank(4, 1); + $t2->swapWith($t4); + $expected = array(1 => 'row1', 2 => 'row4', 3 => 'row3', 4 => 'row2'); + $this->assertEquals($expected, $this->getFixturesArrayWithScope(1), 'swapWith() swaps ranks of the two objects and leaves the other ranks unchanged'); + $expected = array(1 => 'row5', 2 => 'row6'); + $this->assertEquals($expected, $this->getFixturesArrayWithScope(2), 'swapWith() leaves other suites unchanged'); + } + + public function testMoveUp() + { + $t3 = Table12Peer::retrieveByRank(3, 1); + $res = $t3->moveUp(); + $this->assertEquals($t3, $res, 'moveUp() returns the current object'); + $expected = array(1 => 'row1', 2 => 'row3', 3 => 'row2', 4 => 'row4'); + $this->assertEquals($expected, $this->getFixturesArrayWithScope(1), 'moveUp() swaps ranks with the object of higher rank'); + $expected = array(1 => 'row5', 2 => 'row6'); + $this->assertEquals($expected, $this->getFixturesArrayWithScope(2), 'moveUp() leaves other suites unchanged'); + $t3->moveUp(); + $expected = array(1 => 'row3', 2 => 'row1', 3 => 'row2', 4 => 'row4'); + $this->assertEquals($expected, $this->getFixturesArrayWithScope(1), 'moveUp() swaps ranks with the object of higher rank'); + $res = $t3->moveUp(); + $expected = array(1 => 'row3', 2 => 'row1', 3 => 'row2', 4 => 'row4'); + $this->assertEquals($expected, $this->getFixturesArrayWithScope(1), 'moveUp() changes nothing when called on the object at the top'); + } + + public function testMoveDown() + { + $t2 = Table12Peer::retrieveByRank(2, 1); + $res = $t2->moveDown(); + $this->assertEquals($t2, $res, 'moveDown() returns the current object'); + $expected = array(1 => 'row1', 2 => 'row3', 3 => 'row2', 4 => 'row4'); + $this->assertEquals($expected, $this->getFixturesArrayWithScope(1), 'moveDown() swaps ranks with the object of lower rank'); + $expected = array(1 => 'row5', 2 => 'row6'); + $this->assertEquals($expected, $this->getFixturesArrayWithScope(2), 'moveDown() leaves other suites unchanged'); + $t2->moveDown(); + $expected = array(1 => 'row1', 2 => 'row3', 3 => 'row4', 4 => 'row2'); + $this->assertEquals($expected, $this->getFixturesArrayWithScope(1), 'moveDown() swaps ranks with the object of lower rank'); + $res = $t2->moveDown(); + $expected = array(1 => 'row1', 2 => 'row3', 3 => 'row4', 4 => 'row2'); + $this->assertEquals($expected, $this->getFixturesArrayWithScope(1), 'moveDown() changes nothing when called on the object at the bottom'); + } + + public function testMoveToTop() + { + $t3 = Table12Peer::retrieveByRank(3, 1); + $res = $t3->moveToTop(); + $this->assertEquals($t3, $res, 'moveToTop() returns the current object'); + $expected = array(1 => 'row3', 2 => 'row1', 3 => 'row2', 4 => 'row4'); + $this->assertEquals($expected, $this->getFixturesArrayWithScope(1), 'moveToTop() moves to the top'); + $expected = array(1 => 'row5', 2 => 'row6'); + $this->assertEquals($expected, $this->getFixturesArrayWithScope(2), 'moveToTop() leaves other suites unchanged'); + $res = $t3->moveToTop(); + $expected = array(1 => 'row3', 2 => 'row1', 3 => 'row2', 4 => 'row4'); + $this->assertEquals($expected, $this->getFixturesArrayWithScope(1), 'moveToTop() changes nothing when called on the top node'); + } + + public function testMoveToBottom() + { + $t2 = Table12Peer::retrieveByRank(2, 1); + $res = $t2->moveToBottom(); + $this->assertEquals($t2, $res, 'moveToBottom() returns the current object'); + $expected = array(1 => 'row1', 2 => 'row3', 3 => 'row4', 4 => 'row2'); + $this->assertEquals($expected, $this->getFixturesArrayWithScope(1), 'moveToBottom() moves to the bottom'); + $expected = array(1 => 'row5', 2 => 'row6'); + $this->assertEquals($expected, $this->getFixturesArrayWithScope(2), 'moveToBottom() leaves other suites unchanged'); + $res = $t2->moveToBottom(); + $expected = array(1 => 'row1', 2 => 'row3', 3 => 'row4', 4 => 'row2'); + $this->assertEquals($expected, $this->getFixturesArrayWithScope(1), 'moveToBottom() changes nothing when called on the bottom node'); + } + + public function testRemoveFromList() + { + $t2 = Table12Peer::retrieveByRank(2, 1); + $res = $t2->removeFromList(); + $this->assertTrue($res instanceof Table12, 'removeFromList() returns the current object'); + $this->assertNull($res->getRank(), 'removeFromList() resets the object\'s rank'); + Table12Peer::clearInstancePool(); + $expected = array(1 => 'row1', 2 => 'row2', 3 => 'row3', 4 => 'row4'); + $this->assertEquals($expected, $this->getFixturesArrayWithScope(1), 'removeFromList() does not change the list until the object is saved'); + $t2->save(); + Table12Peer::clearInstancePool(); + $expected = array(1 => 'row1', 2 => 'row3', 3 => 'row4'); + $this->assertEquals($expected, $this->getFixturesArrayWithScope(1), 'removeFromList() changes the list once the object is saved'); + $expected = array(1 => 'row5', 2 => 'row6'); + $this->assertEquals($expected, $this->getFixturesArrayWithScope(2), 'removeFromList() leaves other suites unchanged'); + } + +} \ No newline at end of file diff --git a/library/propel/test/testsuite/generator/behavior/sortable/SortableBehaviorPeerBuilderModifierTest.php b/library/propel/test/testsuite/generator/behavior/sortable/SortableBehaviorPeerBuilderModifierTest.php new file mode 100644 index 000000000..3ffb26b7e --- /dev/null +++ b/library/propel/test/testsuite/generator/behavior/sortable/SortableBehaviorPeerBuilderModifierTest.php @@ -0,0 +1,83 @@ +populateTable11(); + } + + public function testStaticAttributes() + { + $this->assertEquals(Table11Peer::RANK_COL, 'table11.SORTABLE_RANK'); + } + + public function testGetMaxRank() + { + $this->assertEquals(4, Table11Peer::getMaxRank(), 'getMaxRank() returns the maximum rank'); + $t4 = Table11Peer::retrieveByRank(4); + $t4->delete(); + $this->assertEquals(3, Table11Peer::getMaxRank(), 'getMaxRank() returns the maximum rank'); + Table11Peer::doDeleteAll(); + $this->assertNull(Table11Peer::getMaxRank(), 'getMaxRank() returns null for empty tables'); + } + public function testRetrieveByRank() + { + $t = Table11Peer::retrieveByRank(5); + $this->assertNull($t, 'retrieveByRank() returns null for an unknown rank'); + $t3 = Table11Peer::retrieveByRank(3); + $this->assertEquals(3, $t3->getRank(), 'retrieveByRank() returns the object with the required rank'); + $this->assertEquals('row3', $t3->getTitle(), 'retrieveByRank() returns the object with the required rank'); + } + + public function testReorder() + { + $objects = Table11Peer::doSelect(new Criteria()); + $ids = array(); + foreach ($objects as $object) { + $ids[]= $object->getPrimaryKey(); + } + $ranks = array(4, 3, 2, 1); + $order = array_combine($ids, $ranks); + Table11Peer::reorder($order); + $expected = array(1 => 'row3', 2 => 'row2', 3 => 'row4', 4 => 'row1'); + $this->assertEquals($expected, $this->getFixturesArray(), 'reorder() reorders the suite'); + } + + public function testDoSelectOrderByRank() + { + $objects = Table11Peer::doSelectOrderByRank(); + $oldRank = 0; + while ($object = array_shift($objects)) { + $this->assertTrue($object->getRank() > $oldRank); + $oldRank = $object->getRank(); + } + $objects = Table11Peer::doSelectOrderByRank(null, Criteria::DESC); + $oldRank = 10; + while ($object = array_shift($objects)) { + $this->assertTrue($object->getRank() < $oldRank); + $oldRank = $object->getRank(); + } + } + + +} \ No newline at end of file diff --git a/library/propel/test/testsuite/generator/behavior/sortable/SortableBehaviorPeerBuilderModifierWithScopeTest.php b/library/propel/test/testsuite/generator/behavior/sortable/SortableBehaviorPeerBuilderModifierWithScopeTest.php new file mode 100644 index 000000000..f9a80f6e5 --- /dev/null +++ b/library/propel/test/testsuite/generator/behavior/sortable/SortableBehaviorPeerBuilderModifierWithScopeTest.php @@ -0,0 +1,114 @@ +populateTable12(); + } + + public function testStaticAttributes() + { + $this->assertEquals(Table12Peer::RANK_COL, 'table12.POSITION'); + $this->assertEquals(Table12Peer::SCOPE_COL, 'table12.MY_SCOPE_COLUMN'); + } + + public function testGetMaxRank() + { + $this->assertEquals(4, Table12Peer::getMaxRank(1), 'getMaxRank() returns the maximum rank of the suite'); + $this->assertEquals(2, Table12Peer::getMaxRank(2), 'getMaxRank() returns the maximum rank of the suite'); + $t4 = Table12Peer::retrieveByRank(4, 1); + $t4->delete(); + $this->assertEquals(3, Table12Peer::getMaxRank(1), 'getMaxRank() returns the maximum rank'); + Table12Peer::doDeleteAll(); + $this->assertNull(Table12Peer::getMaxRank(1), 'getMaxRank() returns null for empty tables'); + } + public function testRetrieveByRank() + { + $t = Table12Peer::retrieveByRank(5, 1); + $this->assertNull($t, 'retrieveByRank() returns null for an unknown rank'); + $t3 = Table12Peer::retrieveByRank(3, 1); + $this->assertEquals(3, $t3->getRank(), 'retrieveByRank() returns the object with the required rank in the required suite'); + $this->assertEquals('row3', $t3->getTitle(), 'retrieveByRank() returns the object with the required rank in the required suite'); + $t6 = Table12Peer::retrieveByRank(2, 2); + $this->assertEquals(2, $t6->getRank(), 'retrieveByRank() returns the object with the required rank in the required suite'); + $this->assertEquals('row6', $t6->getTitle(), 'retrieveByRank() returns the object with the required rank in the required suite'); + } + + public function testReorder() + { + $c = new Criteria(); + $c->add(Table12Peer::SCOPE_COL, 1); + $objects = Table12Peer::doSelectOrderByRank($c); + $ids = array(); + foreach ($objects as $object) { + $ids[]= $object->getPrimaryKey(); + } + $ranks = array(4, 3, 2, 1); + $order = array_combine($ids, $ranks); + Table12Peer::reorder($order); + $expected = array(1 => 'row4', 2 => 'row3', 3 => 'row2', 4 => 'row1'); + $this->assertEquals($expected, $this->getFixturesArrayWithScope(1), 'reorder() reorders the suite'); + $expected = array(1 => 'row5', 2 => 'row6'); + $this->assertEquals($expected, $this->getFixturesArrayWithScope(2), 'reorder() leaves other suites unchanged'); + } + + public function testDoSelectOrderByRank() + { + $c = new Criteria(); + $c->add(Table12Peer::SCOPE_COL, 1); + $objects = Table12Peer::doSelectOrderByRank($c); + $oldRank = 0; + while ($object = array_shift($objects)) { + $this->assertTrue($object->getRank() > $oldRank); + $oldRank = $object->getRank(); + } + $c = new Criteria(); + $c->add(Table12Peer::SCOPE_COL, 1); + $objects = Table12Peer::doSelectOrderByRank($c, Criteria::DESC); + $oldRank = 10; + while ($object = array_shift($objects)) { + $this->assertTrue($object->getRank() < $oldRank); + $oldRank = $object->getRank(); + } + } + + public function testRetrieveList() + { + $this->assertEquals(4, count(Table12Peer::retrieveList(1)), 'retrieveList() returns the list of objects in the scope'); + $this->assertEquals(2, count(Table12Peer::retrieveList(2)), 'retrieveList() returns the list of objects in the scope'); + } + + public function testCountList() + { + $this->assertEquals(4, Table12Peer::countList(1), 'countList() returns the list of objects in the scope'); + $this->assertEquals(2, Table12Peer::countList(2), 'countList() returns the list of objects in the scope'); + } + + public function testDeleteList() + { + $this->assertEquals(4, Table12Peer::deleteList(1), 'deleteList() returns the list of objects in the scope'); + $this->assertEquals(2, Table12Peer::doCount(new Criteria()), 'deleteList() deletes the objects in the scope'); + $this->assertEquals(2, Table12Peer::deleteList(2), 'deleteList() returns the list of objects in the scope'); + $this->assertEquals(0, Table12Peer::doCount(new Criteria()), 'deleteList() deletes the objects in the scope'); + } +} \ No newline at end of file diff --git a/library/propel/test/testsuite/generator/behavior/sortable/SortableBehaviorQueryBuilderModifierTest.php b/library/propel/test/testsuite/generator/behavior/sortable/SortableBehaviorQueryBuilderModifierTest.php new file mode 100755 index 000000000..15614f10c --- /dev/null +++ b/library/propel/test/testsuite/generator/behavior/sortable/SortableBehaviorQueryBuilderModifierTest.php @@ -0,0 +1,115 @@ +populateTable11(); + } + + public function testFilterByRank() + { + $this->assertTrue(Table11Query::create()->filterByRank(1) instanceof Table11Query, 'filterByRank() returns the current query object'); + $this->assertEquals('row1', Table11Query::create()->filterByRank(1)->findOne()->getTitle(), 'filterByRank() filters on the rank'); + $this->assertEquals('row4', Table11Query::create()->filterByRank(4)->findOne()->getTitle(), 'filterByRank() filters on the rank'); + $this->assertNull(Table11Query::create()->filterByRank(5)->findOne(), 'filterByRank() filters on the rank, which makes the query return no result on a non-existent rank'); + } + + public function testOrderByRank() + { + $this->assertTrue(Table11Query::create()->orderByRank() instanceof Table11Query, 'orderByRank() returns the current query object'); + // default order + $query = Table11Query::create()->orderByRank(); + $expectedQuery = Table11Query::create()->addAscendingOrderByColumn(Table11Peer::SORTABLE_RANK); + $this->assertEquals($expectedQuery, $query, 'orderByRank() orders the query by rank asc'); + // asc order + $query = Table11Query::create()->orderByRank(Criteria::ASC); + $expectedQuery = Table11Query::create()->addAscendingOrderByColumn(Table11Peer::SORTABLE_RANK); + $this->assertEquals($expectedQuery, $query, 'orderByRank() orders the query by rank, using the argument as sort direction'); + // desc order + $query = Table11Query::create()->orderByRank(Criteria::DESC); + $expectedQuery = Table11Query::create()->addDescendingOrderByColumn(Table11Peer::SORTABLE_RANK); + $this->assertEquals($expectedQuery, $query, 'orderByRank() orders the query by rank, using the argument as sort direction'); + } + + /** + * @expectedException PropelException + */ + public function testOrderByRankIncorrectDirection() + { + Table11Query::create()->orderByRank('foo'); + } + + public function testFindList() + { + $ts = Table11Query::create()->findList(); + $this->assertTrue($ts instanceof PropelObjectCollection, 'findList() returns a collection of objects'); + $this->assertEquals(4, count($ts), 'findList() does not filter the query'); + $this->assertEquals('row1', $ts[0]->getTitle(), 'findList() returns an ordered list'); + $this->assertEquals('row2', $ts[1]->getTitle(), 'findList() returns an ordered list'); + $this->assertEquals('row3', $ts[2]->getTitle(), 'findList() returns an ordered list'); + $this->assertEquals('row4', $ts[3]->getTitle(), 'findList() returns an ordered list'); + } + + public function testFindOneByRank() + { + $this->assertTrue(Table11Query::create()->findOneByRank(1) instanceof Table11, 'findOneByRank() returns an instance of the model object'); + $this->assertEquals('row1', Table11Query::create()->findOneByRank(1)->getTitle(), 'findOneByRank() returns a single item based on the rank'); + $this->assertEquals('row4', Table11Query::create()->findOneByRank(4)->getTitle(), 'findOneByRank() returns a single item based on the rank'); + $this->assertNull(Table11Query::create()->findOneByRank(5), 'findOneByRank() returns no result on a non-existent rank'); + } + + public function testGetMaxRank() + { + $this->assertEquals(4, Table11Query::create()->getMaxRank(), 'getMaxRank() returns the maximum rank'); + // delete one + $t4 = Table11Query::create()->findOneByRank(4); + $t4->delete(); + $this->assertEquals(3, Table11Query::create()->getMaxRank(), 'getMaxRank() returns the maximum rank'); + // add one + $t = new Table11(); + $t->save(); + $this->assertEquals(4, Table11Query::create()->getMaxRank(), 'getMaxRank() returns the maximum rank'); + // delete all + Table11Query::create()->deleteAll(); + $this->assertNull(Table11Query::create()->getMaxRank(), 'getMaxRank() returns null for empty tables'); + // add one + $t = new Table11(); + $t->save(); + $this->assertEquals(1, Table11Query::create()->getMaxRank(), 'getMaxRank() returns the maximum rank'); + } + + public function testReorder() + { + $objects = Table11Query::create()->find(); + $ids = array(); + foreach ($objects as $object) { + $ids[]= $object->getPrimaryKey(); + } + $ranks = array(4, 3, 2, 1); + $order = array_combine($ids, $ranks); + Table11Query::create()->reorder($order); + $expected = array(1 => 'row3', 2 => 'row2', 3 => 'row4', 4 => 'row1'); + $this->assertEquals($expected, $this->getFixturesArray(), 'reorder() reorders the suite'); + } + +} \ No newline at end of file diff --git a/library/propel/test/testsuite/generator/behavior/sortable/SortableBehaviorQueryBuilderModifierWithScopeTest.php b/library/propel/test/testsuite/generator/behavior/sortable/SortableBehaviorQueryBuilderModifierWithScopeTest.php new file mode 100755 index 000000000..db6f41b6d --- /dev/null +++ b/library/propel/test/testsuite/generator/behavior/sortable/SortableBehaviorQueryBuilderModifierWithScopeTest.php @@ -0,0 +1,142 @@ +populateTable12(); + } + + public function testInList() + { + /* List used for tests + scope=1 scope=2 + row1 row5 + row2 row6 + row3 + row4 + */ + $query = Table12Query::create()->inList(1); + $expectedQuery = Table12Query::create()->add(Table12Peer::MY_SCOPE_COLUMN, 1, Criteria::EQUAL); + $this->assertEquals($expectedQuery, $query, 'inList() filters the query by scope'); + $this->assertEquals(4, $query->count(), 'inList() filters the query by scope'); + $query = Table12Query::create()->inList(2); + $expectedQuery = Table12Query::create()->add(Table12Peer::MY_SCOPE_COLUMN, 2, Criteria::EQUAL); + $this->assertEquals($expectedQuery, $query, 'inList() filters the query by scope'); + $this->assertEquals(2, $query->count(), 'inList() filters the query by scope'); + } + + public function testFilterByRank() + { + /* List used for tests + scope=1 scope=2 + row1 row5 + row2 row6 + row3 + row4 + */ + $this->assertEquals('row1', Table12Query::create()->filterByRank(1, 1)->findOne()->getTitle(), 'filterByRank() filters on the rank and the scope'); + $this->assertEquals('row5', Table12Query::create()->filterByRank(1, 2)->findOne()->getTitle(), 'filterByRank() filters on the rank and the scope'); + $this->assertEquals('row4', Table12Query::create()->filterByRank(4, 1)->findOne()->getTitle(), 'filterByRank() filters on the rank and the scope'); + $this->assertNull(Table12Query::create()->filterByRank(4, 2)->findOne(), 'filterByRank() filters on the rank and the scope, which makes the query return no result on a non-existent rank'); + } + + public function testOrderByRank() + { + $this->assertTrue(Table12Query::create()->orderByRank() instanceof Table12Query, 'orderByRank() returns the current query object'); + // default order + $query = Table12Query::create()->orderByRank(); + $expectedQuery = Table12Query::create()->addAscendingOrderByColumn(Table12Peer::POSITION); + $this->assertEquals($expectedQuery, $query, 'orderByRank() orders the query by rank asc'); + // asc order + $query = Table12Query::create()->orderByRank(Criteria::ASC); + $expectedQuery = Table12Query::create()->addAscendingOrderByColumn(Table12Peer::POSITION); + $this->assertEquals($expectedQuery, $query, 'orderByRank() orders the query by rank, using the argument as sort direction'); + // desc order + $query = Table12Query::create()->orderByRank(Criteria::DESC); + $expectedQuery = Table12Query::create()->addDescendingOrderByColumn(Table12Peer::POSITION); + $this->assertEquals($expectedQuery, $query, 'orderByRank() orders the query by rank, using the argument as sort direction'); + } + + public function testFindList() + { + $ts = Table12Query::create()->findList(1); + $this->assertTrue($ts instanceof PropelObjectCollection, 'findList() returns a collection of objects'); + $this->assertEquals(4, count($ts), 'findList() filters the query by scope'); + $this->assertEquals('row1', $ts[0]->getTitle(), 'findList() returns an ordered scoped list'); + $this->assertEquals('row2', $ts[1]->getTitle(), 'findList() returns an ordered scoped list'); + $this->assertEquals('row3', $ts[2]->getTitle(), 'findList() returns an ordered scoped list'); + $this->assertEquals('row4', $ts[3]->getTitle(), 'findList() returns an ordered scoped list'); + $ts = Table12Query::create()->findList(2); + $this->assertEquals(2, count($ts), 'findList() filters the query by scope'); + $this->assertEquals('row5', $ts[0]->getTitle(), 'findList() returns an ordered scoped list'); + $this->assertEquals('row6', $ts[1]->getTitle(), 'findList() returns an ordered scoped list'); + } + + public function testFindOneByRank() + { + $this->assertTrue(Table12Query::create()->findOneByRank(1, 1) instanceof Table12, 'findOneByRank() returns an instance of the model object'); + $this->assertEquals('row1', Table12Query::create()->findOneByRank(1, 1)->getTitle(), 'findOneByRank() returns a single item based on the rank and the scope'); + $this->assertEquals('row5', Table12Query::create()->findOneByRank(1, 2)->getTitle(), 'findOneByRank() returns a single item based on the rank and the scope'); + $this->assertEquals('row4', Table12Query::create()->findOneByRank(4, 1)->getTitle(), 'findOneByRank() returns a single item based on the rank a,d the scope'); + $this->assertNull(Table12Query::create()->findOneByRank(4, 2), 'findOneByRank() returns no result on a non-existent rank and scope'); + } + + public function testGetMaxRank() + { + $this->assertEquals(4, Table12Query::create()->getMaxRank(1), 'getMaxRank() returns the maximum rank in the scope'); + $this->assertEquals(2, Table12Query::create()->getMaxRank(2), 'getMaxRank() returns the maximum rank in the scope'); + // delete one + $t4 = Table12Query::create()->findOneByRank(4, 1); + $t4->delete(); + $this->assertEquals(3, Table12Query::create()->getMaxRank(1), 'getMaxRank() returns the maximum rank'); + // add one + $t = new Table12(); + $t->setMyScopeColumn(1); + $t->save(); + $this->assertEquals(4, Table12Query::create()->getMaxRank(1), 'getMaxRank() returns the maximum rank'); + // delete all + Table12Query::create()->deleteAll(); + $this->assertNull(Table12Query::create()->getMaxRank(1), 'getMaxRank() returns null for empty tables'); + // add one + $t = new Table12(); + $t->setMyScopeColumn(1); + $t->save(); + $this->assertEquals(1, Table12Query::create()->getMaxRank(1), 'getMaxRank() returns the maximum rank'); + } + + public function testReorder() + { + $objects = Table12Query::create()->findList(1); + $ids = array(); + foreach ($objects as $object) { + $ids[]= $object->getPrimaryKey(); + } + $ranks = array(4, 3, 2, 1); + $order = array_combine($ids, $ranks); + Table12Query::create()->reorder($order); + $expected = array(1 => 'row4', 2 => 'row3', 3 => 'row2', 4 => 'row1'); + $this->assertEquals($expected, $this->getFixturesArrayWithScope(1), 'reorder() reorders the suite'); + $expected = array(1 => 'row5', 2 => 'row6'); + $this->assertEquals($expected, $this->getFixturesArrayWithScope(2), 'reorder() leaves other suites unchanged'); + } +} \ No newline at end of file diff --git a/library/propel/test/testsuite/generator/behavior/sortable/SortableBehaviorTest.php b/library/propel/test/testsuite/generator/behavior/sortable/SortableBehaviorTest.php new file mode 100755 index 000000000..6192614a5 --- /dev/null +++ b/library/propel/test/testsuite/generator/behavior/sortable/SortableBehaviorTest.php @@ -0,0 +1,33 @@ +assertEquals(count($table11->getColumns()), 3, 'Sortable adds one columns by default'); + $this->assertTrue(method_exists('Table11', 'getRank'), 'Sortable adds a rank column by default'); + $table12 = Table12Peer::getTableMap(); + $this->assertEquals(count($table12->getColumns()), 4, 'Sortable does not add a column when it already exists'); + $this->assertTrue(method_exists('Table12', 'getPosition'), 'Sortable allows customization of rank_column name'); + } + +} diff --git a/library/propel/test/testsuite/generator/builder/NamespaceTest.php b/library/propel/test/testsuite/generator/builder/NamespaceTest.php new file mode 100755 index 000000000..ff69d6f7e --- /dev/null +++ b/library/propel/test/testsuite/generator/builder/NamespaceTest.php @@ -0,0 +1,225 @@ +markTestSkipped('Namespace support requires PHP 5.3'); + } + parent::setUp(); + Propel::init('fixtures/namespaced/build/conf/bookstore_namespaced-conf.php'); + } + + protected function tearDown() + { + parent::tearDown(); + Propel::init('fixtures/bookstore/build/conf/bookstore-conf.php'); + } + + public function testInsert() + { + $book = new \Foo\Bar\NamespacedBook(); + $book->setTitle('foo'); + $book->save(); + $this->assertFalse($book->isNew()); + + $publisher = new \Baz\NamespacedPublisher(); + $publisher->save(); + $this->assertFalse($publisher->isNew()); + } + + public function testUpdate() + { + $book = new \Foo\Bar\NamespacedBook(); + $book->setTitle('foo'); + $book->save(); + $book->setTitle('bar'); + $book->save(); + $this->assertFalse($book->isNew()); + } + + public function testRelate() + { + $author = new NamespacedAuthor(); + $book = new \Foo\Bar\NamespacedBook(); + $book->setNamespacedAuthor($author); + $book->save(); + $this->assertFalse($book->isNew()); + $this->assertFalse($author->isNew()); + + $author = new NamespacedAuthor(); + $book = new \Foo\Bar\NamespacedBook(); + $author->addNamespacedBook($book); + $author->save(); + $this->assertFalse($book->isNew()); + $this->assertFalse($author->isNew()); + + $publisher = new \Baz\NamespacedPublisher(); + $book = new \Foo\Bar\NamespacedBook(); + $book->setNamespacedPublisher($publisher); + $book->save(); + $this->assertFalse($book->isNew()); + $this->assertFalse($publisher->isNew()); + } + + public function testBasicQuery() + { + \Foo\Bar\NamespacedBookQuery::create()->deleteAll(); + \Baz\NamespacedPublisherQuery::create()->deleteAll(); + $noNamespacedBook = \Foo\Bar\NamespacedBookQuery::create()->findOne(); + $this->assertNull($noNamespacedBook); + $noPublihser = \Baz\NamespacedPublisherQuery::create()->findOne(); + $this->assertNull($noPublihser); + } + + public function testFind() + { + \Foo\Bar\NamespacedBookQuery::create()->deleteAll(); + $book = new \Foo\Bar\NamespacedBook(); + $book->setTitle('War And Peace'); + $book->save(); + $book2 = \Foo\Bar\NamespacedBookQuery::create()->findPk($book->getId()); + $this->assertEquals($book, $book2); + $book3 = \Foo\Bar\NamespacedBookQuery::create()->findOneByTitle($book->getTitle()); + $this->assertEquals($book, $book3); + } + + public function testGetRelatedManyToOne() + { + \Foo\Bar\NamespacedBookQuery::create()->deleteAll(); + \Baz\NamespacedPublisherQuery::create()->deleteAll(); + $publisher = new \Baz\NamespacedPublisher(); + $book = new \Foo\Bar\NamespacedBook(); + $book->setNamespacedPublisher($publisher); + $book->save(); + \Foo\Bar\NamespacedBookPeer::clearInstancePool(); + \Baz\NamespacedPublisherPeer::clearInstancePool(); + $book2 = \Foo\Bar\NamespacedBookQuery::create()->findPk($book->getId()); + $publisher2 = $book2->getNamespacedPublisher(); + $this->assertEquals($publisher->getId(), $publisher2->getId()); + } + + public function testGetRelatedOneToMany() + { + \Foo\Bar\NamespacedBookQuery::create()->deleteAll(); + \Baz\NamespacedPublisherQuery::create()->deleteAll(); + $author = new NamespacedAuthor(); + $book = new \Foo\Bar\NamespacedBook(); + $book->setNamespacedAuthor($author); + $book->save(); + \Foo\Bar\NamespacedBookPeer::clearInstancePool(); + NamespacedAuthorPeer::clearInstancePool(); + $author2 = NamespacedAuthorQuery::create()->findPk($author->getId()); + $book2 = $author2->getNamespacedBooks()->getFirst(); + $this->assertEquals($book->getId(), $book2->getId()); + } + + public function testFindWithManyToOne() + { + \Foo\Bar\NamespacedBookQuery::create()->deleteAll(); + \Baz\NamespacedPublisherQuery::create()->deleteAll(); + $publisher = new \Baz\NamespacedPublisher(); + $book = new \Foo\Bar\NamespacedBook(); + $book->setNamespacedPublisher($publisher); + $book->save(); + \Foo\Bar\NamespacedBookPeer::clearInstancePool(); + \Baz\NamespacedPublisherPeer::clearInstancePool(); + $book2 = \Foo\Bar\NamespacedBookQuery::create() + ->joinWith('NamespacedPublisher') + ->findPk($book->getId()); + $publisher2 = $book2->getNamespacedPublisher(); + $this->assertEquals($publisher->getId(), $publisher2->getId()); + } + + public function testFindWithOneToMany() + { + \Foo\Bar\NamespacedBookQuery::create()->deleteAll(); + NamespacedAuthorQuery::create()->deleteAll(); + $author = new NamespacedAuthor(); + $book = new \Foo\Bar\NamespacedBook(); + $book->setNamespacedAuthor($author); + $book->save(); + \Foo\Bar\NamespacedBookPeer::clearInstancePool(); + NamespacedAuthorPeer::clearInstancePool(); + $author2 = NamespacedAuthorQuery::create() + ->joinWith('NamespacedBook') + ->findPk($author->getId()); + $book2 = $author2->getNamespacedBooks()->getFirst(); + $this->assertEquals($book->getId(), $book2->getId()); + } + + public function testSingleTableInheritance() + { + \Foo\Bar\NamespacedBookstoreEmployeeQuery::create()->deleteAll(); + $emp = new \Foo\Bar\NamespacedBookstoreEmployee(); + $emp->setName('Henry'); + $emp->save(); + $man = new \Foo\Bar\NamespacedBookstoreManager(); + $man->setName('John'); + $man->save(); + $cas = new \Foo\Bar\NamespacedBookstoreCashier(); + $cas->setName('William'); + $cas->save(); + $emps = \Foo\Bar\NamespacedBookstoreEmployeeQuery::create() + ->orderByName() + ->find(); + $this->assertEquals(3, count($emps)); + $this->assertTrue($emps[0] instanceof \Foo\Bar\NamespacedBookstoreEmployee); + $this->assertTrue($emps[1] instanceof \Foo\Bar\NamespacedBookstoreManager); + $this->assertTrue($emps[2] instanceof \Foo\Bar\NamespacedBookstoreCashier); + $nbMan = \Foo\Bar\NamespacedBookstoreManagerQuery::create() + ->count(); + $this->assertEquals(1, $nbMan); + } + + public function testManyToMany() + { + \Foo\Bar\NamespacedBookQuery::create()->deleteAll(); + \Baz\NamespacedBookClubQuery::create()->deleteAll(); + NamespacedBookListRelQuery::create()->deleteAll(); + $book1 = new \Foo\Bar\NamespacedBook(); + $book1->setTitle('bar'); + $book1->save(); + $book2 = new \Foo\Bar\NamespacedBook(); + $book2->setTitle('foo'); + $book2->save(); + $bookClub1 = new \Baz\NamespacedBookClub(); + $bookClub1->addNamespacedBook($book1); + $bookClub1->addNamespacedBook($book2); + $bookClub1->save(); + $bookClub2 = new \Baz\NamespacedBookClub(); + $bookClub2->addNamespacedBook($book1); + $bookClub2->save(); + $this->assertEquals(2, $book1->countNamespacedBookClubs()); + $this->assertEquals(1, $book2->countNamespacedBookClubs()); + $nbRels = NamespacedBookListRelQuery::create()->count(); + $this->assertEquals(3, $nbRels); + $con = Propel::getConnection(NamespacedBookListRelPeer::DATABASE_NAME); + $books = \Foo\Bar\NamespacedBookQuery::create() + ->orderByTitle() + ->joinWith('NamespacedBookListRel') + ->joinWith('NamespacedBookListRel.NamespacedBookClub') + ->find($con); + } + +} diff --git a/library/propel/test/testsuite/generator/builder/om/GeneratedNestedSetObjectTest.php b/library/propel/test/testsuite/generator/builder/om/GeneratedNestedSetObjectTest.php new file mode 100644 index 000000000..e6f7fb943 --- /dev/null +++ b/library/propel/test/testsuite/generator/builder/om/GeneratedNestedSetObjectTest.php @@ -0,0 +1,187 @@ +assertTrue($pp->isRoot(), 'Node must be root'); + } + + /** + * Test xxxNestedSet::isRoot() as false + */ + public function testObjectIsRootFalse() + { + $c = new Criteria(PagePeer::DATABASE_NAME); + $c->add(PagePeer::TITLE, 'school', Criteria::EQUAL); + + $school = PagePeer::doSelectOne($c); + $this->assertFalse($school->isRoot(), 'Node must not be root'); + } + + /** + * Test xxxNestedSet::retrieveParent() as true. + */ + public function testObjectRetrieveParentTrue() + { + $c = new Criteria(PagePeer::DATABASE_NAME); + $c->add(PagePeer::TITLE, 'school', Criteria::EQUAL); + + $school = PagePeer::doSelectOne($c); + $this->assertNotNull($school->retrieveParent(), 'Parent node must exist'); + } + + /** + * Test xxxNestedSet::retrieveParent() as false. + */ + public function testObjectRetrieveParentFalse() + { + $c = new Criteria(PagePeer::DATABASE_NAME); + $c->add(PagePeer::TITLE, 'home', Criteria::EQUAL); + + $home = PagePeer::doSelectOne($c); + $this->assertNull($home->retrieveParent(), 'Parent node must not exist and retrieved not be null'); + } + + /** + * Test xxxNestedSet::hasParent() as true. + */ + public function testObjectHasParentTrue() + { + $c = new Criteria(); + $c->add(PagePeer::TITLE, 'school', Criteria::EQUAL); + + $school = PagePeer::doSelectOne($c); + $this->assertTrue($school->hasParent(), 'Node must have parent node'); + } + + /** + * Test xxxNestedSet::hasParent() as false + */ + public function testObjectHasParentFalse() + { + $c = new Criteria(); + $c->add(PagePeer::TITLE, 'home', Criteria::EQUAL); + + $home = PagePeer::doSelectOne($c); + $this->assertFalse($home->hasParent(), 'Root node must not have parent'); + } + + /** + * Test xxxNestedSet::isLeaf() as true. + */ + public function testObjectIsLeafTrue() + { + $c = new Criteria(); + $c->add(PagePeer::TITLE, 'simulator', Criteria::EQUAL); + + $simulator = PagePeer::doSelectOne($c); + $this->assertTrue($simulator->isLeaf($simulator), 'Node must be a leaf'); + } + + /** + * Test xxxNestedSet::isLeaf() as false + */ + public function testObjectIsLeafFalse() + { + $c = new Criteria(); + $c->add(PagePeer::TITLE, 'contact', Criteria::EQUAL); + + $contact = PagePeer::doSelectOne($c); + $this->assertFalse($contact->isLeaf($contact), 'Node must not be a leaf'); + } + + /** + * Test xxxNestedSet::makeRoot() + */ + public function testObjectMakeRoot() + { + $page = new Page(); + $page->makeRoot(); + $this->assertEquals(1, $page->getLeftValue(), 'Node left value must equal 1'); + $this->assertEquals(2, $page->getRightValue(), 'Node right value must equal 2'); + } + + /** + * Test xxxNestedSet::makeRoot() exception + * @expectedException PropelException + */ + public function testObjectMakeRootException() + { + $c = new Criteria(); + $c->add(PagePeer::TITLE, 'home', Criteria::EQUAL); + + $home = PagePeer::doSelectOne($c); + $home->makeRoot(); + } + + /** + * Test xxxNestedSet::getDescendants() + */ + public function testPeerGetDescendants() + { + $nodesWithoutPool = array(); + CategoryPeer::clearInstancePool(); + $cat = CategoryPeer::retrieveRoot(1); + $children = $cat->getDescendants(); + foreach($children as $child) + { + $nodesWithoutPool[] = $child->getTitle(); + } + $this->assertEquals($nodesWithoutPool, array('Cat_1_1', 'Cat_1_1_1', 'Cat_1_1_1_1')); + } + + /** + * Test xxxNestedSet::getDescendantsTwice() + */ + public function testPeerGetDescendantsTwice() + { + $nodesWithoutPool = array(); + $nodesWithPool = array(); + + CategoryPeer::clearInstancePool(); + $cat = CategoryPeer::retrieveRoot(1); + $children = $cat->getDescendants(); + foreach($children as $child) + { + $nodesWithoutPool[] = $child->getTitle(); + } + + $cat = CategoryPeer::retrieveRoot(1); + $children = $cat->getDescendants(); + foreach($children as $child) + { + $nodesWithPool[] = $child->getTitle(); + } + $this->assertEquals($nodesWithoutPool, $nodesWithPool, 'Retrieved nodes must be the same with and without InstancePooling'); + } +} diff --git a/library/propel/test/testsuite/generator/builder/om/GeneratedNestedSetPeerTest.php b/library/propel/test/testsuite/generator/builder/om/GeneratedNestedSetPeerTest.php new file mode 100644 index 000000000..ae255c010 --- /dev/null +++ b/library/propel/test/testsuite/generator/builder/om/GeneratedNestedSetPeerTest.php @@ -0,0 +1,188 @@ +assertNotNull($pp, 'Node must exist and not be null'); + $this->assertEquals(1, $pp->getLeftValue(), 'Node left value must be equal to 1'); + } + + /** + * Test retrieveRoot() as false + */ + public function testRetrieveRootNotExist() + { + $pp = PagePeer::retrieveRoot(2); + $this->assertNull($pp, 'Root with such scopeId must not exist'); + } + + /** + * Test xxxNestedSetPeer::isRoot() as true + */ + public function testPeerIsRootTrue() + { + $pp = PagePeer::retrieveRoot(1); + $this->assertTrue(PagePeer::isRoot($pp), 'Node must be root'); + } + + /** + * Test xxxNestedSetPeer::isRoot() as false + */ + public function testPeerIsRootFalse() + { + $c = new Criteria(PagePeer::DATABASE_NAME); + $c->add(PagePeer::TITLE, 'school', Criteria::EQUAL); + + $school = PagePeer::doSelectOne($c); + $this->assertFalse(PagePeer::isRoot($school), 'Node must not be root'); + } + + /** + * Test xxxNestedSetPeer::retrieveParent() as true. + */ + public function testPeerRetrieveParentTrue() + { + $c = new Criteria(PagePeer::DATABASE_NAME); + $c->add(PagePeer::TITLE, 'school', Criteria::EQUAL); + + $school = PagePeer::doSelectOne($c); + $this->assertNotNull(PagePeer::retrieveParent($school), 'Parent node must exist'); + } + + /** + * Test xxxNestedSetPeer::retrieveParent() as false. + */ + public function testPeerRetrieveParentFalse() + { + $c = new Criteria(PagePeer::DATABASE_NAME); + $c->add(PagePeer::TITLE, 'home', Criteria::EQUAL); + + $home = PagePeer::doSelectOne($c); + $this->assertNull(PagePeer::retrieveParent($home), 'Parent node must not exist and retrieved not be null'); + } + + /** + * Test xxxNestedSetPeer::hasParent() as true. + */ + public function testPeerHasParentTrue() + { + $c = new Criteria(); + $c->add(PagePeer::TITLE, 'school', Criteria::EQUAL); + + $school = PagePeer::doSelectOne($c); + $this->assertTrue(PagePeer::hasParent($school), 'Node must have parent node'); + } + + /** + * Test xxxNestedSetPeer::hasParent() as false + */ + public function testPeerHasParentFalse() + { + $c = new Criteria(); + $c->add(PagePeer::TITLE, 'home', Criteria::EQUAL); + + $home = PagePeer::doSelectOne($c); + $this->assertFalse(PagePeer::hasParent($home), 'Root node must not have parent'); + } + + /** + * Test xxxNestedSetPeer::isValid() as true. + */ + public function testPeerIsValidTrue() + { + $c = new Criteria(); + $c->add(PagePeer::TITLE, 'school', Criteria::EQUAL); + + $school = PagePeer::doSelectOne($c); + $this->assertTrue(PagePeer::isValid($school), 'Node must be valid'); + } + + /** + * Test xxxNestedSetPeer::isValid() as false + */ + public function testPeerIsValidFalse() + { + $page = new Page(); + $this->assertFalse(PagePeer::isValid($page), 'Node left and right values must be invalid'); + $this->assertFalse(PagePeer::isValid(null), 'Null must be invalid'); + } + + /** + * Test xxxNestedSetPeer::isLeaf() as true. + */ + public function testPeerIsLeafTrue() + { + $c = new Criteria(); + $c->add(PagePeer::TITLE, 'simulator', Criteria::EQUAL); + + $simulator = PagePeer::doSelectOne($c); + $this->assertTrue(PagePeer::isLeaf($simulator), 'Node must be a leaf'); + } + + /** + * Test xxxNestedSetPeer::isLeaf() as false + */ + public function testPeerIsLeafFalse() + { + $c = new Criteria(); + $c->add(PagePeer::TITLE, 'contact', Criteria::EQUAL); + + $contact = PagePeer::doSelectOne($c); + $this->assertFalse(PagePeer::isLeaf($contact), 'Node must not be a leaf'); + } + + /** + * Test xxxNestedSetPeer::createRoot() + */ + public function testPeerCreateRoot() + { + $page = new Page(); + PagePeer::createRoot($page); + $this->assertEquals(1, $page->getLeftValue(), 'Node left value must equal 1'); + $this->assertEquals(2, $page->getRightValue(), 'Node right value must equal 2'); + } + + /** + * Test xxxNestedSetPeer::createRoot() exception + * @expectedException PropelException + */ + public function testPeerCreateRootException() + { + $c = new Criteria(); + $c->add(PagePeer::TITLE, 'home', Criteria::EQUAL); + + $home = PagePeer::doSelectOne($c); + PagePeer::createRoot($home); + } + +} diff --git a/library/propel/test/testsuite/generator/builder/om/GeneratedNestedSetTest.php b/library/propel/test/testsuite/generator/builder/om/GeneratedNestedSetTest.php new file mode 100644 index 000000000..b09178551 --- /dev/null +++ b/library/propel/test/testsuite/generator/builder/om/GeneratedNestedSetTest.php @@ -0,0 +1,120 @@ +getDepth()) + , $item->getId() , ': ' + , $item->getTitle() + , ' [', $item->getLeftValue(), ':', $item->getRightValue() , ']' + . "\n"; + } + } + + /** + * Adds a new Page row with specified parent Id. + * + * @param int $parentId + */ + protected function addNewChildPage($parentId) + { + $db = Propel::getConnection(PagePeer::DATABASE_NAME); + + //$db->beginTransaction(); + + $parent = PagePeer::retrieveByPK($parentId); + $page = new Page(); + $page->setTitle('new page '.time()); + $page->insertAsLastChildOf($parent); + $page->save(); + + //$db->commit(); + } + + /** + * Asserts that the Page table tree integrity is intact. + */ + protected function assertPageTreeIntegrity() + { + $db = Propel::getConnection(PagePeer::DATABASE_NAME); + + $values = array(); + $log = ''; + + foreach ($db->query('SELECT Id, LeftChild, RightChild, Title FROM Page', PDO::FETCH_NUM) as $row) { + + list($id, $leftChild, $rightChild, $title) = $row; + + if (!in_array($leftChild, $values)) { + $values[] = (int) $leftChild; + } else { + $this->fail('Duplicate LeftChild value '.$leftChild); + } + + if (!in_array($rightChild, $values)) { + $values[] = (int) $rightChild; + } else { + $this->fail('Duplicate RightChild value '.$rightChild); + } + + $log .= "[$id($leftChild:$rightChild)]"; + } + + sort($values); + + if ($values[count($values)-1] != count($values)) { + $message = sprintf("Tree integrity NOT ok (%s)\n", $log); + $message .= sprintf('Integrity error: value count: %d, high value: %d', count($values), $values[count($values)-1]); + $this->fail($message); + } + + } + + /** + * Tests adding a node to the Page tree. + */ + public function testAdd() + { + $db = Propel::getConnection(PagePeer::DATABASE_NAME); + + // I'm not sure if the specific ID matters, but this should match original + // code. The ID will change with subsequent runs (e.g. the first time it will be 11) + $startId = $db->query('SELECT MIN(Id) FROM Page')->fetchColumn(); + $this->addNewChildPage($startId + 10); + $this->assertPageTreeIntegrity(); + } + +} diff --git a/library/propel/test/testsuite/generator/builder/om/GeneratedObjectLobTest.php b/library/propel/test/testsuite/generator/builder/om/GeneratedObjectLobTest.php new file mode 100644 index 000000000..290e73c4f --- /dev/null +++ b/library/propel/test/testsuite/generator/builder/om/GeneratedObjectLobTest.php @@ -0,0 +1,289 @@ + + * @package generator.builder.om + */ +class GeneratedObjectLobTest extends BookstoreEmptyTestBase +{ + + /** + * Array of filenames pointing to blob/clob files indexed by the basename. + * + * @var array string[] + */ + protected $sampleLobFiles = array(); + + protected function setUp() + { + parent::setUp(); + BookstoreDataPopulator::populate(); + $this->sampleLobFiles['tin_drum.gif'] = TESTS_BASE_DIR . '/etc/lob/tin_drum.gif'; + $this->sampleLobFiles['tin_drum.txt'] = TESTS_BASE_DIR . '/etc/lob/tin_drum.txt'; + $this->sampleLobFiles['propel.gif'] = TESTS_BASE_DIR . '/etc/lob/propel.gif'; + } + + /** + * Gets a LOB filename. + * + * @param string $basename Basename of LOB filename to return (if left blank, will choose random file). + * @return string + * @throws Exception - if specified basename doesn't correspond to a registered LOB filename + */ + protected function getLobFile($basename = null) + { + if ($basename === null) { + $basename = array_rand($this->sampleLobFiles); + } + + if (isset($this->sampleLobFiles[$basename])) { + return $this->sampleLobFiles[$basename]; + } else { + throw new Exception("Invalid base LOB filename: $basename"); + } + } + + /** + * Test the LOB results returned in a resultset. + */ + public function testLobResults() + { + + $blob_path = $this->getLobFile('tin_drum.gif'); + $clob_path = $this->getLobFile('tin_drum.txt'); + + $book = BookPeer::doSelectOne(new Criteria()); + + $m1 = new Media(); + $m1->setBook($book); + $m1->setCoverImage(file_get_contents($blob_path)); + $m1->setExcerpt(file_get_contents($clob_path)); + $m1->save(); + $m1_id = $m1->getId(); + + $m1->reload(); + + $img = $m1->getCoverImage(); + $txt = $m1->getExcerpt(); + + $this->assertType('resource', $img, "Expected results of BLOB method to be a resource."); + $this->assertType('string', $txt, "Expected results of CLOB method to be a string."); + + $stat = fstat($img); + $size = $stat['size']; + + $this->assertEquals(filesize($blob_path), $size, "Expected filesize to match stat(blobrsc)"); + $this->assertEquals(filesize($clob_path), strlen($txt), "Expected filesize to match clob strlen"); + } + + /** + * Test to make sure that file pointer is not when it is fetched + * from the object. + * + * This is actually a test for correct behavior and does not completely fix + * the associated ticket (which was resolved wontfix). + * + * This does test the rewind-after-save functionality, however. + * + * @link http://propel.phpdb.org/trac/ticket/531 + */ + public function testLobRepeatRead() + { + $blob_path = $this->getLobFile('tin_drum.gif'); + $clob_path = $this->getLobFile('tin_drum.txt'); + + $book = BookPeer::doSelectOne(new Criteria()); + + $m1 = new Media(); + $m1->setBook($book); + $m1->setCoverImage(file_get_contents($blob_path)); + $m1->setExcerpt(file_get_contents($clob_path)); + $m1->save(); + + $img = $m1->getCoverImage(); + + // 1) Assert that this resource has been rewound. + + $this->assertEquals(0, ftell($img), "Expected position of cursor in file pointer to be 0"); + + // 1) Assert that we've got a valid stream to start with + + $this->assertType('resource', $img, "Expected results of BLOB method to be a resource."); + + // read first 100 bytes + $firstBytes = fread($img, 100); + + $img2 = $m1->getCoverImage(); + $this->assertSame($img, $img2, "Assert that the two resources are the same."); + + // read next 100 bytes + $nextBytes = fread($img, 100); + + $this->assertNotEquals(bin2hex($firstBytes), bin2hex($nextBytes), "Expected the first 100 and next 100 bytes to not be identical."); + } + + /** + * Tests the setting of null LOBs + */ + public function testLobNulls() + { + $book = BookPeer::doSelectOne(new Criteria()); + + $m1 = new Media(); + $m1->setBook($book); + $this->assertTrue($m1->getCoverImage() === null, "Initial LOB value for a new object should be null."); + + $m1->save(); + $m1_id = $m1->getId(); + + $m2 = new Media(); + $m2->setBook($book); + $m2->setCoverImage(null); + $this->assertTrue($m2->getCoverImage() === null, "Setting a LOB to null should cause accessor to return null."); + + $m2->save(); + $m2_id = $m2->getId(); + + $m1->reload(); + $this->assertTrue($m1->getCoverImage() === null, "Default null LOB value should be null after a reload."); + + $m2->reload(); + $this->assertTrue($m2->getCoverImage() === null, "LOB value set to null should be null after a reload."); + } + + /** + * Tests the setting of LOB (BLOB and CLOB) values. + */ + public function testLobSetting() + { + $blob_path = $this->getLobFile('tin_drum.gif'); + $blob2_path = $this->getLobFile('propel.gif'); + + $clob_path = $this->getLobFile('tin_drum.txt'); + $book = BookPeer::doSelectOne(new Criteria()); + + $m1 = new Media(); + $m1->setBook($book); + $m1->setCoverImage(file_get_contents($blob_path)); + $m1->setExcerpt(file_get_contents($clob_path)); + $m1->save(); + $m1_id = $m1->getId(); + + // 1) Assert that we've got a valid stream to start with + $img = $m1->getCoverImage(); + $this->assertType('resource', $img, "Expected results of BLOB method to be a resource."); + + // 2) Test setting a BLOB column with file contents + $m1->setCoverImage(file_get_contents($blob2_path)); + $this->assertType('resource', $m1->getCoverImage(), "Expected to get a resource back after setting BLOB with file contents."); + + // commit those changes & reload + $m1->save(); + + // 3) Verify that we've got a valid resource after reload + $m1->reload(); + $this->assertType('resource', $m1->getCoverImage(), "Expected to get a resource back after setting reloading object."); + + // 4) Test isModified() behavior + $fp = fopen("php://temp", "r+"); + fwrite($fp, file_get_contents($blob2_path)); + + $m1->setCoverImage($fp); + $this->assertTrue($m1->isModified(), "Expected Media object to be modified, despite fact that stream is to same data"); + + // 5) Test external modification of the stream (and re-setting it into the object) + $stream = $m1->getCoverImage(); + fwrite($stream, file_get_contents($blob_path)); // change the contents of the stream + + $m1->setCoverImage($stream); + + $this->assertTrue($m1->isModified(), "Expected Media object to be modified when stream contents changed."); + $this->assertNotEquals(file_get_contents($blob2_path), stream_get_contents($m1->getCoverImage())); + + $m1->save(); + + // 6) Assert that when we call the setter with a stream, that the file in db gets updated. + + $m1->reload(); // start with a fresh copy from db + + // Ensure that object is set up correctly + $this->assertNotEquals(file_get_contents($blob_path), stream_get_contents($m1->getCoverImage()), "The object is not correctly set up to verify the stream-setting test."); + + $fp = fopen($blob_path, "r"); + $m1->setCoverImage($fp); + $m1->save(); + $m1->reload(); // refresh from db + + // Assert that we've updated the db + $this->assertEquals(file_get_contents($blob_path), stream_get_contents($m1->getCoverImage()), "Expected the updated BLOB value after setting with a stream."); + + // 7) Assert that 'w' mode works + + } + + public function testLobSetting_WriteMode() + { + $blob_path = $this->getLobFile('tin_drum.gif'); + $blob2_path = $this->getLobFile('propel.gif'); + + $clob_path = $this->getLobFile('tin_drum.txt'); + $book = BookPeer::doSelectOne(new Criteria()); + + $m1 = new Media(); + $m1->setBook($book); + $m1->setCoverImage(file_get_contents($blob_path)); + $m1->setExcerpt(file_get_contents($clob_path)); + $m1->save(); + + MediaPeer::clearInstancePool(); + + // make sure we have the latest from the db: + $m2 = MediaPeer::retrieveByPK($m1->getId()); + + // now attempt to assign a temporary stream, opened in 'w' mode, to the db + + $stream = fopen("php://memory", 'w'); + fwrite($stream, file_get_contents($blob2_path)); + $m2->setCoverImage($stream); + $m2->save(); + fclose($stream); + + $m2->reload(); + $this->assertEquals(file_get_contents($blob2_path), stream_get_contents($m2->getCoverImage()), "Expected contents to match when setting stream w/ 'w' mode"); + + $stream2 = fopen("php://memory", 'w+'); + fwrite($stream2, file_get_contents($blob_path)); + rewind($stream2); + $this->assertEquals(file_get_contents($blob_path), stream_get_contents($stream2), "Expecting setup to be correct"); + + $m2->setCoverImage($stream2); + $m2->save(); + $m2->reload(); + + $this->assertEquals(file_get_contents($blob_path), stream_get_contents($m2->getCoverImage()), "Expected contents to match when setting stream w/ 'w+' mode"); + + } + +} diff --git a/library/propel/test/testsuite/generator/builder/om/GeneratedObjectRelTest.php b/library/propel/test/testsuite/generator/builder/om/GeneratedObjectRelTest.php new file mode 100644 index 000000000..465545560 --- /dev/null +++ b/library/propel/test/testsuite/generator/builder/om/GeneratedObjectRelTest.php @@ -0,0 +1,352 @@ + + * @package generator.builder.om + */ +class GeneratedObjectRelTest extends BookstoreEmptyTestBase +{ + + protected function setUp() + { + parent::setUp(); + } + + /** + * Tests one side of a bi-directional setting of many-to-many relationships. + */ + public function testManyToMany_Dir1() + { + $list = new BookClubList(); + $list->setGroupLeader('Archimedes Q. Porter'); + // No save ... + + $book = new Book(); + $book->setTitle( "Jungle Expedition Handbook" ); + $book->setISBN('TEST'); + // No save ... + + $this->assertEquals(0, count($list->getBookListRels()) ); + $this->assertEquals(0, count($book->getBookListRels()) ); + $this->assertEquals(0, count(BookListRelPeer::doSelect(new Criteria())) ); + + $xref = new BookListRel(); + $xref->setBook($book); + $list->addBookListRel($xref); + + $this->assertEquals(1, count($list->getBookListRels())); + $this->assertEquals(1, count($book->getBookListRels()) ); + $this->assertEquals(0, count(BookListRelPeer::doSelect(new Criteria())) ); + + $list->save(); + + $this->assertEquals(1, count($list->getBookListRels()) ); + $this->assertEquals(1, count($book->getBookListRels()) ); + $this->assertEquals(1, count(BookListRelPeer::doSelect(new Criteria())) ); + + } + + /** + * Tests reverse setting of one of many-to-many relationship, with all saves cascaded. + */ + public function testManyToMany_Dir2_Unsaved() + { + $list = new BookClubList(); + $list->setGroupLeader('Archimedes Q. Porter'); + // No save ... + + $book = new Book(); + $book->setTitle( "Jungle Expedition Handbook" ); + $book->setISBN('TEST'); + // No save (yet) ... + + $this->assertEquals(0, count($list->getBookListRels()) ); + $this->assertEquals(0, count($book->getBookListRels()) ); + $this->assertEquals(0, count(BookListRelPeer::doSelect(new Criteria())) ); + + $xref = new BookListRel(); + $xref->setBookClubList($list); + $book->addBookListRel($xref); + + $this->assertEquals(1, count($list->getBookListRels()) ); + $this->assertEquals(1, count($book->getBookListRels()) ); + $this->assertEquals(0, count(BookListRelPeer::doSelect(new Criteria())) ); + $book->save(); + + $this->assertEquals(1, count($list->getBookListRels()) ); + $this->assertEquals(1, count($book->getBookListRels()) ); + $this->assertEquals(1, count(BookListRelPeer::doSelect(new Criteria())) ); + + } + + /** + * Tests reverse setting of relationships, saving one of the objects first. + * @link http://propel.phpdb.org/trac/ticket/508 + */ + public function testManyToMany_Dir2_Saved() + { + $list = new BookClubList(); + $list->setGroupLeader('Archimedes Q. Porter'); + $list->save(); + + $book = new Book(); + $book->setTitle( "Jungle Expedition Handbook" ); + $book->setISBN('TEST'); + // No save (yet) ... + + $this->assertEquals(0, count($list->getBookListRels()) ); + $this->assertEquals(0, count($book->getBookListRels()) ); + $this->assertEquals(0, count(BookListRelPeer::doSelect(new Criteria())) ); + + // Now set the relationship from the opposite direction. + + $xref = new BookListRel(); + $xref->setBookClubList($list); + $book->addBookListRel($xref); + + $this->assertEquals(1, count($list->getBookListRels()) ); + $this->assertEquals(1, count($book->getBookListRels()) ); + $this->assertEquals(0, count(BookListRelPeer::doSelect(new Criteria())) ); + $book->save(); + + $this->assertEquals(1, count($list->getBookListRels()) ); + $this->assertEquals(1, count($book->getBookListRels()) ); + $this->assertEquals(1, count(BookListRelPeer::doSelect(new Criteria())) ); + + } + + public function testManyToManyGetterExists() + { + $this->assertTrue(method_exists('BookClubList', 'getBooks'), 'Object generator correcly adds getter for the crossRefFk'); + $this->assertFalse(method_exists('BookClubList', 'getBookClubLists'), 'Object generator correcly adds getter for the crossRefFk'); + } + + public function testManyToManyGetterNewObject() + { + $blc1 = new BookClubList(); + $books = $blc1->getBooks(); + $this->assertTrue($books instanceof PropelObjectCollection, 'getCrossRefFK() returns a Propel collection'); + $this->assertEquals('Book', $books->getModel(), 'getCrossRefFK() returns a collection of the correct model'); + $this->assertEquals(0, count($books), 'getCrossRefFK() returns an empty list for new objects'); + $query = BookQuery::create() + ->filterByTitle('Harry Potter and the Order of the Phoenix'); + $books = $blc1->getBooks($query); + $this->assertEquals(0, count($books), 'getCrossRefFK() accepts a query as first parameter'); + } + + public function testManyToManyGetter() + { + BookstoreDataPopulator::populate(); + $blc1 = BookClubListQuery::create()->findOneByGroupLeader('Crazyleggs'); + $books = $blc1->getBooks(); + $this->assertTrue($books instanceof PropelObjectCollection, 'getCrossRefFK() returns a Propel collection'); + $this->assertEquals('Book', $books->getModel(), 'getCrossRefFK() returns a collection of the correct model'); + $this->assertEquals(2, count($books), 'getCrossRefFK() returns the correct list of objects'); + $query = BookQuery::create() + ->filterByTitle('Harry Potter and the Order of the Phoenix'); + $books = $blc1->getBooks($query); + $this->assertEquals(1, count($books), 'getCrossRefFK() accepts a query as first parameter'); + } + + public function testManyToManyCounterExists() + { + $this->assertTrue(method_exists('BookClubList', 'countBooks'), 'Object generator correcly adds counter for the crossRefFk'); + $this->assertFalse(method_exists('BookClubList', 'countBookClubLists'), 'Object generator correcly adds counter for the crossRefFk'); + } + + public function testManyToManyCounterNewObject() + { + $blc1 = new BookClubList(); + $nbBooks = $blc1->countBooks(); + $this->assertEquals(0, $nbBooks, 'countCrossRefFK() returns 0 for new objects'); + $query = BookQuery::create() + ->filterByTitle('Harry Potter and the Order of the Phoenix'); + $nbBooks = $blc1->countBooks($query); + $this->assertEquals(0, $nbBooks, 'countCrossRefFK() accepts a query as first parameter'); + } + + public function testManyToManyCounter() + { + BookstoreDataPopulator::populate(); + $blc1 = BookClubListQuery::create()->findOneByGroupLeader('Crazyleggs'); + $nbBooks = $blc1->countBooks(); + $this->assertEquals(2, $nbBooks, 'countCrossRefFK() returns the correct list of objects'); + $query = BookQuery::create() + ->filterByTitle('Harry Potter and the Order of the Phoenix'); + $nbBooks = $blc1->countBooks($query); + $this->assertEquals(1, $nbBooks, 'countCrossRefFK() accepts a query as first parameter'); + } + + public function testManyToManyAdd() + { + $list = new BookClubList(); + $list->setGroupLeader('Archimedes Q. Porter'); + + $book = new Book(); + $book->setTitle( "Jungle Expedition Handbook" ); + $book->setISBN('TEST'); + + $list->addBook($book); + $this->assertEquals(1, $list->countBooks(), 'addCrossFk() sets the internal collection properly'); + $this->assertEquals(1, $list->countBookListRels(), 'addCrossFk() sets the internal cross reference collection properly'); + + $list->save(); + $this->assertFalse($book->isNew(), 'related object is saved if added'); + $rels = $list->getBookListRels(); + $rel = $rels[0]; + $this->assertFalse($rel->isNew(), 'cross object is saved if added'); + + $list->clearBookListRels(); + $list->clearBooks(); + $books = $list->getBooks(); + $expected = new PropelObjectCollection(array($book)); + $expected->setModel('Book'); + $this->assertEquals($expected, $books, 'addCrossFk() adds the object properly'); + $this->assertEquals(1, $list->countBookListRels()); + } + + + /** + * Test behavior of columns that are implicated in multiple foreign keys. + * @link http://propel.phpdb.org/trac/ticket/228 + */ + public function testMultiFkImplication() + { + BookstoreDataPopulator::populate(); + // Create a new bookstore, contest, bookstore_contest, and bookstore_contest_entry + $b = new Bookstore(); + $b->setStoreName("Foo!"); + $b->save(); + + $c = new Contest(); + $c->setName("Bookathon Contest"); + $c->save(); + + $bc = new BookstoreContest(); + $bc->setBookstore($b); + $bc->setContest($c); + $bc->save(); + + $c = new Customer(); + $c->setName("Happy Customer"); + $c->save(); + + $bce = new BookstoreContestEntry(); + $bce->setBookstore($b); + $bce->setBookstoreContest($bc); + $bce->setCustomer($c); + $bce->save(); + + $bce->setBookstoreId(null); + + $this->assertNull($bce->getBookstoreContest()); + $this->assertNull($bce->getBookstore()); + } + + /** + * Test the clearing of related object collection. + * @link http://propel.phpdb.org/trac/ticket/529 + */ + public function testClearRefFk() + { + BookstoreDataPopulator::populate(); + $book = new Book(); + $book->setISBN("Foo-bar-baz"); + $book->setTitle("The book title"); + + // No save ... + + $r = new Review(); + $r->setReviewedBy('Me'); + $r->setReviewDate(new DateTime("now")); + + $book->addReview($r); + + // No save (yet) ... + + $this->assertEquals(1, count($book->getReviews()) ); + $book->clearReviews(); + $this->assertEquals(0, count($book->getReviews())); + } + + /** + * This tests to see whether modified objects are being silently overwritten by calls to fk accessor methods. + * @link http://propel.phpdb.org/trac/ticket/509#comment:5 + */ + public function testModifiedObjectOverwrite() + { + BookstoreDataPopulator::populate(); + $author = new Author(); + $author->setFirstName("John"); + $author->setLastName("Public"); + + $books = $author->getBooks(); // empty, of course + $this->assertEquals(0, count($books), "Expected empty collection."); + + $book = new Book(); + $book->setTitle("A sample book"); + $book->setISBN("INITIAL ISBN"); + + $author->addBook($book); + + $author->save(); + + $book->setISBN("MODIFIED ISBN"); + + $books = $author->getBooks(); + $this->assertEquals(1, count($books), "Expected 1 book."); + $this->assertSame($book, $books[0], "Expected the same object to be returned by fk accessor."); + $this->assertEquals("MODIFIED ISBN", $books[0]->getISBN(), "Expected the modified value NOT to have been overwritten."); + } + + public function testFKGetterUseInstancePool() + { + BookstoreDataPopulator::populate(); + BookPeer::clearInstancePool(); + AuthorPeer::clearInstancePool(); + $con = Propel::getConnection(BookPeer::DATABASE_NAME); + $author = AuthorPeer::doSelectOne(new Criteria(), $con); + // populate book instance pool + $books = $author->getBooks(null, $con); + $sql = $con->getLastExecutedQuery(); + $author = $books[0]->getAuthor($con); + $this->assertEquals($sql, $con->getLastExecutedQuery(), 'refFK getter uses instance pool if possible'); + } + + public function testRefFKGetJoin() + { + BookstoreDataPopulator::populate(); + BookPeer::clearInstancePool(); + AuthorPeer::clearInstancePool(); + PublisherPeer::clearInstancePool(); + $con = Propel::getConnection(BookPeer::DATABASE_NAME); + $author = AuthorPeer::doSelectOne(new Criteria(), $con); + // populate book instance pool + $books = $author->getBooksJoinPublisher(null, $con); + $sql = $con->getLastExecutedQuery(); + $publisher = $books[0]->getPublisher($con); + $this->assertEquals($sql, $con->getLastExecutedQuery(), 'refFK getter uses instance pool if possible'); + } +} diff --git a/library/propel/test/testsuite/generator/builder/om/GeneratedObjectTest.php b/library/propel/test/testsuite/generator/builder/om/GeneratedObjectTest.php new file mode 100644 index 000000000..63dead162 --- /dev/null +++ b/library/propel/test/testsuite/generator/builder/om/GeneratedObjectTest.php @@ -0,0 +1,1355 @@ + + * @package generator.builder.om + */ +class GeneratedObjectTest extends BookstoreEmptyTestBase +{ + protected function setUp() + { + parent::setUp(); + require_once 'tools/helpers/bookstore/behavior/TestAuthor.php'; + } + + /** + * Test saving an object after setting default values for it. + */ + public function testSaveWithDefaultValues() + { + // From the schema.xml, I am relying on the following: + // - that 'Penguin' is the default Name for a Publisher + // - that 2001-01-01 is the default ReviewDate for a Review + + // 1) check regular values (VARCHAR) + $pub = new Publisher(); + $pub->setName('Penguin'); + $pub->save(); + $this->assertTrue($pub->getId() !== null, "Expect Publisher to have been saved when default value set."); + + // 2) check date/time values + $review = new Review(); + // note that this is different from how it's represented in schema, but should resolve to same unix timestamp + $review->setReviewDate('2001-01-01'); + $this->assertTrue($review->isModified(), "Expect Review to have been marked 'modified' after default date/time value set."); + + } + + /** + * Test isModified() to be false after setting default value second time + */ + public function testDefaultValueSetTwice() + { + $pub = new Publisher(); + $pub->setName('Penguin'); + $pub->save(); + + $pubId = $pub->getId(); + + PublisherPeer::clearInstancePool(); + + $pub2 = PublisherPeer::retrieveByPK($pubId); + $pub2->setName('Penguin'); + $this->assertFalse($pub2->isModified(), "Expect Publisher to be not modified after setting default value second time."); + } + + public function testHasApplyDefaultValues() + { + $this->assertTrue(method_exists('Publisher', 'applyDefaultValues'), 'Tables with default values should have an applyDefaultValues() method'); + $this->assertFalse(method_exists('Book', 'applyDefaultValues'), 'Tables with no default values should not have an applyDefaultValues() method'); + } + + /** + * Test default return values. + */ + public function testDefaultValues() + { + $r = new Review(); + $this->assertEquals('2001-01-01', $r->getReviewDate('Y-m-d')); + + $this->assertFalse($r->isModified(), "expected isModified() to be false"); + + $acct = new BookstoreEmployeeAccount(); + $this->assertEquals(true, $acct->getEnabled()); + $this->assertFalse($acct->isModified()); + + $acct->setLogin("testuser"); + $acct->setPassword("testpass"); + $this->assertTrue($acct->isModified()); + } + + /** + * Tests the use of default expressions and the reloadOnInsert and reloadOnUpdate attributes. + * + * @link http://propel.phpdb.org/trac/ticket/378 + * @link http://propel.phpdb.org/trac/ticket/555 + */ + public function testDefaultExpresions() + { + if (Propel::getDb(BookstoreEmployeePeer::DATABASE_NAME) instanceof DBSqlite) { + $this->markTestSkipped("Cannot test default expressions with SQLite"); + } + + $b = new Bookstore(); + $b->setStoreName("Foo!"); + $b->save(); + + $employee = new BookstoreEmployee(); + $employee->setName("Johnny Walker"); + + $acct = new BookstoreEmployeeAccount(); + $acct->setBookstoreEmployee($employee); + $acct->setLogin("test-login"); + + $this->assertNull($acct->getCreated(), "Expected created column to be NULL."); + $this->assertNull($acct->getAuthenticator(), "Expected authenticator column to be NULL."); + + $acct->save(); + + $acct = BookstoreEmployeeAccountPeer::retrieveByPK($acct->getEmployeeId()); + + $this->assertNotNull($acct->getAuthenticator(), "Expected a valid (non-NULL) authenticator column after save."); + $this->assertEquals('Password', $acct->getAuthenticator(), "Expected authenticator='Password' after save."); + $this->assertNotNull($acct->getCreated(), "Expected a valid date after retrieving saved object."); + + $now = new DateTime("now"); + $this->assertEquals($now->format("Y-m-d"), $acct->getCreated("Y-m-d")); + + $acct->setCreated($now); + $this->assertEquals($now->format("Y-m-d"), $acct->getCreated("Y-m-d")); + + // Unfortunately we can't really test the conjunction of reloadOnInsert and reloadOnUpdate when using just + // default values. (At least not in a cross-db way.) + } + + /** + * Tests the use of default expressions and the reloadOnInsert attribute. + * + * @link http://propel.phpdb.org/trac/ticket/378 + * @link http://propel.phpdb.org/trac/ticket/555 + */ + public function testDefaultExpresions_ReloadOnInsert() + { + if (Propel::getDb(BookstoreEmployeePeer::DATABASE_NAME) instanceof DBSqlite) { + $this->markTestSkipped("Cannot test default date expressions with SQLite"); + } + + // Create a new bookstore, contest, bookstore_contest, and bookstore_contest_entry + + $b = new Bookstore(); + $b->setStoreName("Barnes & Noble"); + $b->save(); + + $c = new Contest(); + $c->setName("Bookathon Contest"); + $c->save(); + + $bc = new BookstoreContest(); + $bc->setBookstore($b); + $bc->setContest($c); + $bc->save(); + + $c = new Customer(); + $c->setName("Happy Customer"); + $c->save(); + + $bce = new BookstoreContestEntry(); + $bce->setBookstore($b); + $bce->setBookstoreContest($bc); + $bce->setCustomer($c); + $bce->save(); + + $this->assertNotNull($bce->getEntryDate(), "Expected a non-null entry_date after save."); + } + + /** + * Tests the overriding reloadOnInsert at runtime. + * + * @link http://propel.phpdb.org/trac/ticket/378 + * @link http://propel.phpdb.org/trac/ticket/555 + */ + public function testDefaultExpresions_ReloadOnInsert_Override() + { + if (Propel::getDb(BookstoreEmployeePeer::DATABASE_NAME) instanceof DBSqlite) { + $this->markTestSkipped("Cannot test default date expressions with SQLite"); + } + + // Create a new bookstore, contest, bookstore_contest, and bookstore_contest_entry + $b = new Bookstore(); + $b->setStoreName("Barnes & Noble"); + $b->save(); + + $c = new Contest(); + $c->setName("Bookathon Contest"); + $c->save(); + + $bc = new BookstoreContest(); + $bc->setBookstore($b); + $bc->setContest($c); + $bc->save(); + + $c = new Customer(); + $c->setName("Happy Customer"); + $c->save(); + + $bce = new BookstoreContestEntry(); + $bce->setBookstore($b); + $bce->setBookstoreContest($bc); + $bce->setCustomer($c); + $bce->save(null, $skipReload=true); + + $this->assertNull($bce->getEntryDate(), "Expected a NULL entry_date after save."); + } + + /** + * Tests the use of default expressions and the reloadOnUpdate attribute. + * + * @link http://propel.phpdb.org/trac/ticket/555 + */ + public function testDefaultExpresions_ReloadOnUpdate() + { + $b = new Bookstore(); + $b->setStoreName("Foo!"); + $b->save(); + + $sale = new BookstoreSale(); + $sale->setBookstore(BookstorePeer::doSelectOne(new Criteria())); + $sale->setSaleName("Spring Sale"); + $sale->save(); + + // Expect that default values are set, but not default expressions + $this->assertNull($sale->getDiscount(), "Expected discount to be NULL."); + + $sale->setSaleName("Winter Clearance"); + $sale->save(); + // Since reloadOnUpdate = true, we expect the discount to be set now. + + $this->assertNotNull($sale->getDiscount(), "Expected discount to be non-NULL after save."); + } + + /** + * Tests the overriding reloadOnUpdate at runtime. + * + * @link http://propel.phpdb.org/trac/ticket/378 + * @link http://propel.phpdb.org/trac/ticket/555 + */ + public function testDefaultExpresions_ReloadOnUpdate_Override() + { + $b = new Bookstore(); + $b->setStoreName("Foo!"); + $b->save(); + + $sale = new BookstoreSale(); + $sale->setBookstore(BookstorePeer::doSelectOne(new Criteria())); + $sale->setSaleName("Spring Sale"); + $sale->save(); + + // Expect that default values are set, but not default expressions + $this->assertNull($sale->getDiscount(), "Expected discount to be NULL."); + + $sale->setSaleName("Winter Clearance"); + $sale->save(null, $skipReload=true); + + // Since reloadOnUpdate = true, we expect the discount to be set now. + + $this->assertNull($sale->getDiscount(), "Expected NULL value for discount after save."); + } + + /** + * Test the behavior of date/time/values. + * This requires that the model was built with propel.useDateTimeClass=true. + */ + public function testTemporalValues_PreEpoch() + { + $r = new Review(); + + $preEpochDate = new DateTime('1602-02-02'); + + $r->setReviewDate($preEpochDate); + + $this->assertEquals('1602-02-02', $r->getReviewDate(null)->format("Y-m-d")); + + $r->setReviewDate('1702-02-02'); + + $this->assertTrue($r->isModified()); + + $this->assertEquals('1702-02-02', $r->getReviewDate(null)->format("Y-m-d")); + + // Now test for setting null + $r->setReviewDate(null); + $this->assertNull($r->getReviewDate()); + + } + + /** + * Test setting invalid date/time. + */ + public function testSetTemporalValue_Invalid() + { + $this->markTestSkipped(); + // FIXME - Figure out why this doesn't work (causes a PHP ERROR instead of throwing Exception) in + // the Phing+PHPUnit context + $r = new Review(); + try { + $r->setReviewDate("Invalid Date"); + $this->fail("Expected PropelException when setting date column w/ invalid date"); + } catch (PropelException $x) { + print "Caught expected PropelException: " . $x->__toString(); + } + } + + /** + * Test setting TIMESTAMP columns w/ unix int timestamp. + */ + public function testTemporalValues_Unix() + { + $store = new Bookstore(); + $store->setStoreName("test"); + $store->setStoreOpenTime(strtotime('12:55')); + $store->save(); + $this->assertEquals('12:55', $store->getStoreOpenTime(null)->format('H:i')); + + $acct = new BookstoreEmployeeAccount(); + $acct->setCreated(time()); + $this->assertEquals(date('Y-m-d H:i'), $acct->getCreated('Y-m-d H:i')); + + $review = new Review(); + $review->setReviewDate(time()); + $this->assertEquals(date('Y-m-d'), $review->getReviewDate('Y-m-d')); + } + + /** + * Test setting empty temporal values. + * @link http://propel.phpdb.org/trac/ticket/586 + */ + public function testTemporalValues_Empty() + { + $review = new Review(); + $review->setReviewDate(''); + $this->assertNull($review->getReviewDate()); + } + + /** + * Test setting TIME columns. + */ + public function testTemporalValues_TimeSetting() + { + $store = new Bookstore(); + $store->setStoreName("test"); + $store->setStoreOpenTime("12:55"); + $store->save(); + + $store = new Bookstore(); + $store->setStoreName("test2"); + $store->setStoreOpenTime(new DateTime("12:55")); + $store->save(); + } + + /** + * Test setting TIME columns. + */ + public function testTemporalValues_DateSetting() + { + BookstoreDataPopulator::populate(); + + $r = new Review(); + $r->setBook(BookPeer::doSelectOne(new Criteria())); + $r->setReviewDate(new DateTime('1999-12-20')); + $r->setReviewedBy("Hans"); + $r->setRecommended(false); + $r->save(); + } + + /** + * Testing creating & saving new object & instance pool. + */ + public function testObjectInstances_New() + { + $emp = new BookstoreEmployee(); + $emp->setName(md5(microtime())); + $emp->save(); + $id = $emp->getId(); + + $retrieved = BookstoreEmployeePeer::retrieveByPK($id); + $this->assertSame($emp, $retrieved, "Expected same object (from instance pool)"); + } + + /** + * + */ + public function testObjectInstances_Fkeys() + { + // Establish a relationship between one employee and account + // and then change the employee_id and ensure that the account + // is not pulling the old employee. + + $pub1 = new Publisher(); + $pub1->setName('Publisher 1'); + $pub1->save(); + + $pub2 = new Publisher(); + $pub2->setName('Publisher 2'); + $pub2->save(); + + $book = new Book(); + $book->setTitle("Book Title"); + $book->setISBN("1234"); + $book->setPublisher($pub1); + $book->save(); + + $this->assertSame($pub1, $book->getPublisher()); + + // now change values behind the scenes + $con = Propel::getConnection(BookstoreEmployeeAccountPeer::DATABASE_NAME); + $con->exec("UPDATE " . BookPeer::TABLE_NAME . " SET " + . " publisher_id = " . $pub2->getId() + . " WHERE id = " . $book->getId()); + + + $book2 = BookPeer::retrieveByPK($book->getId()); + $this->assertSame($book, $book2, "Expected same book object instance"); + + $this->assertEquals($pub1->getId(), $book->getPublisherId(), "Expected book to have OLD publisher id before reload()"); + + $book->reload(); + + $this->assertEquals($pub2->getId(), $book->getPublisherId(), "Expected book to have new publisher id"); + $this->assertSame($pub2, $book->getPublisher(), "Expected book to have new publisher object associated."); + + // Now let's set it back, just to be double sure ... + + $con->exec("UPDATE " . BookPeer::TABLE_NAME . " SET " + . " publisher_id = " . $pub1->getId() + . " WHERE id = " . $book->getId()); + + $book->reload(); + + $this->assertEquals($pub1->getId(), $book->getPublisherId(), "Expected book to have old publisher id (again)."); + $this->assertSame($pub1, $book->getPublisher(), "Expected book to have old publisher object associated (again)."); + + } + + /** + * Test the effect of typecast on primary key values and instance pool retrieval. + */ + public function testObjectInstancePoolTypecasting() + { + $reader = new BookReader(); + $reader->setName("Tester"); + $reader->save(); + $readerId = $reader->getId(); + + $book = new Book(); + $book->setTitle("BookTest"); + $book->setISBN("TEST"); + $book->save(); + $bookId = $book->getId(); + + $opinion = new BookOpinion(); + $opinion->setBookId((string)$bookId); + $opinion->setReaderId((string)$readerId); + $opinion->setRating(5); + $opinion->setRecommendToFriend(false); + $opinion->save(); + + + $opinion2 = BookOpinionPeer::retrieveByPK($bookId, $readerId); + + $this->assertSame($opinion, $opinion2, "Expected same object to be retrieved from differently type-casted primary key values."); + + } + + /** + * Test the reload() method. + */ + public function testReload() + { + BookstoreDataPopulator::populate(); + $a = AuthorPeer::doSelectOne(new Criteria()); + + $origName = $a->getFirstName(); + + $a->setFirstName(md5(time())); + + $this->assertNotEquals($origName, $a->getFirstName()); + $this->assertTrue($a->isModified()); + + $a->reload(); + + $this->assertEquals($origName, $a->getFirstName()); + $this->assertFalse($a->isModified()); + + } + + /** + * Test reload(deep=true) method. + */ + public function testReloadDeep() + { + BookstoreDataPopulator::populate(); + + // arbitrary book + $b = BookPeer::doSelectOne(new Criteria()); + + // arbitrary, different author + $c = new Criteria(); + $c->add(AuthorPeer::ID, $b->getAuthorId(), Criteria::NOT_EQUAL); + $a = AuthorPeer::doSelectOne($c); + + $origAuthor = $b->getAuthor(); + + $b->setAuthor($a); + + $this->assertNotEquals($origAuthor, $b->getAuthor(), "Expected just-set object to be different from obj from DB"); + $this->assertTrue($b->isModified()); + + $b->reload($deep=true); + + $this->assertEquals($origAuthor, $b->getAuthor(), "Expected object in DB to be restored"); + $this->assertFalse($a->isModified()); + } + + /** + * Test saving an object and getting correct number of affected rows from save(). + * This includes tests of cascading saves to fk-related objects. + */ + public function testSaveReturnValues() + { + + $author = new Author(); + $author->setFirstName("Mark"); + $author->setLastName("Kurlansky"); + // do not save + + $pub = new Publisher(); + $pub->setName("Penguin Books"); + // do not save + + $book = new Book(); + $book->setTitle("Salt: A World History"); + $book->setISBN("0142001619"); + $book->setAuthor($author); + $book->setPublisher($pub); + + $affected = $book->save(); + $this->assertEquals(3, $affected, "Expected 3 affected rows when saving book + publisher + author."); + + // change nothing ... + $affected = $book->save(); + $this->assertEquals(0, $affected, "Expected 0 affected rows when saving already-saved book."); + + // modify the book (UPDATE) + $book->setTitle("Salt A World History"); + $affected = $book->save(); + $this->assertEquals(1, $affected, "Expected 1 affected row when saving modified book."); + + // modify the related author + $author->setLastName("Kurlanski"); + $affected = $book->save(); + $this->assertEquals(1, $affected, "Expected 1 affected row when saving book with updated author."); + + // modify both the related author and the book + $author->setLastName("Kurlansky"); + $book->setTitle("Salt: A World History"); + $affected = $book->save(); + $this->assertEquals(2, $affected, "Expected 2 affected rows when saving updated book with updated author."); + + } + + /** + * Test deleting an object using the delete() method. + */ + public function testDelete() + { + BookstoreDataPopulator::populate(); + + // 1) grab an arbitrary object + $book = BookPeer::doSelectOne(new Criteria()); + $bookId = $book->getId(); + + // 2) delete it + $book->delete(); + + // 3) make sure it can't be save()d now that it's deleted + try { + $book->setTitle("Will Fail"); + $book->save(); + $this->fail("Expect an exception to be thrown when attempting to save() a deleted object."); + } catch (PropelException $e) {} + + // 4) make sure that it doesn't exist in db + $book = BookPeer::retrieveByPK($bookId); + $this->assertNull($book, "Expect NULL from retrieveByPK on deleted Book."); + + } + + /** + * + */ + public function testNoColsModified() + { + $e1 = new BookstoreEmployee(); + $e1->setName('Employee 1'); + + $e2 = new BookstoreEmployee(); + $e2->setName('Employee 2'); + + $super = new BookstoreEmployee(); + // we don't know who the supervisor is yet + $super->addSubordinate($e1); + $super->addSubordinate($e2); + + $affected = $super->save(); + + } + + /** + * Tests new one-to-one functionality. + */ + public function testOneToOne() + { + BookstoreDataPopulator::populate(); + + $emp = BookstoreEmployeePeer::doSelectOne(new Criteria()); + + $acct = new BookstoreEmployeeAccount(); + $acct->setBookstoreEmployee($emp); + $acct->setLogin("testuser"); + $acct->setPassword("testpass"); + + $this->assertSame($emp->getBookstoreEmployeeAccount(), $acct, "Expected same object instance."); + } + + /** + * Test the type sensitivity of the resturning columns. + * + */ + public function testTypeSensitive() + { + BookstoreDataPopulator::populate(); + + $book = BookPeer::doSelectOne(new Criteria()); + + $r = new Review(); + $r->setReviewedBy("testTypeSensitive Tester"); + $r->setReviewDate(time()); + $r->setBook($book); + $r->setRecommended(true); + $r->save(); + + $id = $r->getId(); + unset($r); + + // clear the instance cache to force reload from database. + ReviewPeer::clearInstancePool(); + BookPeer::clearInstancePool(); + + // reload and verify that the types are the same + $r2 = ReviewPeer::retrieveByPK($id); + + $this->assertType('integer', $r2->getId(), "Expected getId() to return an integer."); + $this->assertType('string', $r2->getReviewedBy(), "Expected getReviewedBy() to return a string."); + $this->assertType('boolean', $r2->getRecommended(), "Expected getRecommended() to return a boolean."); + $this->assertType('Book', $r2->getBook(), "Expected getBook() to return a Book."); + $this->assertType('float', $r2->getBook()->getPrice(), "Expected Book->getPrice() to return a float."); + $this->assertType('DateTime', $r2->getReviewDate(null), "Expected Book->getReviewDate() to return a DateTime."); + + } + + /** + * This is a test for expected exceptions when saving UNIQUE. + * See http://propel.phpdb.org/trac/ticket/2 + */ + public function testSaveUnique() + { + // The whole test is in a transaction, but this test needs real transactions + $this->con->commit(); + + $emp = new BookstoreEmployee(); + $emp->setName(md5(microtime())); + + $acct = new BookstoreEmployeeAccount(); + $acct->setBookstoreEmployee($emp); + $acct->setLogin("foo"); + $acct->setPassword("bar"); + $acct->save(); + + // now attempt to create a new acct + $acct2 = $acct->copy(); + + try { + $acct2->save(); + $this->fail("Expected PropelException in first attempt to save object with duplicate value for UNIQUE constraint."); + } catch (Exception $x) { + try { + // attempt to save it again + $acct3 = $acct->copy(); + $acct3->save(); + $this->fail("Expected PropelException in second attempt to save object with duplicate value for UNIQUE constraint."); + } catch (Exception $x) { + // this is expected. + } + // now let's double check that it can succeed if we're not violating the constraint. + $acct3->setLogin("foo2"); + $acct3->save(); + } + + $this->con->beginTransaction(); + } + + /** + * Test for correct reporting of isModified(). + */ + public function testIsModified() + { + // 1) Basic test + + $a = new Author(); + $a->setFirstName("John"); + $a->setLastName("Doe"); + $a->setAge(25); + + $this->assertTrue($a->isModified(), "Expected Author to be modified after setting values."); + + $a->save(); + + $this->assertFalse($a->isModified(), "Expected Author to be unmodified after saving set values."); + + // 2) Test behavior with setting vars of different types + + // checking setting int col to string val + $a->setAge('25'); + $this->assertFalse($a->isModified(), "Expected Author to be unmodified after setting int column to string-cast of same value."); + + $a->setFirstName("John2"); + $this->assertTrue($a->isModified(), "Expected Author to be modified after changing string column value."); + + // checking setting string col to int val + $a->setFirstName("1"); + $a->save(); + $this->assertFalse($a->isModified(), "Expected Author to be unmodified after saving set values."); + + $a->setFirstName(1); + $this->assertFalse($a->isModified(), "Expected Author to be unmodified after setting string column to int-cast of same value."); + + // 3) Test for appropriate behavior of NULL + + // checking "" -> NULL + $a->setFirstName(""); + $a->save(); + $this->assertFalse($a->isModified(), "Expected Author to be unmodified after saving set values."); + + $a->setFirstName(null); + $this->assertTrue($a->isModified(), "Expected Author to be modified after changing empty string column value to NULL."); + + $a->setFirstName("John"); + $a->setAge(0); + $a->save(); + $this->assertFalse($a->isModified(), "Expected Author to be unmodified after saving set values."); + + $a->setAge(null); + $this->assertTrue($a->isModified(), "Expected Author to be modified after changing 0-value int column to NULL."); + + $a->save(); + $this->assertFalse($a->isModified(), "Expected Author to be unmodified after saving set values."); + + $a->setAge(0); + $this->assertTrue($a->isModified(), "Expected Author to be modified after changing NULL-value int column to 0."); + + } + + /** + * Test the BaseObject#equals(). + */ + public function testEquals() + { + BookstoreDataPopulator::populate(); + + $b = BookPeer::doSelectOne(new Criteria()); + $c = new Book(); + $c->setId($b->getId()); + $this->assertTrue($b->equals($c), "Expected Book objects to be equal()"); + + $a = new Author(); + $a->setId($b->getId()); + $this->assertFalse($b->equals($a), "Expected Book and Author with same primary key NOT to match."); + } + + /** + * Test checking for non-default values. + * @see http://propel.phpdb.org/trac/ticket/331 + */ + public function testHasOnlyDefaultValues() + { + $emp = new BookstoreEmployee(); + $emp->setName(md5(microtime())); + + $acct2 = new BookstoreEmployeeAccount(); + + $acct = new BookstoreEmployeeAccount(); + $acct->setBookstoreEmployee($emp); + $acct->setLogin("foo"); + $acct->setPassword("bar"); + $acct->save(); + + $this->assertFalse($acct->isModified(), "Expected BookstoreEmployeeAccount NOT to be modified after save()."); + + $acct->setEnabled(true); + $acct->setPassword($acct2->getPassword()); + + $this->assertTrue($acct->isModified(), "Expected BookstoreEmployeeAccount to be modified after setting default values."); + + $this->assertTrue($acct->hasOnlyDefaultValues(), "Expected BookstoreEmployeeAccount to not have only default values."); + + $acct->setPassword("bar"); + $this->assertFalse($acct->hasOnlyDefaultValues(), "Expected BookstoreEmployeeAccount to have at one non-default value after setting one value to non-default."); + + // Test a default date/time value + $r = new Review(); + $r->setReviewDate(new DateTime("now")); + $this->assertFalse($r->hasOnlyDefaultValues()); + } + + public function testDefaultFkColVal() + { + BookstoreDataPopulator::populate(); + + $sale = new BookstoreSale(); + $this->assertEquals(1, $sale->getBookstoreId(), "Expected BookstoreSale object to have a default bookstore_id of 1."); + + $bookstore = BookstorePeer::doSelectOne(new Criteria()); + + $sale->setBookstore($bookstore); + $this->assertEquals($bookstore->getId(), $sale->getBookstoreId(), "Expected FK id to have changed when assigned a valid FK."); + + $sale->setBookstore(null); + $this->assertEquals(1, $sale->getBookstoreId(), "Expected BookstoreSale object to have reset to default ID."); + + $sale->setPublisher(null); + $this->assertEquals(null, $sale->getPublisherId(), "Expected BookstoreSale object to have reset to NULL publisher ID."); + } + + public function testCountRefFk() + { + $book = new Book(); + $book->setTitle("Test Book"); + $book->setISBN("TT-EE-SS-TT"); + + $num = 5; + + for ($i=2; $i < $num + 2; $i++) { + $r = new Review(); + $r->setReviewedBy('Hans ' . $num); + $dt = new DateTime("now"); + $dt->modify("-".$i." weeks"); + $r->setReviewDate($dt); + $r->setRecommended(($i % 2) == 0); + $book->addReview($r); + } + + $this->assertEquals($num, $book->countReviews(), "Expected countReviews to return $num"); + $this->assertEquals($num, count($book->getReviews()), "Expected getReviews to return $num reviews"); + + $book->save(); + + BookPeer::clearInstancePool(); + ReviewPeer::clearInstancePool(); + + $book = BookPeer::retrieveByPK($book->getId()); + $this->assertEquals($num, $book->countReviews(), "Expected countReviews() to return $num (after save)"); + $this->assertEquals($num, count($book->getReviews()), "Expected getReviews() to return $num (after save)"); + + // Now set different criteria and expect different results + $c = new Criteria(); + $c->add(ReviewPeer::RECOMMENDED, false); + $this->assertEquals(floor($num/2), $book->countReviews($c), "Expected " . floor($num/2) . " results from countReviews(recomm=false)"); + + // Change Criteria, run again -- expect different. + $c = new Criteria(); + $c->add(ReviewPeer::RECOMMENDED, true); + $this->assertEquals(ceil($num/2), count($book->getReviews($c)), "Expected " . ceil($num/2) . " results from getReviews(recomm=true)"); + + $this->assertEquals($num, $book->countReviews(), "Expected countReviews to return $num with new empty Criteria"); + } + + /** + * Test copyInto method. + */ + public function testCopyInto_Deep() + { + BookstoreDataPopulator::populate(); + + // Test a "normal" object + $c = new Criteria(); + $c->add(BookPeer::TITLE, 'Harry%', Criteria::LIKE); + + $book = BookPeer::doSelectOne($c); + $reviews = $book->getReviews(); + + $b2 = $book->copy(true); + $this->assertType('Book', $b2); + $this->assertNull($b2->getId()); + + $r2 = $b2->getReviews(); + + $this->assertEquals(count($reviews), count($r2)); + + // Test a one-to-one object + $emp = BookstoreEmployeePeer::doSelectOne(new Criteria()); + $e2 = $emp->copy(true); + + $this->assertType('BookstoreEmployee', $e2); + $this->assertNull($e2->getId()); + + $this->assertEquals($emp->getBookstoreEmployeeAccount()->getLogin(), $e2->getBookstoreEmployeeAccount()->getLogin()); + } + + /** + * Test copying when an object has composite primary key. + * @link http://propel.phpdb.org/trac/ticket/618 + */ + public function testCopy_CompositePK() + { + $br = new BookReader(); + $br->setName("TestReader"); + $br->save(); + $br->copy(); + + $b = new Book(); + $b->setTitle("TestBook"); + $b->setISBN("XX-XX-XX-XX"); + $b->save(); + + $op = new BookOpinion(); + $op->setBookReader($br); + $op->setBook($b); + $op->setRating(10); + $op->setRecommendToFriend(true); + $op->save(); + + + $br2 = $br->copy(true); + + $this->assertNull($br2->getId()); + + $opinions = $br2->getBookOpinions(); + $this->assertEquals(1, count($opinions), "Expected to have a related BookOpinion after copy()"); + + // We DO expect the reader_id to be null + $this->assertNull($opinions[0]->getReaderId()); + // but we DO NOT expect the book_id to be null + $this->assertEquals($op->getBookId(), $opinions[0]->getBookId()); + } + + public function testToArray() + { + $b = new Book(); + $b->setTitle('Don Juan'); + + $arr1 = $b->toArray(); + $expectedKeys = array( + 'Id', + 'Title', + 'ISBN', + 'Price', + 'PublisherId', + 'AuthorId' + ); + $this->assertEquals($expectedKeys, array_keys($arr1), 'toArray() returns an associative array with BasePeer::TYPE_PHPNAME keys by default'); + $this->assertEquals('Don Juan', $arr1['Title'], 'toArray() returns an associative array representation of the object'); + } + + public function testToArrayKeyType() + { + $b = new Book(); + $b->setTitle('Don Juan'); + + $arr1 = $b->toArray(BasePeer::TYPE_COLNAME); + $expectedKeys = array( + BookPeer::ID, + BookPeer::TITLE, + BookPeer::ISBN, + BookPeer::PRICE, + BookPeer::PUBLISHER_ID, + BookPeer::AUTHOR_ID + ); + $this->assertEquals($expectedKeys, array_keys($arr1), 'toArray() accepts a $keyType parameter to change the result keys'); + $this->assertEquals('Don Juan', $arr1[BookPeer::TITLE], 'toArray() returns an associative array representation of the object'); + } + + /** + * Test the toArray() method with new lazyLoad param. + * @link http://propel.phpdb.org/trac/ticket/527 + */ + public function testToArrayLazyLoad() + { + BookstoreDataPopulator::populate(); + + $c = new Criteria(); + $c->add(MediaPeer::COVER_IMAGE, null, Criteria::NOT_EQUAL); + $c->add(MediaPeer::EXCERPT, null, Criteria::NOT_EQUAL); + + $m = MediaPeer::doSelectOne($c); + if ($m === null) { + $this->fail("Test requires at least one media row w/ cover_image and excerpt NOT NULL"); + } + + $arr1 = $m->toArray(BasePeer::TYPE_COLNAME); + $this->assertNotNull($arr1[MediaPeer::COVER_IMAGE]); + $this->assertType('resource', $arr1[MediaPeer::COVER_IMAGE]); + + $arr2 = $m->toArray(BasePeer::TYPE_COLNAME, false); + $this->assertNull($arr2[MediaPeer::COVER_IMAGE]); + $this->assertNull($arr2[MediaPeer::EXCERPT]); + + $diffKeys = array_keys(array_diff($arr1, $arr2)); + + $expectedDiff = array(MediaPeer::COVER_IMAGE, MediaPeer::EXCERPT); + + $this->assertEquals($expectedDiff, $diffKeys); + } + + public function testToArrayIncludeForeignObjects() + { + BookstoreDataPopulator::populate(); + BookPeer::clearInstancePool(); + AuthorPeer::clearInstancePool(); + PublisherPeer::clearInstancePool(); + + $c = new Criteria(); + $c->add(BookPeer::TITLE, 'Don Juan'); + $books = BookPeer::doSelectJoinAuthor($c); + $book = $books[0]; + + $arr1 = $book->toArray(BasePeer::TYPE_PHPNAME, null, true); + $expectedKeys = array( + 'Id', + 'Title', + 'ISBN', + 'Price', + 'PublisherId', + 'AuthorId', + 'Author' + ); + $this->assertEquals($expectedKeys, array_keys($arr1), 'toArray() can return sub arrays for hydrated related objects'); + $this->assertEquals('George', $arr1['Author']['FirstName'], 'toArray() can return sub arrays for hydrated related objects'); + + $c = new Criteria(); + $c->add(BookPeer::TITLE, 'Don Juan'); + $books = BookPeer::doSelectJoinAll($c); + $book = $books[0]; + + $arr2 = $book->toArray(BasePeer::TYPE_PHPNAME, null, true); + $expectedKeys = array( + 'Id', + 'Title', + 'ISBN', + 'Price', + 'PublisherId', + 'AuthorId', + 'Publisher', + 'Author' + ); + $this->assertEquals($expectedKeys, array_keys($arr2), 'toArray() can return sub arrays for hydrated related objects'); + } + + /** + * Test regexp validator for ticket:542 + * @link http://propel.phpdb.org/trac/ticket/542 + */ + public function testRegexValidator() + { + $b = new Bookstore(); + $b->setWebsite("http://this.is.valid.com/foo.bar"); + $res = $b->validate(); + $this->assertTrue($res, "Expected URL to validate"); + } + + /** + * Test that setting the auto-increment primary key will result in exception. + */ + public function testSettingAutoIncrementPK() + { + // The whole test is in a transaction, but this test needs real transactions + $this->con->commit(); + + $b = new Bookstore(); + $b->setId(1); + $b->setStoreName("Test"); + try { + $b->save(); + $this->fail("Expected setting auto-increment primary key to result in Exception"); + } catch (Exception $x) { + $this->assertType('PropelException', $x); + } + + // ... but we should silently ignore NULL values, since these are really + // the same as "not set" in PHP world. + $b = new Bookstore(); + $b->setId(null); + $b->setStoreName("Test2"); + try { + $b->save(); + } catch (Exception $x) { + $this->fail("Expected no exception when setting auto-increment primary key to NULL"); + } + // success ... + + $this->con->beginTransaction(); + } + + /** + * Checks wether we are allowed to specify the primary key on a + * table with allowPkInsert=true set + * + * saves the object, gets it from data-source again and then compares + * them for equality (thus the instance pool is also checked) + */ + public function testAllowPkInsertOnIdMethodNativeTable() + { + $cu = new Customer; + $cu->setPrimaryKey(100000); + $cu->save(); + + $this->assertEquals(100000, $cu->getPrimaryKey()); + + $cu2 = CustomerPeer::retrieveByPk(100000); + + $this->assertSame($cu, $cu2); + } + /** + * Checks if it is allowed to save new, empty objects with a auto increment column + */ + public function testAllowEmptyWithAutoIncrement() + { + $bookreader = new BookReader(); + $bookreader->save(); + + $this->assertFalse($bookreader->isNew() ); + } + + /** + * Test foreign key relationships based on references to unique cols but not PK. + * @link http://propel.phpdb.org/trac/ticket/691 + */ + public function testUniqueFkRel() + { + $employee = new BookstoreEmployee(); + $employee->setName("Johnny Walker"); + + $acct = new BookstoreEmployeeAccount(); + $acct->setBookstoreEmployee($employee); + $acct->setLogin("test-login"); + $acct->save(); + $acctId = $acct->getEmployeeId(); + + $al = new AcctAuditLog(); + $al->setBookstoreEmployeeAccount($acct); + $al->save(); + $alId = $al->getId(); + + BookstoreEmployeePeer::clearInstancePool(); + BookstoreEmployeeAccountPeer::clearInstancePool(); + AcctAuditLogPeer::clearInstancePool(); + + $al2 = AcctAuditLogPeer::retrieveByPK($alId); + /* @var $al2 AcctAuditLog */ + $mapacct = $al2->getBookstoreEmployeeAccount(); + $lookupacct = BookstoreEmployeeAccountPeer::retrieveByPK($acctId); + + $logs = $lookupacct->getAcctAuditLogs(); + + $this->assertTrue(count($logs) == 1, "Expected 1 audit log result."); + $this->assertEquals($logs[0]->getId(), $al->getId(), "Expected returned audit log to match created audit log."); + } + + public function testIsPrimaryKeyNull() + { + $b = new Book(); + $this->assertTrue($b->isPrimaryKeyNull()); + $b->setPrimaryKey(123); + $this->assertFalse($b->isPrimaryKeyNull()); + $b->setPrimaryKey(null); + $this->assertTrue($b->isPrimaryKeyNull()); + } + + public function testIsPrimaryKeyNullCompmosite() + { + $b = new BookOpinion(); + $this->assertTrue($b->isPrimaryKeyNull()); + $b->setPrimaryKey(array(123, 456)); + $this->assertFalse($b->isPrimaryKeyNull()); + $b->setPrimaryKey(array(123, null)); + $this->assertFalse($b->isPrimaryKeyNull()); + $b->setPrimaryKey(array(null, 456)); + $this->assertFalse($b->isPrimaryKeyNull()); + $b->setPrimaryKey(array(null, null)); + $this->assertTrue($b->isPrimaryKeyNull()); + } + + public function testAddPrimaryString() + { + $this->assertFalse(method_exists('Author', '__toString'), 'addPrimaryString() does not add a __toString() method if no column has the primaryString attribute'); + $this->assertTrue(method_exists('Book', '__toString'), 'addPrimaryString() adds a __toString() method if a column has the primaryString attribute'); + $book = new Book(); + $book->setTitle('foo'); + $this->assertEquals((string) $book, 'foo', 'addPrimaryString() adds a __toString() method returning the value of the the first column where primaryString is true'); + } + + public function testPreInsert() + { + $author = new TestAuthor(); + $author->setFirstName("bogus"); + $author->setLastName("Lastname"); + $author->save(); + $this->assertEquals('PreInsertedFirstname', $author->getFirstName()); + } + + public function testPreUpdate() + { + $author = new TestAuthor(); + $author->setFirstName("bogus"); + $author->setLastName("Lastname"); + $author->save(); + $author->setNew(false); + $author->save(); + $this->assertEquals('PreUpdatedFirstname', $author->getFirstName()); + } + + public function testPostInsert() + { + $author = new TestAuthor(); + $author->setFirstName("bogus"); + $author->setLastName("Lastname"); + $author->save(); + $this->assertEquals('PostInsertedLastName', $author->getLastName()); + } + + public function testPostUpdate() + { + $author = new TestAuthor(); + $author->setFirstName("bogus"); + $author->setLastName("Lastname"); + $author->save(); + $author->setNew(false); + $author->save(); + $this->assertEquals('PostUpdatedLastName', $author->getLastName()); + } + + public function testPreSave() + { + $author = new TestAuthor(); + $author->setFirstName("bogus"); + $author->setLastName("Lastname"); + $author->save(); + $this->assertEquals('pre@save.com', $author->getEmail()); + } + + public function testPreSaveFalse() + { + $con = Propel::getConnection(AuthorPeer::DATABASE_NAME); + $author = new TestAuthorSaveFalse(); + $author->setFirstName("bogus"); + $author->setLastName("Lastname"); + $res = $author->save($con); + $this->assertEquals(0, $res); + $this->assertEquals('pre@save.com', $author->getEmail()); + $this->assertNotEquals(115, $author->getAge()); + $this->assertTrue($author->isNew()); + $this->assertEquals(1, $con->getNestedTransactionCount()); + } + + public function testPostSave() + { + $author = new TestAuthor(); + $author->setFirstName("bogus"); + $author->setLastName("Lastname"); + $author->save(); + $this->assertEquals(115, $author->getAge()); + } + + public function testPreDelete() + { + $author = new TestAuthor(); + $author->setFirstName("bogus"); + $author->setLastName("Lastname"); + $author->save(); + $author->delete(); + $this->assertEquals("Pre-Deleted", $author->getFirstName()); + } + + public function testPreDeleteFalse() + { + $con = Propel::getConnection(AuthorPeer::DATABASE_NAME); + $author = new TestAuthorDeleteFalse(); + $author->setFirstName("bogus"); + $author->setLastName("Lastname"); + $author->save($con); + $author->delete($con); + $this->assertEquals("Pre-Deleted", $author->getFirstName()); + $this->assertNotEquals("Post-Deleted", $author->getLastName()); + $this->assertFalse($author->isDeleted()); + $this->assertEquals(1, $con->getNestedTransactionCount()); + } + + public function testPostDelete() + { + $author = new TestAuthor(); + $author->setFirstName("bogus"); + $author->setLastName("Lastname"); + $author->save(); + $author->delete(); + $this->assertEquals("Post-Deleted", $author->getLastName()); + } + + public function testMagicVirtualColumnGetter() + { + $book = new Book(); + $book->setVirtualColumn('Foo', 'bar'); + $this->assertEquals('bar', $book->getFoo(), 'generated __call() catches getters for virtual columns'); + $book = new Book(); + $book->setVirtualColumn('foo', 'bar'); + $this->assertEquals('bar', $book->getFoo(), 'generated __call() catches getters for virtual columns starting with a lowercase character'); + } + + public static function conditionsForTestReadOnly() + { + return array( + array('reload'), + array('delete'), + array('save'), + array('doSave'), + ); + } + + /** + * @dataProvider conditionsForTestReadOnly + */ + public function testReadOnly($method) + { + $cv = new ContestView(); + $this->assertFalse(method_exists($cv, $method), 'readOnly tables end up with no ' . $method . ' method in the generated object class'); + } +} \ No newline at end of file diff --git a/library/propel/test/testsuite/generator/builder/om/GeneratedPeerDoDeleteTest.php b/library/propel/test/testsuite/generator/builder/om/GeneratedPeerDoDeleteTest.php new file mode 100644 index 000000000..b68a53ba4 --- /dev/null +++ b/library/propel/test/testsuite/generator/builder/om/GeneratedPeerDoDeleteTest.php @@ -0,0 +1,545 @@ + + * @package generator.builder.om + */ +class GeneratedPeerDoDeleteTest extends BookstoreEmptyTestBase +{ + protected function setUp() + { + parent::setUp(); + BookstoreDataPopulator::populate(); + } + + /** + * Test ability to delete multiple rows via single Criteria object. + */ + public function testDoDelete_MultiTable() { + + $selc = new Criteria(); + $selc->add(BookPeer::TITLE, "Harry Potter and the Order of the Phoenix"); + $hp = BookPeer::doSelectOne($selc); + + // print "Attempting to delete [multi-table] by found pk: "; + $c = new Criteria(); + $c->add(BookPeer::ID, $hp->getId()); + // The only way for multi-delete to work currently + // is to specify the author_id and publisher_id (i.e. the fkeys + // have to be in the criteria). + $c->add(AuthorPeer::ID, $hp->getAuthorId()); + $c->add(PublisherPeer::ID, $hp->getPublisherId()); + $c->setSingleRecord(true); + BookPeer::doDelete($c); + + //print_r(AuthorPeer::doSelect(new Criteria())); + + // check to make sure the right # of records was removed + $this->assertEquals(3, count(AuthorPeer::doSelect(new Criteria())), "Expected 3 authors after deleting."); + $this->assertEquals(3, count(PublisherPeer::doSelect(new Criteria())), "Expected 3 publishers after deleting."); + $this->assertEquals(3, count(BookPeer::doSelect(new Criteria())), "Expected 3 books after deleting."); + } + + /** + * Test using a complex criteria to delete multiple rows from a single table. + */ + public function testDoDelete_ComplexCriteria() { + + //print "Attempting to delete books by complex criteria: "; + $c = new Criteria(); + $cn = $c->getNewCriterion(BookPeer::ISBN, "043935806X"); + $cn->addOr($c->getNewCriterion(BookPeer::ISBN, "0380977427")); + $cn->addOr($c->getNewCriterion(BookPeer::ISBN, "0140422161")); + $c->add($cn); + BookPeer::doDelete($c); + + // now there should only be one book left; "The Tin Drum" + + $books = BookPeer::doSelect(new Criteria()); + + $this->assertEquals(1, count($books), "Expected 1 book remaining after deleting."); + $this->assertEquals("The Tin Drum", $books[0]->getTitle(), "Expect the only remaining book to be 'The Tin Drum'"); + } + + /** + * Test that cascading deletes are happening correctly (whether emulated or native). + */ + public function testDoDelete_Cascade_Simple() + { + + // The 'media' table will cascade from book deletes + + // 1) Assert the row exists right now + + $medias = MediaPeer::doSelect(new Criteria()); + $this->assertTrue(count($medias) > 0, "Expected to find at least one row in 'media' table."); + $media = $medias[0]; + $mediaId = $media->getId(); + + // 2) Delete the owning book + + $owningBookId = $media->getBookId(); + BookPeer::doDelete($owningBookId); + + // 3) Assert that the media row is now also gone + + $obj = MediaPeer::retrieveByPK($mediaId); + $this->assertNull($obj, "Expect NULL when retrieving on no matching Media."); + + } + + /** + * Test that cascading deletes are happening correctly for composite pk. + * @link http://propel.phpdb.org/trac/ticket/544 + */ + public function testDoDelete_Cascade_CompositePK() + { + + $origBceCount = BookstoreContestEntryPeer::doCount(new Criteria()); + + $cust1 = new Customer(); + $cust1->setName("Cust1"); + $cust1->save(); + + $cust2 = new Customer(); + $cust2->setName("Cust2"); + $cust2->save(); + + $c1 = new Contest(); + $c1->setName("Contest1"); + $c1->save(); + + $c2 = new Contest(); + $c2->setName("Contest2"); + $c2->save(); + + $store1 = new Bookstore(); + $store1->setStoreName("Store1"); + $store1->save(); + + $bc1 = new BookstoreContest(); + $bc1->setBookstore($store1); + $bc1->setContest($c1); + $bc1->save(); + + $bc2 = new BookstoreContest(); + $bc2->setBookstore($store1); + $bc2->setContest($c2); + $bc2->save(); + + $bce1 = new BookstoreContestEntry(); + $bce1->setEntryDate("now"); + $bce1->setCustomer($cust1); + $bce1->setBookstoreContest($bc1); + $bce1->save(); + + $bce2 = new BookstoreContestEntry(); + $bce2->setEntryDate("now"); + $bce2->setCustomer($cust1); + $bce2->setBookstoreContest($bc2); + $bce2->save(); + + // Now, if we remove $bc1, we expect *only* bce1 to be no longer valid. + + BookstoreContestPeer::doDelete($bc1); + + $newCount = BookstoreContestEntryPeer::doCount(new Criteria()); + + $this->assertEquals($origBceCount + 1, $newCount, "Expected new number of rows in BCE to be orig + 1"); + + $bcetest = BookstoreContestEntryPeer::retrieveByPK($store1->getId(), $c1->getId(), $cust1->getId()); + $this->assertNull($bcetest, "Expected BCE for store1 to be cascade deleted."); + + $bcetest2 = BookstoreContestEntryPeer::retrieveByPK($store1->getId(), $c2->getId(), $cust1->getId()); + $this->assertNotNull($bcetest2, "Expected BCE for store2 to NOT be cascade deleted."); + + } + + /** + * Test that onDelete="SETNULL" is happening correctly (whether emulated or native). + */ + public function testDoDelete_SetNull() { + + // The 'author_id' column in 'book' table will be set to null when author is deleted. + + // 1) Get an arbitrary book + $c = new Criteria(); + $book = BookPeer::doSelectOne($c); + $bookId = $book->getId(); + $authorId = $book->getAuthorId(); + unset($book); + + // 2) Delete the author for that book + AuthorPeer::doDelete($authorId); + + // 3) Assert that the book.author_id column is now NULL + + $book = BookPeer::retrieveByPK($bookId); + $this->assertNull($book->getAuthorId(), "Expect the book.author_id to be NULL after the author was removed."); + + } + + /** + * Test deleting a row by passing in the primary key to the doDelete() method. + */ + public function testDoDelete_ByPK() { + + // 1) get an arbitrary book + $book = BookPeer::doSelectOne(new Criteria()); + $bookId = $book->getId(); + + // 2) now delete that book + BookPeer::doDelete($bookId); + + // 3) now make sure it's gone + $obj = BookPeer::retrieveByPK($bookId); + $this->assertNull($obj, "Expect NULL when retrieving on no matching Book."); + + } + + public function testDoDelete_ByPks() { + // 1) get all of the books + $books = BookPeer::doSelect(new Criteria()); + $bookCount = count($books); + + // 2) we have enough books to do this test + $this->assertGreaterThan(1, $bookCount, 'There are at least two books'); + + // 3) select two random books + $book1 = $books[0]; + $book2 = $books[1]; + + // 4) delete the books + BookPeer::doDelete(array($book1->getId(), $book2->getId())); + + // 5) we should have two less books than before + $this->assertEquals($bookCount-2, BookPeer::doCount(new Criteria()), 'Two books deleted successfully.'); + } + + /** + * Test deleting a row by passing the generated object to doDelete(). + */ + public function testDoDelete_ByObj() { + + // 1) get an arbitrary book + $book = BookPeer::doSelectOne(new Criteria()); + $bookId = $book->getId(); + + // 2) now delete that book + BookPeer::doDelete($book); + + // 3) now make sure it's gone + $obj = BookPeer::retrieveByPK($bookId); + $this->assertNull($obj, "Expect NULL when retrieving on no matching Book."); + + } + + + /** + * Test the doDeleteAll() method for single table. + */ + public function testDoDeleteAll() { + + BookPeer::doDeleteAll(); + $this->assertEquals(0, count(BookPeer::doSelect(new Criteria())), "Expect all book rows to have been deleted."); + } + + /** + * Test the state of the instance pool after a doDeleteAll() call. + */ + public function testDoDeleteAllInstancePool() + { + $review = ReviewPeer::doSelectOne(new Criteria); + $book = $review->getBook(); + BookPeer::doDeleteAll(); + $this->assertNull(BookPeer::retrieveByPk($book->getId()), 'doDeleteAll invalidates instance pool'); + $this->assertNull(ReviewPeer::retrieveByPk($review->getId()), 'doDeleteAll invalidates instance pool of releted tables with ON DELETE CASCADE'); + } + + /** + * Test the doDeleteAll() method when onDelete="CASCADE". + */ + public function testDoDeleteAll_Cascade() { + + BookPeer::doDeleteAll(); + $this->assertEquals(0, count(MediaPeer::doSelect(new Criteria())), "Expect all media rows to have been cascade deleted."); + $this->assertEquals(0, count(ReviewPeer::doSelect(new Criteria())), "Expect all review rows to have been cascade deleted."); + } + + /** + * Test the doDeleteAll() method when onDelete="SETNULL". + */ + public function testDoDeleteAll_SetNull() { + + $c = new Criteria(); + $c->add(BookPeer::AUTHOR_ID, null, Criteria::NOT_EQUAL); + + // 1) make sure there are some books with valid authors + $this->assertTrue(count(BookPeer::doSelect($c)) > 0, "Expect some book.author_id columns that are not NULL."); + + // 2) delete all the authors + AuthorPeer::doDeleteAll(); + + // 3) now verify that the book.author_id columns are all nul + $this->assertEquals(0, count(BookPeer::doSelect($c)), "Expect all book.author_id columns to be NULL."); + } + + /** + * @link http://propel.phpdb.org/trac/ticket/519 + */ + public function testDoDeleteCompositePK() + { + $con = Propel::getConnection(BookPeer::DATABASE_NAME); + + ReaderFavoritePeer::doDeleteAll(); + // Create books with IDs 1 to 3 + // Create readers with IDs 1 and 2 + + $this->createBookWithId(1); + $this->createBookWithId(2); + $this->createBookWithId(3); + $this->createReaderWithId(1); + $this->createReaderWithId(2); + + for ($i=1; $i <= 3; $i++) { + for ($j=1; $j <= 2; $j++) { + $bo = new BookOpinion(); + $bo->setBookId($i); + $bo->setReaderId($j); + $bo->save(); + + $rf = new ReaderFavorite(); + $rf->setBookId($i); + $rf->setReaderId($j); + $rf->save(); + } + } + + $this->assertEquals(6, ReaderFavoritePeer::doCount(new Criteria())); + + // Now delete 2 of those rows (2 is special in that it is the number of rows + // being deleted, as well as the number of things in the primary key) + ReaderFavoritePeer::doDelete(array(array(1,1), array(2,2))); + $this->assertEquals(4, ReaderFavoritePeer::doCount(new Criteria())); + + //Note: these composite PK's are pairs of (BookId, ReaderId) + $this->assertNotNull(ReaderFavoritePeer::retrieveByPK(2,1)); + $this->assertNotNull(ReaderFavoritePeer::retrieveByPK(1,2)); + $this->assertNotNull(ReaderFavoritePeer::retrieveByPk(3,1)); + $this->assertNotNull(ReaderFavoritePeer::retrieveByPk(3,2)); + $this->assertNull(ReaderFavoritePeer::retrieveByPK(1,1)); + $this->assertNull(ReaderFavoritePeer::retrieveByPK(2,2)); + + //test deletion of a single composite PK + ReaderFavoritePeer::doDelete(array(3,1)); + $this->assertEquals(3, ReaderFavoritePeer::doCount(new Criteria())); + $this->assertNotNull(ReaderFavoritePeer::retrieveByPK(2,1)); + $this->assertNotNull(ReaderFavoritePeer::retrieveByPK(1,2)); + $this->assertNotNull(ReaderFavoritePeer::retrieveByPk(3,2)); + $this->assertNull(ReaderFavoritePeer::retrieveByPK(1,1)); + $this->assertNull(ReaderFavoritePeer::retrieveByPK(2,2)); + $this->assertNull(ReaderFavoritePeer::retrieveByPk(3,1)); + + //test deleting the last three + ReaderFavoritePeer::doDelete(array(array(2,1), array(1,2), array(3,2))); + $this->assertEquals(0, ReaderFavoritePeer::doCount(new Criteria())); + } + + /** + * Test the doInsert() method when passed a Criteria object. + */ + public function testDoInsert_Criteria() { + + $name = "A Sample Publisher - " . time(); + + $values = new Criteria(); + $values->add(PublisherPeer::NAME, $name); + PublisherPeer::doInsert($values); + + $c = new Criteria(); + $c->add(PublisherPeer::NAME, $name); + + $matches = PublisherPeer::doSelect($c); + $this->assertEquals(1, count($matches), "Expect there to be exactly 1 publisher just-inserted."); + $this->assertTrue( 1 != $matches[0]->getId(), "Expected to have different ID than one put in values Criteria."); + + } + + /** + * Test the doInsert() method when passed a generated object. + */ + public function testDoInsert_Obj() { + + $name = "A Sample Publisher - " . time(); + + $values = new Publisher(); + $values->setName($name); + PublisherPeer::doInsert($values); + + $c = new Criteria(); + $c->add(PublisherPeer::NAME, $name); + + $matches = PublisherPeer::doSelect($c); + $this->assertEquals(1, count($matches), "Expect there to be exactly 1 publisher just-inserted."); + $this->assertTrue( 1 != $matches[0]->getId(), "Expected to have different ID than one put in values Criteria."); + + } + + /** + * Tests the return type of doCount*() methods. + */ + public function testDoCountType() + { + $c = new Criteria(); + $this->assertType('integer', BookPeer::doCount($c), "Expected doCount() to return an integer."); + $this->assertType('integer', BookPeer::doCountJoinAll($c), "Expected doCountJoinAll() to return an integer."); + $this->assertType('integer', BookPeer::doCountJoinAuthor($c), "Expected doCountJoinAuthor() to return an integer."); + } + + /** + * Tests the doCount() method with limit/offset. + */ + public function testDoCountLimitOffset() + { + BookPeer::doDeleteAll(); + + for ($i=0; $i < 25; $i++) { + $b = new Book(); + $b->setTitle("Book $i"); + $b->setISBN("ISBN $i"); + $b->save(); + } + + $c = new Criteria(); + $totalCount = BookPeer::doCount($c); + + $this->assertEquals(25, $totalCount); + + $c2 = new Criteria(); + $c2->setLimit(10); + $this->assertEquals(10, BookPeer::doCount($c2)); + + $c3 = new Criteria(); + $c3->setOffset(10); + $this->assertEquals(15, BookPeer::doCount($c3)); + + $c4 = new Criteria(); + $c4->setOffset(5); + $c4->setLimit(5); + $this->assertEquals(5, BookPeer::doCount($c4)); + + $c5 = new Criteria(); + $c5->setOffset(20); + $c5->setLimit(10); + $this->assertEquals(5, BookPeer::doCount($c5)); + } + + /** + * Test doCountJoin*() methods. + */ + public function testDoCountJoin() + { + BookPeer::doDeleteAll(); + + for ($i=0; $i < 25; $i++) { + $b = new Book(); + $b->setTitle("Book $i"); + $b->setISBN("ISBN $i"); + $b->save(); + } + + $c = new Criteria(); + $totalCount = BookPeer::doCount($c); + + $this->assertEquals($totalCount, BookPeer::doCountJoinAuthor($c)); + $this->assertEquals($totalCount, BookPeer::doCountJoinPublisher($c)); + } + + /** + * Test doCountJoin*() methods with ORDER BY columns in Criteria. + * @link http://propel.phpdb.org/trac/ticket/627 + */ + public function testDoCountJoinWithOrderBy() + { + $c = new Criteria(BookPeer::DATABASE_NAME); + $c->addAscendingOrderByColumn(BookPeer::ID); + + // None of these should not throw an exception! + BookPeer::doCountJoinAll($c); + BookPeer::doCountJoinAllExceptAuthor($c); + BookPeer::doCountJoinAuthor($c); + } + + /** + * Test passing null values to removeInstanceFromPool(). + */ + public function testRemoveInstanceFromPool_Null() + { + // if it throws an exception, then it's broken. + try { + BookPeer::removeInstanceFromPool(null); + } catch (Exception $x) { + $this->fail("Expected to get no exception when removing an instance from the pool."); + } + } + + /** + * @see testDoDeleteCompositePK() + */ + private function createBookWithId($id) + { + $con = Propel::getConnection(BookPeer::DATABASE_NAME); + $b = BookPeer::retrieveByPK($id); + if (!$b) { + $b = new Book(); + $b->setTitle("Book$id")->setISBN("BookISBN$id")->save(); + $b1Id = $b->getId(); + $sql = "UPDATE " . BookPeer::TABLE_NAME . " SET id = ? WHERE id = ?"; + $stmt = $con->prepare($sql); + $stmt->bindValue(1, $id); + $stmt->bindValue(2, $b1Id); + $stmt->execute(); + } + } + + /** + * @see testDoDeleteCompositePK() + */ + private function createReaderWithId($id) + { + $con = Propel::getConnection(BookReaderPeer::DATABASE_NAME); + $r = BookReaderPeer::retrieveByPK($id); + if (!$r) { + $r = new BookReader(); + $r->setName('Reader'.$id)->save(); + $r1Id = $r->getId(); + $sql = "UPDATE " . BookReaderPeer::TABLE_NAME . " SET id = ? WHERE id = ?"; + $stmt = $con->prepare($sql); + $stmt->bindValue(1, $id); + $stmt->bindValue(2, $r1Id); + $stmt->execute(); + } + } + +} diff --git a/library/propel/test/testsuite/generator/builder/om/GeneratedPeerDoSelectTest.php b/library/propel/test/testsuite/generator/builder/om/GeneratedPeerDoSelectTest.php new file mode 100644 index 000000000..e6be9d3f6 --- /dev/null +++ b/library/propel/test/testsuite/generator/builder/om/GeneratedPeerDoSelectTest.php @@ -0,0 +1,439 @@ + + * @package generator.builder.om + */ +class GeneratedPeerDoSelectTest extends BookstoreEmptyTestBase +{ + protected function setUp() + { + parent::setUp(); + BookstoreDataPopulator::populate(); + } + + public function testDoSelect() + { + $books = BookPeer::doSelect(new Criteria()); + $this->assertEquals(4, count($books), 'doSelect() with an empty Criteria returns all results'); + $book1 = $books[0]; + + $c = new Criteria(); + $c->add(BookPeer::ID, $book1->getId()); + $res = BookPeer::doSelect($c); + $this->assertEquals(array($book1), $res, 'doSelect() accepts a Criteria object with a condition'); + + $c = new Criteria(); + $c->add(BookPeer::ID, $book1->getId()); + $c->add(BookPeer::TITLE, $book1->getTitle()); + $res = BookPeer::doSelect($c); + $this->assertEquals(array($book1), $res, 'doSelect() accepts a Criteria object with several condition'); + + $c = new Criteria(); + $c->add(BookPeer::ID, 'foo'); + $res = BookPeer::doSelect($c); + $this->assertEquals(array(), $res, 'doSelect() accepts an incorrect Criteria'); + } + + /** + * Tests performing doSelect() and doSelectJoin() using LIMITs. + */ + public function testDoSelect_Limit() { + + // 1) get the total number of items in a particular table + $count = BookPeer::doCount(new Criteria()); + + $this->assertTrue($count > 1, "Need more than 1 record in books table to perform this test."); + + $limitcount = $count - 1; + + $lc = new Criteria(); + $lc->setLimit($limitcount); + + $results = BookPeer::doSelect($lc); + + $this->assertEquals($limitcount, count($results), "Expected $limitcount results from BookPeer::doSelect()"); + + // re-create it just to avoid side-effects + $lc2 = new Criteria(); + $lc2->setLimit($limitcount); + $results2 = BookPeer::doSelectJoinAuthor($lc2); + + $this->assertEquals($limitcount, count($results2), "Expected $limitcount results from BookPeer::doSelectJoinAuthor()"); + + } + + /** + * Test the basic functionality of the doSelectJoin*() methods. + */ + public function testDoSelectJoin() + { + + BookPeer::clearInstancePool(); + + $c = new Criteria(); + + $books = BookPeer::doSelect($c); + $obj = $books[0]; + // $size = strlen(serialize($obj)); + + BookPeer::clearInstancePool(); + + $joinBooks = BookPeer::doSelectJoinAuthor($c); + $obj2 = $joinBooks[0]; + $obj2Array = $obj2->toArray(BasePeer::TYPE_PHPNAME, true, true); + // $joinSize = strlen(serialize($obj2)); + + $this->assertEquals(count($books), count($joinBooks), "Expected to find same number of rows in doSelectJoin*() call as doSelect() call."); + + // $this->assertTrue($joinSize > $size, "Expected a serialized join object to be larger than a non-join object."); + + $this->assertTrue(array_key_exists('Author', $obj2Array)); + } + + /** + * Test the doSelectJoin*() methods when the related object is NULL. + */ + public function testDoSelectJoin_NullFk() + { + $b1 = new Book(); + $b1->setTitle("Test NULLFK 1"); + $b1->setISBN("NULLFK-1"); + $b1->save(); + + $b2 = new Book(); + $b2->setTitle("Test NULLFK 2"); + $b2->setISBN("NULLFK-2"); + $b2->setAuthor(new Author()); + $b2->getAuthor()->setFirstName("Hans")->setLastName("L"); + $b2->save(); + + BookPeer::clearInstancePool(); + AuthorPeer::clearInstancePool(); + + $c = new Criteria(); + $c->add(BookPeer::ISBN, 'NULLFK-%', Criteria::LIKE); + $c->addAscendingOrderByColumn(BookPeer::ISBN); + + $matches = BookPeer::doSelectJoinAuthor($c); + $this->assertEquals(2, count($matches), "Expected 2 matches back from new books; got back " . count($matches)); + + $this->assertNull($matches[0]->getAuthor(), "Expected first book author to be null"); + $this->assertType('Author', $matches[1]->getAuthor(), "Expected valid Author object for second book."); + } + + public function testDoSelectJoinOneToOne() + { + $con = Propel::getConnection(); + $count = $con->getQueryCount(); + Propel::disableInstancePooling(); + $c = new Criteria(); + $accs = BookstoreEmployeeAccountPeer::doSelectJoinBookstoreEmployee($c); + Propel::enableInstancePooling(); + $this->assertEquals(1, $con->getQueryCount() - $count, 'doSelectJoin() makes only one query in a one-to-one relationship'); + } + + public function testDoSelectOne() + { + $books = BookPeer::doSelect(new Criteria()); + $book1 = $books[0]; + + $c = new Criteria(); + $c->add(BookPeer::ID, $book1->getId()); + $res = BookPeer::doSelectOne($c); + $this->assertEquals($book1, $res, 'doSelectOne() returns a single object'); + + $c = new Criteria(); + $c->add(BookPeer::ID, 'foo'); + $res = BookPeer::doSelectOne($c); + $this->assertNull($res, 'doSelectOne() returns null if the Criteria matches no record'); + } + + public function testObjectInstances() + { + + $sample = BookPeer::doSelectOne(new Criteria()); + $samplePk = $sample->getPrimaryKey(); + + // 1) make sure consecutive calls to retrieveByPK() return the same object. + + $b1 = BookPeer::retrieveByPK($samplePk); + $b2 = BookPeer::retrieveByPK($samplePk); + + $sampleval = md5(microtime()); + + $this->assertTrue($b1 === $b2, "Expected object instances to match for calls with same retrieveByPK() method signature."); + + // 2) make sure that calls to doSelect also return references to the same objects. + $allbooks = BookPeer::doSelect(new Criteria()); + foreach ($allbooks as $testb) { + if ($testb->getPrimaryKey() == $b1->getPrimaryKey()) { + $this->assertTrue($testb === $b1, "Expected same object instance from doSelect() as from retrieveByPK()"); + } + } + + // 3) test fetching related objects + $book = BookPeer::retrieveByPK($samplePk); + + $bookauthor = $book->getAuthor(); + + $author = AuthorPeer::retrieveByPK($bookauthor->getId()); + + $this->assertTrue($bookauthor === $author, "Expected same object instance when calling fk object accessor as retrieveByPK()"); + + // 4) test a doSelectJoin() + $morebooks = BookPeer::doSelectJoinAuthor(new Criteria()); + for ($i=0,$j=0; $j < count($morebooks); $i++, $j++) { + $testb1 = $allbooks[$i]; + $testb2 = $allbooks[$j]; + $this->assertTrue($testb1 === $testb2, "Expected the same objects from consecutive doSelect() calls."); + // we could probably also test this by just verifying that $book & $testb are the same + if ($testb1->getPrimaryKey() === $book) { + $this->assertTrue($book->getAuthor() === $testb1->getAuthor(), "Expected same author object in calls to pkey-matching books."); + } + } + + + // 5) test creating a new object, saving it, and then retrieving that object (should all be same instance) + $b = new BookstoreEmployee(); + $b->setName("Testing"); + $b->setJobTitle("Testing"); + $b->save(); + + $empId = $b->getId(); + + $this->assertSame($b, BookstoreEmployeePeer::retrieveByPK($empId), "Expected newly saved object to be same instance as pooled."); + + } + + /** + * Test inheritance features. + */ + public function testInheritance() + { + $manager = new BookstoreManager(); + $manager->setName("Manager 1"); + $manager->setJobTitle("Warehouse Manager"); + $manager->save(); + $managerId = $manager->getId(); + + $employee = new BookstoreEmployee(); + $employee->setName("Employee 1"); + $employee->setJobTitle("Janitor"); + $employee->setSupervisorId($managerId); + $employee->save(); + $empId = $employee->getId(); + + $cashier = new BookstoreCashier(); + $cashier->setName("Cashier 1"); + $cashier->setJobTitle("Cashier"); + $cashier->save(); + $cashierId = $cashier->getId(); + + // 1) test the pooled instances' + $c = new Criteria(); + $c->add(BookstoreEmployeePeer::ID, array($managerId, $empId, $cashierId), Criteria::IN); + $c->addAscendingOrderByColumn(BookstoreEmployeePeer::ID); + + $objects = BookstoreEmployeePeer::doSelect($c); + + $this->assertEquals(3, count($objects), "Expected 3 objects to be returned."); + + list($o1, $o2, $o3) = $objects; + + $this->assertSame($o1, $manager); + $this->assertSame($o2, $employee); + $this->assertSame($o3, $cashier); + + // 2) test a forced reload from database + BookstoreEmployeePeer::clearInstancePool(); + + list($o1,$o2,$o3) = BookstoreEmployeePeer::doSelect($c); + + $this->assertTrue($o1 instanceof BookstoreManager, "Expected BookstoreManager object, got " . get_class($o1)); + $this->assertTrue($o2 instanceof BookstoreEmployee, "Expected BookstoreEmployee object, got " . get_class($o2)); + $this->assertTrue($o3 instanceof BookstoreCashier, "Expected BookstoreCashier object, got " . get_class($o3)); + + } + + /** + * Test hydration of joined rows that contain lazy load columns. + * @link http://propel.phpdb.org/trac/ticket/464 + */ + public function testHydrationJoinLazyLoad() + { + BookstoreEmployeeAccountPeer::doDeleteAll(); + BookstoreEmployeePeer::doDeleteAll(); + AcctAccessRolePeer::doDeleteAll(); + + $bemp2 = new BookstoreEmployee(); + $bemp2->setName("Pieter"); + $bemp2->setJobTitle("Clerk"); + $bemp2->save(); + + $role = new AcctAccessRole(); + $role->setName("Admin"); + + $bempacct = new BookstoreEmployeeAccount(); + $bempacct->setBookstoreEmployee($bemp2); + $bempacct->setAcctAccessRole($role); + $bempacct->setLogin("john"); + $bempacct->setPassword("johnp4ss"); + $bempacct->save(); + + $c = new Criteria(); + $results = BookstoreEmployeeAccountPeer::doSelectJoinAll($c); + $o = $results[0]; + + $this->assertEquals('Admin', $o->getAcctAccessRole()->getName()); + } + + /** + * Testing foreign keys with multiple referrer columns. + * @link http://propel.phpdb.org/trac/ticket/606 + */ + public function testMultiColFk() + { + $con = Propel::getConnection(BookPeer::DATABASE_NAME); + + ReaderFavoritePeer::doDeleteAll(); + + $b1 = new Book(); + $b1->setTitle("Book1"); + $b1->setISBN("ISBN-1"); + $b1->save(); + + $r1 = new BookReader(); + $r1-> setName("Me"); + $r1->save(); + + $bo1 = new BookOpinion(); + $bo1->setBookId($b1->getId()); + $bo1->setReaderId($r1->getId()); + $bo1->setRating(9); + $bo1->setRecommendToFriend(true); + $bo1->save(); + + $rf1 = new ReaderFavorite(); + $rf1->setReaderId($r1->getId()); + $rf1->setBookId($b1->getId()); + $rf1->save(); + + $c = new Criteria(ReaderFavoritePeer::DATABASE_NAME); + $c->add(ReaderFavoritePeer::BOOK_ID, $b1->getId()); + $c->add(ReaderFavoritePeer::READER_ID, $r1->getId()); + + $results = ReaderFavoritePeer::doSelectJoinBookOpinion($c); + $this->assertEquals(1, count($results), "Expected 1 result"); + } + + /** + * Testing foreign keys with multiple referrer columns. + * @link http://propel.phpdb.org/trac/ticket/606 + */ + public function testMultiColJoin() + { + BookstoreContestPeer::doDeleteAll(); + BookstoreContestEntryPeer::doDeleteAll(); + + $bs = new Bookstore(); + $bs->setStoreName("Test1"); + $bs->setPopulationServed(5); + $bs->save(); + $bs1Id = $bs->getId(); + + $bs2 = new Bookstore(); + $bs2->setStoreName("Test2"); + $bs2->setPopulationServed(5); + $bs2->save(); + $bs2Id = $bs2->getId(); + + $ct1 = new Contest(); + $ct1->setName("Contest1!"); + $ct1->save(); + $ct1Id = $ct1->getId(); + + $ct2 = new Contest(); + $ct2->setName("Contest2!"); + $ct2->save(); + $ct2Id = $ct2->getId(); + + $cmr = new Customer(); + $cmr->setName("Customer1"); + $cmr->save(); + $cmr1Id = $cmr->getId(); + + $cmr2 = new Customer(); + $cmr2->setName("Customer2"); + $cmr2->save(); + $cmr2Id = $cmr2->getId(); + + $contest = new BookstoreContest(); + $contest->setBookstoreId($bs1Id); + $contest->setContestId($ct1Id); + $contest->save(); + + $contest = new BookstoreContest(); + $contest->setBookstoreId($bs2Id); + $contest->setContestId($ct1Id); + $contest->save(); + + $entry = new BookstoreContestEntry(); + $entry->setBookstoreId($bs1Id); + $entry->setContestId($ct1Id); + $entry->setCustomerId($cmr1Id); + $entry->save(); + + $entry = new BookstoreContestEntry(); + $entry->setBookstoreId($bs1Id); + $entry->setContestId($ct1Id); + $entry->setCustomerId($cmr2Id); + $entry->save(); + + // Note: this test isn't really working very well. We setup fkeys that + // require that the BookstoreContest rows exist and then try to violate + // the rules ... :-/ This may work in some lenient databases, but an error + // is expected here. + + /* + * Commented out for now ... though without it, this test may not really be testing anything + $entry = new BookstoreContestEntry(); + $entry->setBookstoreId($bs1Id); + $entry->setContestId($ct2Id); + $entry->setCustomerId($cmr2Id); + $entry->save(); + */ + + + $c = new Criteria(); + $c->addJoin(array(BookstoreContestEntryPeer::BOOKSTORE_ID, BookstoreContestEntryPeer::CONTEST_ID), array(BookstoreContestPeer::BOOKSTORE_ID, BookstoreContestPeer::CONTEST_ID) ); + + $results = BookstoreContestEntryPeer::doSelect($c); + $this->assertEquals(2, count($results) ); + foreach ($results as $result) { + $this->assertEquals($bs1Id, $result->getBookstoreId() ); + $this->assertEquals($ct1Id, $result->getContestId() ); + } + } +} diff --git a/library/propel/test/testsuite/generator/builder/om/GeneratedPeerTest.php b/library/propel/test/testsuite/generator/builder/om/GeneratedPeerTest.php new file mode 100644 index 000000000..91d3755ce --- /dev/null +++ b/library/propel/test/testsuite/generator/builder/om/GeneratedPeerTest.php @@ -0,0 +1,90 @@ + + * @package generator.builder.om + */ +class GeneratedPeerTest extends BookstoreTestBase +{ + public function testAlias() + { + $this->assertEquals('foo.ID', BookPeer::alias('foo', BookPeer::ID), 'alias() returns a column name using the table alias'); + $this->assertEquals('book.ID', BookPeer::alias('book', BookPeer::ID), 'alias() returns a column name using the table alias'); + $this->assertEquals('foo.COVER_IMAGE', MediaPeer::alias('foo', MediaPeer::COVER_IMAGE), 'alias() also works for lazy-loaded columns'); + $this->assertEquals('foo.SUBTITLE', EssayPeer::alias('foo', EssayPeer::SUBTITLE), 'alias() also works for columns with custom phpName'); + } + + public function testAddSelectColumns() + { + $c = new Criteria(); + BookPeer::addSelectColumns($c); + $expected = array( + BookPeer::ID, + BookPeer::TITLE, + BookPeer::ISBN, + BookPeer::PRICE, + BookPeer::PUBLISHER_ID, + BookPeer::AUTHOR_ID + ); + $this->assertEquals($expected, $c->getSelectColumns(), 'addSelectColumns() adds the columns of the model to the criteria'); + } + + public function testAddSelectColumnsLazyLoad() + { + $c = new Criteria(); + MediaPeer::addSelectColumns($c); + $expected = array( + MediaPeer::ID, + MediaPeer::BOOK_ID + ); + $this->assertEquals($expected, $c->getSelectColumns(), 'addSelectColumns() does not add lazy loaded columns'); + } + + public function testAddSelectColumnsAlias() + { + $c = new Criteria(); + BookPeer::addSelectColumns($c, 'foo'); + $expected = array( + 'foo.ID', + 'foo.TITLE', + 'foo.ISBN', + 'foo.PRICE', + 'foo.PUBLISHER_ID', + 'foo.AUTHOR_ID' + ); + $this->assertEquals($expected, $c->getSelectColumns(), 'addSelectColumns() uses the second parameter as a table alias'); + } + + public function testAddSelectColumnsAliasLazyLoad() + { + $c = new Criteria(); + MediaPeer::addSelectColumns($c, 'bar'); + $expected = array( + 'bar.ID', + 'bar.BOOK_ID' + ); + $this->assertEquals($expected, $c->getSelectColumns(), 'addSelectColumns() does not add lazy loaded columns but uses the second parameter as an alias'); + } + +} diff --git a/library/propel/test/testsuite/generator/builder/om/OMBuilderNamespaceTest.php b/library/propel/test/testsuite/generator/builder/om/OMBuilderNamespaceTest.php new file mode 100755 index 000000000..c78f9532a --- /dev/null +++ b/library/propel/test/testsuite/generator/builder/om/OMBuilderNamespaceTest.php @@ -0,0 +1,149 @@ +addTable($t); + $builder = new TestableOMBuilder2($t); + $this->assertNull($builder->getNamespace(), 'Builder namespace is null when neither the db nor the table have namespace'); + } + + public function testDbNamespace() + { + $d = new Database('fooDb'); + $d->setNamespace('Foo\\Bar'); + $t = new Table('fooTable'); + $d->addTable($t); + $builder = new TestableOMBuilder2($t); + $this->assertEquals('Foo\\Bar', $builder->getNamespace(), 'Builder namespace is the database namespace when no table namespace is set'); + } + + public function testTableNamespace() + { + $d = new Database('fooDb'); + $t = new Table('fooTable'); + $t->setNamespace('Foo\\Bar'); + $d->addTable($t); + $builder = new TestableOMBuilder2($t); + $this->assertEquals('Foo\\Bar', $builder->getNamespace(), 'Builder namespace is the table namespace when no database namespace is set'); + } + + public function testAbsoluteTableNamespace() + { + $d = new Database('fooDb'); + $t = new Table('fooTable'); + $t->setNamespace('\\Foo\\Bar'); + $d->addTable($t); + $builder = new TestableOMBuilder2($t); + $this->assertEquals('Foo\\Bar', $builder->getNamespace(), 'Builder namespace is the table namespace when it is set as absolute'); + } + + public function testAbsoluteTableNamespaceAndDbNamespace() + { + $d = new Database('fooDb'); + $d->setNamespace('Baz'); + $t = new Table('fooTable'); + $t->setNamespace('\\Foo\\Bar'); + $d->addTable($t); + $builder = new TestableOMBuilder2($t); + $this->assertEquals('Foo\\Bar', $builder->getNamespace(), 'Builder namespace is the table namespace when it is set as absolute'); + } + + public function testTableNamespaceAndDbNamespace() + { + $d = new Database('fooDb'); + $d->setNamespace('Baz'); + $t = new Table('fooTable'); + $t->setNamespace('Foo\\Bar'); + $d->addTable($t); + $builder = new TestableOMBuilder2($t); + $this->assertEquals('Baz\\Foo\\Bar', $builder->getNamespace(), 'Builder namespace is composed from the database and table namespaces when both are set'); + } + + public function testDeclareClassNamespace() + { + $builder = new TestableOMBuilder2(new Table('fooTable')); + $builder->declareClassNamespace('Foo'); + $this->assertEquals(array('' => array('Foo')), $builder->getDeclaredClasses()); + $builder->declareClassNamespace('Bar'); + $this->assertEquals(array('' => array('Foo', 'Bar')), $builder->getDeclaredClasses()); + $builder->declareClassNamespace('Foo'); + $this->assertEquals(array('' => array('Foo', 'Bar')), $builder->getDeclaredClasses()); + $builder = new TestableOMBuilder2(new Table('fooTable')); + $builder->declareClassNamespace('Foo', 'Foo'); + $this->assertEquals(array('Foo' => array('Foo')), $builder->getDeclaredClasses()); + $builder->declareClassNamespace('Bar', 'Foo'); + $this->assertEquals(array('Foo' => array('Foo', 'Bar')), $builder->getDeclaredClasses()); + $builder->declareClassNamespace('Foo', 'Foo'); + $this->assertEquals(array('Foo' => array('Foo', 'Bar')), $builder->getDeclaredClasses()); + $builder->declareClassNamespace('Bar', 'Bar'); + $this->assertEquals(array('Foo' => array('Foo', 'Bar'), 'Bar' => array('Bar')), $builder->getDeclaredClasses()); + } + + public function testGetDeclareClass() + { + $builder = new TestableOMBuilder2(new Table('fooTable')); + $this->assertEquals(array(), $builder->getDeclaredClasses()); + $builder->declareClass('\\Foo'); + $this->assertEquals(array('Foo'), $builder->getDeclaredClasses('')); + $builder->declareClass('Bar'); + $this->assertEquals(array('Foo', 'Bar'), $builder->getDeclaredClasses('')); + $builder->declareClass('Foo\\Bar'); + $this->assertEquals(array('Bar'), $builder->getDeclaredClasses('Foo')); + $builder->declareClass('Foo\\Bar\\Baz'); + $this->assertEquals(array('Bar'), $builder->getDeclaredClasses('Foo')); + $this->assertEquals(array('Baz'), $builder->getDeclaredClasses('Foo\\Bar')); + $builder->declareClass('\\Hello\\World'); + $this->assertEquals(array('World'), $builder->getDeclaredClasses('Hello')); + } + + public function testDeclareClasses() + { + $builder = new TestableOMBuilder2(new Table('fooTable')); + $builder->declareClasses('Foo', '\\Bar', 'Baz\\Baz', 'Hello\\Cruel\\World'); + $expected = array( + '' => array('Foo', 'Bar'), + 'Baz' => array('Baz'), + 'Hello\\Cruel' => array('World') + ); + $this->assertEquals($expected, $builder->getDeclaredClasses()); + } +} + +class TestableOMBuilder2 extends OMBuilder +{ + public static function getRelatedBySuffix(ForeignKey $fk) + { + return parent::getRelatedBySuffix($fk); + } + + public static function getRefRelatedBySuffix(ForeignKey $fk) + { + return parent::getRefRelatedBySuffix($fk); + } + + public function getUnprefixedClassname() {} +} \ No newline at end of file diff --git a/library/propel/test/testsuite/generator/builder/om/OMBuilderTest.php b/library/propel/test/testsuite/generator/builder/om/OMBuilderTest.php new file mode 100644 index 000000000..1d7162e70 --- /dev/null +++ b/library/propel/test/testsuite/generator/builder/om/OMBuilderTest.php @@ -0,0 +1,89 @@ +parseFile('fixtures/bookstore/schema.xml'); + $this->database = $appData->getDatabase("bookstore"); + } + + protected function getForeignKey($tableName, $index) + { + $fks = $this->database->getTable($tableName)->getForeignKeys(); + return $fks[$index]; + } + + public static function getRelatedBySuffixDataProvider() + { + return array( + array('book', 0, '', ''), + array('essay', 0, 'RelatedByFirstAuthor', 'RelatedByFirstAuthor'), + array('essay', 1, 'RelatedBySecondAuthor', 'RelatedBySecondAuthor'), + array('essay', 2, 'RelatedById', 'RelatedByNextEssayId'), + array('bookstore_employee', 0, 'RelatedById', 'RelatedBySupervisorId'), + array('composite_essay', 0, 'RelatedById0', 'RelatedByFirstEssayId'), + array('composite_essay', 1, 'RelatedById1', 'RelatedBySecondEssayId'), + array('man', 0, 'RelatedByWifeId', 'RelatedByWifeId'), + array('woman', 0, 'RelatedByHusbandId', 'RelatedByHusbandId'), + ); + } + + /** + * @dataProvider getRelatedBySuffixDataProvider + */ + public function testGetRelatedBySuffix($table, $index, $expectedSuffix, $expectedReverseSuffix) + { + $fk = $this->getForeignKey($table, $index); + $this->assertEquals($expectedSuffix, TestableOMBuilder::getRefRelatedBySuffix($fk)); + $this->assertEquals($expectedReverseSuffix, TestableOMBuilder::getRelatedBySuffix($fk)); + } + + public function testClear() + { + $b = new Book(); + $b->setNew(false); + $b->clear(); + $this->assertTrue($b->isNew(), 'clear() sets the object to new'); + $b = new Book(); + $b->setDeleted(true); + $b->clear(); + $this->assertFalse($b->isDeleted(), 'clear() sets the object to not deleted'); + } +} + +class TestableOMBuilder extends OMBuilder +{ + public static function getRelatedBySuffix(ForeignKey $fk) + { + return parent::getRelatedBySuffix($fk); + } + + public static function getRefRelatedBySuffix(ForeignKey $fk) + { + return parent::getRefRelatedBySuffix($fk); + } + + public function getUnprefixedClassname() {} +} \ No newline at end of file diff --git a/library/propel/test/testsuite/generator/builder/om/PHP5TableMapBuilderTest.php b/library/propel/test/testsuite/generator/builder/om/PHP5TableMapBuilderTest.php new file mode 100644 index 000000000..f6463e51b --- /dev/null +++ b/library/propel/test/testsuite/generator/builder/om/PHP5TableMapBuilderTest.php @@ -0,0 +1,149 @@ +databaseMap = Propel::getDatabaseMap('bookstore'); + } + + public function testColumnDefaultValue() + { + $table = $this->databaseMap->getTableByPhpName('BookstoreEmployeeAccount'); + $this->assertNull($table->getColumn('login')->getDefaultValue(), 'null default values are correctly mapped'); + $this->assertEquals('\'@\'\'34"', $table->getColumn('password')->getDefaultValue(), 'string default values are correctly escaped and mapped'); + $this->assertTrue($table->getColumn('enabled')->getDefaultValue(), 'boolean default values are correctly mapped'); + $this->assertFalse($table->getColumn('not_enabled')->getDefaultValue(), 'boolean default values are correctly mapped'); + $this->assertEquals('CURRENT_TIMESTAMP', $table->getColumn('created')->getDefaultValue(), 'expression default values are correctly mapped'); + $this->assertNull($table->getColumn('role_id')->getDefaultValue(), 'explicit null default values are correctly mapped'); + } + + public function testRelationCount() + { + $bookTable = $this->databaseMap->getTableByPhpName('Book'); + $this->assertEquals(9, count($bookTable->getRelations()), 'The map builder creates relations for both incoming and outgoing keys'); + } + + public function testSimpleRelationName() + { + $bookTable = $this->databaseMap->getTableByPhpName('Book'); + $this->assertTrue($bookTable->hasRelation('Publisher'), 'The map builder creates relations based on the foreign table name, calemized'); + $this->assertTrue($bookTable->hasRelation('BookListRel'), 'The map builder creates relations based on the foreign table phpName, if provided'); + } + + public function testAliasRelationName() + { + $bookEmpTable = $this->databaseMap->getTableByPhpName('BookstoreEmployee'); + $this->assertTrue($bookEmpTable->hasRelation('Supervisor'), 'The map builder creates relations based on the foreign key phpName'); + $this->assertTrue($bookEmpTable->hasRelation('Subordinate'), 'The map builder creates relations based on the foreign key refPhpName'); + } + + public function testDuplicateRelationName() + { + $essayTable = $this->databaseMap->getTableByPhpName('Essay'); + $this->assertTrue($essayTable->hasRelation('AuthorRelatedByFirstAuthor'), 'The map builder creates relations based on the foreign table name and the foreign key'); + $this->assertTrue($essayTable->hasRelation('AuthorRelatedBySecondAuthor'), 'The map builder creates relations based on the foreign table name and the foreign key'); + } + + public function testRelationDirectionManyToOne() + { + $bookTable = $this->databaseMap->getTableByPhpName('Book'); + $this->assertEquals(RelationMap::MANY_TO_ONE, $bookTable->getRelation('Publisher')->getType(), 'The map builder creates MANY_TO_ONE relations for every foreign key'); + $this->assertEquals(RelationMap::MANY_TO_ONE, $bookTable->getRelation('Author')->getType(), 'The map builder creates MANY_TO_ONE relations for every foreign key'); + } + + public function testRelationDirectionOneToMany() + { + $bookTable = $this->databaseMap->getTableByPhpName('Book'); + $this->assertEquals(RelationMap::ONE_TO_MANY, $bookTable->getRelation('Review')->getType(), 'The map builder creates ONE_TO_MANY relations for every incoming foreign key'); + $this->assertEquals(RelationMap::ONE_TO_MANY, $bookTable->getRelation('Media')->getType(), 'The map builder creates ONE_TO_MANY relations for every incoming foreign key'); + $this->assertEquals(RelationMap::ONE_TO_MANY, $bookTable->getRelation('BookListRel')->getType(), 'The map builder creates ONE_TO_MANY relations for every incoming foreign key'); + $this->assertEquals(RelationMap::ONE_TO_MANY, $bookTable->getRelation('BookOpinion')->getType(), 'The map builder creates ONE_TO_MANY relations for every incoming foreign key'); + $this->assertEquals(RelationMap::ONE_TO_MANY, $bookTable->getRelation('ReaderFavorite')->getType(), 'The map builder creates ONE_TO_MANY relations for every incoming foreign key'); + $this->assertEquals(RelationMap::ONE_TO_MANY, $bookTable->getRelation('BookstoreContest')->getType(), 'The map builder creates ONE_TO_MANY relations for every incoming foreign key'); + } + + public function testRelationDirectionOneToOne() + { + $bookEmpTable = $this->databaseMap->getTableByPhpName('BookstoreEmployee'); + $this->assertEquals(RelationMap::ONE_TO_ONE, $bookEmpTable->getRelation('BookstoreEmployeeAccount')->getType(), 'The map builder creates ONE_TO_ONE relations for every incoming foreign key to a primary key'); + } + + public function testRelationDirectionManyToMAny() + { + $bookTable = $this->databaseMap->getTableByPhpName('Book'); + $this->assertEquals(RelationMap::MANY_TO_MANY, $bookTable->getRelation('BookClubList')->getType(), 'The map builder creates MANY_TO_MANY relations for every cross key'); + } + + public function testRelationsColumns() + { + $bookTable = $this->databaseMap->getTableByPhpName('Book'); + $expectedMapping = array('book.PUBLISHER_ID' => 'publisher.ID'); + $this->assertEquals($expectedMapping, $bookTable->getRelation('Publisher')->getColumnMappings(), 'The map builder adds columns in the correct order for foreign keys'); + $expectedMapping = array('review.BOOK_ID' => 'book.ID'); + $this->assertEquals($expectedMapping, $bookTable->getRelation('Review')->getColumnMappings(), 'The map builder adds columns in the correct order for incoming foreign keys'); + $publisherTable = $this->databaseMap->getTableByPhpName('Publisher'); + $expectedMapping = array('book.PUBLISHER_ID' => 'publisher.ID'); + $this->assertEquals($expectedMapping, $publisherTable->getRelation('Book')->getColumnMappings(), 'The map builder adds local columns where the foreign key lies'); + $rfTable = $this->databaseMap->getTableByPhpName('ReaderFavorite'); + $expectedMapping = array( + 'reader_favorite.BOOK_ID' => 'book_opinion.BOOK_ID', + 'reader_favorite.READER_ID' => 'book_opinion.READER_ID' + ); + $this->assertEquals($expectedMapping, $rfTable->getRelation('BookOpinion')->getColumnMappings(), 'The map builder adds all columns for composite foreign keys'); + $expectedMapping = array(); + $this->assertEquals($expectedMapping, $bookTable->getRelation('BookClubList')->getColumnMappings(), 'The map builder provides no column mapping for many-to-many relationships'); + } + + public function testRelationOnDelete() + { + $bookTable = $this->databaseMap->getTableByPhpName('Book'); + $this->assertEquals('SET NULL', $bookTable->getRelation('Publisher')->getOnDelete(), 'The map builder adds columns with the correct onDelete'); + } + + public function testRelationOnUpdate() + { + $bookTable = $this->databaseMap->getTableByPhpName('Book'); + $this->assertNull($bookTable->getRelation('Publisher')->getOnUpdate(), 'The map builder adds columns with onDelete null by default'); + $this->assertEquals('CASCADE', $bookTable->getRelation('Author')->getOnUpdate(), 'The map builder adds columns with the correct onUpdate'); + } + + public function testBehaviors() + { + $bookTable = $this->databaseMap->getTableByPhpName('Book'); + $this->assertEquals($bookTable->getBehaviors(), array(), 'getBehaviors() returns an empty array when no behaviors are registered'); + $tmap = Propel::getDatabaseMap(Table1Peer::DATABASE_NAME)->getTable(Table1Peer::TABLE_NAME); + $expectedBehaviorParams = array('timestampable' => array('create_column' => 'created_on', 'update_column' => 'updated_on')); + $this->assertEquals($tmap->getBehaviors(), $expectedBehaviorParams, 'The map builder creates a getBehaviors() method to retrieve behaviors parameters when behaviors are registered'); + } + + public function testSingleTableInheritance() + { + $bookTable = $this->databaseMap->getTableByPhpName('Book'); + $this->assertFalse($bookTable->isSingleTableInheritance(), 'isSingleTabkeInheritance() returns false by default'); + + $empTable = $this->databaseMap->getTableByPhpName('BookstoreEmployee'); + $this->assertTrue($empTable->isSingleTableInheritance(), 'isSingleTabkeInheritance() returns true for tables using single table inheritance'); + + } +} diff --git a/library/propel/test/testsuite/generator/builder/om/QueryBuilderInheritanceTest.php b/library/propel/test/testsuite/generator/builder/om/QueryBuilderInheritanceTest.php new file mode 100755 index 000000000..10fbb1e23 --- /dev/null +++ b/library/propel/test/testsuite/generator/builder/om/QueryBuilderInheritanceTest.php @@ -0,0 +1,95 @@ +assertTrue($query instanceof BookstoreCashierQuery, 'the create() factory returns an instance of the correct class'); + } + + public function testFindFilter() + { + BookstoreDataPopulator::depopulate($this->con); + $employee = new BookstoreEmployee(); + $employee->save($this->con); + $manager = new BookstoreManager(); + $manager->save($this->con); + $cashier1 = new BookstoreCashier(); + $cashier1->save($this->con); + $cashier2 = new BookstoreCashier(); + $cashier2->save($this->con); + $nbEmp = BookstoreEmployeeQuery::create()->count($this->con); + $this->assertEquals(4, $nbEmp, 'find() in main query returns all results'); + $nbMan = BookstoreManagerQuery::create()->count($this->con); + $this->assertEquals(1, $nbMan, 'find() in sub query returns only child results'); + $nbCash = BookstoreCashierQuery::create()->count($this->con); + $this->assertEquals(2, $nbCash, 'find() in sub query returns only child results'); + } + + public function testUpdateFilter() + { + BookstoreDataPopulator::depopulate($this->con); + $manager = new BookstoreManager(); + $manager->save($this->con); + $cashier1 = new BookstoreCashier(); + $cashier1->save($this->con); + $cashier2 = new BookstoreCashier(); + $cashier2->save($this->con); + BookstoreManagerQuery::create()->update(array('Name' => 'foo'), $this->con); + $nbMan = BookstoreEmployeeQuery::create() + ->filterByName('foo') + ->count($this->con); + $this->assertEquals(1, $nbMan, 'Update in sub query affects only child results'); + } + + public function testDeleteFilter() + { + BookstoreDataPopulator::depopulate($this->con); + $manager = new BookstoreManager(); + $manager->save($this->con); + $cashier1 = new BookstoreCashier(); + $cashier1->save($this->con); + $cashier2 = new BookstoreCashier(); + $cashier2->save($this->con); + BookstoreManagerQuery::create() + ->filterByName() + ->delete(); + $nbCash = BookstoreEmployeeQuery::create()->count(); + $this->assertEquals(2, $nbCash, 'Delete in sub query affects only child results'); + } + + public function testDeleteAllFilter() + { + BookstoreDataPopulator::depopulate($this->con); + $manager = new BookstoreManager(); + $manager->save($this->con); + $cashier1 = new BookstoreCashier(); + $cashier1->save($this->con); + $cashier2 = new BookstoreCashier(); + $cashier2->save($this->con); + BookstoreManagerQuery::create()->deleteAll(); + $nbCash = BookstoreEmployeeQuery::create()->count(); + $this->assertEquals(2, $nbCash, 'Delete in sub query affects only child results'); + } +} + diff --git a/library/propel/test/testsuite/generator/builder/om/QueryBuilderTest.php b/library/propel/test/testsuite/generator/builder/om/QueryBuilderTest.php new file mode 100644 index 000000000..056c1afd4 --- /dev/null +++ b/library/propel/test/testsuite/generator/builder/om/QueryBuilderTest.php @@ -0,0 +1,912 @@ +assertTrue($q instanceof ModelCriteria, 'Model query extends ModelCriteria'); + } + + public function testConstructor() + { + $query = new BookQuery(); + $this->assertEquals($query->getDbName(), 'bookstore', 'Constructor sets dabatase name'); + $this->assertEquals($query->getModelName(), 'Book', 'Constructor sets model name'); + } + + public function testCreate() + { + $query = BookQuery::create(); + $this->assertTrue($query instanceof BookQuery, 'create() returns an object of its class'); + $this->assertEquals($query->getDbName(), 'bookstore', 'create() sets dabatase name'); + $this->assertEquals($query->getModelName(), 'Book', 'create() sets model name'); + $query = BookQuery::create('foo'); + $this->assertTrue($query instanceof BookQuery, 'create() returns an object of its class'); + $this->assertEquals($query->getDbName(), 'bookstore', 'create() sets dabatase name'); + $this->assertEquals($query->getModelName(), 'Book', 'create() sets model name'); + $this->assertEquals($query->getModelAlias(), 'foo', 'create() can set the model alias'); + } + + public function testCreateCustom() + { + // see the myBookQuery class definition at the end of this file + $query = myCustomBookQuery::create(); + $this->assertTrue($query instanceof myCustomBookQuery, 'create() returns an object of its class'); + $this->assertTrue($query instanceof BookQuery, 'create() returns an object of its class'); + $this->assertEquals($query->getDbName(), 'bookstore', 'create() sets dabatase name'); + $this->assertEquals($query->getModelName(), 'Book', 'create() sets model name'); + $query = myCustomBookQuery::create('foo'); + $this->assertTrue($query instanceof myCustomBookQuery, 'create() returns an object of its class'); + $this->assertEquals($query->getDbName(), 'bookstore', 'create() sets dabatase name'); + $this->assertEquals($query->getModelName(), 'Book', 'create() sets model name'); + $this->assertEquals($query->getModelAlias(), 'foo', 'create() can set the model alias'); + } + + public function testBasePreSelect() + { + $method = new ReflectionMethod('Table2Query', 'basePreSelect'); + $this->assertEquals('ModelCriteria', $method->getDeclaringClass()->getName(), 'BaseQuery does not override basePreSelect() by default'); + + $method = new ReflectionMethod('Table3Query', 'basePreSelect'); + $this->assertEquals('BaseTable3Query', $method->getDeclaringClass()->getName(), 'BaseQuery overrides basePreSelect() when a behavior is registered'); + } + + public function testBasePreDelete() + { + $method = new ReflectionMethod('Table2Query', 'basePreDelete'); + $this->assertEquals('ModelCriteria', $method->getDeclaringClass()->getName(), 'BaseQuery does not override basePreDelete() by default'); + + $method = new ReflectionMethod('Table3Query', 'basePreDelete'); + $this->assertEquals('BaseTable3Query', $method->getDeclaringClass()->getName(), 'BaseQuery overrides basePreDelete() when a behavior is registered'); + } + + public function testBasePostDelete() + { + $method = new ReflectionMethod('Table2Query', 'basePostDelete'); + $this->assertEquals('ModelCriteria', $method->getDeclaringClass()->getName(), 'BaseQuery does not override basePostDelete() by default'); + + $method = new ReflectionMethod('Table3Query', 'basePostDelete'); + $this->assertEquals('BaseTable3Query', $method->getDeclaringClass()->getName(), 'BaseQuery overrides basePostDelete() when a behavior is registered'); + } + + public function testBasePreUpdate() + { + $method = new ReflectionMethod('Table2Query', 'basePreUpdate'); + $this->assertEquals('ModelCriteria', $method->getDeclaringClass()->getName(), 'BaseQuery does not override basePreUpdate() by default'); + + $method = new ReflectionMethod('Table3Query', 'basePreUpdate'); + $this->assertEquals('BaseTable3Query', $method->getDeclaringClass()->getName(), 'BaseQuery overrides basePreUpdate() when a behavior is registered'); + } + + public function testBasePostUpdate() + { + $method = new ReflectionMethod('Table2Query', 'basePostUpdate'); + $this->assertEquals('ModelCriteria', $method->getDeclaringClass()->getName(), 'BaseQuery does not override basePostUpdate() by default'); + + $method = new ReflectionMethod('Table3Query', 'basePostUpdate'); + $this->assertEquals('BaseTable3Query', $method->getDeclaringClass()->getName(), 'BaseQuery overrides basePostUpdate() when a behavior is registered'); + } + + public function testQuery() + { + BookstoreDataPopulator::depopulate(); + BookstoreDataPopulator::populate(); + + $q = new BookQuery(); + $book = $q + ->setModelAlias('b') + ->where('b.Title like ?', 'Don%') + ->orderBy('b.ISBN', 'desc') + ->findOne(); + $this->assertTrue($book instanceof Book); + $this->assertEquals('Don Juan', $book->getTitle()); + } + + public function testFindPk() + { + $method = new ReflectionMethod('Table4Query', 'findPk'); + $this->assertEquals('BaseTable4Query', $method->getDeclaringClass()->getName(), 'BaseQuery overrides findPk()'); + } + + public function testFindPkSimpleKey() + { + BookstoreDataPopulator::depopulate(); + BookstoreDataPopulator::populate(); + + BookPeer::clearInstancePool(); + $con = Propel::getConnection('bookstore'); + + // prepare the test data + $c = new ModelCriteria('bookstore', 'Book'); + $c->orderBy('Book.Id', 'desc'); + $testBook = $c->findOne(); + $count = $con->getQueryCount(); + + BookPeer::clearInstancePool(); + + $q = new BookQuery(); + $book = $q->findPk($testBook->getId()); + $this->assertEquals($testBook, $book, 'BaseQuery overrides findPk() to make it faster'); + $this->assertEquals($count+1, $con->getQueryCount(), 'findPk() issues a database query when instance pool is empty'); + + $q = new BookQuery(); + $book = $q->findPk($testBook->getId()); + $this->assertEquals($testBook, $book, 'BaseQuery overrides findPk() to make it faster'); + $this->assertEquals($count+1, $con->getQueryCount(), 'findPk() does not issue a database query when instance is in pool'); + } + + public function testFindPkCompositeKey() + { + BookstoreDataPopulator::depopulate(); + BookstoreDataPopulator::populate(); + + // save all books to make sure related objects are also saved - BookstoreDataPopulator keeps some unsaved + $c = new ModelCriteria('bookstore', 'Book'); + $books = $c->find(); + foreach ($books as $book) { + $book->save(); + } + + BookPeer::clearInstancePool(); + + // retrieve the test data + $c = new ModelCriteria('bookstore', 'BookListRel'); + $bookListRelTest = $c->findOne(); + $pk = $bookListRelTest->getPrimaryKey(); + + $q = new BookListRelQuery(); + $bookListRel = $q->findPk($pk); + $this->assertEquals($bookListRelTest, $bookListRel, 'BaseQuery overrides findPk() for composite primary keysto make it faster'); + } + + public function testFindPks() + { + $method = new ReflectionMethod('Table4Query', 'findPks'); + $this->assertEquals('BaseTable4Query', $method->getDeclaringClass()->getName(), 'BaseQuery overrides findPks()'); + } + + public function testFindPksSimpleKey() + { + BookstoreDataPopulator::depopulate(); + BookstoreDataPopulator::populate(); + + BookPeer::clearInstancePool(); + + // prepare the test data + $c = new ModelCriteria('bookstore', 'Book'); + $c->orderBy('Book.Id', 'desc'); + $testBooks = $c->find(); + $testBook1 = $testBooks->pop(); + $testBook2 = $testBooks->pop(); + + $q = new BookQuery(); + $books = $q->findPks(array($testBook1->getId(), $testBook2->getId())); + $this->assertEquals(array($testBook1, $testBook2), $books->getData(), 'BaseQuery overrides findPks() to make it faster'); + } + + public function testFindPksCompositeKey() + { + BookstoreDataPopulator::depopulate(); + BookstoreDataPopulator::populate(); + + // save all books to make sure related objects are also saved - BookstoreDataPopulator keeps some unsaved + $c = new ModelCriteria('bookstore', 'Book'); + $books = $c->find(); + foreach ($books as $book) { + $book->save(); + } + + BookPeer::clearInstancePool(); + + // retrieve the test data + $c = new ModelCriteria('bookstore', 'BookListRel'); + $bookListRelTest = $c->find(); + $search = array(); + foreach ($bookListRelTest as $obj) { + $search[]= $obj->getPrimaryKey(); + } + + $q = new BookListRelQuery(); + $objs = $q->findPks($search); + $this->assertEquals($bookListRelTest, $objs, 'BaseQuery overrides findPks() for composite primary keys to make it work'); + } + + public function testFilterBy() + { + foreach (BookPeer::getFieldNames(BasePeer::TYPE_PHPNAME) as $colName) { + $filterMethod = 'filterBy' . $colName; + $this->assertTrue(method_exists('BookQuery', $filterMethod), 'QueryBuilder adds filterByColumn() methods for every column'); + $q = BookQuery::create()->$filterMethod(1); + $this->assertTrue($q instanceof BookQuery, 'filterByColumn() returns the current query instance'); + } + } + + public function testFilterByPrimaryKeySimpleKey() + { + $q = BookQuery::create()->filterByPrimaryKey(12); + $q1 = BookQuery::create()->add(BookPeer::ID, 12, Criteria::EQUAL); + $this->assertEquals($q1, $q, 'filterByPrimaryKey() translates to a Criteria::EQUAL in the PK column'); + + $q = BookQuery::create()->setModelAlias('b', true)->filterByPrimaryKey(12); + $q1 = BookQuery::create()->setModelAlias('b', true)->add('b.ID', 12, Criteria::EQUAL); + $this->assertEquals($q1, $q, 'filterByPrimaryKey() uses true table alias if set'); + } + + public function testFilterByPrimaryKeyCompositeKey() + { + BookstoreDataPopulator::depopulate(); + BookstoreDataPopulator::populate(); + + // save all books to make sure related objects are also saved - BookstoreDataPopulator keeps some unsaved + $c = new ModelCriteria('bookstore', 'Book'); + $books = $c->find(); + foreach ($books as $book) { + $book->save(); + } + + BookPeer::clearInstancePool(); + + // retrieve the test data + $c = new ModelCriteria('bookstore', 'BookListRel'); + $bookListRelTest = $c->findOne(); + $pk = $bookListRelTest->getPrimaryKey(); + + $q = new BookListRelQuery(); + $q->filterByPrimaryKey($pk); + + $q1 = BookListRelQuery::create() + ->add(BookListRelPeer::BOOK_ID, $pk[0], Criteria::EQUAL) + ->add(BookListRelPeer::BOOK_CLUB_LIST_ID, $pk[1], Criteria::EQUAL); + $this->assertEquals($q1, $q, 'filterByPrimaryKey() translates to a Criteria::EQUAL in the PK columns'); + } + + public function testFilterByPrimaryKeysSimpleKey() + { + $q = BookQuery::create()->filterByPrimaryKeys(array(10, 11, 12)); + $q1 = BookQuery::create()->add(BookPeer::ID, array(10, 11, 12), Criteria::IN); + $this->assertEquals($q1, $q, 'filterByPrimaryKeys() translates to a Criteria::IN on the PK column'); + + $q = BookQuery::create()->setModelAlias('b', true)->filterByPrimaryKeys(array(10, 11, 12)); + $q1 = BookQuery::create()->setModelAlias('b', true)->add('b.ID', array(10, 11, 12), Criteria::IN); + $this->assertEquals($q1, $q, 'filterByPrimaryKeys() uses true table alias if set'); + } + + public function testFilterByPrimaryKeysCompositeKey() + { + BookstoreDataPopulator::depopulate(); + BookstoreDataPopulator::populate(); + + // save all books to make sure related objects are also saved - BookstoreDataPopulator keeps some unsaved + $c = new ModelCriteria('bookstore', 'Book'); + $books = $c->find(); + foreach ($books as $book) { + $book->save(); + } + + BookPeer::clearInstancePool(); + + // retrieve the test data + $c = new ModelCriteria('bookstore', 'BookListRel'); + $bookListRelTest = $c->find(); + $search = array(); + foreach ($bookListRelTest as $obj) { + $search[]= $obj->getPrimaryKey(); + } + + $q = new BookListRelQuery(); + $q->filterByPrimaryKeys($search); + + $q1 = BookListRelQuery::create(); + foreach ($search as $key) { + $cton0 = $q1->getNewCriterion(BookListRelPeer::BOOK_ID, $key[0], Criteria::EQUAL); + $cton1 = $q1->getNewCriterion(BookListRelPeer::BOOK_CLUB_LIST_ID, $key[1], Criteria::EQUAL); + $cton0->addAnd($cton1); + $q1->addOr($cton0); + } + $this->assertEquals($q1, $q, 'filterByPrimaryKeys() translates to a series of Criteria::EQUAL in the PK columns'); + } + + public function testFilterByIntegerPk() + { + $q = BookQuery::create()->filterById(12); + $q1 = BookQuery::create()->add(BookPeer::ID, 12, Criteria::EQUAL); + $this->assertEquals($q1, $q, 'filterByPkColumn() translates to a Criteria::EQUAL by default'); + + $q = BookQuery::create()->filterById(12, Criteria::NOT_EQUAL); + $q1 = BookQuery::create()->add(BookPeer::ID, 12, Criteria::NOT_EQUAL); + $this->assertEquals($q1, $q, 'filterByPkColumn() accepts an optional comparison operator'); + + $q = BookQuery::create()->setModelAlias('b', true)->filterById(12); + $q1 = BookQuery::create()->setModelAlias('b', true)->add('b.ID', 12, Criteria::EQUAL); + $this->assertEquals($q1, $q, 'filterByPkColumn() uses true table alias if set'); + + $q = BookQuery::create()->filterById(array(10, 11, 12)); + $q1 = BookQuery::create()->add(BookPeer::ID, array(10, 11, 12), Criteria::IN); + $this->assertEquals($q1, $q, 'filterByPkColumn() translates to a Criteria::IN when passed a simple array key'); + + $q = BookQuery::create()->filterById(array(10, 11, 12), Criteria::NOT_IN); + $q1 = BookQuery::create()->add(BookPeer::ID, array(10, 11, 12), Criteria::NOT_IN); + $this->assertEquals($q1, $q, 'filterByPkColumn() accepts a comparison when passed a simple array key'); + } + + public function testFilterByNumber() + { + $q = BookQuery::create()->filterByPrice(12); + $q1 = BookQuery::create()->add(BookPeer::PRICE, 12, Criteria::EQUAL); + $this->assertEquals($q1, $q, 'filterByNumColumn() translates to a Criteria::EQUAL by default'); + + $q = BookQuery::create()->filterByPrice(12, Criteria::NOT_EQUAL); + $q1 = BookQuery::create()->add(BookPeer::PRICE, 12, Criteria::NOT_EQUAL); + $this->assertEquals($q1, $q, 'filterByNumColumn() accepts an optional comparison operator'); + + $q = BookQuery::create()->setModelAlias('b', true)->filterByPrice(12); + $q1 = BookQuery::create()->setModelAlias('b', true)->add('b.PRICE', 12, Criteria::EQUAL); + $this->assertEquals($q1, $q, 'filterByNumColumn() uses true table alias if set'); + + $q = BookQuery::create()->filterByPrice(array(10, 11, 12)); + $q1 = BookQuery::create()->add(BookPeer::PRICE, array(10, 11, 12), Criteria::IN); + $this->assertEquals($q1, $q, 'filterByNumColumn() translates to a Criteria::IN when passed a simple array key'); + + $q = BookQuery::create()->filterByPrice(array(10, 11, 12), Criteria::NOT_IN); + $q1 = BookQuery::create()->add(BookPeer::PRICE, array(10, 11, 12), Criteria::NOT_IN); + $this->assertEquals($q1, $q, 'filterByNumColumn() accepts a comparison when passed a simple array key'); + + $q = BookQuery::create()->filterByPrice(array('min' => 10)); + $q1 = BookQuery::create()->add(BookPeer::PRICE, 10, Criteria::GREATER_EQUAL); + $this->assertEquals($q1, $q, 'filterByNumColumn() translates to a Criteria::GREATER_EQUAL when passed a \'min\' key'); + + $q = BookQuery::create()->filterByPrice(array('max' => 12)); + $q1 = BookQuery::create()->add(BookPeer::PRICE, 12, Criteria::LESS_EQUAL); + $this->assertEquals($q1, $q, 'filterByNumColumn() translates to a Criteria::LESS_EQUAL when passed a \'max\' key'); + + $q = BookQuery::create()->filterByPrice(array('min' => 10, 'max' => 12)); + $q1 = BookQuery::create() + ->add(BookPeer::PRICE, 10, Criteria::GREATER_EQUAL) + ->addAnd(BookPeer::PRICE, 12, Criteria::LESS_EQUAL); + $this->assertEquals($q1, $q, 'filterByNumColumn() translates to a between when passed both a \'min\' and a \'max\' key'); + } + + public function testFilterByTimestamp() + { + $q = BookstoreEmployeeAccountQuery::create()->filterByCreated(12); + $q1 = BookstoreEmployeeAccountQuery::create()->add(BookstoreEmployeeAccountPeer::CREATED, 12, Criteria::EQUAL); + $this->assertEquals($q1, $q, 'filterByDateColumn() translates to a Criteria::EQUAL by default'); + + $q = BookstoreEmployeeAccountQuery::create()->filterByCreated(12, Criteria::NOT_EQUAL); + $q1 = BookstoreEmployeeAccountQuery::create()->add(BookstoreEmployeeAccountPeer::CREATED, 12, Criteria::NOT_EQUAL); + $this->assertEquals($q1, $q, 'filterByDateColumn() accepts an optional comparison operator'); + + $q = BookstoreEmployeeAccountQuery::create()->setModelAlias('b', true)->filterByCreated(12); + $q1 = BookstoreEmployeeAccountQuery::create()->setModelAlias('b', true)->add('b.CREATED', 12, Criteria::EQUAL); + $this->assertEquals($q1, $q, 'filterByDateColumn() uses true table alias if set'); + + $q = BookstoreEmployeeAccountQuery::create()->filterByCreated(array('min' => 10)); + $q1 = BookstoreEmployeeAccountQuery::create()->add(BookstoreEmployeeAccountPeer::CREATED, 10, Criteria::GREATER_EQUAL); + $this->assertEquals($q1, $q, 'filterByDateColumn() translates to a Criteria::GREATER_EQUAL when passed a \'min\' key'); + + $q = BookstoreEmployeeAccountQuery::create()->filterByCreated(array('max' => 12)); + $q1 = BookstoreEmployeeAccountQuery::create()->add(BookstoreEmployeeAccountPeer::CREATED, 12, Criteria::LESS_EQUAL); + $this->assertEquals($q1, $q, 'filterByDateColumn() translates to a Criteria::LESS_EQUAL when passed a \'max\' key'); + + $q = BookstoreEmployeeAccountQuery::create()->filterByCreated(array('min' => 10, 'max' => 12)); + $q1 = BookstoreEmployeeAccountQuery::create() + ->add(BookstoreEmployeeAccountPeer::CREATED, 10, Criteria::GREATER_EQUAL) + ->addAnd(BookstoreEmployeeAccountPeer::CREATED, 12, Criteria::LESS_EQUAL); + $this->assertEquals($q1, $q, 'filterByDateColumn() translates to a between when passed both a \'min\' and a \'max\' key'); + } + + public function testFilterByString() + { + $q = BookQuery::create()->filterByTitle('foo'); + $q1 = BookQuery::create()->add(BookPeer::TITLE, 'foo', Criteria::EQUAL); + $this->assertEquals($q1, $q, 'filterByStringColumn() translates to a Criteria::EQUAL by default'); + + $q = BookQuery::create()->filterByTitle('foo', Criteria::NOT_EQUAL); + $q1 = BookQuery::create()->add(BookPeer::TITLE, 'foo', Criteria::NOT_EQUAL); + $this->assertEquals($q1, $q, 'filterByStringColumn() accepts an optional comparison operator'); + + $q = BookQuery::create()->setModelAlias('b', true)->filterByTitle('foo'); + $q1 = BookQuery::create()->setModelAlias('b', true)->add('b.TITLE', 'foo', Criteria::EQUAL); + $this->assertEquals($q1, $q, 'filterByStringColumn() uses true table alias if set'); + + $q = BookQuery::create()->filterByTitle(array('foo', 'bar')); + $q1 = BookQuery::create()->add(BookPeer::TITLE, array('foo', 'bar'), Criteria::IN); + $this->assertEquals($q1, $q, 'filterByStringColumn() translates to a Criteria::IN when passed an array'); + + $q = BookQuery::create()->filterByTitle(array('foo', 'bar'), Criteria::NOT_IN); + $q1 = BookQuery::create()->add(BookPeer::TITLE, array('foo', 'bar'), Criteria::NOT_IN); + $this->assertEquals($q1, $q, 'filterByStringColumn() accepts a comparison when passed an array'); + + $q = BookQuery::create()->filterByTitle('foo%'); + $q1 = BookQuery::create()->add(BookPeer::TITLE, 'foo%', Criteria::LIKE); + $this->assertEquals($q1, $q, 'filterByStringColumn() translates to a Criteria::LIKE when passed a string with a % wildcard'); + + $q = BookQuery::create()->filterByTitle('foo%', Criteria::NOT_LIKE); + $q1 = BookQuery::create()->add(BookPeer::TITLE, 'foo%', Criteria::NOT_LIKE); + $this->assertEquals($q1, $q, 'filterByStringColumn() accepts a comparison when passed a string with a % wildcard'); + + $q = BookQuery::create()->filterByTitle('foo%', Criteria::EQUAL); + $q1 = BookQuery::create()->add(BookPeer::TITLE, 'foo%', Criteria::EQUAL); + $this->assertEquals($q1, $q, 'filterByStringColumn() accepts a comparison when passed a string with a % wildcard'); + + $q = BookQuery::create()->filterByTitle('*foo'); + $q1 = BookQuery::create()->add(BookPeer::TITLE, '%foo', Criteria::LIKE); + $this->assertEquals($q1, $q, 'filterByStringColumn() translates to a Criteria::LIKE when passed a string with a * wildcard, and turns * into %'); + + $q = BookQuery::create()->filterByTitle('*f%o*o%'); + $q1 = BookQuery::create()->add(BookPeer::TITLE, '%f%o%o%', Criteria::LIKE); + $this->assertEquals($q1, $q, 'filterByStringColumn() translates to a Criteria::LIKE when passed a string with mixed wildcards, and turns *s into %s'); + } + + public function testFilterByBoolean() + { + $q = ReviewQuery::create()->filterByRecommended(true); + $q1 = ReviewQuery::create()->add(ReviewPeer::RECOMMENDED, true, Criteria::EQUAL); + $this->assertEquals($q1, $q, 'filterByBooleanColumn() translates to a Criteria::EQUAL by default'); + + $q = ReviewQuery::create()->filterByRecommended(true, Criteria::NOT_EQUAL); + $q1 = ReviewQuery::create()->add(ReviewPeer::RECOMMENDED, true, Criteria::NOT_EQUAL); + $this->assertEquals($q1, $q, 'filterByBooleanColumn() accepts an optional comparison operator'); + + $q = ReviewQuery::create()->filterByRecommended(false); + $q1 = ReviewQuery::create()->add(ReviewPeer::RECOMMENDED, false, Criteria::EQUAL); + $this->assertEquals($q1, $q, 'filterByBooleanColumn() translates to a Criteria::EQUAL by default'); + + $q = ReviewQuery::create()->setModelAlias('b', true)->filterByRecommended(true); + $q1 = ReviewQuery::create()->setModelAlias('b', true)->add('b.RECOMMENDED', true, Criteria::EQUAL); + $this->assertEquals($q1, $q, 'filterByBooleanColumn() uses true table alias if set'); + + $q = ReviewQuery::create()->filterByRecommended('true'); + $q1 = ReviewQuery::create()->add(ReviewPeer::RECOMMENDED, true, Criteria::EQUAL); + $this->assertEquals($q1, $q, 'filterByBooleanColumn() translates to a = true when passed a true string'); + + $q = ReviewQuery::create()->filterByRecommended('yes'); + $q1 = ReviewQuery::create()->add(ReviewPeer::RECOMMENDED, true, Criteria::EQUAL); + $this->assertEquals($q1, $q, 'filterByBooleanColumn() translates to a = true when passed a true string'); + + $q = ReviewQuery::create()->filterByRecommended('1'); + $q1 = ReviewQuery::create()->add(ReviewPeer::RECOMMENDED, true, Criteria::EQUAL); + $this->assertEquals($q1, $q, 'filterByBooleanColumn() translates to a = true when passed a true string'); + + $q = ReviewQuery::create()->filterByRecommended('false'); + $q1 = ReviewQuery::create()->add(ReviewPeer::RECOMMENDED, false, Criteria::EQUAL); + $this->assertEquals($q1, $q, 'filterByBooleanColumn() translates to a = false when passed a false string'); + + $q = ReviewQuery::create()->filterByRecommended('no'); + $q1 = ReviewQuery::create()->add(ReviewPeer::RECOMMENDED, false, Criteria::EQUAL); + $this->assertEquals($q1, $q, 'filterByBooleanColumn() translates to a = false when passed a false string'); + + $q = ReviewQuery::create()->filterByRecommended('0'); + $q1 = ReviewQuery::create()->add(ReviewPeer::RECOMMENDED, false, Criteria::EQUAL); + $this->assertEquals($q1, $q, 'filterByBooleanColumn() translates to a = false when passed a false string'); + } + + public function testFilterByFk() + { + $this->assertTrue(method_exists('BookQuery', 'filterByAuthor'), 'QueryBuilder adds filterByFk() methods'); + $this->assertTrue(method_exists('BookQuery', 'filterByPublisher'), 'QueryBuilder adds filterByFk() methods for all fkeys'); + + $this->assertTrue(method_exists('EssayQuery', 'filterByAuthorRelatedByFirstAuthor'), 'QueryBuilder adds filterByFk() methods for several fkeys on the same table'); + $this->assertTrue(method_exists('EssayQuery', 'filterByAuthorRelatedBySecondAuthor'), 'QueryBuilder adds filterByFk() methods for several fkeys on the same table'); + } + + public function testFilterByFkSimpleKey() + { + BookstoreDataPopulator::depopulate(); + BookstoreDataPopulator::populate(); + + // prepare the test data + $testBook = BookQuery::create() + ->innerJoin('Book.Author') // just in case there are books with no author + ->findOne(); + $testAuthor = $testBook->getAuthor(); + + $book = BookQuery::create() + ->filterByAuthor($testAuthor) + ->findOne(); + $this->assertEquals($testBook, $book, 'Generated query handles filterByFk() methods correctly for simple fkeys'); + + $q = BookQuery::create()->filterByAuthor($testAuthor); + $q1 = BookQuery::create()->add(BookPeer::AUTHOR_ID, $testAuthor->getId(), Criteria::EQUAL); + $this->assertEquals($q1, $q, 'filterByFk() translates to a Criteria::EQUAL by default'); + + $q = BookQuery::create()->filterByAuthor($testAuthor, Criteria::NOT_EQUAL); + $q1 = BookQuery::create()->add(BookPeer::AUTHOR_ID, $testAuthor->getId(), Criteria::NOT_EQUAL); + $this->assertEquals($q1, $q, 'filterByFk() accepts an optional comparison operator'); + } + + public function testFilterByFkCompositeKey() + { + BookstoreDataPopulator::depopulate(); + BookstoreDataPopulator::populate(); + BookstoreDataPopulator::populateOpinionFavorite(); + + // prepare the test data + $testOpinion = BookOpinionQuery::create() + ->innerJoin('BookOpinion.ReaderFavorite') // just in case there are books with no author + ->findOne(); + $testFavorite = $testOpinion->getReaderFavorite(); + + $favorite = ReaderFavoriteQuery::create() + ->filterByBookOpinion($testOpinion) + ->findOne(); + $this->assertEquals($testFavorite, $favorite, 'Generated query handles filterByFk() methods correctly for composite fkeys'); + } + + public function testFilterByRefFk() + { + $this->assertTrue(method_exists('BookQuery', 'filterByReview'), 'QueryBuilder adds filterByRefFk() methods'); + $this->assertTrue(method_exists('BookQuery', 'filterByMedia'), 'QueryBuilder adds filterByRefFk() methods for all fkeys'); + + $this->assertTrue(method_exists('AuthorQuery', 'filterByEssayRelatedByFirstAuthor'), 'QueryBuilder adds filterByRefFk() methods for several fkeys on the same table'); + $this->assertTrue(method_exists('AuthorQuery', 'filterByEssayRelatedBySecondAuthor'), 'QueryBuilder adds filterByRefFk() methods for several fkeys on the same table'); + } + + public function testFilterByRefFkSimpleKey() + { + BookstoreDataPopulator::depopulate(); + BookstoreDataPopulator::populate(); + + // prepare the test data + $testBook = BookQuery::create() + ->innerJoin('Book.Author') // just in case there are books with no author + ->findOne(); + $testAuthor = $testBook->getAuthor(); + + $author = AuthorQuery::create() + ->filterByBook($testBook) + ->findOne(); + $this->assertEquals($testAuthor, $author, 'Generated query handles filterByRefFk() methods correctly for simple fkeys'); + + $q = AuthorQuery::create()->filterByBook($testBook); + $q1 = AuthorQuery::create()->add(AuthorPeer::ID, $testBook->getAuthorId(), Criteria::EQUAL); + $this->assertEquals($q1, $q, 'filterByRefFk() translates to a Criteria::EQUAL by default'); + + $q = AuthorQuery::create()->filterByBook($testBook, Criteria::NOT_EQUAL); + $q1 = AuthorQuery::create()->add(AuthorPeer::ID, $testBook->getAuthorId(), Criteria::NOT_EQUAL); + $this->assertEquals($q1, $q, 'filterByRefFk() accepts an optional comparison operator'); + } + + public function testFilterByRefFkCompositeKey() + { + BookstoreDataPopulator::depopulate(); + BookstoreDataPopulator::populate(); + BookstoreDataPopulator::populateOpinionFavorite(); + + // prepare the test data + $testOpinion = BookOpinionQuery::create() + ->innerJoin('BookOpinion.ReaderFavorite') // just in case there are books with no author + ->findOne(); + $testFavorite = $testOpinion->getReaderFavorite(); + + $opinion = BookOpinionQuery::create() + ->filterByReaderFavorite($testFavorite) + ->findOne(); + $this->assertEquals($testOpinion, $opinion, 'Generated query handles filterByRefFk() methods correctly for composite fkeys'); + } + + public function testFilterByCrossFK() + { + $this->assertTrue(method_exists('BookQuery', 'filterByBookClubList'), 'Generated query handles filterByCrossRefFK() for many-to-many relationships'); + $this->assertFalse(method_exists('BookQuery', 'filterByBook'), 'Generated query handles filterByCrossRefFK() for many-to-many relationships'); + BookstoreDataPopulator::depopulate(); + BookstoreDataPopulator::populate(); + $blc1 = BookClubListQuery::create()->findOneByGroupLeader('Crazyleggs'); + $nbBooks = BookQuery::create() + ->filterByBookClubList($blc1) + ->count(); + $this->assertEquals(2, $nbBooks, 'Generated query handles filterByCrossRefFK() methods correctly'); + } + + public function testJoinFk() + { + $q = BookQuery::create() + ->joinAuthor(); + $q1 = BookQuery::create() + ->join('Book.Author', Criteria::LEFT_JOIN); + $this->assertTrue($q->equals($q1), 'joinFk() translates to a left join on non-required columns'); + + $q = ReviewQuery::create() + ->joinBook(); + $q1 = ReviewQuery::create() + ->join('Review.Book', Criteria::INNER_JOIN); + $this->assertTrue($q->equals($q1), 'joinFk() translates to an inner join on required columns'); + + $q = BookQuery::create() + ->joinAuthor('a'); + $q1 = BookQuery::create() + ->join('Book.Author a', Criteria::LEFT_JOIN); + $this->assertTrue($q->equals($q1), 'joinFk() accepts a relation alias as first parameter'); + + $q = BookQuery::create() + ->joinAuthor('', Criteria::INNER_JOIN); + $q1 = BookQuery::create() + ->join('Book.Author', Criteria::INNER_JOIN); + $this->assertTrue($q->equals($q1), 'joinFk() accepts a join type as second parameter'); + + $q = EssayQuery::create() + ->joinAuthorRelatedBySecondAuthor(); + $q1 = EssayQuery::create() + ->join('Essay.AuthorRelatedBySecondAuthor', "INNER JOIN"); + $this->assertTrue($q->equals($q1), 'joinFk() translates to a "INNER JOIN" when this is defined as defaultJoin in the schema'); + } + + public function testJoinFkAlias() + { + $q = BookQuery::create('b') + ->joinAuthor('a'); + $q1 = BookQuery::create('b') + ->join('b.Author a', Criteria::LEFT_JOIN); + $this->assertTrue($q->equals($q1), 'joinFk() works fine with table aliases'); + + $q = BookQuery::create() + ->setModelAlias('b', true) + ->joinAuthor('a'); + $q1 = BookQuery::create() + ->setModelAlias('b', true) + ->join('b.Author a', Criteria::LEFT_JOIN); + $this->assertTrue($q->equals($q1), 'joinFk() works fine with true table aliases'); + } + + public function testJoinRefFk() + { + $q = AuthorQuery::create() + ->joinBook(); + $q1 = AuthorQuery::create() + ->join('Author.Book', Criteria::LEFT_JOIN); + $this->assertTrue($q->equals($q1), 'joinRefFk() translates to a left join on non-required columns'); + + $q = BookQuery::create() + ->joinreview(); + $q1 = BookQuery::create() + ->join('Book.Review', Criteria::INNER_JOIN); + $this->assertTrue($q->equals($q1), 'joinRefFk() translates to an inner join on required columns'); + + $q = AuthorQuery::create() + ->joinBook('b'); + $q1 = AuthorQuery::create() + ->join('Author.Book b', Criteria::LEFT_JOIN); + $this->assertTrue($q->equals($q1), 'joinRefFk() accepts a relation alias as first parameter'); + + $q = AuthorQuery::create() + ->joinBook('', Criteria::INNER_JOIN); + $q1 = AuthorQuery::create() + ->join('Author.Book', Criteria::INNER_JOIN); + $this->assertTrue($q->equals($q1), 'joinRefFk() accepts a join type as second parameter'); + + $q = AuthorQuery::create() + ->joinEssayRelatedBySecondAuthor(); + $q1 = AuthorQuery::create() + ->join('Author.EssayRelatedBySecondAuthor', Criteria::INNER_JOIN); + $this->assertTrue($q->equals($q1), 'joinRefFk() translates to a "INNER JOIN" when this is defined as defaultJoin in the schema'); + } + + public function testUseFkQuerySimple() + { + $q = BookQuery::create() + ->useAuthorQuery() + ->filterByFirstName('Leo') + ->endUse(); + $q1 = BookQuery::create() + ->join('Book.Author', Criteria::LEFT_JOIN) + ->add(AuthorPeer::FIRST_NAME, 'Leo', Criteria::EQUAL); + $this->assertTrue($q->equals($q1), 'useFkQuery() translates to a condition on a left join on non-required columns'); + + $q = ReviewQuery::create() + ->useBookQuery() + ->filterByTitle('War And Peace') + ->endUse(); + $q1 = ReviewQuery::create() + ->join('Review.Book', Criteria::INNER_JOIN) + ->add(BookPeer::TITLE, 'War And Peace', Criteria::EQUAL); + $this->assertTrue($q->equals($q1), 'useFkQuery() translates to a condition on aninner join on required columns'); + } + + public function testUseFkQueryJoinType() + { + $q = BookQuery::create() + ->useAuthorQuery(null, Criteria::LEFT_JOIN) + ->filterByFirstName('Leo') + ->endUse(); + $q1 = BookQuery::create() + ->join('Book.Author', Criteria::LEFT_JOIN) + ->add(AuthorPeer::FIRST_NAME, 'Leo', Criteria::EQUAL); + $this->assertTrue($q->equals($q1), 'useFkQuery() accepts a join type as second parameter'); + } + + public function testUseFkQueryAlias() + { + $q = BookQuery::create() + ->useAuthorQuery('a') + ->filterByFirstName('Leo') + ->endUse(); + $join = new ModelJoin(); + $join->setJoinType(Criteria::LEFT_JOIN); + $join->setTableMap(AuthorPeer::getTableMap()); + $join->setRelationMap(BookPeer::getTableMap()->getRelation('Author'), null, 'a'); + $join->setRelationAlias('a'); + $q1 = BookQuery::create() + ->addAlias('a', AuthorPeer::TABLE_NAME) + ->addJoinObject($join, 'a') + ->add('a.FIRST_NAME', 'Leo', Criteria::EQUAL); + $this->assertTrue($q->equals($q1), 'useFkQuery() uses the first argument as a table alias'); + } + + public function testUseFkQueryMixed() + { + $q = BookQuery::create() + ->useAuthorQuery() + ->filterByFirstName('Leo') + ->endUse() + ->filterByTitle('War And Peace'); + $q1 = BookQuery::create() + ->join('Book.Author', Criteria::LEFT_JOIN) + ->add(AuthorPeer::FIRST_NAME, 'Leo', Criteria::EQUAL) + ->add(BookPeer::TITLE, 'War And Peace', Criteria::EQUAL); + $this->assertTrue($q->equals($q1), 'useFkQuery() allows combining conditions on main and related query'); + } + + public function testUseFkQueryTwice() + { + $q = BookQuery::create() + ->useAuthorQuery() + ->filterByFirstName('Leo') + ->endUse() + ->useAuthorQuery() + ->filterByLastName('Tolstoi') + ->endUse(); + $q1 = BookQuery::create() + ->join('Book.Author', Criteria::LEFT_JOIN) + ->add(AuthorPeer::FIRST_NAME, 'Leo', Criteria::EQUAL) + ->add(AuthorPeer::LAST_NAME, 'Tolstoi', Criteria::EQUAL); + $this->assertTrue($q->equals($q1), 'useFkQuery() called twice on the same relation does not create two joins'); + } + + public function testUseFkQueryTwiceTwoAliases() + { + $q = BookQuery::create() + ->useAuthorQuery('a') + ->filterByFirstName('Leo') + ->endUse() + ->useAuthorQuery('b') + ->filterByLastName('Tolstoi') + ->endUse(); + $join1 = new ModelJoin(); + $join1->setJoinType(Criteria::LEFT_JOIN); + $join1->setTableMap(AuthorPeer::getTableMap()); + $join1->setRelationMap(BookPeer::getTableMap()->getRelation('Author'), null, 'a'); + $join1->setRelationAlias('a'); + $join2 = new ModelJoin(); + $join2->setJoinType(Criteria::LEFT_JOIN); + $join2->setTableMap(AuthorPeer::getTableMap()); + $join2->setRelationMap(BookPeer::getTableMap()->getRelation('Author'), null, 'b'); + $join2->setRelationAlias('b'); + $q1 = BookQuery::create() + ->addAlias('a', AuthorPeer::TABLE_NAME) + ->addJoinObject($join1, 'a') + ->add('a.FIRST_NAME', 'Leo', Criteria::EQUAL) + ->addAlias('b', AuthorPeer::TABLE_NAME) + ->addJoinObject($join2, 'b') + ->add('b.LAST_NAME', 'Tolstoi', Criteria::EQUAL); + $this->assertTrue($q->equals($q1), 'useFkQuery() called twice on the same relation with two aliases creates two joins'); + } + + public function testUseFkQueryNested() + { + $q = ReviewQuery::create() + ->useBookQuery() + ->useAuthorQuery() + ->filterByFirstName('Leo') + ->endUse() + ->endUse(); + $q1 = ReviewQuery::create() + ->join('Review.Book', Criteria::INNER_JOIN) + ->join('Book.Author', Criteria::LEFT_JOIN) + ->add(AuthorPeer::FIRST_NAME, 'Leo', Criteria::EQUAL); + // embedded queries create joins that keep a relation to the parent + // as this is not testable, we need to use another testing technique + $params = array(); + $result = BasePeer::createSelectSql($q, $params); + $expectedParams = array(); + $expectedResult = BasePeer::createSelectSql($q1, $expectedParams); + $this->assertEquals($expectedParams, $params, 'useFkQuery() called nested creates two joins'); + $this->assertEquals($expectedResult, $result, 'useFkQuery() called nested creates two joins'); + } + + public function testUseFkQueryTwoRelations() + { + $q = BookQuery::create() + ->useAuthorQuery() + ->filterByFirstName('Leo') + ->endUse() + ->usePublisherQuery() + ->filterByName('Penguin') + ->endUse(); + $q1 = BookQuery::create() + ->join('Book.Author', Criteria::LEFT_JOIN) + ->add(AuthorPeer::FIRST_NAME, 'Leo', Criteria::EQUAL) + ->join('Book.Publisher', Criteria::LEFT_JOIN) + ->add(PublisherPeer::NAME, 'Penguin', Criteria::EQUAL); + $this->assertTrue($q->equals($q1), 'useFkQuery() called twice on two relations creates two joins'); + } + + public function testPrune() + { + $q = BookQuery::create()->prune(); + $this->assertTrue($q instanceof BookQuery, 'prune() returns the current Query object'); + } + + public function testPruneSimpleKey() + { + BookstoreDataPopulator::depopulate(); + BookstoreDataPopulator::populate(); + + $nbBooks = BookQuery::create()->prune()->count(); + $this->assertEquals(4, $nbBooks, 'prune() does nothing when passed a null object'); + + $testBook = BookQuery::create()->findOne(); + $nbBooks = BookQuery::create()->prune($testBook)->count(); + $this->assertEquals(3, $nbBooks, 'prune() removes an object from the result'); + } + + public function testPruneCompositeKey() + { + BookstoreDataPopulator::depopulate(); + BookstoreDataPopulator::populate(); + + // save all books to make sure related objects are also saved - BookstoreDataPopulator keeps some unsaved + $c = new ModelCriteria('bookstore', 'Book'); + $books = $c->find(); + foreach ($books as $book) { + $book->save(); + } + + BookPeer::clearInstancePool(); + + $nbBookListRel = BookListRelQuery::create()->prune()->count(); + $this->assertEquals(2, $nbBookListRel, 'prune() does nothing when passed a null object'); + + $testBookListRel = BookListRelQuery::create()->findOne(); + $nbBookListRel = BookListRelQuery::create()->prune($testBookListRel)->count(); + $this->assertEquals(1, $nbBookListRel, 'prune() removes an object from the result'); + } +} + +class myCustomBookQuery extends BookQuery +{ + public static function create($modelAlias = null, $criteria = null) + { + if ($criteria instanceof myCustomBookQuery) { + return $criteria; + } + $query = new myCustomBookQuery(); + if (null !== $modelAlias) { + $query->setModelAlias($modelAlias); + } + if ($criteria instanceof Criteria) { + $query->mergeWith($criteria); + } + return $query; + } + +} diff --git a/library/propel/test/testsuite/generator/builder/util/PropelTemplateTest.php b/library/propel/test/testsuite/generator/builder/util/PropelTemplateTest.php new file mode 100644 index 000000000..bba32ac12 --- /dev/null +++ b/library/propel/test/testsuite/generator/builder/util/PropelTemplateTest.php @@ -0,0 +1,54 @@ +setTemplate('Hello, '); + $res = $t->render(); + $this->assertEquals('Hello, 3', $res); + } + + public function testRenderStringOneParam() + { + $t = new PropelTemplate(); + $t->setTemplate('Hello, '); + $res = $t->render(array('name' => 'John')); + $this->assertEquals('Hello, John', $res); + } + + public function testRenderStringParams() + { + $time = time(); + $t = new PropelTemplate(); + $t->setTemplate('Hello, , it is to go!'); + $res = $t->render(array('name' => 'John', 'time' => $time)); + $this->assertEquals('Hello, John, it is ' . $time . ' to go!', $res); + } + + public function testRenderFile() + { + $t = new PropelTemplate(); + $t->setTemplateFile(dirname(__FILE__).'/template.php'); + $res = $t->render(array('name' => 'John')); + $this->assertEquals('Hello, John', $res); + } +} \ No newline at end of file diff --git a/library/propel/test/testsuite/generator/builder/util/template.php b/library/propel/test/testsuite/generator/builder/util/template.php new file mode 100644 index 000000000..2ae040501 --- /dev/null +++ b/library/propel/test/testsuite/generator/builder/util/template.php @@ -0,0 +1 @@ +Hello, \ No newline at end of file diff --git a/library/propel/test/testsuite/generator/model/BehaviorTest.php b/library/propel/test/testsuite/generator/model/BehaviorTest.php new file mode 100644 index 000000000..af1daecb8 --- /dev/null +++ b/library/propel/test/testsuite/generator/model/BehaviorTest.php @@ -0,0 +1,113 @@ +Martin Poeschl + * @version $Revision: 1773 $ + * @package generator.model + */ +class BehaviorTest extends PHPUnit_Framework_TestCase { + + private $xmlToAppData; + private $appData; + + public function testSetupObject() + { + $b = new Behavior(); + $b->loadFromXML(array('name' => 'foo')); + $this->assertEquals($b->getName(), 'foo', 'setupObject() sets the Behavior name from XML attributes'); + } + + public function testName() + { + $b = new Behavior(); + $this->assertNull($b->getName(), 'Behavior name is null by default'); + $b->setName('foo'); + $this->assertEquals($b->getName(), 'foo', 'setName() sets the name, and getName() gets it'); + } + + public function testTable() + { + $b = new Behavior(); + $this->assertNull($b->getTable(), 'Behavior Table is null by default'); + $t = new Table(); + $t->setName('fooTable'); + $b->setTable($t); + $this->assertEquals($b->getTable(), $t, 'setTable() sets the name, and getTable() gets it'); + } + + public function testParameters() + { + $b = new Behavior(); + $this->assertEquals($b->getParameters(), array(), 'Behavior parameters is an empty array by default'); + $b->addParameter(array('name' => 'foo', 'value' => 'bar')); + $this->assertEquals($b->getParameters(), array('foo' => 'bar'), 'addParameter() sets a parameter from an associative array'); + $b->addParameter(array('name' => 'foo2', 'value' => 'bar2')); + $this->assertEquals($b->getParameters(), array('foo' => 'bar', 'foo2' => 'bar2'), 'addParameter() adds a parameter from an associative array'); + $b->addParameter(array('name' => 'foo', 'value' => 'bar3')); + $this->assertEquals($b->getParameters(), array('foo' => 'bar3', 'foo2' => 'bar2'), 'addParameter() changes a parameter from an associative array'); + $this->assertEquals($b->getParameter('foo'), 'bar3', 'getParameter() retrieves a parameter value by name'); + $b->setParameters(array('foo3' => 'bar3', 'foo4' => 'bar4')); + $this->assertEquals($b->getParameters(), array('foo3' => 'bar3', 'foo4' => 'bar4'), 'setParameters() changes the whole parameter array'); + } + + /** + * test if the tables get the package name from the properties file + * + */ + public function testXmlToAppData() + { + include_once 'builder/util/XmlToAppData.php'; + $this->xmlToAppData = new XmlToAppData(new MysqlPlatform(), "defaultpackage", null); + $this->appData = $this->xmlToAppData->parseFile('fixtures/bookstore/behavior-timestampable-schema.xml'); + $table = $this->appData->getDatabase("bookstore-behavior")->getTable('table1'); + $behaviors = $table->getBehaviors(); + $this->assertEquals(count($behaviors), 1, 'XmlToAppData ads as many behaviors as there are behaviors tags'); + $behavior = $table->getBehavior('timestampable'); + $this->assertEquals($behavior->getTable()->getName(), 'table1', 'XmlToAppData sets the behavior table correctly'); + $this->assertEquals($behavior->getParameters(), array('create_column' => 'created_on', 'update_column' => 'updated_on'), 'XmlToAppData sets the behavior parameters correctly'); + } + + public function testMofifyTable() + { + set_include_path(get_include_path() . PATH_SEPARATOR . "fixtures/bookstore/build/classes"); + Propel::init('fixtures/bookstore/build/conf/bookstore-conf.php'); + $tmap = Propel::getDatabaseMap(Table2Peer::DATABASE_NAME)->getTable(Table2Peer::TABLE_NAME); + $this->assertEquals(count($tmap->getColumns()), 4, 'A behavior can modify its table by implementing modifyTable()'); + } + + public function testModifyDatabase() + { + set_include_path(get_include_path() . PATH_SEPARATOR . "fixtures/bookstore/build/classes"); + require_once dirname(__FILE__) . '/../../../../runtime/lib/Propel.php'; + Propel::init('fixtures/bookstore/build/conf/bookstore-conf.php'); + $tmap = Propel::getDatabaseMap(Table3Peer::DATABASE_NAME)->getTable(Table3Peer::TABLE_NAME); + $this->assertTrue(array_key_exists('do_nothing', $tmap->getBehaviors()), 'A database behavior is automatically copied to all its table'); + } + + public function testGetColumnForParameter() + { + $this->xmlToAppData = new XmlToAppData(new MysqlPlatform(), "defaultpackage", null); + $this->appData = $this->xmlToAppData->parseFile('fixtures/bookstore/behavior-timestampable-schema.xml'); + + $table = $this->appData->getDatabase("bookstore-behavior")->getTable('table1'); + $behavior = $table->getBehavior('timestampable'); + $this->assertEquals($table->getColumn('created_on'), $behavior->getColumnForParameter('create_column'), 'getColumnForParameter() returns the configured column for behavior based on a parameter name'); + + } +} diff --git a/library/propel/test/testsuite/generator/model/ColumnTest.php b/library/propel/test/testsuite/generator/model/ColumnTest.php new file mode 100644 index 000000000..dbaf69432 --- /dev/null +++ b/library/propel/test/testsuite/generator/model/ColumnTest.php @@ -0,0 +1,81 @@ +Martin Poeschl + * @version $Revision: 1612 $ + * @package generator.model + */ +class ColumnTest extends PHPUnit_Framework_TestCase { + + /** + * Tests static Column::makeList() method. + * @deprecated - Column::makeList() is deprecated and set to be removed in 1.3 + */ + public function testMakeList() + { + $expected = "`Column0`, `Column1`, `Column2`, `Column3`, `Column4`"; + $objArray = array(); + for ($i=0; $i<5; $i++) { + $c = new Column(); + $c->setName("Column" . $i); + $objArray[] = $c; + } + + $list = Column::makeList($objArray, new MySQLPlatform()); + $this->assertEquals($expected, $list, sprintf("Expected '%s' match, got '%s' ", var_export($expected, true), var_export($list,true))); + + $strArray = array(); + for ($i=0; $i<5; $i++) { + $strArray[] = "Column" . $i; + } + + $list = Column::makeList($strArray, new MySQLPlatform()); + $this->assertEquals($expected, $list, sprintf("Expected '%s' match, got '%s' ", var_export($expected, true), var_export($list,true))); + + } + + public function testPhpNamingMethod() + { + set_include_path(get_include_path() . PATH_SEPARATOR . "fixtures/bookstore/build/classes"); + Propel::init('fixtures/bookstore/build/conf/bookstore-conf.php'); + $bookTmap = Propel::getDatabaseMap(BookPeer::DATABASE_NAME)->getTable(BookPeer::TABLE_NAME); + $this->assertEquals('AuthorId', $bookTmap->getColumn('AUTHOR_ID')->getPhpName(), 'setPhpName() uses the default phpNamingMethod'); + $pageTmap = Propel::getDatabaseMap(PagePeer::DATABASE_NAME)->getTable(PagePeer::TABLE_NAME); + $this->assertEquals('LeftChild', $pageTmap->getColumn('LEFTCHILD')->getPhpName(), 'setPhpName() uses the configured phpNamingMethod'); + } + + public function testGetConstantName() + { + $xmlToAppData = new XmlToAppData(new MysqlPlatform(), "defaultpackage", null); + $appData = $xmlToAppData->parseFile('fixtures/bookstore/behavior-timestampable-schema.xml'); + $column = $appData->getDatabase("bookstore-behavior")->getTable('table1')->getColumn('title'); + $this->assertEquals('Table1Peer::TITLE', $column->getConstantName(), 'getConstantName() returns the complete constant name by default'); + } + + public function testIsLocalColumnsRequired() + { + $xmlToAppData = new XmlToAppData(new MysqlPlatform(), "defaultpackage", null); + $appData = $xmlToAppData->parseFile('fixtures/bookstore/schema.xml'); + $fk = $appData->getDatabase("bookstore")->getTable('book')->getColumnForeignKeys('publisher_id'); + $this->assertFalse($fk[0]->isLocalColumnsRequired()); + $fk = $appData->getDatabase("bookstore")->getTable('review')->getColumnForeignKeys('book_id'); + $this->assertTrue($fk[0]->isLocalColumnsRequired()); + } + +} diff --git a/library/propel/test/testsuite/generator/model/NameFactoryTest.php b/library/propel/test/testsuite/generator/model/NameFactoryTest.php new file mode 100644 index 000000000..e65ae05f4 --- /dev/null +++ b/library/propel/test/testsuite/generator/model/NameFactoryTest.php @@ -0,0 +1,151 @@ +Unit tests for class NameFactory and known + * NameGenerator implementations.

    + * + *

    To add more tests, add entries to the ALGORITHMS, + * INPUTS, and OUTPUTS arrays, and code to + * the makeInputs() method.

    + * + *

    This test assumes that it's being run using the MySQL database + * adapter, DBMM. MySQL has a column length limit of 64 + * characters.

    + * + * @author Daniel Rall + * @version $Id: NameFactoryTest.php 1612 2010-03-16 22:56:21Z francois $ + * @package generator.model + */ +class NameFactoryTest extends BaseTestCase +{ + /** The database to mimic in generating the SQL. */ + const DATABASE_TYPE = "mysql"; + + /** + * The list of known name generation algorithms, specified as the + * fully qualified class names to NameGenerator + * implementations. + */ + private static $ALGORITHMS = array(NameFactory::CONSTRAINT_GENERATOR, NameFactory::PHP_GENERATOR); + + /** + * Two dimensional arrays of inputs for each algorithm. + */ + private static $INPUTS = array(); + + + /** + * Given the known inputs, the expected name outputs. + */ + private static $OUTPUTS = array(); + + /** + * Used as an input. + */ + private $database; + + /** + * Creates a new instance. + * + */ + public function __construct() { + + self::$INPUTS = array( + array( array(self::makeString(61), "I", 1), + array(self::makeString(61), "I", 2), + array(self::makeString(65), "I", 3), + array(self::makeString(4), "FK", 1), + array(self::makeString(5), "FK", 2) + ), + array( + array("MY_USER", NameGenerator::CONV_METHOD_UNDERSCORE), + array("MY_USER", NameGenerator::CONV_METHOD_PHPNAME), + array("MY_USER", NameGenerator::CONV_METHOD_NOCHANGE) + ) + ); + + + self::$OUTPUTS = array( + array( + self::makeString(60) . "_I_1", + self::makeString(60) . "_I_2", + self::makeString(60) . "_I_3", + self::makeString(4) . "_FK_1", + self::makeString(5) . "_FK_2"), + array("MyUser", "MYUSER", "MY_USER") + ); + + } + + /** + * Creates a string of the specified length consisting entirely of + * the character A. Useful for simulating table + * names, etc. + * + * @param int $len the number of characters to include in the string + * @return a string of length len with every character an 'A' + */ + private static function makeString($len) { + $buf = ""; + for ($i = 0; $i < $len; $i++) { + $buf .= 'A'; + } + return $buf; + } + + /** Sets up the Propel model. */ + public function setUp() + { + $appData = new AppData(new MysqlPlatform()); + $this->database = new Database(); + $appData->addDatabase($this->database); + } + + /** + * @throws Exception on fail + */ + public function testNames() { + for ($algoIndex = 0; $algoIndex < count(self::$ALGORITHMS); $algoIndex++) { + $algo = self::$ALGORITHMS[$algoIndex]; + $algoInputs = self::$INPUTS[$algoIndex]; + for ($i = 0; $i < count($algoInputs); $i++) { + $inputs = $this->makeInputs($algo, $algoInputs[$i]); + $generated = NameFactory::generateName($algo, $inputs); + $expected = self::$OUTPUTS[$algoIndex][$i]; + $this->assertEquals($expected, $generated, 0, "Algorithm " . $algo . " failed to generate an unique name"); + } + } + } + + /** + * Creates the list of arguments to pass to the specified type of + * NameGenerator implementation. + * + * @param algo The class name of the NameGenerator to + * create an argument list for. + * @param inputs The (possibly partial) list inputs from which to + * generate the final list. + * @return the list of arguments to pass to the NameGenerator + */ + private function makeInputs($algo, $inputs) + { + if (NameFactory::CONSTRAINT_GENERATOR == $algo) { + array_unshift($inputs, $this->database); + } + return $inputs; + } + +} diff --git a/library/propel/test/testsuite/generator/model/PhpNameGeneratorTest.php b/library/propel/test/testsuite/generator/model/PhpNameGeneratorTest.php new file mode 100644 index 000000000..ecde25cc6 --- /dev/null +++ b/library/propel/test/testsuite/generator/model/PhpNameGeneratorTest.php @@ -0,0 +1,55 @@ +Martin Poeschl + * @version $Revision: 1612 $ + * @package generator.model + */ +class PhpNameGeneratorTest extends PHPUnit_Framework_TestCase +{ + public static function testPhpnameMethodDataProvider() + { + return array( + array('foo', 'Foo'), + array('Foo', 'Foo'), + array('FOO', 'FOO'), + array('123', '123'), + array('foo_bar', 'FooBar'), + array('bar_1', 'Bar1'), + array('bar_0', 'Bar0'), + array('my_CLASS_name', 'MyCLASSName'), + ); + } + + /** + * @dataProvider testPhpnameMethodDataProvider + */ + public function testPhpnameMethod($input, $output) + { + $generator = new TestablePhpNameGenerator(); + $this->assertEquals($output, $generator->phpnameMethod($input)); + } + +} + +class TestablePhpNameGenerator extends PhpNameGenerator +{ + public function phpnameMethod($schemaName) + { + return parent::phpnameMethod($schemaName); + } +} \ No newline at end of file diff --git a/library/propel/test/testsuite/generator/model/TableTest.php b/library/propel/test/testsuite/generator/model/TableTest.php new file mode 100644 index 000000000..d77a52f1a --- /dev/null +++ b/library/propel/test/testsuite/generator/model/TableTest.php @@ -0,0 +1,109 @@ +xmlToAppData = new XmlToAppData(new MysqlPlatform(), "defaultpackage", null); + + //$this->appData = $this->xmlToAppData->parseFile(dirname(__FILE__) . "/tabletest-schema.xml"); + $this->appData = $this->xmlToAppData->parseFile("etc/schema/tabletest-schema.xml"); + + $db = $this->appData->getDatabase("iddb"); + $expected = IDMethod::NATIVE; + $result = $db->getDefaultIdMethod(); + $this->assertEquals($expected, $result); + + $table2 = $db->getTable("table_native"); + $expected = IDMethod::NATIVE; + $result = $table2->getIdMethod(); + $this->assertEquals($expected, $result); + + $table = $db->getTable("table_none"); + $expected = IDMethod::NO_ID_METHOD; + $result = $table->getIdMethod(); + $this->assertEquals($expected, $result); + } + + public function testGeneratorConfig() + { + $xmlToAppData = new XmlToAppData(new MysqlPlatform(), "defaultpackage", null); + $appData = $xmlToAppData->parseFile('fixtures/bookstore/behavior-timestampable-schema.xml'); + $table = $appData->getDatabase("bookstore-behavior")->getTable('table1'); + $config = new GeneratorConfig(); + $config->setBuildProperties(array('propel.foo.bar.class' => 'bazz')); + $table->getDatabase()->getAppData()->getPlatform()->setGeneratorConfig($config); + $this->assertThat($table->getGeneratorConfig(), $this->isInstanceOf('GeneratorConfig'), 'getGeneratorConfig() returns an instance of the generator configuration'); + $this->assertEquals($table->getGeneratorConfig()->getBuildProperty('fooBarClass'), 'bazz', 'getGeneratorConfig() returns the instance of the generator configuration used in the platform'); + } + + public function testAddBehavior() + { + $platform = new MysqlPlatform(); + $config = new GeneratorConfig(); + $config->setBuildProperties(array( + 'propel.behavior.timestampable.class' => 'behavior.TimestampableBehavior' + )); + $platform->setGeneratorConfig($config); + $xmlToAppData = new XmlToAppData($platform, "defaultpackage", null); + $appData = $xmlToAppData->parseFile('fixtures/bookstore/behavior-timestampable-schema.xml'); + $table = $appData->getDatabase("bookstore-behavior")->getTable('table1'); + $this->assertThat($table->getBehavior('timestampable'), $this->isInstanceOf('TimestampableBehavior'), 'addBehavior() uses the behavior class defined in build.properties'); + } + + public function testUniqueColumnName() + { + $platform = new MysqlPlatform(); + $config = new GeneratorConfig(); + $platform->setGeneratorConfig($config); + $xmlToAppData = new XmlToAppData($platform, 'defaultpackage', null); + try + { + $appData = $xmlToAppData->parseFile('fixtures/unique-column/column-schema.xml'); + $this->fail('Parsing file with duplicate column names in one table throws exception'); + } catch (EngineException $e) { + $this->assertTrue(true, 'Parsing file with duplicate column names in one table throws exception'); + } + } + + public function testUniqueTableName() + { + $platform = new MysqlPlatform(); + $config = new GeneratorConfig(); + $platform->setGeneratorConfig($config); + $xmlToAppData = new XmlToAppData($platform, 'defaultpackage', null); + try { + $appData = $xmlToAppData->parseFile('fixtures/unique-column/table-schema.xml'); + $this->fail('Parsing file with duplicate table name throws exception'); + } catch (EngineException $e) { + $this->assertTrue(true, 'Parsing file with duplicate table name throws exception'); + } + } +} diff --git a/library/propel/test/testsuite/generator/platform/DefaultPlatformTest.php b/library/propel/test/testsuite/generator/platform/DefaultPlatformTest.php new file mode 100644 index 000000000..db1c91ef9 --- /dev/null +++ b/library/propel/test/testsuite/generator/platform/DefaultPlatformTest.php @@ -0,0 +1,45 @@ +getPlatform(); + + $unquoted = "Nice"; + $quoted = $p->quote($unquoted); + + $this->assertEquals("'$unquoted'", $quoted); + + + $unquoted = "Naughty ' string"; + $quoted = $p->quote($unquoted); + $expected = "'Naughty '' string'"; + $this->assertEquals($expected, $quoted); + } + +} diff --git a/library/propel/test/testsuite/generator/platform/PlatformTestBase.php b/library/propel/test/testsuite/generator/platform/PlatformTestBase.php new file mode 100644 index 000000000..f384ca8c0 --- /dev/null +++ b/library/propel/test/testsuite/generator/platform/PlatformTestBase.php @@ -0,0 +1,55 @@ +platform = new $clazz(); + } + + /** + * + */ + protected function tearDown() + { + parent::tearDown(); + } + + /** + * + * @return Platform + */ + protected function getPlatform() + { + return $this->platform; + } + +} diff --git a/library/propel/test/testsuite/generator/platform/SqlitePlatformTest.php b/library/propel/test/testsuite/generator/platform/SqlitePlatformTest.php new file mode 100644 index 000000000..4380485e3 --- /dev/null +++ b/library/propel/test/testsuite/generator/platform/SqlitePlatformTest.php @@ -0,0 +1,48 @@ +pdo = new PDO("sqlite::memory:"); + + } + + public function tearDown() + { + parent::tearDown(); + } + + public function testQuoteConnected() + { + $p = $this->getPlatform(); + $p->setConnection($this->pdo); + + $unquoted = "Naughty ' string"; + $quoted = $p->quote($unquoted); + + $expected = "'Naughty '' string'"; + $this->assertEquals($expected, $quoted); + } + +} diff --git a/library/propel/test/testsuite/misc/BookstoreTest.php b/library/propel/test/testsuite/misc/BookstoreTest.php new file mode 100644 index 000000000..c0f63e28e --- /dev/null +++ b/library/propel/test/testsuite/misc/BookstoreTest.php @@ -0,0 +1,870 @@ + + * @package misc + */ +class BookstoreTest extends BookstoreEmptyTestBase +{ + public function testScenario() + { + // Add publisher records + // --------------------- + + try { + $scholastic = new Publisher(); + $scholastic->setName("Scholastic"); + // do not save, will do later to test cascade + + $morrow = new Publisher(); + $morrow->setName("William Morrow"); + $morrow->save(); + $morrow_id = $morrow->getId(); + + $penguin = new Publisher(); + $penguin->setName("Penguin"); + $penguin->save(); + $penguin_id = $penguin->getId(); + + $vintage = new Publisher(); + $vintage->setName("Vintage"); + $vintage->save(); + $vintage_id = $vintage->getId(); + $this->assertTrue(true, 'Save Publisher records'); + } catch (Exception $e) { + $this->fail('Save publisher records'); + } + + // Add author records + // ------------------ + + try { + $rowling = new Author(); + $rowling->setFirstName("J.K."); + $rowling->setLastName("Rowling"); + // do not save, will do later to test cascade + + $stephenson = new Author(); + $stephenson->setFirstName("Neal"); + $stephenson->setLastName("Stephenson"); + $stephenson->save(); + $stephenson_id = $stephenson->getId(); + + $byron = new Author(); + $byron->setFirstName("George"); + $byron->setLastName("Byron"); + $byron->save(); + $byron_id = $byron->getId(); + + $grass = new Author(); + $grass->setFirstName("Gunter"); + $grass->setLastName("Grass"); + $grass->save(); + $grass_id = $grass->getId(); + $this->assertTrue(true, 'Save Author records'); + } catch (Exception $e) { + $this->fail('Save Author records'); + } + + // Add book records + // ---------------- + + try { + $phoenix = new Book(); + $phoenix->setTitle("Harry Potter and the Order of the Phoenix"); + $phoenix->setISBN("043935806X"); + $phoenix->setAuthor($rowling); + $phoenix->setPublisher($scholastic); + $phoenix->save(); + $phoenix_id = $phoenix->getId(); + $this->assertFalse($rowling->isNew(), 'saving book also saves related author'); + $this->assertFalse($scholastic->isNew(), 'saving book also saves related publisher'); + + $qs = new Book(); + $qs->setISBN("0380977427"); + $qs->setTitle("Quicksilver"); + $qs->setAuthor($stephenson); + $qs->setPublisher($morrow); + $qs->save(); + $qs_id = $qs->getId(); + + $dj = new Book(); + $dj->setISBN("0140422161"); + $dj->setTitle("Don Juan"); + $dj->setAuthor($byron); + $dj->setPublisher($penguin); + $dj->save(); + $dj_id = $qs->getId(); + + $td = new Book(); + $td->setISBN("067972575X"); + $td->setTitle("The Tin Drum"); + $td->setAuthor($grass); + $td->setPublisher($vintage); + $td->save(); + $td_id = $td->getId(); + $this->assertTrue(true, 'Save Book records'); + } catch (Exception $e) { + $this->fail('Save Author records'); + } + + // Add review records + // ------------------ + + try { + $r1 = new Review(); + $r1->setBook($phoenix); + $r1->setReviewedBy("Washington Post"); + $r1->setRecommended(true); + $r1->setReviewDate(time()); + $r1->save(); + $r1_id = $r1->getId(); + + $r2 = new Review(); + $r2->setBook($phoenix); + $r2->setReviewedBy("New York Times"); + $r2->setRecommended(false); + $r2->setReviewDate(time()); + $r2->save(); + $r2_id = $r2->getId(); + $this->assertTrue(true, 'Save Review records'); + } catch (Exception $e) { + $this->fail('Save Review records'); + } + + // Perform a "complex" search + // -------------------------- + + $crit = new Criteria(); + $crit->add(BookPeer::TITLE, 'Harry%', Criteria::LIKE); + $results = BookPeer::doSelect($crit); + $this->assertEquals(1, count($results)); + + $crit2 = new Criteria(); + $crit2->add(BookPeer::ISBN, array("0380977427", "0140422161"), Criteria::IN); + $results = BookPeer::doSelect($crit2); + $this->assertEquals(2, count($results)); + + // Perform a "limit" search + // ------------------------ + + $crit = new Criteria(); + $crit->setLimit(2); + $crit->setOffset(1); + $crit->addAscendingOrderByColumn(BookPeer::TITLE); + + $results = BookPeer::doSelect($crit); + $this->assertEquals(2, count($results)); + + // we ordered on book title, so we expect to get + $this->assertEquals("Harry Potter and the Order of the Phoenix", $results[0]->getTitle()); + $this->assertEquals("Quicksilver", $results[1]->getTitle()); + + // Perform a lookup & update! + // -------------------------- + + // Updating just-created book title + // First finding book by PK (=$qs_id) .... + $qs_lookup = BookPeer::retrieveByPk($qs_id); + $this->assertNotNull($qs_lookup, 'just-created book can be found by pk'); + + $new_title = "Quicksilver (".crc32(uniqid(rand())).")"; + // Attempting to update found object + $qs_lookup->setTitle($new_title); + $qs_lookup->save(); + + // Making sure object was correctly updated: "; + $qs_lookup2 = BookPeer::retrieveByPk($qs_id); + $this->assertEquals($new_title, $qs_lookup2->getTitle()); + + // Test some basic DATE / TIME stuff + // --------------------------------- + + // that's the control timestamp. + $control = strtotime('2004-02-29 00:00:00'); + + // should be two in the db + $r = ReviewPeer::doSelectOne(new Criteria()); + $r_id = $r->getId(); + $r->setReviewDate($control); + $r->save(); + + $r2 = ReviewPeer::retrieveByPk($r_id); + + $this->assertEquals(new Datetime('2004-02-29 00:00:00'), $r2->getReviewDate(null), 'ability to fetch DateTime'); + $this->assertEquals($control, $r2->getReviewDate('U'), 'ability to fetch native unix timestamp'); + $this->assertEquals('2-29-2004', $r2->getReviewDate('n-j-Y'), 'ability to use date() formatter'); + + // Handle BLOB/CLOB Columns + // ------------------------ + + $blob_path = dirname(__FILE__) . '/../../etc/lob/tin_drum.gif'; + $blob2_path = dirname(__FILE__) . '/../../etc/lob/propel.gif'; + $clob_path = dirname(__FILE__) . '/../../etc/lob/tin_drum.txt'; + + $m1 = new Media(); + $m1->setBook($phoenix); + $m1->setCoverImage(file_get_contents($blob_path)); + $m1->setExcerpt(file_get_contents($clob_path)); + $m1->save(); + $m1_id = $m1->getId(); + + $m1_lookup = MediaPeer::retrieveByPk($m1_id); + + $this->assertNotNull($m1_lookup, 'Can find just-created media item'); + $this->assertEquals(file_get_contents($blob_path), stream_get_contents($m1_lookup->getCoverImage()), 'BLOB was correctly updated'); + $this->assertEquals(file_get_contents($clob_path), (string) $m1_lookup->getExcerpt(), 'CLOB was correctly updated'); + + // now update the BLOB column and save it & check the results + $m1_lookup->setCoverImage(file_get_contents($blob2_path)); + $m1_lookup->save(); + + $m2_lookup = MediaPeer::retrieveByPk($m1_id); + $this->assertNotNull($m2_lookup, 'Can find just-created media item'); + + $this->assertEquals(file_get_contents($blob2_path), stream_get_contents($m2_lookup->getCoverImage()), 'BLOB was correctly overwritten'); + + // Test Validators + // --------------- + + require_once 'tools/helpers/bookstore/validator/ISBNValidator.php'; + + $bk1 = new Book(); + $bk1->setTitle("12345"); // min length is 10 + $ret = $bk1->validate(); + + $this->assertFalse($ret, 'validation failed'); + $failures = $bk1->getValidationFailures(); + $this->assertEquals(1, count($failures), '1 validation message was returned'); + + $el = array_shift($failures); + $this->assertContains("must be more than", $el->getMessage(), 'Expected validation message was returned'); + + $bk2 = new Book(); + $bk2->setTitle("Don Juan"); + $ret = $bk2->validate(); + + $this->assertFalse($ret, 'validation failed'); + + $failures = $bk2->getValidationFailures(); + $this->assertEquals(1, count($failures), '1 validation message was returned'); + + $el = array_shift($failures); + $this->assertContains("Book title already in database.", $el->getMessage(), 'Expected validation message was returned'); + + //Now trying some more complex validation. + $auth1 = new Author(); + $auth1->setFirstName("Hans"); + // last name required; will fail + + $bk1->setAuthor($auth1); + + $rev1 = new Review(); + $rev1->setReviewDate("08/09/2001"); + // will fail: reviewed_by column required + + $bk1->addReview($rev1); + + $ret2 = $bk1->validate(); + $this->assertFalse($ret2, 'validation failed'); + + $failures2 = $bk1->getValidationFailures(); + + $this->assertEquals(3, count($failures2), '3 validation messages were returned'); + + $expectedKeys = array( + AuthorPeer::LAST_NAME, + BookPeer::TITLE, + ReviewPeer::REVIEWED_BY, + ); + $this->assertEquals($expectedKeys, array_keys($failures2), 'correct columns failed'); + + $bk2 = new Book(); + $bk2->setTitle("12345678901"); // passes + + $auth2 = new Author(); + $auth2->setLastName("Blah"); //passes + $auth2->setEmail("some@body.com"); //passes + $auth2->setAge(50); //passes + $bk2->setAuthor($auth2); + + $rev2 = new Review(); + $rev2->setReviewedBy("Me!"); // passes + $rev2->setStatus("new"); // passes + $bk2->addReview($rev2); + + $ret3 = $bk2->validate(); + + $this->assertTrue($ret3, 'complex validation can pass'); + + // Testing doCount() functionality + // ------------------------------- + + $c = new Criteria(); + $records = BookPeer::doSelect($c); + $count = BookPeer::doCount($c); + + $this->assertEquals($count, count($records), 'correct number of results'); + + // Test many-to-many relationships + // --------------- + + // init book club list 1 with 2 books + + $blc1 = new BookClubList(); + $blc1->setGroupLeader("Crazyleggs"); + $blc1->setTheme("Happiness"); + + $brel1 = new BookListRel(); + $brel1->setBook($phoenix); + + $brel2 = new BookListRel(); + $brel2->setBook($dj); + + $blc1->addBookListRel($brel1); + $blc1->addBookListRel($brel2); + + $blc1->save(); + + $this->assertNotNull($blc1->getId(), 'BookClubList 1 was saved'); + + // init book club list 2 with 1 book + + $blc2 = new BookClubList(); + $blc2->setGroupLeader("John Foo"); + $blc2->setTheme("Default"); + + $brel3 = new BookListRel(); + $brel3->setBook($phoenix); + + $blc2->addBookListRel($brel3); + + $blc2->save(); + + $this->assertNotNull($blc2->getId(), 'BookClubList 2 was saved'); + + // re-fetch books and lists from db to be sure that nothing is cached + + $crit = new Criteria(); + $crit->add(BookPeer::ID, $phoenix->getId()); + $phoenix = BookPeer::doSelectOne($crit); + $this->assertNotNull($phoenix, "book 'phoenix' has been re-fetched from db"); + + $crit = new Criteria(); + $crit->add(BookClubListPeer::ID, $blc1->getId()); + $blc1 = BookClubListPeer::doSelectOne($crit); + $this->assertNotNull($blc1, 'BookClubList 1 has been re-fetched from db'); + + $crit = new Criteria(); + $crit->add(BookClubListPeer::ID, $blc2->getId()); + $blc2 = BookClubListPeer::doSelectOne($crit); + $this->assertNotNull($blc2, 'BookClubList 2 has been re-fetched from db'); + + $relCount = $phoenix->countBookListRels(); + $this->assertEquals(2, $relCount, "book 'phoenix' has 2 BookListRels"); + + $relCount = $blc1->countBookListRels(); + $this->assertEquals(2, $relCount, 'BookClubList 1 has 2 BookListRels'); + + $relCount = $blc2->countBookListRels(); + $this->assertEquals(1, $relCount, 'BookClubList 2 has 1 BookListRel'); + + // Cleanup (tests DELETE) + // ---------------------- + + // Removing books that were just created + // First finding book by PK (=$phoenix_id) .... + $hp = BookPeer::retrieveByPk($phoenix_id); + $this->assertNotNull($hp, 'Could find just-created book'); + + // Attempting to delete [multi-table] by found pk + $c = new Criteria(); + $c->add(BookPeer::ID, $hp->getId()); + // The only way for cascading to work currently + // is to specify the author_id and publisher_id (i.e. the fkeys + // have to be in the criteria). + $c->add(AuthorPeer::ID, $hp->getAuthor()->getId()); + $c->add(PublisherPeer::ID, $hp->getPublisher()->getId()); + $c->setSingleRecord(true); + BookPeer::doDelete($c); + + // Checking to make sure correct records were removed. + $this->assertEquals(3, AuthorPeer::doCount(new Criteria()), 'Correct records were removed from author table'); + $this->assertEquals(3, PublisherPeer::doCount(new Criteria()), 'Correct records were removed from publisher table'); + $this->assertEquals(3, BookPeer::doCount(new Criteria()), 'Correct records were removed from book table'); + + // Attempting to delete books by complex criteria + $c = new Criteria(); + $cn = $c->getNewCriterion(BookPeer::ISBN, "043935806X"); + $cn->addOr($c->getNewCriterion(BookPeer::ISBN, "0380977427")); + $cn->addOr($c->getNewCriterion(BookPeer::ISBN, "0140422161")); + $c->add($cn); + BookPeer::doDelete($c); + + // Attempting to delete book [id = $td_id] + $td->delete(); + + // Attempting to delete authors + AuthorPeer::doDelete($stephenson_id); + AuthorPeer::doDelete($byron_id); + $grass->delete(); + + // Attempting to delete publishers + PublisherPeer::doDelete($morrow_id); + PublisherPeer::doDelete($penguin_id); + $vintage->delete(); + + // These have to be deleted manually also since we have onDelete + // set to SETNULL in the foreign keys in book. Is this correct? + $rowling->delete(); + $scholastic->delete(); + $blc1->delete(); + $blc2->delete(); + + $this->assertEquals(array(), AuthorPeer::doSelect(new Criteria()), 'no records in [author] table'); + $this->assertEquals(array(), PublisherPeer::doSelect(new Criteria()), 'no records in [publisher] table'); + $this->assertEquals(array(), BookPeer::doSelect(new Criteria()), 'no records in [book] table'); + $this->assertEquals(array(), ReviewPeer::doSelect(new Criteria()), 'no records in [review] table'); + $this->assertEquals(array(), MediaPeer::doSelect(new Criteria()), 'no records in [media] table'); + $this->assertEquals(array(), BookClubListPeer::doSelect(new Criteria()), 'no records in [book_club_list] table'); + $this->assertEquals(array(), BookListRelPeer::doSelect(new Criteria()), 'no records in [book_x_list] table'); + + } + + public function testScenarioUsingQuery() + { + // Add publisher records + // --------------------- + + try { + $scholastic = new Publisher(); + $scholastic->setName("Scholastic"); + // do not save, will do later to test cascade + + $morrow = new Publisher(); + $morrow->setName("William Morrow"); + $morrow->save(); + $morrow_id = $morrow->getId(); + + $penguin = new Publisher(); + $penguin->setName("Penguin"); + $penguin->save(); + $penguin_id = $penguin->getId(); + + $vintage = new Publisher(); + $vintage->setName("Vintage"); + $vintage->save(); + $vintage_id = $vintage->getId(); + $this->assertTrue(true, 'Save Publisher records'); + } catch (Exception $e) { + $this->fail('Save publisher records'); + } + + // Add author records + // ------------------ + + try { + $rowling = new Author(); + $rowling->setFirstName("J.K."); + $rowling->setLastName("Rowling"); + // do not save, will do later to test cascade + + $stephenson = new Author(); + $stephenson->setFirstName("Neal"); + $stephenson->setLastName("Stephenson"); + $stephenson->save(); + $stephenson_id = $stephenson->getId(); + + $byron = new Author(); + $byron->setFirstName("George"); + $byron->setLastName("Byron"); + $byron->save(); + $byron_id = $byron->getId(); + + $grass = new Author(); + $grass->setFirstName("Gunter"); + $grass->setLastName("Grass"); + $grass->save(); + $grass_id = $grass->getId(); + $this->assertTrue(true, 'Save Author records'); + } catch (Exception $e) { + $this->fail('Save Author records'); + } + + // Add book records + // ---------------- + + try { + $phoenix = new Book(); + $phoenix->setTitle("Harry Potter and the Order of the Phoenix"); + $phoenix->setISBN("043935806X"); + $phoenix->setAuthor($rowling); + $phoenix->setPublisher($scholastic); + $phoenix->save(); + $phoenix_id = $phoenix->getId(); + $this->assertFalse($rowling->isNew(), 'saving book also saves related author'); + $this->assertFalse($scholastic->isNew(), 'saving book also saves related publisher'); + + $qs = new Book(); + $qs->setISBN("0380977427"); + $qs->setTitle("Quicksilver"); + $qs->setAuthor($stephenson); + $qs->setPublisher($morrow); + $qs->save(); + $qs_id = $qs->getId(); + + $dj = new Book(); + $dj->setISBN("0140422161"); + $dj->setTitle("Don Juan"); + $dj->setAuthor($byron); + $dj->setPublisher($penguin); + $dj->save(); + $dj_id = $qs->getId(); + + $td = new Book(); + $td->setISBN("067972575X"); + $td->setTitle("The Tin Drum"); + $td->setAuthor($grass); + $td->setPublisher($vintage); + $td->save(); + $td_id = $td->getId(); + $this->assertTrue(true, 'Save Book records'); + } catch (Exception $e) { + $this->fail('Save Author records'); + } + + // Add review records + // ------------------ + + try { + $r1 = new Review(); + $r1->setBook($phoenix); + $r1->setReviewedBy("Washington Post"); + $r1->setRecommended(true); + $r1->setReviewDate(time()); + $r1->save(); + $r1_id = $r1->getId(); + + $r2 = new Review(); + $r2->setBook($phoenix); + $r2->setReviewedBy("New York Times"); + $r2->setRecommended(false); + $r2->setReviewDate(time()); + $r2->save(); + $r2_id = $r2->getId(); + $this->assertTrue(true, 'Save Review records'); + } catch (Exception $e) { + $this->fail('Save Review records'); + } + + // Perform a "complex" search + // -------------------------- + + $results = BookQuery::create() + ->filterByTitle('Harry%') + ->find(); + $this->assertEquals(1, count($results)); + + $results = BookQuery::create() + ->where('Book.ISBN IN ?', array("0380977427", "0140422161")) + ->find(); + $this->assertEquals(2, count($results)); + + // Perform a "limit" search + // ------------------------ + + $results = BookQuery::create() + ->limit(2) + ->offset(1) + ->orderByTitle() + ->find(); + $this->assertEquals(2, count($results)); + // we ordered on book title, so we expect to get + $this->assertEquals("Harry Potter and the Order of the Phoenix", $results[0]->getTitle()); + $this->assertEquals("Quicksilver", $results[1]->getTitle()); + + // Perform a lookup & update! + // -------------------------- + + // Updating just-created book title + // First finding book by PK (=$qs_id) .... + $qs_lookup = BookQuery::create()->findPk($qs_id); + $this->assertNotNull($qs_lookup, 'just-created book can be found by pk'); + + $new_title = "Quicksilver (".crc32(uniqid(rand())).")"; + // Attempting to update found object + $qs_lookup->setTitle($new_title); + $qs_lookup->save(); + + // Making sure object was correctly updated: "; + $qs_lookup2 = BookQuery::create()->findPk($qs_id); + $this->assertEquals($new_title, $qs_lookup2->getTitle()); + + // Test some basic DATE / TIME stuff + // --------------------------------- + + // that's the control timestamp. + $control = strtotime('2004-02-29 00:00:00'); + + // should be two in the db + $r = ReviewQuery::create()->findOne(); + $r_id = $r->getId(); + $r->setReviewDate($control); + $r->save(); + + $r2 = ReviewQuery::create()->findPk($r_id); + $this->assertEquals(new Datetime('2004-02-29 00:00:00'), $r2->getReviewDate(null), 'ability to fetch DateTime'); + $this->assertEquals($control, $r2->getReviewDate('U'), 'ability to fetch native unix timestamp'); + $this->assertEquals('2-29-2004', $r2->getReviewDate('n-j-Y'), 'ability to use date() formatter'); + + // Handle BLOB/CLOB Columns + // ------------------------ + + $blob_path = dirname(__FILE__) . '/../../etc/lob/tin_drum.gif'; + $blob2_path = dirname(__FILE__) . '/../../etc/lob/propel.gif'; + $clob_path = dirname(__FILE__) . '/../../etc/lob/tin_drum.txt'; + + $m1 = new Media(); + $m1->setBook($phoenix); + $m1->setCoverImage(file_get_contents($blob_path)); + $m1->setExcerpt(file_get_contents($clob_path)); + $m1->save(); + $m1_id = $m1->getId(); + + $m1_lookup = MediaQuery::create()->findPk($m1_id); + + $this->assertNotNull($m1_lookup, 'Can find just-created media item'); + $this->assertEquals(file_get_contents($blob_path), stream_get_contents($m1_lookup->getCoverImage()), 'BLOB was correctly updated'); + $this->assertEquals(file_get_contents($clob_path), (string) $m1_lookup->getExcerpt(), 'CLOB was correctly updated'); + + // now update the BLOB column and save it & check the results + $m1_lookup->setCoverImage(file_get_contents($blob2_path)); + $m1_lookup->save(); + + $m2_lookup = MediaQuery::create()->findPk($m1_id); + $this->assertNotNull($m2_lookup, 'Can find just-created media item'); + + $this->assertEquals(file_get_contents($blob2_path), stream_get_contents($m2_lookup->getCoverImage()), 'BLOB was correctly overwritten'); + + // Test Validators + // --------------- + + require_once 'tools/helpers/bookstore/validator/ISBNValidator.php'; + + $bk1 = new Book(); + $bk1->setTitle("12345"); // min length is 10 + $ret = $bk1->validate(); + + $this->assertFalse($ret, 'validation failed'); + $failures = $bk1->getValidationFailures(); + $this->assertEquals(1, count($failures), '1 validation message was returned'); + + $el = array_shift($failures); + $this->assertContains("must be more than", $el->getMessage(), 'Expected validation message was returned'); + + $bk2 = new Book(); + $bk2->setTitle("Don Juan"); + $ret = $bk2->validate(); + + $this->assertFalse($ret, 'validation failed'); + + $failures = $bk2->getValidationFailures(); + $this->assertEquals(1, count($failures), '1 validation message was returned'); + + $el = array_shift($failures); + $this->assertContains("Book title already in database.", $el->getMessage(), 'Expected validation message was returned'); + + //Now trying some more complex validation. + $auth1 = new Author(); + $auth1->setFirstName("Hans"); + // last name required; will fail + + $bk1->setAuthor($auth1); + + $rev1 = new Review(); + $rev1->setReviewDate("08/09/2001"); + // will fail: reviewed_by column required + + $bk1->addReview($rev1); + + $ret2 = $bk1->validate(); + $this->assertFalse($ret2, 'validation failed'); + + $failures2 = $bk1->getValidationFailures(); + + $this->assertEquals(3, count($failures2), '3 validation messages were returned'); + + $expectedKeys = array( + AuthorPeer::LAST_NAME, + BookPeer::TITLE, + ReviewPeer::REVIEWED_BY, + ); + $this->assertEquals($expectedKeys, array_keys($failures2), 'correct columns failed'); + + $bk2 = new Book(); + $bk2->setTitle("12345678901"); // passes + + $auth2 = new Author(); + $auth2->setLastName("Blah"); //passes + $auth2->setEmail("some@body.com"); //passes + $auth2->setAge(50); //passes + $bk2->setAuthor($auth2); + + $rev2 = new Review(); + $rev2->setReviewedBy("Me!"); // passes + $rev2->setStatus("new"); // passes + $bk2->addReview($rev2); + + $ret3 = $bk2->validate(); + + $this->assertTrue($ret3, 'complex validation can pass'); + + // Testing doCount() functionality + // ------------------------------- + + // old way + $c = new Criteria(); + $records = BookPeer::doSelect($c); + $count = BookPeer::doCount($c); + $this->assertEquals($count, count($records), 'correct number of results'); + + // new way + $count = BookQuery::create()->count(); + $this->assertEquals($count, count($records), 'correct number of results'); + + // Test many-to-many relationships + // --------------- + + // init book club list 1 with 2 books + + $blc1 = new BookClubList(); + $blc1->setGroupLeader("Crazyleggs"); + $blc1->setTheme("Happiness"); + + $brel1 = new BookListRel(); + $brel1->setBook($phoenix); + + $brel2 = new BookListRel(); + $brel2->setBook($dj); + + $blc1->addBookListRel($brel1); + $blc1->addBookListRel($brel2); + + $blc1->save(); + + $this->assertNotNull($blc1->getId(), 'BookClubList 1 was saved'); + + // init book club list 2 with 1 book + + $blc2 = new BookClubList(); + $blc2->setGroupLeader("John Foo"); + $blc2->setTheme("Default"); + + $brel3 = new BookListRel(); + $brel3->setBook($phoenix); + + $blc2->addBookListRel($brel3); + + $blc2->save(); + + $this->assertNotNull($blc2->getId(), 'BookClubList 2 was saved'); + + // re-fetch books and lists from db to be sure that nothing is cached + + $crit = new Criteria(); + $crit->add(BookPeer::ID, $phoenix->getId()); + $phoenix = BookPeer::doSelectOne($crit); + $this->assertNotNull($phoenix, "book 'phoenix' has been re-fetched from db"); + + $crit = new Criteria(); + $crit->add(BookClubListPeer::ID, $blc1->getId()); + $blc1 = BookClubListPeer::doSelectOne($crit); + $this->assertNotNull($blc1, 'BookClubList 1 has been re-fetched from db'); + + $crit = new Criteria(); + $crit->add(BookClubListPeer::ID, $blc2->getId()); + $blc2 = BookClubListPeer::doSelectOne($crit); + $this->assertNotNull($blc2, 'BookClubList 2 has been re-fetched from db'); + + $relCount = $phoenix->countBookListRels(); + $this->assertEquals(2, $relCount, "book 'phoenix' has 2 BookListRels"); + + $relCount = $blc1->countBookListRels(); + $this->assertEquals(2, $relCount, 'BookClubList 1 has 2 BookListRels'); + + $relCount = $blc2->countBookListRels(); + $this->assertEquals(1, $relCount, 'BookClubList 2 has 1 BookListRel'); + + // Cleanup (tests DELETE) + // ---------------------- + + // Removing books that were just created + // First finding book by PK (=$phoenix_id) .... + $hp = BookQuery::create()->findPk($phoenix_id); + $this->assertNotNull($hp, 'Could find just-created book'); + + // Attempting to delete [multi-table] by found pk + $c = new Criteria(); + $c->add(BookPeer::ID, $hp->getId()); + // The only way for cascading to work currently + // is to specify the author_id and publisher_id (i.e. the fkeys + // have to be in the criteria). + $c->add(AuthorPeer::ID, $hp->getAuthor()->getId()); + $c->add(PublisherPeer::ID, $hp->getPublisher()->getId()); + $c->setSingleRecord(true); + BookPeer::doDelete($c); + + // Checking to make sure correct records were removed. + $this->assertEquals(3, AuthorPeer::doCount(new Criteria()), 'Correct records were removed from author table'); + $this->assertEquals(3, PublisherPeer::doCount(new Criteria()), 'Correct records were removed from publisher table'); + $this->assertEquals(3, BookPeer::doCount(new Criteria()), 'Correct records were removed from book table'); + + // Attempting to delete books by complex criteria + BookQuery::create() + ->filterByISBN("043935806X") + ->orWhere('Book.ISBN = ?', "0380977427") + ->orWhere('Book.ISBN = ?', "0140422161") + ->delete(); + + // Attempting to delete book [id = $td_id] + $td->delete(); + + // Attempting to delete authors + AuthorQuery::create()->filterById($stephenson_id)->delete(); + AuthorQuery::create()->filterById($byron_id)->delete(); + $grass->delete(); + + // Attempting to delete publishers + PublisherQuery::create()->filterById($morrow_id)->delete(); + PublisherQuery::create()->filterById($penguin_id)->delete(); + $vintage->delete(); + + // These have to be deleted manually also since we have onDelete + // set to SETNULL in the foreign keys in book. Is this correct? + $rowling->delete(); + $scholastic->delete(); + $blc1->delete(); + $blc2->delete(); + + $this->assertEquals(array(), AuthorPeer::doSelect(new Criteria()), 'no records in [author] table'); + $this->assertEquals(array(), PublisherPeer::doSelect(new Criteria()), 'no records in [publisher] table'); + $this->assertEquals(array(), BookPeer::doSelect(new Criteria()), 'no records in [book] table'); + $this->assertEquals(array(), ReviewPeer::doSelect(new Criteria()), 'no records in [review] table'); + $this->assertEquals(array(), MediaPeer::doSelect(new Criteria()), 'no records in [media] table'); + $this->assertEquals(array(), BookClubListPeer::doSelect(new Criteria()), 'no records in [book_club_list] table'); + $this->assertEquals(array(), BookListRelPeer::doSelect(new Criteria()), 'no records in [book_x_list] table'); + } +} \ No newline at end of file diff --git a/library/propel/test/testsuite/misc/CharacterEncodingTest.php b/library/propel/test/testsuite/misc/CharacterEncodingTest.php new file mode 100644 index 000000000..dfc9c03e7 --- /dev/null +++ b/library/propel/test/testsuite/misc/CharacterEncodingTest.php @@ -0,0 +1,112 @@ + + * @package misc + */ +class CharacterEncodingTest extends BookstoreTestBase +{ + /** + * Database adapter. + * @var DBAdapter + */ + private $adapter; + + public function setUp() + { + parent::setUp(); + if (!extension_loaded('iconv')) { + throw new Exception("Character-encoding tests require iconv extension to be loaded."); + } + } + + public function testUtf8() + { + $this->markTestSkipped(); + + $db = Propel::getDB(BookPeer::DATABASE_NAME); + + $title = "Смерть на брудершафт. Младенец и черт"; + // 1234567890123456789012345678901234567 + // 1 2 3 + + $a = new Author(); + $a->setFirstName("Б."); + $a->setLastName("ÐКУÐИÐ"); + + $p = new Publisher(); + $p->setName("Детектив роÑÑийÑкий, оÑтроÑÑŽÐ¶ÐµÑ‚Ð½Ð°Ñ Ð¿Ñ€Ð¾Ð·Ð°"); + + $b = new Book(); + $b->setTitle($title); + $b->setISBN("B-59246"); + $b->setAuthor($a); + $b->setPublisher($p); + $b->save(); + + $b->reload(); + + $this->assertEquals(37, iconv_strlen($b->getTitle(), 'utf-8'), "Expected 37 characters (not bytes) in title."); + $this->assertTrue(strlen($b->getTitle()) > iconv_strlen($b->getTitle(), 'utf-8'), "Expected more bytes than characters in title."); + + } + + public function testInvalidCharset() + { + $this->markTestSkipped(); + + $db = Propel::getDB(BookPeer::DATABASE_NAME); + if ($db instanceof DBSQLite) { + $this->markTestSkipped(); + } + + $a = new Author(); + $a->setFirstName("Б."); + $a->setLastName("ÐКУÐИÐ"); + $a->save(); + + $authorNameWindows1251 = iconv("utf-8", "windows-1251", $a->getLastName()); + $a->setLastName($authorNameWindows1251); + + // Different databases seem to handle invalid data differently (no surprise, I guess...) + if ($db instanceof DBPostgres) { + try { + $a->save(); + $this->fail("Expected an exception when saving non-UTF8 data to database."); + } catch (Exception $x) { + print $x; + } + + } else { + + // No exception is thrown by MySQL ... (others need to be tested still) + $a->save(); + $a->reload(); + + $this->assertEquals("",$a->getLastName(), "Expected last_name to be empty (after inserting invalid charset data)"); + } + + } + +} diff --git a/library/propel/test/testsuite/misc/FieldnameRelatedTest.php b/library/propel/test/testsuite/misc/FieldnameRelatedTest.php new file mode 100644 index 000000000..fe43a94bb --- /dev/null +++ b/library/propel/test/testsuite/misc/FieldnameRelatedTest.php @@ -0,0 +1,396 @@ + + * @package misc + */ +class FieldnameRelatedTest extends PHPUnit_Framework_TestCase +{ + protected function setUp() + { + parent::setUp(); + set_include_path(get_include_path() . PATH_SEPARATOR . "fixtures/bookstore/build/classes"); + require_once 'bookstore/map/BookTableMap.php'; + require_once 'bookstore/BookPeer.php'; + require_once 'bookstore/Book.php'; + } + + /** + * Tests if fieldname type constants are defined + */ + public function testFieldNameTypeConstants () { + + $result = defined('BasePeer::TYPE_PHPNAME'); + $this->assertTrue($result); + } + + /** + * Tests the Base[Object]Peer::getFieldNames() method + */ + public function testGetFieldNames () + { + $types = array( + BasePeer::TYPE_PHPNAME, + BasePeer::TYPE_COLNAME, + BasePeer::TYPE_FIELDNAME, + BasePeer::TYPE_NUM + ); + $expecteds = array ( + BasePeer::TYPE_PHPNAME => array( + 0 => 'Id', + 1 => 'Title', + 2 => 'ISBN', + 3 => 'Price', + 4 => 'PublisherId', + 5 => 'AuthorId' + ), + BasePeer::TYPE_STUDLYPHPNAME => array( + 0 => 'id', + 1 => 'title', + 2 => 'iSBN', + 3 => 'price', + 4 => 'publisherId', + 5 => 'authorId' + ), + BasePeer::TYPE_COLNAME => array( + 0 => 'book.ID', + 1 => 'book.TITLE', + 2 => 'book.ISBN', + 3 => 'book.PRICE', + 4 => 'book.PUBLISHER_ID', + 5 => 'book.AUTHOR_ID' + ), + BasePeer::TYPE_FIELDNAME => array( + 0 => 'id', + 1 => 'title', + 2 => 'isbn', + 3 => 'price', + 4 => 'publisher_id', + 5 => 'author_id' + ), + BasePeer::TYPE_NUM => array( + 0 => 0, + 1 => 1, + 2 => 2, + 3 => 3, + 4 => 4, + 5 => 5 + ) + ); + + foreach ($types as $type) { + $results[$type] = BookPeer::getFieldnames($type); + $this->assertEquals( + $expecteds[$type], + $results[$type], + 'expected was: ' . print_r($expecteds[$type], 1) . + 'but getFieldnames() returned ' . print_r($results[$type], 1) + ); + } + } + + /** + * Tests the Base[Object]Peer::translateFieldName() method + */ + public function testTranslateFieldName () { + + $types = array( + BasePeer::TYPE_PHPNAME, + BasePeer::TYPE_STUDLYPHPNAME, + BasePeer::TYPE_COLNAME, + BasePeer::TYPE_FIELDNAME, + BasePeer::TYPE_NUM + ); + $expecteds = array ( + BasePeer::TYPE_PHPNAME => 'AuthorId', + BasePeer::TYPE_STUDLYPHPNAME => 'authorId', + BasePeer::TYPE_COLNAME => 'book.AUTHOR_ID', + BasePeer::TYPE_FIELDNAME => 'author_id', + BasePeer::TYPE_NUM => 5, + ); + foreach ($types as $fromType) { + foreach ($types as $toType) { + $name = $expecteds[$fromType]; + $expected = $expecteds[$toType]; + $result = BookPeer::translateFieldName($name, $fromType, $toType); + $this->assertEquals($expected, $result); + } + } + } + + /** + * Tests the BasePeer::getFieldNames() method + */ + public function testGetFieldNamesStatic () { + + $types = array( + BasePeer::TYPE_PHPNAME, + BasePeer::TYPE_STUDLYPHPNAME, + BasePeer::TYPE_COLNAME, + BasePeer::TYPE_FIELDNAME, + BasePeer::TYPE_NUM + ); + $expecteds = array ( + BasePeer::TYPE_PHPNAME => array( + 0 => 'Id', + 1 => 'Title', + 2 => 'ISBN', + 3 => 'Price', + 4 => 'PublisherId', + 5 => 'AuthorId' + ), + BasePeer::TYPE_STUDLYPHPNAME => array( + 0 => 'id', + 1 => 'title', + 2 => 'iSBN', + 3 => 'price', + 4 => 'publisherId', + 5 => 'authorId' + ), + BasePeer::TYPE_COLNAME => array( + 0 => 'book.ID', + 1 => 'book.TITLE', + 2 => 'book.ISBN', + 3 => 'book.PRICE', + 4 => 'book.PUBLISHER_ID', + 5 => 'book.AUTHOR_ID' + ), + BasePeer::TYPE_FIELDNAME => array( + 0 => 'id', + 1 => 'title', + 2 => 'isbn', + 3 => 'price', + 4 => 'publisher_id', + 5 => 'author_id' + ), + BasePeer::TYPE_NUM => array( + 0 => 0, + 1 => 1, + 2 => 2, + 3 => 3, + 4 => 4, + 5 => 5 + ) + ); + + foreach ($types as $type) { + $results[$type] = BasePeer::getFieldnames('Book', $type); + $this->assertEquals( + $expecteds[$type], + $results[$type], + 'expected was: ' . print_r($expecteds[$type], 1) . + 'but getFieldnames() returned ' . print_r($results[$type], 1) + ); + } + } + + /** + * Tests the BasePeer::translateFieldName() method + */ + public function testTranslateFieldNameStatic () { + + $types = array( + BasePeer::TYPE_PHPNAME, + BasePeer::TYPE_STUDLYPHPNAME, + BasePeer::TYPE_COLNAME, + BasePeer::TYPE_FIELDNAME, + BasePeer::TYPE_NUM + ); + $expecteds = array ( + BasePeer::TYPE_PHPNAME => 'AuthorId', + BasePeer::TYPE_STUDLYPHPNAME => 'authorId', + BasePeer::TYPE_COLNAME => 'book.AUTHOR_ID', + BasePeer::TYPE_FIELDNAME => 'author_id', + BasePeer::TYPE_NUM => 5, + ); + foreach ($types as $fromType) { + foreach ($types as $toType) { + $name = $expecteds[$fromType]; + $expected = $expecteds[$toType]; + $result = BasePeer::translateFieldName('Book', $name, $fromType, $toType); + $this->assertEquals($expected, $result); + } + } + } + + /** + * Tests the Base[Object]::getByName() method + */ + public function testGetByName() { + + $types = array( + BasePeer::TYPE_PHPNAME => 'Title', + BasePeer::TYPE_STUDLYPHPNAME => 'title', + BasePeer::TYPE_COLNAME => 'book.TITLE', + BasePeer::TYPE_FIELDNAME => 'title', + BasePeer::TYPE_NUM => 1 + ); + + $book = new Book(); + $book->setTitle('Harry Potter and the Order of the Phoenix'); + + $expected = 'Harry Potter and the Order of the Phoenix'; + foreach ($types as $type => $name) { + $result = $book->getByName($name, $type); + $this->assertEquals($expected, $result); + } + } + + /** + * Tests the Base[Object]::setByName() method + */ + public function testSetByName() { + + $book = new Book(); + $types = array( + BasePeer::TYPE_PHPNAME => 'Title', + BasePeer::TYPE_STUDLYPHPNAME => 'title', + BasePeer::TYPE_COLNAME => 'book.TITLE', + BasePeer::TYPE_FIELDNAME => 'title', + BasePeer::TYPE_NUM => 1 + ); + + $title = 'Harry Potter and the Order of the Phoenix'; + foreach ($types as $type => $name) { + $book->setByName($name, $title, $type); + $result = $book->getTitle(); + $this->assertEquals($title, $result); + } + } + + /** + * Tests the Base[Object]::fromArray() method + * + * this also tests populateFromArray() because that's an alias + */ + public function testFromArray(){ + + $types = array( + BasePeer::TYPE_PHPNAME, + BasePeer::TYPE_STUDLYPHPNAME, + BasePeer::TYPE_COLNAME, + BasePeer::TYPE_FIELDNAME, + BasePeer::TYPE_NUM + ); + $expecteds = array ( + BasePeer::TYPE_PHPNAME => array ( + 'Title' => 'Harry Potter and the Order of the Phoenix', + 'ISBN' => '043935806X' + ), + BasePeer::TYPE_STUDLYPHPNAME => array ( + 'title' => 'Harry Potter and the Order of the Phoenix', + 'iSBN' => '043935806X' + ), + BasePeer::TYPE_COLNAME => array ( + 'book.TITLE' => 'Harry Potter and the Order of the Phoenix', + 'book.ISBN' => '043935806X' + ), + BasePeer::TYPE_FIELDNAME => array ( + 'title' => 'Harry Potter and the Order of the Phoenix', + 'isbn' => '043935806X' + ), + BasePeer::TYPE_NUM => array ( + '1' => 'Harry Potter and the Order of the Phoenix', + '2' => '043935806X' + ) + ); + + $book = new Book(); + + foreach ($types as $type) { + $expected = $expecteds[$type]; + $book->fromArray($expected, $type); + $result = array(); + foreach (array_keys($expected) as $key) { + $result[$key] = $book->getByName($key, $type); + } + $this->assertEquals( + $expected, + $result, + 'expected was: ' . print_r($expected, 1) . + 'but fromArray() returned ' . print_r($result, 1) + ); + } + } + + /** + * Tests the Base[Object]::toArray() method + */ + public function testToArray(){ + + $types = array( + BasePeer::TYPE_PHPNAME, + BasePeer::TYPE_STUDLYPHPNAME, + BasePeer::TYPE_COLNAME, + BasePeer::TYPE_FIELDNAME, + BasePeer::TYPE_NUM + ); + + $book = new Book(); + $book->fromArray(array ( + 'Title' => 'Harry Potter and the Order of the Phoenix', + 'ISBN' => '043935806X' + )); + + $expecteds = array ( + BasePeer::TYPE_PHPNAME => array ( + 'Title' => 'Harry Potter and the Order of the Phoenix', + 'ISBN' => '043935806X' + ), + BasePeer::TYPE_STUDLYPHPNAME => array ( + 'title' => 'Harry Potter and the Order of the Phoenix', + 'iSBN' => '043935806X' + ), + BasePeer::TYPE_COLNAME => array ( + 'book.TITLE' => 'Harry Potter and the Order of the Phoenix', + 'book.ISBN' => '043935806X' + ), + BasePeer::TYPE_FIELDNAME => array ( + 'title' => 'Harry Potter and the Order of the Phoenix', + 'isbn' => '043935806X' + ), + BasePeer::TYPE_NUM => array ( + '1' => 'Harry Potter and the Order of the Phoenix', + '2' => '043935806X' + ) + ); + + foreach ($types as $type) { + $expected = $expecteds[$type]; + $result = $book->toArray($type); + // remove ID since its autoincremented at each test iteration + $result = array_slice($result, 1, 2, true); + $this->assertEquals( + $expected, + $result, + 'expected was: ' . print_r($expected, 1) . + 'but toArray() returned ' . print_r($result, 1) + ); + } + } +} diff --git a/library/propel/test/testsuite/misc/Ticket520Test.php b/library/propel/test/testsuite/misc/Ticket520Test.php new file mode 100644 index 000000000..e95390b05 --- /dev/null +++ b/library/propel/test/testsuite/misc/Ticket520Test.php @@ -0,0 +1,250 @@ +setFirstName("Douglas"); + $a->setLastName("Adams"); + + $b1 = new Book(); + $b1->setTitle("The Hitchhikers Guide To The Galaxy"); + $a->addBook($b1); + + $b2 = new Book(); + $b2->setTitle("The Restaurant At The End Of The Universe"); + $a->addBook($b2); + + // Passing no Criteria means "use the internal collection or query the database" + // in that case two objects are added, so it should return 2 + $books = $a->getBooks(); + $this->assertEquals(2, count($books)); + } + + public function testNewObjectsNotAvailableWithCriteria() + { + $a = new Author(); + $a->setFirstName("Douglas"); + $a->setLastName("Adams"); + + $b1 = new Book(); + $b1->setTitle("The Hitchhikers Guide To The Galaxy"); + $a->addBook($b1); + + $b2 = new Book(); + $b2->setTitle("The Restaurant At The End Of The Universe"); + $a->addBook($b2); + + $c = new Criteria(); + $c->add(BookPeer::TITLE, "%Hitchhiker%", Criteria::LIKE); + + $guides = $a->getBooks($c); + $this->assertEquals(0, count($guides), 'Passing a Criteria means "force a database query"'); + } + + public function testNewObjectsAvailableAfterCriteria() + { + $a = new Author(); + $a->setFirstName("Douglas"); + $a->setLastName("Adams"); + + $b1 = new Book(); + $b1->setTitle("The Hitchhikers Guide To The Galaxy"); + $a->addBook($b1); + + $b2 = new Book(); + $b2->setTitle("The Restaurant At The End Of The Universe"); + $a->addBook($b2); + + $c = new Criteria(); + $c->add(BookPeer::TITLE, "%Hitchhiker%", Criteria::LIKE); + + $guides = $a->getBooks($c); + + $books = $a->getBooks(); + $this->assertEquals(2, count($books), 'A previous query with a Criteria does not erase the internal collection'); + } + + public function testSavedObjectsWithCriteria() + { + $a = new Author(); + $a->setFirstName("Douglas"); + $a->setLastName("Adams"); + + $b1 = new Book(); + $b1->setTitle("The Hitchhikers Guide To The Galaxy"); + $a->addBook($b1); + + $b2 = new Book(); + $b2->setTitle("The Restaurant At The End Of The Universe"); + $a->addBook($b2); + + $c = new Criteria(); + $c->add(BookPeer::TITLE, "%Hitchhiker%", Criteria::LIKE); + + $guides = $a->getBooks($c); + + $a->save(); + $booksAfterSave = $a->getBooks($c); + $this->assertEquals(1, count($booksAfterSave), 'A previous query with a Criteria is not cached'); + } + + public function testAddNewObjectAfterSave() + { + $a = new Author(); + $a->setFirstName("Douglas"); + $a->setLastName("Adams"); + + $a->save(); + + $b1 = new Book(); + $b1->setTitle("The Hitchhikers Guide To The Galaxy"); + $a->addBook($b1); + + $books = $a->getBooks(); + $this->assertEquals(1, count($books)); + $this->assertTrue($books->contains($b1)); + + /* Now this is the initial ticket 520: If we have a saved author, + add a new book but happen to call getBooks() before we call save() again, + the book used to be lost. */ + $a->save(); + $this->assertFalse($b1->isNew(), 'related objects are also saved after fetching them'); + } + + public function testAddNewObjectAfterSaveWithPoisonedCache() + { + /* This is like testAddNewObjectAfterSave(), + but this time we "poison" the author's $colBooks cache + before adding the book by calling getBooks(). */ + + $a = new Author(); + $a->setFirstName("Douglas"); + $a->setLastName("Adams"); + + $a->save(); + $a->getBooks(); + + $b1 = new Book(); + $b1->setTitle("The Hitchhikers Guide To The Galaxy"); + $a->addBook($b1); + + $books = $a->getBooks(); + $this->assertEquals(1, count($books)); + $this->assertTrue($books->contains($b1), 'new related objects not deleted after fetching them'); + } + + public function testCachePoisoning() + { + /* Like testAddNewObjectAfterSaveWithPoisonedCache, emphasizing + cache poisoning. */ + + $a = new Author(); + $a->setFirstName("Douglas"); + $a->setLastName("Adams"); + + $a->save(); + + $c = new Criteria(); + $c->add(BookPeer::TITLE, "%Restaurant%", Criteria::LIKE); + + $this->assertEquals(0, count($a->getBooks($c))); + + $b1 = new Book(); + $b1->setTitle("The Hitchhikers Guide To The Galaxy"); + $a->addBook($b1); + + /* Like testAddNewObjectAfterSaveWithPoisonedCache, but this time + with a real criteria. */ + $this->assertEquals(0, count($a->getBooks($c))); + + $a->save(); + $this->assertFalse($b1->isNew()); + $this->assertEquals(0, count($a->getBooks($c))); + } + + public function testDeletedBookDisappears() + { + $this->markTestSkipped(); + + $a = new Author(); + $a->setFirstName("Douglas"); + $a->setLastName("Adams"); + + $b1 = new Book(); + $b1->setTitle("The Hitchhikers Guide To The Galaxy"); + $a->addBook($b1); + + $b2 = new Book(); + $b2->setTitle("The Restaurant At The End Of The Universe"); + $a->addBook($b2); + + /* As you cannot write $a->remove($b2), you have to delete $b2 + directly. */ + + /* All objects unsaved. As of revision 851, this circumvents the + $colBooks cache. Anyway, fails because getBooks() never checks if + a colBooks entry has been deleted. */ + $this->assertEquals(2, count($a->getBooks())); + $b2->delete(); + $this->assertEquals(1, count($a->getBooks())); + + /* Even if we had saved everything before and the delete() had + actually updated the DB, the $b2 would still be a "zombie" in + $a's $colBooks field. */ + } + + public function testNewObjectsGetLostOnJoin() { + /* While testNewObjectsAvailableWhenSaveNotCalled passed as of + revision 851, in this case we call getBooksJoinPublisher() instead + of just getBooks(). get...Join...() does not contain the check whether + the current object is new, it will always consult the DB and lose the + new objects entirely. Thus the test fails. (At least for Propel 1.2 ?!?) */ + $this->markTestSkipped(); + + $a = new Author(); + $a->setFirstName("Douglas"); + $a->setLastName("Adams"); + + $p = new Publisher(); + $p->setName('Pan Books Ltd.'); + + $b1 = new Book(); + $b1->setTitle("The Hitchhikers Guide To The Galaxy"); + $b1->setPublisher($p); // uh... did not check that :^) + $a->addBook($b1); + + $b2 = new Book(); + $b2->setTitle("The Restaurant At The End Of The Universe"); + $b2->setPublisher($p); + $a->addBook($b2); + + $books = $a->getBooksJoinPublisher(); + $this->assertEquals(2, count($books)); + $this->assertContains($b1, $books); + $this->assertContains($b2, $books); + + $a->save(); + $this->assertFalse($b1->isNew()); + $this->assertFalse($b2->isNew()); + } + +} diff --git a/library/propel/test/testsuite/runtime/adapter/DBOracleTest.php b/library/propel/test/testsuite/runtime/adapter/DBOracleTest.php new file mode 100644 index 000000000..25f1f1d17 --- /dev/null +++ b/library/propel/test/testsuite/runtime/adapter/DBOracleTest.php @@ -0,0 +1,47 @@ +setDbName('oracle'); + BookPeer::addSelectColumns($c); + $c->setLimit(1); + $params = array(); + $sql = BasePeer::createSelectSql($c, $params); + $this->assertEquals('SELECT B.* FROM (SELECT A.*, rownum AS PROPEL_ROWNUM FROM (SELECT book.ID, book.TITLE, book.ISBN, book.PRICE, book.PUBLISHER_ID, book.AUTHOR_ID FROM book) A ) B WHERE B.PROPEL_ROWNUM <= 1', $sql, 'applyLimit() creates a subselect with the original column names by default'); + } + + public function testApplyLimitDuplicateColumnName() + { + Propel::setDb('oracle', new DBOracle()); + $c = new Criteria(); + $c->setDbName('oracle'); + BookPeer::addSelectColumns($c); + AuthorPeer::addSelectColumns($c); + $c->setLimit(1); + $params = array(); + $sql = BasePeer::createSelectSql($c, $params); + $this->assertEquals('SELECT B.* FROM (SELECT A.*, rownum AS PROPEL_ROWNUM FROM (SELECT book.ID AS book_ID, book.TITLE AS book_TITLE, book.ISBN AS book_ISBN, book.PRICE AS book_PRICE, book.PUBLISHER_ID AS book_PUBLISHER_ID, book.AUTHOR_ID AS book_AUTHOR_ID, author.ID AS author_ID, author.FIRST_NAME AS author_FIRST_NAME, author.LAST_NAME AS author_LAST_NAME, author.EMAIL AS author_EMAIL, author.AGE AS author_AGESELECT book.ID, book.TITLE, book.ISBN, book.PRICE, book.PUBLISHER_ID, book.AUTHOR_ID, author.ID, author.FIRST_NAME, author.LAST_NAME, author.EMAIL, author.AGE FROM book, author) A ) B WHERE B.PROPEL_ROWNUM <= 1', $sql, 'applyLimit() creates a subselect with aliased column names when a duplicate column name is found'); + } + +} \ No newline at end of file diff --git a/library/propel/test/testsuite/runtime/collection/PropelArrayCollectionTest.php b/library/propel/test/testsuite/runtime/collection/PropelArrayCollectionTest.php new file mode 100644 index 000000000..a6a374936 --- /dev/null +++ b/library/propel/test/testsuite/runtime/collection/PropelArrayCollectionTest.php @@ -0,0 +1,152 @@ +con); + } + + public function testSave() + { + $books = PropelQuery::from('Book')->setFormatter(ModelCriteria::FORMAT_ARRAY)->find(); + foreach ($books as &$book) { + $book['Title'] = 'foo'; + } + $books->save(); + // check that the modifications are persisted + BookPeer::clearInstancePool(); + $books = PropelQuery::from('Book')->find(); + foreach ($books as $book) { + $this->assertEquals('foo', $book->getTitle('foo')); + } + } + + public function testDelete() + { + $books = PropelQuery::from('Book')->setFormatter(ModelCriteria::FORMAT_ARRAY)->find(); + $books->delete(); + // check that the modifications are persisted + BookPeer::clearInstancePool(); + $books = PropelQuery::from('Book')->find(); + $this->assertEquals(0, count($books)); + } + + public function testGetPrimaryKeys() + { + $books = PropelQuery::from('Book')->setFormatter(ModelCriteria::FORMAT_ARRAY)->find(); + $pks = $books->getPrimaryKeys(); + $this->assertEquals(4, count($pks)); + + $keys = array('Book_0', 'Book_1', 'Book_2', 'Book_3'); + $this->assertEquals($keys, array_keys($pks)); + + $pks = $books->getPrimaryKeys(false); + $keys = array(0, 1, 2, 3); + $this->assertEquals($keys, array_keys($pks)); + + $bookObjects = PropelQuery::from('Book')->find(); + foreach ($pks as $key => $value) { + $this->assertEquals($bookObjects[$key]->getPrimaryKey(), $value); + } + } + + public function testFromArray() + { + $author = new Author(); + $author->setFirstName('Jane'); + $author->setLastName('Austen'); + $author->save(); + $books = array( + array('Title' => 'Mansfield Park', 'AuthorId' => $author->getId()), + array('Title' => 'Pride And PRejudice', 'AuthorId' => $author->getId()) + ); + $col = new PropelArrayCollection(); + $col->setModel('Book'); + $col->fromArray($books); + $col->save(); + + $nbBooks = PropelQuery::from('Book')->count(); + $this->assertEquals(6, $nbBooks); + + $booksByJane = PropelQuery::from('Book b') + ->join('b.Author a') + ->where('a.LastName = ?', 'Austen') + ->count(); + $this->assertEquals(2, $booksByJane); + } + + public function testToArray() + { + $books = PropelQuery::from('Book')->setFormatter(ModelCriteria::FORMAT_ARRAY)->find(); + $booksArray = $books->toArray(); + $this->assertEquals(4, count($booksArray)); + + $bookObjects = PropelQuery::from('Book')->find(); + foreach ($booksArray as $key => $book) { + $this->assertEquals($bookObjects[$key]->toArray(), $book); + } + + $booksArray = $books->toArray(); + $keys = array(0, 1, 2, 3); + $this->assertEquals($keys, array_keys($booksArray)); + + $booksArray = $books->toArray(null, true); + $keys = array('Book_0', 'Book_1', 'Book_2', 'Book_3'); + $this->assertEquals($keys, array_keys($booksArray)); + + $booksArray = $books->toArray('Title'); + $keys = array('Harry Potter and the Order of the Phoenix', 'Quicksilver', 'Don Juan', 'The Tin Drum'); + $this->assertEquals($keys, array_keys($booksArray)); + + $booksArray = $books->toArray('Title', true); + $keys = array('Book_Harry Potter and the Order of the Phoenix', 'Book_Quicksilver', 'Book_Don Juan', 'Book_The Tin Drum'); + $this->assertEquals($keys, array_keys($booksArray)); + } + + public function getWorkerObject() + { + $col = new TestablePropelArrayCollection(); + $col->setModel('Book'); + $book = $col->getWorkerObject(); + $this->assertTrue($book instanceof Book, 'getWorkerObject() returns an object of the collection model'); + $book->foo = 'bar'; + $this->assertEqual('bar', $col->getWorkerObject()->foo, 'getWorkerObject() returns always the same object'); + } + + /** + * @expectedException PropelException + */ + public function testGetWorkerObjectNoModel() + { + $col = new TestablePropelArrayCollection(); + $col->getWorkerObject(); + } + +} + +class TestablePropelArrayCollection extends PropelArrayCollection +{ + public function getWorkerObject() + { + return parent::getWorkerObject(); + } +} \ No newline at end of file diff --git a/library/propel/test/testsuite/runtime/collection/PropelCollectionTest.php b/library/propel/test/testsuite/runtime/collection/PropelCollectionTest.php new file mode 100644 index 000000000..fa76def29 --- /dev/null +++ b/library/propel/test/testsuite/runtime/collection/PropelCollectionTest.php @@ -0,0 +1,353 @@ +assertEquals('bar1', $col[0], 'PropelCollection allows access via $foo[$index]'); + $this->assertEquals('bar2', $col[1], 'PropelCollection allows access via $foo[$index]'); + $this->assertEquals('bar3', $col[2], 'PropelCollection allows access via $foo[$index]'); + } + + public function testGetData() + { + $col = new PropelCollection(); + $this->assertEquals(array(), $col->getData(), 'getData() returns an empty array for empty collections'); + $data = array('bar1', 'bar2', 'bar3'); + $col = new PropelCollection($data); + $this->assertEquals($data, $col->getData(), 'getData() returns the collection data'); + $col[0] = 'bar4'; + $this->assertEquals('bar1', $data[0], 'getData() returns a copy of the collection data'); + } + + public function testSetData() + { + $col = new PropelCollection(); + $col->setData(array()); + $this->assertEquals(array(), $col->getArrayCopy(), 'setData() can set data to an empty array'); + + $data = array('bar1', 'bar2', 'bar3'); + $col = new PropelCollection(); + $col->setData($data); + $this->assertEquals($data, $col->getArrayCopy(), 'setData() sets the collection data'); + } + + public function testGetPosition() + { + $col = new PropelCollection(); + $this->assertEquals(0, $col->getPosition(), 'getPosition() returns 0 on an empty collection'); + $data = array('bar1', 'bar2', 'bar3'); + $col = new PropelCollection($data); + $expectedPositions = array(0, 1, 2); + foreach ($col as $element) { + $this->assertEquals(array_shift($expectedPositions), $col->getPosition(), 'getPosition() returns the current position'); + $this->assertEquals($element, $col->getCurrent(), 'getPosition() does not change the current position'); + } + } + + public function testGetFirst() + { + $col = new PropelCollection(); + $this->assertNull($col->getFirst(), 'getFirst() returns null on an empty collection'); + $data = array('bar1', 'bar2', 'bar3'); + $col = new PropelCollection($data); + $this->assertEquals('bar1', $col->getFirst(), 'getFirst() returns value of the first element in the collection'); + } + + public function testIsFirst() + { + $col = new PropelCollection(); + $this->assertTrue($col->isFirst(), 'isFirst() returns true on an empty collection'); + $data = array('bar1', 'bar2', 'bar3'); + $col = new PropelCollection($data); + $expectedRes = array(true, false, false); + foreach ($col as $element) { + $this->assertEquals(array_shift($expectedRes), $col->isFirst(), 'isFirst() returns true only for the first element'); + $this->assertEquals($element, $col->getCurrent(), 'isFirst() does not change the current position'); + } + } + + public function testGetPrevious() + { + $col = new PropelCollection(); + $this->assertNull($col->getPrevious(), 'getPrevious() returns null on an empty collection'); + $data = array('bar1', 'bar2', 'bar3'); + $col = new PropelCollection($data); + $this->assertNull($col->getPrevious(), 'getPrevious() returns null when the internal pointer is at the beginning of the list'); + $col->getNext(); + $this->assertEquals('bar1', $col->getPrevious(), 'getPrevious() returns the previous element'); + $this->assertEquals('bar1', $col->getCurrent(), 'getPrevious() decrements the internal pointer'); + } + + public function testGetCurrent() + { + $col = new PropelCollection(); + $this->assertNull($col->getCurrent(), 'getCurrent() returns null on an empty collection'); + $data = array('bar1', 'bar2', 'bar3'); + $col = new PropelCollection($data); + $this->assertEquals('bar1', $col->getCurrent(), 'getCurrent() returns the value of the first element when the internal pointer is at the beginning of the list'); + foreach ($col as $key => $value) { + $this->assertEquals($value, $col->getCurrent(), 'getCurrent() returns the value of the current element in the collection'); + } + } + + public function testGetNext() + { + $col = new PropelCollection(); + $this->assertNull($col->getNext(), 'getNext() returns null on an empty collection'); + $data = array('bar1', 'bar2', 'bar3'); + $col = new PropelCollection($data); + $this->assertEquals('bar2', $col->getNext(), 'getNext() returns the second element when the internal pointer is at the beginning of the list'); + $this->assertEquals('bar2', $col->getCurrent(), 'getNext() increments the internal pointer'); + $col->getNext(); + $this->assertNull($col->getNext(), 'getNext() returns null when the internal pointer is at the end of the list'); + } + + public function testGetLast() + { + $col = new PropelCollection(); + $this->assertNull($col->getLast(), 'getLast() returns null on an empty collection'); + $data = array('bar1', 'bar2', 'bar3'); + $col = new PropelCollection($data); + $this->assertEquals('bar3', $col->getLast(), 'getLast() returns the last element'); + $this->assertEquals('bar3', $col->getCurrent(), 'getLast() moves the internal pointer to the last element'); + } + + public function testIsLAst() + { + $col = new PropelCollection(); + $this->assertTrue($col->isLast(), 'isLast() returns true on an empty collection'); + $data = array('bar1', 'bar2', 'bar3'); + $col = new PropelCollection($data); + $expectedRes = array(false, false, true); + foreach ($col as $element) { + $this->assertEquals(array_shift($expectedRes), $col->isLast(), 'isLast() returns true only for the last element'); + $this->assertEquals($element, $col->getCurrent(), 'isLast() does not change the current position'); + } + } + + public function testIsEmpty() + { + $col = new PropelCollection(); + $this->assertTrue($col->isEmpty(), 'isEmpty() returns true on an empty collection'); + $data = array('bar1', 'bar2', 'bar3'); + $col = new PropelCollection($data); + $this->assertFalse($col->isEmpty(), 'isEmpty() returns false on a non empty collection'); + } + + public function testIsOdd() + { + $col = new PropelCollection(); + $this->assertFalse($col->isOdd(), 'isOdd() returns false on an empty collection'); + $data = array('bar1', 'bar2', 'bar3'); + $col = new PropelCollection(); + $col->setData($data); + foreach ($col as $key => $value) { + $this->assertEquals((boolean) ($key % 2), $col->isOdd(), 'isOdd() returns true only when the key is odd'); + } + } + + public function testIsEven() + { + $col = new PropelCollection(); + $this->assertTrue($col->isEven(), 'isEven() returns true on an empty collection'); + $data = array('bar1', 'bar2', 'bar3'); + $col = new PropelCollection(); + $col->setData($data); + foreach ($col as $key => $value) { + $this->assertEquals(!(boolean) ($key % 2), $col->isEven(), 'isEven() returns true only when the key is even'); + } + } + + public function testGet() + { + $col = new PropelCollection(array('foo', 'bar')); + $this->assertEquals('foo', $col->get(0), 'get() returns an element from its key'); + } + + /** + * @expectedException PropelException + */ + public function testGetUnknownOffset() + { + $col = new PropelCollection(); + $bar = $col->get('foo'); + } + + public function testPop() + { + $col = new PropelCollection(); + $this->assertNull($col->pop(), 'pop() returns null on an empty collection'); + $data = array('bar1', 'bar2', 'bar3'); + $col = new PropelCollection($data); + $this->assertEquals('bar3', $col->pop(), 'pop() returns the last element of the collection'); + $this->assertEquals(array('bar1', 'bar2'), $col->getData(), 'pop() removes the last element of the collection'); + } + + public function testShift() + { + $col = new PropelCollection(); + $this->assertNull($col->shift(), 'shift() returns null on an empty collection'); + $data = array('bar1', 'bar2', 'bar3'); + $col = new PropelCollection($data); + $this->assertEquals('bar1', $col->shift(), 'shift() returns the first element of the collection'); + $this->assertEquals(array('bar2', 'bar3'), $col->getData(), 'shift() removes the first element of the collection'); + } + + public function testPrepend() + { + $col = new PropelCollection(); + $this->assertEquals(1, $col->prepend('a'), 'prepend() returns 1 on an empty collection'); + $data = array('bar1', 'bar2', 'bar3'); + $col = new PropelCollection($data); + $this->assertEquals(4, $col->prepend('bar4'), 'prepend() returns the new number of elements in the collection when adding a variable'); + $this->assertEquals(array('bar4', 'bar1', 'bar2', 'bar3'), $col->getData(), 'prepend() adds new element to the beginning of the collection'); + } + + public function testSet() + { + $col = new PropelCollection(); + $col->set(4, 'bar'); + $this->assertEquals(array(4 => 'bar'), $col->getData(), 'set() adds an element to the collection with a key'); + + $col = new PropelCollection(); + $col->set(null, 'foo'); + $col->set(null, 'bar'); + $this->assertEquals(array('foo', 'bar'), $col->getData(), 'set() adds an element to the collection without a key'); + } + + public function testRemove() + { + $col = new PropelCollection(); + $col[0] = 'bar'; + $col[1] = 'baz'; + $col->remove(1); + $this->assertEquals(array('bar'), $col->getData(), 'remove() removes an element from its key'); + } + + /** + * @expectedException PropelException + */ + public function testRemoveUnknownOffset() + { + $col = new PropelCollection(); + $col->remove(2); + } + + public function testClear() + { + $col = new PropelCollection(); + $col->clear(); + $this->assertEquals(array(), $col->getData(), 'clear() empties the collection'); + $data = array('bar1', 'bar2', 'bar3'); + $col = new PropelCollection($data); + $col->clear(); + $this->assertEquals(array(), $col->getData(), 'clear() empties the collection'); + } + + public function testContains() + { + $col = new PropelCollection(); + $this->assertFalse($col->contains('foo_1'), 'contains() returns false on an empty collection'); + $data = array('bar1', 'bar2', 'bar3'); + $col = new PropelCollection($data); + $this->assertTrue($col->contains('bar1'), 'contains() returns true when the key exists'); + $this->assertFalse($col->contains('bar4'), 'contains() returns false when the key does not exist'); + } + + public function testSearch() + { + $col = new PropelCollection(); + $this->assertFalse($col->search('bar1'), 'search() returns false on an empty collection'); + $data = array('bar1', 'bar2', 'bar3'); + $col = new PropelCollection($data); + $this->assertEquals(1, $col->search('bar2'), 'search() returns the key when the element exists'); + $this->assertFalse($col->search('bar4'), 'search() returns false when the element does not exist'); + } + + public function testSerializable() + { + $data = array('bar1', 'bar2', 'bar3'); + $col = new PropelCollection($data); + $col->setModel('Foo'); + $serializedCol = serialize($col); + + $col2 = unserialize($serializedCol); + $this->assertEquals($col, $col2, 'PropelCollection is serializable'); + } + + public function testGetIterator() + { + $data = array('bar1', 'bar2', 'bar3'); + $col = new PropelCollection($data); + $it1 = $col->getIterator(); + $it2 = $col->getIterator(); + $this->assertNotSame($it1, $it2, 'getIterator() returns always a new iterator'); + } + + public function testGetInternalIterator() + { + $data = array('bar1', 'bar2', 'bar3'); + $col = new PropelCollection($data); + $it1 = $col->getInternalIterator(); + $it2 = $col->getINternalIterator(); + $this->assertSame($it1, $it2, 'getInternalIterator() returns always the same iterator'); + $col->getInternalIterator()->next(); + $this->assertEquals('bar2', $col->getInternalIterator()->current(), 'getInternalIterator() returns always the same iterator'); + } + + public function testGetPeerClass() + { + $col = new PropelCollection(); + $col->setModel('Book'); + $this->assertEquals('BookPeer', $col->getPeerClass(), 'getPeerClass() returns the Peer class for the collection model'); + } + + /** + * @expectedException PropelException + */ + public function testGetPeerClassNoModel() + { + $col = new PropelCollection(); + $col->getPeerClass(); + } + + public function testGetConnection() + { + $col = new PropelCollection(); + $col->setModel('Book'); + $con = Propel::getConnection(BookPeer::DATABASE_NAME); + $this->assertEquals($con, $col->getConnection(), 'getConnection() returns a connection for the collection model'); + $con = Propel::getConnection(BookPeer::DATABASE_NAME, Propel::CONNECTION_WRITE); + $this->assertEquals($con, $col->getConnection(Propel::CONNECTION_WRITE), 'getConnection() accepts a connection type parameter'); + } + + /** + * @expectedException PropelException + */ + public function testGetConnectionNoModel() + { + $col = new PropelCollection(); + $col->getConnection(); + } + +} \ No newline at end of file diff --git a/library/propel/test/testsuite/runtime/collection/PropelObjectCollectionTest.php b/library/propel/test/testsuite/runtime/collection/PropelObjectCollectionTest.php new file mode 100644 index 000000000..55c14f2a9 --- /dev/null +++ b/library/propel/test/testsuite/runtime/collection/PropelObjectCollectionTest.php @@ -0,0 +1,236 @@ +con); + } + + public function testSave() + { + $books = PropelQuery::from('Book')->find(); + foreach ($books as $book) { + $book->setTitle('foo'); + } + $books->save(); + // check that all the books are saved + foreach ($books as $book) { + $this->assertFalse($book->isModified()); + } + // check that the modifications are persisted + BookPeer::clearInstancePool(); + $books = PropelQuery::from('Book')->find(); + foreach ($books as $book) { + $this->assertEquals('foo', $book->getTitle('foo')); + } + } + + public function testDelete() + { + $books = PropelQuery::from('Book')->find(); + $books->delete(); + // check that all the books are deleted + foreach ($books as $book) { + $this->assertTrue($book->isDeleted()); + } + // check that the modifications are persisted + BookPeer::clearInstancePool(); + $books = PropelQuery::from('Book')->find(); + $this->assertEquals(0, count($books)); + } + + public function testGetPrimaryKeys() + { + $books = PropelQuery::from('Book')->find(); + $pks = $books->getPrimaryKeys(); + $this->assertEquals(4, count($pks)); + + $keys = array('Book_0', 'Book_1', 'Book_2', 'Book_3'); + $this->assertEquals($keys, array_keys($pks)); + + $pks = $books->getPrimaryKeys(false); + $keys = array(0, 1, 2, 3); + $this->assertEquals($keys, array_keys($pks)); + + foreach ($pks as $key => $value) { + $this->assertEquals($books[$key]->getPrimaryKey(), $value); + } + } + + public function testFromArray() + { + $author = new Author(); + $author->setFirstName('Jane'); + $author->setLastName('Austen'); + $author->save(); + $books = array( + array('Title' => 'Mansfield Park', 'AuthorId' => $author->getId()), + array('Title' => 'Pride And PRejudice', 'AuthorId' => $author->getId()) + ); + $col = new PropelObjectCollection(); + $col->setModel('Book'); + $col->fromArray($books); + $col->save(); + + $nbBooks = PropelQuery::from('Book')->count(); + $this->assertEquals(6, $nbBooks); + + $booksByJane = PropelQuery::from('Book b') + ->join('b.Author a') + ->where('a.LastName = ?', 'Austen') + ->count(); + $this->assertEquals(2, $booksByJane); + } + + public function testToArray() + { + $books = PropelQuery::from('Book')->find(); + $booksArray = $books->toArray(); + $this->assertEquals(4, count($booksArray)); + + foreach ($booksArray as $key => $book) { + $this->assertEquals($books[$key]->toArray(), $book); + } + + $booksArray = $books->toArray(); + $keys = array(0, 1, 2, 3); + $this->assertEquals($keys, array_keys($booksArray)); + + $booksArray = $books->toArray(null, true); + $keys = array('Book_0', 'Book_1', 'Book_2', 'Book_3'); + $this->assertEquals($keys, array_keys($booksArray)); + + $booksArray = $books->toArray('Title'); + $keys = array('Harry Potter and the Order of the Phoenix', 'Quicksilver', 'Don Juan', 'The Tin Drum'); + $this->assertEquals($keys, array_keys($booksArray)); + + $booksArray = $books->toArray('Title', true); + $keys = array('Book_Harry Potter and the Order of the Phoenix', 'Book_Quicksilver', 'Book_Don Juan', 'Book_The Tin Drum'); + $this->assertEquals($keys, array_keys($booksArray)); + } + + public function testGetArrayCopy() + { + $books = PropelQuery::from('Book')->find(); + $booksArray = $books->getArrayCopy(); + $this->assertEquals(4, count($booksArray)); + + foreach ($booksArray as $key => $book) { + $this->assertEquals($books[$key], $book); + } + + $booksArray = $books->getArrayCopy(); + $keys = array(0, 1, 2, 3); + $this->assertEquals($keys, array_keys($booksArray)); + + $booksArray = $books->getArrayCopy(null, true); + $keys = array('Book_0', 'Book_1', 'Book_2', 'Book_3'); + $this->assertEquals($keys, array_keys($booksArray)); + + $booksArray = $books->getArrayCopy('Title'); + $keys = array('Harry Potter and the Order of the Phoenix', 'Quicksilver', 'Don Juan', 'The Tin Drum'); + $this->assertEquals($keys, array_keys($booksArray)); + + $booksArray = $books->getArrayCopy('Title', true); + $keys = array('Book_Harry Potter and the Order of the Phoenix', 'Book_Quicksilver', 'Book_Don Juan', 'Book_The Tin Drum'); + $this->assertEquals($keys, array_keys($booksArray)); + } + + public function testToKeyValue() + { + $books = PropelQuery::from('Book')->find(); + + $expected = array(); + foreach ($books as $book) { + $expected[$book->getTitle()] = $book->getISBN(); + } + $booksArray = $books->toKeyValue('Title', 'ISBN'); + $this->assertEquals(4, count($booksArray)); + $this->assertEquals($expected, $booksArray, 'toKeyValue() turns the collection to an associative array'); + + $expected = array(); + foreach ($books as $book) { + $expected[$book->getISBN()] = $book->getTitle(); + } + $booksArray = $books->toKeyValue('ISBN'); + $this->assertEquals($expected, $booksArray, 'toKeyValue() uses __toString() for the value if no second field name is passed'); + + $expected = array(); + foreach ($books as $book) { + $expected[$book->getId()] = $book->getTitle(); + } + $booksArray = $books->toKeyValue(); + $this->assertEquals($expected, $booksArray, 'toKeyValue() uses primary key for the key and __toString() for the value if no field name is passed'); + } + + public function testPopulateRelation() + { + AuthorPeer::clearInstancePool(); + BookPeer::clearInstancePool(); + $authors = AuthorQuery::create()->find(); + $books = $authors->populateRelation('Book'); + $this->assertTrue($books instanceof PropelObjectCollection, 'populateRelation() returns a PropelCollection instance'); + $this->assertEquals('Book', $books->getModel(), 'populateRelation() returns a collection of the related objects'); + $this->assertEquals(4, count($books), 'populateRelation() the list of related objects'); + } + + public function testPopulateRelationCriteria() + { + AuthorPeer::clearInstancePool(); + BookPeer::clearInstancePool(); + $authors = AuthorQuery::create()->find(); + $c = new Criteria(); + $c->setLimit(3); + $books = $authors->populateRelation('Book', $c); + $this->assertEquals(3, count($books), 'populateRelation() accepts an optional criteria object to filter the query'); + } + + public function testPopulateRelationOneToMany() + { + $con = Propel::getConnection(); + AuthorPeer::clearInstancePool(); + BookPeer::clearInstancePool(); + $authors = AuthorQuery::create()->find($con); + $count = $con->getQueryCount(); + $books = $authors->populateRelation('Book', null, $con); + foreach ($authors as $author) { + foreach ($author->getBooks() as $book) { + $this->assertEquals($author, $book->getAuthor()); + } + } + $this->assertEquals($count + 1, $con->getQueryCount(), 'populateRelation() populates a one-to-many relationship with a single supplementary query'); + } + + public function testPopulateRelationManyToOne() + { + $con = Propel::getConnection(); + AuthorPeer::clearInstancePool(); + BookPeer::clearInstancePool(); + $books = BookQuery::create()->find($con); + $count = $con->getQueryCount(); + $books->populateRelation('Author', null, $con); + foreach ($books as $book) { + $author = $book->getAuthor(); + } + $this->assertEquals($count + 1, $con->getQueryCount(), 'populateRelation() populates a many-to-one relationship with a single supplementary query'); + } +} \ No newline at end of file diff --git a/library/propel/test/testsuite/runtime/collection/PropelOnDemandCollectionTest.php b/library/propel/test/testsuite/runtime/collection/PropelOnDemandCollectionTest.php new file mode 100644 index 000000000..8ceddb22e --- /dev/null +++ b/library/propel/test/testsuite/runtime/collection/PropelOnDemandCollectionTest.php @@ -0,0 +1,76 @@ +con); + $this->books = PropelQuery::from('Book')->setFormatter(ModelCriteria::FORMAT_ON_DEMAND)->find(); + } + + public function testSetFormatter() + { + $this->assertTrue($this->books instanceof PropelOnDemandCollection); + $this->assertEquals(4, count($this->books)); + } + + public function testKeys() + { + $i = 0; + foreach ($this->books as $key => $book) { + $this->assertEquals($i, $key); + $i++; + } + } + + /** + * @expectedException PropelException + */ + public function testoffsetExists() + { + $this->books->offsetExists(2); + } + + /** + * @expectedException PropelException + */ + public function testoffsetGet() + { + $this->books->offsetGet(2); + } + + /** + * @expectedException PropelException + */ + public function testoffsetSet() + { + $this->books->offsetSet(2, 'foo'); + } + + /** + * @expectedException PropelException + */ + public function testoffsetUnset() + { + $this->books->offsetUnset(2); + } + +} \ No newline at end of file diff --git a/library/propel/test/testsuite/runtime/collection/PropelOnDemandIteratorTest.php b/library/propel/test/testsuite/runtime/collection/PropelOnDemandIteratorTest.php new file mode 100755 index 000000000..587f4602e --- /dev/null +++ b/library/propel/test/testsuite/runtime/collection/PropelOnDemandIteratorTest.php @@ -0,0 +1,59 @@ +con); + } + + public function testInstancePoolingDisabled() + { + Propel::enableInstancePooling(); + $books = PropelQuery::from('Book') + ->setFormatter(ModelCriteria::FORMAT_ON_DEMAND) + ->find($this->con); + foreach ($books as $book) { + $this->assertFalse(Propel::isInstancePoolingEnabled()); + } + } + + public function testInstancePoolingReenabled() + { + Propel::enableInstancePooling(); + $books = PropelQuery::from('Book') + ->setFormatter(ModelCriteria::FORMAT_ON_DEMAND) + ->find($this->con); + foreach ($books as $book) { + } + $this->assertTrue(Propel::isInstancePoolingEnabled()); + + Propel::disableInstancePooling(); + $books = PropelQuery::from('Book') + ->setFormatter(ModelCriteria::FORMAT_ON_DEMAND) + ->find($this->con); + foreach ($books as $book) { + } + $this->assertFalse(Propel::isInstancePoolingEnabled()); + Propel::enableInstancePooling(); + } + +} \ No newline at end of file diff --git a/library/propel/test/testsuite/runtime/connection/PropelPDOTest.php b/library/propel/test/testsuite/runtime/connection/PropelPDOTest.php new file mode 100644 index 000000000..0befc1d1d --- /dev/null +++ b/library/propel/test/testsuite/runtime/connection/PropelPDOTest.php @@ -0,0 +1,425 @@ +assertFalse($con->getAttribute(PropelPDO::PROPEL_ATTR_CACHE_PREPARES)); + $con->setAttribute(PropelPDO::PROPEL_ATTR_CACHE_PREPARES, true); + $this->assertTrue($con->getAttribute(PropelPDO::PROPEL_ATTR_CACHE_PREPARES)); + + $con->setAttribute(PDO::ATTR_CASE, PDO::CASE_LOWER); + $this->assertEquals(PDO::CASE_LOWER, $con->getAttribute(PDO::ATTR_CASE)); + } + + public function testNestedTransactionCommit() + { + $con = Propel::getConnection(BookPeer::DATABASE_NAME); + $driver = $con->getAttribute(PDO::ATTR_DRIVER_NAME); + + $this->assertEquals(0, $con->getNestedTransactionCount(), 'nested transaction is equal to 0 before transaction'); + $this->assertFalse($con->isInTransaction(), 'PropelPDO is not in transaction by default'); + + $con->beginTransaction(); + + $this->assertEquals(1, $con->getNestedTransactionCount(), 'nested transaction is incremented after main transaction begin'); + $this->assertTrue($con->isInTransaction(), 'PropelPDO is in transaction after main transaction begin'); + + try { + + $a = new Author(); + $a->setFirstName('Test'); + $a->setLastName('User'); + $a->save($con); + $authorId = $a->getId(); + $this->assertNotNull($authorId, "Expected valid new author ID"); + + $con->beginTransaction(); + + $this->assertEquals(2, $con->getNestedTransactionCount(), 'nested transaction is incremented after nested transaction begin'); + $this->assertTrue($con->isInTransaction(), 'PropelPDO is in transaction after nested transaction begin'); + + try { + + $a2 = new Author(); + $a2->setFirstName('Test2'); + $a2->setLastName('User2'); + $a2->save($con); + $authorId2 = $a2->getId(); + $this->assertNotNull($authorId2, "Expected valid new author ID"); + + $con->commit(); + + $this->assertEquals(1, $con->getNestedTransactionCount(), 'nested transaction decremented after nested transaction commit'); + $this->assertTrue($con->isInTransaction(), 'PropelPDO is in transaction after main transaction commit'); + + } catch (Exception $e) { + $con->rollBack(); + throw $e; + } + + $con->commit(); + + $this->assertEquals(0, $con->getNestedTransactionCount(), 'nested transaction decremented after main transaction commit'); + $this->assertFalse($con->isInTransaction(), 'PropelPDO is not in transaction after main transaction commit'); + + } catch (Exception $e) { + $con->rollBack(); + } + + AuthorPeer::clearInstancePool(); + $at = AuthorPeer::retrieveByPK($authorId); + $this->assertNotNull($at, "Committed transaction is persisted in database"); + $at2 = AuthorPeer::retrieveByPK($authorId2); + $this->assertNotNull($at2, "Committed transaction is persisted in database"); + } + + /** + * @link http://propel.phpdb.org/trac/ticket/699 + */ + public function testNestedTransactionRollBackRethrow() + { + $con = Propel::getConnection(BookPeer::DATABASE_NAME); + $driver = $con->getAttribute(PDO::ATTR_DRIVER_NAME); + + $con->beginTransaction(); + try { + + $a = new Author(); + $a->setFirstName('Test'); + $a->setLastName('User'); + $a->save($con); + $authorId = $a->getId(); + + $this->assertNotNull($authorId, "Expected valid new author ID"); + + $con->beginTransaction(); + + $this->assertEquals(2, $con->getNestedTransactionCount(), 'nested transaction is incremented after nested transaction begin'); + $this->assertTrue($con->isInTransaction(), 'PropelPDO is in transaction after nested transaction begin'); + + try { + $con->exec('INVALID SQL'); + $this->fail("Expected exception on invalid SQL"); + } catch (PDOException $x) { + $con->rollBack(); + + $this->assertEquals(1, $con->getNestedTransactionCount(), 'nested transaction decremented after nested transaction rollback'); + $this->assertTrue($con->isInTransaction(), 'PropelPDO is in transaction after main transaction rollback'); + + throw $x; + } + + $con->commit(); + } catch (Exception $x) { + $con->rollBack(); + } + + AuthorPeer::clearInstancePool(); + $at = AuthorPeer::retrieveByPK($authorId); + $this->assertNull($at, "Rolled back transaction is not persisted in database"); + } + + /** + * @link http://propel.phpdb.org/trac/ticket/699 + */ + public function testNestedTransactionRollBackSwallow() + { + $con = Propel::getConnection(BookPeer::DATABASE_NAME); + $driver = $con->getAttribute(PDO::ATTR_DRIVER_NAME); + + $con->beginTransaction(); + try { + + $a = new Author(); + $a->setFirstName('Test'); + $a->setLastName('User'); + $a->save($con); + + $authorId = $a->getId(); + $this->assertNotNull($authorId, "Expected valid new author ID"); + + $con->beginTransaction(); + try { + + $a2 = new Author(); + $a2->setFirstName('Test2'); + $a2->setLastName('User2'); + $a2->save($con); + $authorId2 = $a2->getId(); + $this->assertNotNull($authorId2, "Expected valid new author ID"); + + $con->exec('INVALID SQL'); + $this->fail("Expected exception on invalid SQL"); + } catch (PDOException $e) { + $con->rollBack(); + // NO RETHROW + } + + $a3 = new Author(); + $a3->setFirstName('Test2'); + $a3->setLastName('User2'); + $a3->save($con); + + $authorId3 = $a3->getId(); + $this->assertNotNull($authorId3, "Expected valid new author ID"); + + $con->commit(); + $this->fail("Commit fails after a nested rollback"); + } catch (PropelException $e) { + $this->assertTrue(true, "Commit fails after a nested rollback"); + $con->rollback(); + } + + AuthorPeer::clearInstancePool(); + $at = AuthorPeer::retrieveByPK($authorId); + $this->assertNull($at, "Rolled back transaction is not persisted in database"); + $at2 = AuthorPeer::retrieveByPK($authorId2); + $this->assertNull($at2, "Rolled back transaction is not persisted in database"); + $at3 = AuthorPeer::retrieveByPK($authorId3); + $this->assertNull($at3, "Rolled back nested transaction is not persisted in database"); + } + + public function testNestedTransactionForceRollBack() + { + $con = Propel::getConnection(BookPeer::DATABASE_NAME); + $driver = $con->getAttribute(PDO::ATTR_DRIVER_NAME); + + // main transaction + $con->beginTransaction(); + + $a = new Author(); + $a->setFirstName('Test'); + $a->setLastName('User'); + $a->save($con); + $authorId = $a->getId(); + + // nested transaction + $con->beginTransaction(); + + $a2 = new Author(); + $a2->setFirstName('Test2'); + $a2->setLastName('User2'); + $a2->save($con); + $authorId2 = $a2->getId(); + + // force rollback + $con->forceRollback(); + + $this->assertEquals(0, $con->getNestedTransactionCount(), 'nested transaction is null after nested transaction forced rollback'); + $this->assertFalse($con->isInTransaction(), 'PropelPDO is not in transaction after nested transaction force rollback'); + + AuthorPeer::clearInstancePool(); + $at = AuthorPeer::retrieveByPK($authorId); + $this->assertNull($at, "Rolled back transaction is not persisted in database"); + $at2 = AuthorPeer::retrieveByPK($authorId2); + $this->assertNull($at2, "Forced Rolled back nested transaction is not persisted in database"); + } + + public function testLatestQuery() + { + $con = Propel::getConnection(BookPeer::DATABASE_NAME); + $con->setLastExecutedQuery(123); + $this->assertEquals(123, $con->getLastExecutedQuery(), 'PropelPDO has getter and setter for last executed query'); + } + + public function testLatestQueryMoreThanTenArgs() + { + $con = Propel::getConnection(BookPeer::DATABASE_NAME); + $c = new Criteria(); + $c->add(BookPeer::ID, array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1), Criteria::IN); + $books = BookPeer::doSelect($c, $con); + $expected = "SELECT book.ID, book.TITLE, book.ISBN, book.PRICE, book.PUBLISHER_ID, book.AUTHOR_ID FROM `book` WHERE book.ID IN (1,1,1,1,1,1,1,1,1,1,1,1)"; + $this->assertEquals($expected, $con->getLastExecutedQuery(), 'PropelPDO correctly replaces arguments in queries'); + } + + public function testQueryCount() + { + $con = Propel::getConnection(BookPeer::DATABASE_NAME); + $count = $con->getQueryCount(); + $con->incrementQueryCount(); + $this->assertEquals($count + 1, $con->getQueryCount(), 'PropelPDO has getter and incrementer for query count'); + } + + public function testUseDebug() + { + $con = Propel::getConnection(BookPeer::DATABASE_NAME); + $con->useDebug(false); + $this->assertEquals(array('PDOStatement'), $con->getAttribute(PDO::ATTR_STATEMENT_CLASS), 'Statement is PDOStatement when debug is false'); + $con->useDebug(true); + $this->assertEquals(array('DebugPDOStatement', array($con)), $con->getAttribute(PDO::ATTR_STATEMENT_CLASS), 'statement is DebugPDOStament when debug is true'); + } + + public function testDebugLatestQuery() + { + $con = Propel::getConnection(BookPeer::DATABASE_NAME); + $c = new Criteria(); + $c->add(BookPeer::TITLE, 'Harry%s', Criteria::LIKE); + + $con->useDebug(false); + $this->assertEquals('', $con->getLastExecutedQuery(), 'PropelPDO reinitializes the latest query when debug is set to false'); + + $books = BookPeer::doSelect($c, $con); + $this->assertEquals('', $con->getLastExecutedQuery(), 'PropelPDO does not update the last executed query when useLogging is false'); + + $con->useDebug(true); + $books = BookPeer::doSelect($c, $con); + $latestExecutedQuery = "SELECT book.ID, book.TITLE, book.ISBN, book.PRICE, book.PUBLISHER_ID, book.AUTHOR_ID FROM `book` WHERE book.TITLE LIKE 'Harry%s'"; + if (!Propel::getDB(BookPeer::DATABASE_NAME)->useQuoteIdentifier()) { + $latestExecutedQuery = str_replace('`', '', $latestExecutedQuery); + } + $this->assertEquals($latestExecutedQuery, $con->getLastExecutedQuery(), 'PropelPDO updates the last executed query when useLogging is true'); + + BookPeer::doDeleteAll($con); + $latestExecutedQuery = "DELETE FROM `book`"; + $this->assertEquals($latestExecutedQuery, $con->getLastExecutedQuery(), 'PropelPDO updates the last executed query on delete operations'); + + $sql = 'DELETE FROM book WHERE 1=1'; + $con->exec($sql); + $this->assertEquals($sql, $con->getLastExecutedQuery(), 'PropelPDO updates the last executed query on exec operations'); + + $sql = 'DELETE FROM book WHERE 2=2'; + $con->query($sql); + $this->assertEquals($sql, $con->getLastExecutedQuery(), 'PropelPDO updates the last executed query on query operations'); + + $stmt = $con->prepare('DELETE FROM book WHERE 1=:p1'); + $stmt->bindValue(':p1', '2'); + $stmt->execute(); + $this->assertEquals("DELETE FROM book WHERE 1='2'", $con->getLastExecutedQuery(), 'PropelPDO updates the last executed query on prapared statements'); + + $con->useDebug(false); + $this->assertEquals('', $con->getLastExecutedQuery(), 'PropelPDO reinitializes the latest query when debug is set to false'); + + $con->useDebug(true); + } + + public function testDebugQueryCount() + { + $con = Propel::getConnection(BookPeer::DATABASE_NAME); + $c = new Criteria(); + $c->add(BookPeer::TITLE, 'Harry%s', Criteria::LIKE); + + $con->useDebug(false); + $this->assertEquals(0, $con->getQueryCount(), 'PropelPDO does not update the query count when useLogging is false'); + + $books = BookPeer::doSelect($c, $con); + $this->assertEquals(0, $con->getQueryCount(), 'PropelPDO does not update the query count when useLogging is false'); + + $con->useDebug(true); + $books = BookPeer::doSelect($c, $con); + $this->assertEquals(1, $con->getQueryCount(), 'PropelPDO updates the query count when useLogging is true'); + + BookPeer::doDeleteAll($con); + $this->assertEquals(2, $con->getQueryCount(), 'PropelPDO updates the query count on delete operations'); + + $sql = 'DELETE FROM book WHERE 1=1'; + $con->exec($sql); + $this->assertEquals(3, $con->getQueryCount(), 'PropelPDO updates the query count on exec operations'); + + $sql = 'DELETE FROM book WHERE 2=2'; + $con->query($sql); + $this->assertEquals(4, $con->getQueryCount(), 'PropelPDO updates the query count on query operations'); + + $stmt = $con->prepare('DELETE FROM book WHERE 1=:p1'); + $stmt->bindValue(':p1', '2'); + $stmt->execute(); + $this->assertEquals(5, $con->getQueryCount(), 'PropelPDO updates the query count on prapared statements'); + + $con->useDebug(false); + $this->assertEquals(0, $con->getQueryCount(), 'PropelPDO reinitializes the query count when debug is set to false'); + + $con->useDebug(true); + } + + public function testDebugLog() + { + $con = Propel::getConnection(BookPeer::DATABASE_NAME); + $config = Propel::getConfiguration(PropelConfiguration::TYPE_OBJECT); + + // save data to return to normal state after test + $logger = $con->getLogger(); + + $testLog = new myLogger(); + $con->setLogger($testLog); + + $logEverything = array('PropelPDO::exec', 'PropelPDO::query', 'PropelPDO::beginTransaction', 'PropelPDO::commit', 'PropelPDO::rollBack', 'DebugPDOStatement::execute'); + Propel::getConfiguration(PropelConfiguration::TYPE_OBJECT)->setParameter("debugpdo.logging.methods", $logEverything); + $con->useDebug(true); + + // test transaction log + $con->beginTransaction(); + $this->assertEquals('log: Begin transaction', $testLog->latestMessage, 'PropelPDO logs begin transation in debug mode'); + + $con->commit(); + $this->assertEquals('log: Commit transaction', $testLog->latestMessage, 'PropelPDO logs commit transation in debug mode'); + + $con->beginTransaction(); + $con->rollBack(); + $this->assertEquals('log: Rollback transaction', $testLog->latestMessage, 'PropelPDO logs rollback transation in debug mode'); + + $con->beginTransaction(); + $testLog->latestMessage = ''; + $con->beginTransaction(); + $this->assertEquals('', $testLog->latestMessage, 'PropelPDO does not log nested begin transation in debug mode'); + $con->commit(); + $this->assertEquals('', $testLog->latestMessage, 'PropelPDO does not log nested commit transation in debug mode'); + $con->beginTransaction(); + $con->rollBack(); + $this->assertEquals('', $testLog->latestMessage, 'PropelPDO does not log nested rollback transation in debug mode'); + $con->rollback(); + + // test query log + $con->beginTransaction(); + + $c = new Criteria(); + $c->add(BookPeer::TITLE, 'Harry%s', Criteria::LIKE); + + $books = BookPeer::doSelect($c, $con); + $latestExecutedQuery = "SELECT book.ID, book.TITLE, book.ISBN, book.PRICE, book.PUBLISHER_ID, book.AUTHOR_ID FROM `book` WHERE book.TITLE LIKE 'Harry%s'"; + $this->assertEquals('log: ' . $latestExecutedQuery, $testLog->latestMessage, 'PropelPDO logs queries and populates bound parameters in debug mode'); + + BookPeer::doDeleteAll($con); + $latestExecutedQuery = "DELETE FROM `book`"; + $this->assertEquals('log: ' . $latestExecutedQuery, $testLog->latestMessage, 'PropelPDO logs deletion queries in debug mode'); + + $latestExecutedQuery = 'DELETE FROM book WHERE 1=1'; + $con->exec($latestExecutedQuery); + $this->assertEquals('log: ' . $latestExecutedQuery, $testLog->latestMessage, 'PropelPDO logs exec queries in debug mode'); + + $con->commit(); + + // return to normal state after test + $con->setLogger($logger); + $config->setParameter("debugpdo.logging.methods", array('PropelPDO::exec', 'PropelPDO::query', 'DebugPDOStatement::execute')); + } +} + +class myLogger +{ + public $latestMessage = ''; + + public function __call($method, $arguments) + { + $this->latestMessage = $method . ': ' . array_shift($arguments); + } +} \ No newline at end of file diff --git a/library/propel/test/testsuite/runtime/formatter/PropelArrayFormatterTest.php b/library/propel/test/testsuite/runtime/formatter/PropelArrayFormatterTest.php new file mode 100644 index 000000000..b1bcde670 --- /dev/null +++ b/library/propel/test/testsuite/runtime/formatter/PropelArrayFormatterTest.php @@ -0,0 +1,129 @@ +query('SELECT * FROM book'); + $formatter = new PropelArrayFormatter(); + try { + $books = $formatter->format($stmt); + $this->fail('PropelArrayFormatter::format() throws an exception when called with no valid criteria'); + } catch (PropelException $e) { + $this->assertTrue(true,'PropelArrayFormatter::format() throws an exception when called with no valid criteria'); + } + } + + public function testFormatManyResults() + { + $con = Propel::getConnection(BookPeer::DATABASE_NAME); + + $stmt = $con->query('SELECT * FROM book'); + $formatter = new PropelArrayFormatter(); + $formatter->init(new ModelCriteria('bookstore', 'Book')); + $books = $formatter->format($stmt); + + $this->assertTrue($books instanceof PropelCollection, 'PropelArrayFormatter::format() returns a PropelCollection'); + $this->assertEquals(4, count($books), 'PropelArrayFormatter::format() returns as many rows as the results in the query'); + foreach ($books as $book) { + $this->assertTrue(is_array($book), 'PropelArrayFormatter::format() returns an array of arrays'); + } + } + + public function testFormatOneResult() + { + $con = Propel::getConnection(BookPeer::DATABASE_NAME); + + $stmt = $con->query('SELECT * FROM book WHERE book.TITLE = "Quicksilver"'); + $formatter = new PropelArrayFormatter(); + $formatter->init(new ModelCriteria('bookstore', 'Book')); + $books = $formatter->format($stmt); + + $this->assertTrue($books instanceof PropelCollection, 'PropelArrayFormatter::format() returns a PropelCollection'); + $this->assertEquals(1, count($books), 'PropelArrayFormatter::format() returns as many rows as the results in the query'); + $book = $books->shift(); + $this->assertTrue(is_array($book), 'PropelArrayFormatter::format() returns an array of arrays'); + $this->assertEquals('Quicksilver', $book['Title'], 'PropelArrayFormatter::format() returns the arrays matching the query'); + $expected = array('Id', 'Title', 'ISBN', 'Price', 'PublisherId', 'AuthorId'); + $this->assertEquals($expected, array_keys($book), 'PropelArrayFormatter::format() returns an associative array with column phpNames as keys'); + } + + public function testFormatNoResult() + { + $con = Propel::getConnection(BookPeer::DATABASE_NAME); + + $stmt = $con->query('SELECT * FROM book WHERE book.TITLE = "foo"'); + $formatter = new PropelArrayFormatter(); + $formatter->init(new ModelCriteria('bookstore', 'Book')); + $books = $formatter->format($stmt); + + $this->assertTrue($books instanceof PropelCollection, 'PropelArrayFormatter::format() returns a PropelCollection'); + $this->assertEquals(0, count($books), 'PropelArrayFormatter::format() returns as many rows as the results in the query'); + } + + public function testFormatOneNoCriteria() + { + $con = Propel::getConnection(BookPeer::DATABASE_NAME); + + $stmt = $con->query('SELECT * FROM book'); + $formatter = new PropelArrayFormatter(); + try { + $book = $formatter->formatOne($stmt); + $this->fail('PropelArrayFormatter::formatOne() throws an exception when called with no valid criteria'); + } catch (PropelException $e) { + $this->assertTrue(true,'PropelArrayFormatter::formatOne() throws an exception when called with no valid criteria'); + } + } + + public function testFormatOneManyResults() + { + $con = Propel::getConnection(BookPeer::DATABASE_NAME); + + $stmt = $con->query('SELECT * FROM book'); + $formatter = new PropelArrayFormatter(); + $formatter->init(new ModelCriteria('bookstore', 'Book')); + $book = $formatter->formatOne($stmt); + + $this->assertTrue(is_array($book), 'PropelArrayFormatter::formatOne() returns an array'); + $this->assertEquals(array('Id', 'Title', 'ISBN', 'Price', 'PublisherId', 'AuthorId'), array_keys($book), 'PropelArrayFormatter::formatOne() returns a single row even if the query has many results'); + } + + public function testFormatOneNoResult() + { + $con = Propel::getConnection(BookPeer::DATABASE_NAME); + + $stmt = $con->query('SELECT * FROM book WHERE book.TITLE = "foo"'); + $formatter = new PropelArrayFormatter(); + $formatter->init(new ModelCriteria('bookstore', 'Book')); + $book = $formatter->formatOne($stmt); + + $this->assertNull($book, 'PropelArrayFormatter::formatOne() returns null when no result'); + } + + +} diff --git a/library/propel/test/testsuite/runtime/formatter/PropelArrayFormatterWithTest.php b/library/propel/test/testsuite/runtime/formatter/PropelArrayFormatterWithTest.php new file mode 100644 index 000000000..e0addfc46 --- /dev/null +++ b/library/propel/test/testsuite/runtime/formatter/PropelArrayFormatterWithTest.php @@ -0,0 +1,383 @@ +findOne($con); + $count = $con->getQueryCount(); + $this->assertEquals($book['Title'], 'Don Juan', 'Main object is correctly hydrated ' . $msg); + $author = $book['Author']; + $this->assertEquals($author['LastName'], 'Byron', 'Related object is correctly hydrated ' . $msg); + $publisher = $book['Publisher']; + $this->assertEquals($publisher['Name'], 'Penguin', 'Related object is correctly hydrated ' . $msg); + } + + public function testFindOneWith() + { + BookstoreDataPopulator::populate(); + BookPeer::clearInstancePool(); + AuthorPeer::clearInstancePool(); + $c = new ModelCriteria('bookstore', 'Book'); + $c->setFormatter(ModelCriteria::FORMAT_ARRAY); + $c->orderBy('Book.Title'); + $c->join('Book.Author'); + $c->with('Author'); + $c->join('Book.Publisher'); + $c->with('Publisher'); + $this->assertCorrectHydration1($c, 'without instance pool'); + } + + public function testFindOneWithAlias() + { + BookstoreDataPopulator::populate(); + BookPeer::clearInstancePool(); + AuthorPeer::clearInstancePool(); + $c = new ModelCriteria('bookstore', 'Book'); + $c->setFormatter(ModelCriteria::FORMAT_ARRAY); + $c->orderBy('Book.Title'); + $c->join('Book.Author a'); + $c->with('a'); + $c->join('Book.Publisher p'); + $c->with('p'); + $this->assertCorrectHydration1($c, 'with alias'); + } + + public function testFindOneWithMainAlias() + { + BookstoreDataPopulator::populate(); + BookPeer::clearInstancePool(); + AuthorPeer::clearInstancePool(); + $c = new ModelCriteria('bookstore', 'Book'); + $c->setFormatter(ModelCriteria::FORMAT_ARRAY); + $c->setModelAlias('b', true); + $c->orderBy('b.Title'); + $c->join('b.Author a'); + $c->with('a'); + $c->join('b.Publisher p'); + $c->with('p'); + $this->assertCorrectHydration1($c, 'with main alias'); + } + + public function testFindOneWithUsingInstancePool() + { + BookstoreDataPopulator::populate(); + // instance pool contains all objects by default, since they were just populated + $c = new ModelCriteria('bookstore', 'Book'); + $c->setFormatter(ModelCriteria::FORMAT_ARRAY); + $c->orderBy('Book.Title'); + $c->join('Book.Author'); + $c->with('Author'); + $c->join('Book.Publisher'); + $c->with('Publisher'); + $this->assertCorrectHydration1($c, 'with instance pool'); + } + + public function testFindOneWithEmptyLeftJoin() + { + // save a book with no author + $b = new Book(); + $b->setTitle('Foo'); + $b->save(); + $c = new ModelCriteria('bookstore', 'Book'); + $c->setFormatter(ModelCriteria::FORMAT_ARRAY); + $c->where('Book.Title = ?', 'Foo'); + $c->leftJoin('Book.Author'); + $c->with('Author'); + $con = Propel::getConnection(BookPeer::DATABASE_NAME); + $book = $c->findOne($con); + $count = $con->getQueryCount(); + $author = $book['Author']; + $this->assertEquals(array(), $author, 'Related object is not hydrated if empty'); + } + + public function testFindOneWithRelationName() + { + BookstoreDataPopulator::populate(); + BookstoreEmployeePeer::clearInstancePool(); + $c = new ModelCriteria('bookstore', 'BookstoreEmployee'); + $c->setFormatter(ModelCriteria::FORMAT_ARRAY); + $c->join('BookstoreEmployee.Supervisor s'); + $c->with('s'); + $c->where('s.Name = ?', 'John'); + $emp = $c->findOne(); + $this->assertEquals($emp['Name'], 'Pieter', 'Main object is correctly hydrated'); + $sup = $emp['Supervisor']; + $this->assertEquals($sup['Name'], 'John', 'Related object is correctly hydrated'); + } + + /** + * @see http://www.propelorm.org/ticket/959 + */ + public function testFindOneWithSameRelatedObject() + { + BookPeer::doDeleteAll(); + AuthorPeer::doDeleteAll(); + $auth = new Author(); + $auth->setFirstName('John'); + $auth->save(); + $book1 = new Book(); + $book1->setTitle('Hello'); + $book1->setAuthor($auth); + $book1->save(); + $book2 = new Book(); + $book2->setTitle('World'); + $book2->setAuthor($auth); + $book2->save(); + BookPeer::clearInstancePool(); + AuthorPeer::clearInstancePool(); + + $c = new ModelCriteria('bookstore', 'Book'); + $c->setFormatter(ModelCriteria::FORMAT_ARRAY); + $c->join('Book.Author'); + $c->with('Author'); + $books = $c->find(); + + $this->assertEquals(2, count($books)); + $firstBook = $books[0]; + $this->assertTrue(isset($firstBook['Author'])); + $secondBook = $books[1]; + $this->assertTrue(isset($secondBook['Author'])); + } + + public function testFindOneWithDuplicateRelation() + { + EssayPeer::doDeleteAll(); + $auth1 = new Author(); + $auth1->setFirstName('John'); + $auth1->save(); + $auth2 = new Author(); + $auth2->setFirstName('Jack'); + $auth2->save(); + $essay = new Essay(); + $essay->setTitle('Foo'); + $essay->setFirstAuthor($auth1->getId()); + $essay->setSecondAuthor($auth2->getId()); + $essay->save(); + AuthorPeer::clearInstancePool(); + EssayPeer::clearInstancePool(); + + $c = new ModelCriteria('bookstore', 'Essay'); + $c->setFormatter(ModelCriteria::FORMAT_ARRAY); + $c->join('Essay.AuthorRelatedByFirstAuthor'); + $c->with('AuthorRelatedByFirstAuthor'); + $c->where('Essay.Title = ?', 'Foo'); + $essay = $c->findOne(); + $this->assertEquals($essay['Title'], 'Foo', 'Main object is correctly hydrated'); + $firstAuthor = $essay['AuthorRelatedByFirstAuthor']; + $this->assertEquals($firstAuthor['FirstName'], 'John', 'Related object is correctly hydrated'); + $this->assertFalse(array_key_exists('AuthorRelatedBySecondAuthor', $essay), 'Only related object specified in with() is hydrated'); + } + + public function testFindOneWithDistantClass() + { + BookstoreDataPopulator::populate(); + BookPeer::clearInstancePool(); + AuthorPeer::clearInstancePool(); + ReviewPeer::clearInstancePool(); + $c = new ModelCriteria('bookstore', 'Review'); + $c->setFormatter(ModelCriteria::FORMAT_ARRAY); + $c->where('Review.Recommended = ?', true); + $c->join('Review.Book'); + $c->with('Book'); + $c->join('Book.Author'); + $c->with('Author'); + $review = $c->findOne(); + $this->assertEquals($review['ReviewedBy'], 'Washington Post', 'Main object is correctly hydrated'); + $book = $review['Book']; + $this->assertEquals('Harry Potter and the Order of the Phoenix', $book['Title'], 'Related object is correctly hydrated'); + $author = $book['Author']; + $this->assertEquals('J.K.', $author['FirstName'], 'Related object is correctly hydrated'); + } + + /** + * @expectedException PropelException + */ + public function testFindOneWithOneToManyAndLimit() + { + $c = new ModelCriteria('bookstore', 'Book'); + $c->setFormatter(ModelCriteria::FORMAT_ARRAY); + $c->add(BookPeer::ISBN, '043935806X'); + $c->leftJoin('Book.Review'); + $c->with('Review'); + $c->limit(5); + $books = $c->find(); + } + + public function testFindOneWithOneToMany() + { + BookstoreDataPopulator::populate(); + BookPeer::clearInstancePool(); + AuthorPeer::clearInstancePool(); + ReviewPeer::clearInstancePool(); + $c = new ModelCriteria('bookstore', 'Book'); + $c->setFormatter(ModelCriteria::FORMAT_ARRAY); + $c->add(BookPeer::ISBN, '043935806X'); + $c->leftJoin('Book.Review'); + $c->with('Review'); + $con = Propel::getConnection(BookPeer::DATABASE_NAME); + $books = $c->find($con); + $this->assertEquals(1, count($books), 'with() does not duplicate the main object'); + $book = $books[0]; + $this->assertEquals($book['Title'], 'Harry Potter and the Order of the Phoenix', 'Main object is correctly hydrated'); + $this->assertEquals(array('Id', 'Title', 'ISBN', 'Price', 'PublisherId', 'AuthorId', 'Reviews'), array_keys($book), 'with() adds a plural index for the one to many relationship'); + $reviews = $book['Reviews']; + $this->assertEquals(2, count($reviews), 'Related objects are correctly hydrated'); + $review1 = $reviews[0]; + $this->assertEquals(array('Id', 'ReviewedBy', 'ReviewDate', 'Recommended', 'Status', 'BookId'), array_keys($review1), 'with() Related objects are correctly hydrated'); + } + + public function testFindOneWithOneToManyCustomOrder() + { + $author1 = new Author(); + $author1->setFirstName('AA'); + $author2 = new Author(); + $author2->setFirstName('BB'); + $book1 = new Book(); + $book1->setTitle('Aaa'); + $book1->setAuthor($author1); + $book1->save(); + $book2 = new Book(); + $book2->setTitle('Bbb'); + $book2->setAuthor($author2); + $book2->save(); + $book3 = new Book(); + $book3->setTitle('Ccc'); + $book3->setAuthor($author1); + $book3->save(); + $authors = AuthorQuery::create() + ->setFormatter(ModelCriteria::FORMAT_ARRAY) + ->leftJoin('Author.Book') + ->orderBy('Book.Title') + ->with('Book') + ->find(); + $this->assertEquals(2, count($authors), 'with() used on a many-to-many doesn\'t change the main object count'); + } + + public function testFindOneWithOneToManyThenManyToOne() + { + BookstoreDataPopulator::populate(); + BookPeer::clearInstancePool(); + AuthorPeer::clearInstancePool(); + ReviewPeer::clearInstancePool(); + $c = new ModelCriteria('bookstore', 'Author'); + $c->add(AuthorPeer::LAST_NAME, 'Rowling'); + $c->leftJoinWith('Author.Book'); + $c->leftJoinWith('Book.Review'); + $c->setFormatter(ModelCriteria::FORMAT_ARRAY); + $con = Propel::getConnection(BookPeer::DATABASE_NAME); + $authors = $c->find($con); + $this->assertEquals(1, count($authors), 'with() does not duplicate the main object'); + $rowling = $authors[0]; + $this->assertEquals($rowling['FirstName'], 'J.K.', 'Main object is correctly hydrated'); + $books = $rowling['Books']; + $this->assertEquals(1, count($books), 'Related objects are correctly hydrated'); + $book = $books[0]; + $this->assertEquals($book['Title'], 'Harry Potter and the Order of the Phoenix', 'Related object is correctly hydrated'); + $reviews = $book['Reviews']; + $this->assertEquals(2, count($reviews), 'Related objects are correctly hydrated'); + } + + public function testFindOneWithOneToManyThenManyToOneUsingAlias() + { + BookstoreDataPopulator::populate(); + BookPeer::clearInstancePool(); + AuthorPeer::clearInstancePool(); + ReviewPeer::clearInstancePool(); + $c = new ModelCriteria('bookstore', 'Author'); + $c->add(AuthorPeer::LAST_NAME, 'Rowling'); + $c->leftJoinWith('Author.Book b'); + $c->leftJoinWith('b.Review r'); + $c->setFormatter(ModelCriteria::FORMAT_ARRAY); + $con = Propel::getConnection(BookPeer::DATABASE_NAME); + $authors = $c->find($con); + $this->assertEquals(1, count($authors), 'with() does not duplicate the main object'); + $rowling = $authors[0]; + $this->assertEquals($rowling['FirstName'], 'J.K.', 'Main object is correctly hydrated'); + $books = $rowling['Books']; + $this->assertEquals(1, count($books), 'Related objects are correctly hydrated'); + $book = $books[0]; + $this->assertEquals($book['Title'], 'Harry Potter and the Order of the Phoenix', 'Related object is correctly hydrated'); + $reviews = $book['Reviews']; + $this->assertEquals(2, count($reviews), 'Related objects are correctly hydrated'); + } + + public function testFindOneWithColumn() + { + BookstoreDataPopulator::populate(); + BookPeer::clearInstancePool(); + AuthorPeer::clearInstancePool(); + ReviewPeer::clearInstancePool(); + $c = new ModelCriteria('bookstore', 'Book'); + $c->setFormatter(ModelCriteria::FORMAT_ARRAY); + $c->filterByTitle('The Tin Drum'); + $c->join('Book.Author'); + $c->withColumn('Author.FirstName', 'AuthorName'); + $c->withColumn('Author.LastName', 'AuthorName2'); + $con = Propel::getConnection(BookPeer::DATABASE_NAME); + $book = $c->findOne($con); + $this->assertEquals(array('Id', 'Title', 'ISBN', 'Price', 'PublisherId', 'AuthorId', 'AuthorName', 'AuthorName2'), array_keys($book), 'withColumn() do not change the resulting model class'); + $this->assertEquals('The Tin Drum', $book['Title']); + $this->assertEquals('Gunter', $book['AuthorName'], 'PropelArrayFormatter adds withColumns as columns'); + $this->assertEquals('Grass', $book['AuthorName2'], 'PropelArrayFormatter correctly hydrates all as columns'); + } + + public function testFindOneWithClassAndColumn() + { + BookstoreDataPopulator::populate(); + BookPeer::clearInstancePool(); + AuthorPeer::clearInstancePool(); + ReviewPeer::clearInstancePool(); + $c = new ModelCriteria('bookstore', 'Book'); + $c->setFormatter(ModelCriteria::FORMAT_ARRAY); + $c->filterByTitle('The Tin Drum'); + $c->join('Book.Author'); + $c->withColumn('Author.FirstName', 'AuthorName'); + $c->withColumn('Author.LastName', 'AuthorName2'); + $c->with('Author'); + $con = Propel::getConnection(BookPeer::DATABASE_NAME); + $book = $c->findOne($con); + $this->assertEquals(array('Id', 'Title', 'ISBN', 'Price', 'PublisherId', 'AuthorId', 'Author', 'AuthorName', 'AuthorName2'), array_keys($book), 'withColumn() do not change the resulting model class'); + $this->assertEquals('The Tin Drum', $book['Title']); + $this->assertEquals('Gunter', $book['Author']['FirstName'], 'PropelArrayFormatter correctly hydrates withclass and columns'); + $this->assertEquals('Gunter', $book['AuthorName'], 'PropelArrayFormatter adds withColumns as columns'); + $this->assertEquals('Grass', $book['AuthorName2'], 'PropelArrayFormatter correctly hydrates all as columns'); + } + + public function testFindPkWithOneToMany() + { + BookstoreDataPopulator::populate(); + BookPeer::clearInstancePool(); + AuthorPeer::clearInstancePool(); + ReviewPeer::clearInstancePool(); + $con = Propel::getConnection(BookPeer::DATABASE_NAME); + $book = BookQuery::create() + ->findOneByTitle('Harry Potter and the Order of the Phoenix', $con); + $pk = $book->getPrimaryKey(); + BookPeer::clearInstancePool(); + $book = BookQuery::create() + ->setFormatter(ModelCriteria::FORMAT_ARRAY) + ->joinWith('Review') + ->findPk($pk, $con); + $reviews = $book['Reviews']; + $this->assertEquals(2, count($reviews), 'Related objects are correctly hydrated'); + } +} diff --git a/library/propel/test/testsuite/runtime/formatter/PropelObjectFormatterInheritanceTest.php b/library/propel/test/testsuite/runtime/formatter/PropelObjectFormatterInheritanceTest.php new file mode 100644 index 000000000..95ea1cce3 --- /dev/null +++ b/library/propel/test/testsuite/runtime/formatter/PropelObjectFormatterInheritanceTest.php @@ -0,0 +1,55 @@ +setName('b1'); + $b1->save(); + $b2 = new BookstoreManager(); + $b2->setName('b2'); + $b2->save(); + $b3 = new BookstoreCashier(); + $b3->setName('b3'); + $b3->save(); + } + + public function testFormat() + { + $con = Propel::getConnection(BookPeer::DATABASE_NAME); + BookstoreEmployeePeer::clearInstancePool(); + + $stmt = $con->query('SELECT * FROM bookstore_employee'); + $formatter = new PropelObjectFormatter(); + $formatter->init(new ModelCriteria('bookstore', 'BookstoreEmployee')); + $emps = $formatter->format($stmt); + $expectedClass = array( + 'b1' =>'BookstoreEmployee', + 'b2' =>'BookstoreManager', + 'b3' =>'BookstoreCashier' + ); + foreach ($emps as $emp) { + $this->assertEquals($expectedClass[$emp->getName()], get_class($emp), 'format() creates objects of the correct class when using inheritance'); + } + } + +} diff --git a/library/propel/test/testsuite/runtime/formatter/PropelObjectFormatterTest.php b/library/propel/test/testsuite/runtime/formatter/PropelObjectFormatterTest.php new file mode 100644 index 000000000..7321a2fbf --- /dev/null +++ b/library/propel/test/testsuite/runtime/formatter/PropelObjectFormatterTest.php @@ -0,0 +1,125 @@ +query('SELECT * FROM book'); + $formatter = new PropelObjectFormatter(); + try { + $books = $formatter->format($stmt); + $this->fail('PropelObjectFormatter::format() trows an exception when called with no valid criteria'); + } catch (PropelException $e) { + $this->assertTrue(true,'PropelObjectFormatter::format() trows an exception when called with no valid criteria'); + } + } + + public function testFormatManyResults() + { + $con = Propel::getConnection(BookPeer::DATABASE_NAME); + + $stmt = $con->query('SELECT * FROM book'); + $formatter = new PropelObjectFormatter(); + $formatter->init(new ModelCriteria('bookstore', 'Book')); + $books = $formatter->format($stmt); + + $this->assertTrue($books instanceof PropelCollection, 'PropelObjectFormatter::format() returns a PropelCollection'); + $this->assertEquals(4, count($books), 'PropelObjectFormatter::format() returns as many rows as the results in the query'); + foreach ($books as $book) { + $this->assertTrue($book instanceof Book, 'PropelObjectFormatter::format() returns an array of Model objects'); + } + } + + public function testFormatOneResult() + { + $con = Propel::getConnection(BookPeer::DATABASE_NAME); + + $stmt = $con->query('SELECT * FROM book WHERE book.TITLE = "Quicksilver"'); + $formatter = new PropelObjectFormatter(); + $formatter->init(new ModelCriteria('bookstore', 'Book')); + $books = $formatter->format($stmt); + + $this->assertTrue($books instanceof PropelCollection, 'PropelObjectFormatter::format() returns a PropelCollection'); + $this->assertEquals(1, count($books), 'PropelObjectFormatter::format() returns as many rows as the results in the query'); + $book = $books->shift(); + $this->assertTrue($book instanceof Book, 'PropelObjectFormatter::format() returns an array of Model objects'); + $this->assertEquals('Quicksilver', $book->getTitle(), 'PropelObjectFormatter::format() returns the model objects matching the query'); + } + + public function testFormatNoResult() + { + $con = Propel::getConnection(BookPeer::DATABASE_NAME); + + $stmt = $con->query('SELECT * FROM book WHERE book.TITLE = "foo"'); + $formatter = new PropelObjectFormatter(); + $formatter->init(new ModelCriteria('bookstore', 'Book')); + $books = $formatter->format($stmt); + + $this->assertTrue($books instanceof PropelCollection, 'PropelObjectFormatter::format() returns a PropelCollection'); + $this->assertEquals(0, count($books), 'PropelObjectFormatter::format() returns as many rows as the results in the query'); + } + + public function testFormatOneNoCriteria() + { + $con = Propel::getConnection(BookPeer::DATABASE_NAME); + + $stmt = $con->query('SELECT * FROM book'); + $formatter = new PropelObjectFormatter(); + try { + $book = $formatter->formatOne($stmt); + $this->fail('PropelObjectFormatter::formatOne() throws an exception when called with no valid criteria'); + } catch (PropelException $e) { + $this->assertTrue(true,'PropelObjectFormatter::formatOne() throws an exception when called with no valid criteria'); + } + } + + public function testFormatOneManyResults() + { + $con = Propel::getConnection(BookPeer::DATABASE_NAME); + + $stmt = $con->query('SELECT * FROM book'); + $formatter = new PropelObjectFormatter(); + $formatter->init(new ModelCriteria('bookstore', 'Book')); + $book = $formatter->formatOne($stmt); + + $this->assertTrue($book instanceof Book, 'PropelObjectFormatter::formatOne() returns a model object'); + } + + public function testFormatOneNoResult() + { + $con = Propel::getConnection(BookPeer::DATABASE_NAME); + + $stmt = $con->query('SELECT * FROM book WHERE book.TITLE = "foo"'); + $formatter = new PropelObjectFormatter(); + $formatter->init(new ModelCriteria('bookstore', 'Book')); + $book = $formatter->formatOne($stmt); + + $this->assertNull($book, 'PropelObjectFormatter::formatOne() returns null when no result'); + } + +} diff --git a/library/propel/test/testsuite/runtime/formatter/PropelObjectFormatterWithTest.php b/library/propel/test/testsuite/runtime/formatter/PropelObjectFormatterWithTest.php new file mode 100644 index 000000000..14279d9a3 --- /dev/null +++ b/library/propel/test/testsuite/runtime/formatter/PropelObjectFormatterWithTest.php @@ -0,0 +1,406 @@ +findOne($con); + $count = $con->getQueryCount(); + $this->assertEquals($book->getTitle(), 'Don Juan', 'Main object is correctly hydrated ' . $msg); + $author = $book->getAuthor(); + $this->assertEquals($count, $con->getQueryCount(), 'with() hydrates the related objects to save a query ' . $msg); + $this->assertEquals($author->getLastName(), 'Byron', 'Related object is correctly hydrated ' . $msg); + $publisher = $book->getPublisher(); + $this->assertEquals($count, $con->getQueryCount(), 'with() hydrates the related objects to save a query ' . $msg); + $this->assertEquals($publisher->getName(), 'Penguin', 'Related object is correctly hydrated ' . $msg); + } + + public function testFindOneWith() + { + BookstoreDataPopulator::populate(); + BookPeer::clearInstancePool(); + AuthorPeer::clearInstancePool(); + $c = new ModelCriteria('bookstore', 'Book'); + $c->orderBy('Book.Title'); + $c->join('Book.Author'); + $c->with('Author'); + $c->join('Book.Publisher'); + $c->with('Publisher'); + $this->assertCorrectHydration1($c, 'without instance pool'); + } + + public function testFindOneWithAlias() + { + BookstoreDataPopulator::populate(); + BookPeer::clearInstancePool(); + AuthorPeer::clearInstancePool(); + $c = new ModelCriteria('bookstore', 'Book'); + $c->orderBy('Book.Title'); + $c->join('Book.Author a'); + $c->with('a'); + $c->join('Book.Publisher p'); + $c->with('p'); + $this->assertCorrectHydration1($c, 'with alias'); + } + + public function testFindOneWithMainAlias() + { + BookstoreDataPopulator::populate(); + BookPeer::clearInstancePool(); + AuthorPeer::clearInstancePool(); + $c = new ModelCriteria('bookstore', 'Book'); + $c->setModelAlias('b', true); + $c->orderBy('b.Title'); + $c->join('b.Author a'); + $c->with('a'); + $c->join('b.Publisher p'); + $c->with('p'); + $this->assertCorrectHydration1($c, 'with main alias'); + } + + public function testFindOneWithUsingInstancePool() + { + BookstoreDataPopulator::populate(); + // instance pool contains all objects by default, since they were just populated + $c = new ModelCriteria('bookstore', 'Book'); + $c->orderBy('Book.Title'); + $c->join('Book.Author'); + $c->with('Author'); + $c->join('Book.Publisher'); + $c->with('Publisher'); + $this->assertCorrectHydration1($c, 'with instance pool'); + } + + public function testFindOneWithoutUsingInstancePool() + { + BookstoreDataPopulator::populate(); + Propel::disableInstancePooling(); + $c = new ModelCriteria('bookstore', 'Book'); + $c->orderBy('Book.Title'); + $c->join('Book.Author'); + $c->with('Author'); + $c->join('Book.Publisher'); + $c->with('Publisher'); + $this->assertCorrectHydration1($c, 'without instance pool'); + Propel::enableInstancePooling(); + } + + public function testFindOneWithEmptyLeftJoin() + { + // save a book with no author + $b = new Book(); + $b->setTitle('Foo'); + $b->save(); + $c = new ModelCriteria('bookstore', 'Book'); + $c->where('Book.Title = ?', 'Foo'); + $c->leftJoin('Book.Author'); + $c->with('Author'); + $con = Propel::getConnection(BookPeer::DATABASE_NAME); + $book = $c->findOne($con); + $count = $con->getQueryCount(); + $author = $book->getAuthor(); + $this->assertNull($author, 'Related object is not hydrated if empty'); + } + + public function testFindOneWithRelationName() + { + BookstoreDataPopulator::populate(); + BookstoreEmployeePeer::clearInstancePool(); + $c = new ModelCriteria('bookstore', 'BookstoreEmployee'); + $c->join('BookstoreEmployee.Supervisor s'); + $c->with('s'); + $c->where('s.Name = ?', 'John'); + $con = Propel::getConnection(BookPeer::DATABASE_NAME); + $emp = $c->findOne($con); + $count = $con->getQueryCount(); + $this->assertEquals($emp->getName(), 'Pieter', 'Main object is correctly hydrated'); + $sup = $emp->getSupervisor(); + $this->assertEquals($count, $con->getQueryCount(), 'with() hydrates the related objects to save a query'); + $this->assertEquals($sup->getName(), 'John', 'Related object is correctly hydrated'); + } + + public function testFindOneWithDuplicateRelation() + { + EssayPeer::doDeleteAll(); + $auth1 = new Author(); + $auth1->setFirstName('John'); + $auth1->save(); + $auth2 = new Author(); + $auth2->setFirstName('Jack'); + $auth2->save(); + $essay = new Essay(); + $essay->setTitle('Foo'); + $essay->setFirstAuthor($auth1->getId()); + $essay->setSecondAuthor($auth2->getId()); + $essay->save(); + AuthorPeer::clearInstancePool(); + EssayPeer::clearInstancePool(); + + $c = new ModelCriteria('bookstore', 'Essay'); + $c->join('Essay.AuthorRelatedByFirstAuthor'); + $c->with('AuthorRelatedByFirstAuthor'); + $c->where('Essay.Title = ?', 'Foo'); + $con = Propel::getConnection(BookPeer::DATABASE_NAME); + $essay = $c->findOne($con); + $count = $con->getQueryCount(); + $this->assertEquals($essay->getTitle(), 'Foo', 'Main object is correctly hydrated'); + $firstAuthor = $essay->getAuthorRelatedByFirstAuthor(); + $this->assertEquals($count, $con->getQueryCount(), 'with() hydrates the related objects to save a query'); + $this->assertEquals($firstAuthor->getFirstName(), 'John', 'Related object is correctly hydrated'); + $secondAuthor = $essay->getAuthorRelatedBySecondAuthor(); + $this->assertEquals($count + 1, $con->getQueryCount(), 'with() does not hydrate objects not in with'); + } + + public function testFindOneWithDistantClass() + { + BookstoreDataPopulator::populate(); + BookPeer::clearInstancePool(); + AuthorPeer::clearInstancePool(); + ReviewPeer::clearInstancePool(); + Propel::enableInstancePooling(); + $c = new ModelCriteria('bookstore', 'Review'); + $c->where('Review.Recommended = ?', true); + $c->join('Review.Book'); + $c->with('Book'); + $c->join('Book.Author'); + $c->with('Author'); + $con = Propel::getConnection(BookPeer::DATABASE_NAME); + $review = $c->findOne($con); + $count = $con->getQueryCount(); + $this->assertEquals($review->getReviewedBy(), 'Washington Post', 'Main object is correctly hydrated'); + $book = $review->getBook(); + $this->assertEquals($count, $con->getQueryCount(), 'with() hydrates the related objects to save a query'); + $this->assertEquals('Harry Potter and the Order of the Phoenix', $book->getTitle(), 'Related object is correctly hydrated'); + $author = $book->getAuthor(); + $this->assertEquals($count, $con->getQueryCount(), 'with() hydrates the related objects to save a query'); + $this->assertEquals('J.K.', $author->getFirstName(), 'Related object is correctly hydrated'); + } + + /** + * @expectedException PropelException + */ + public function testFindOneWithOneToManyAndLimit() + { + $c = new ModelCriteria('bookstore', 'Book'); + $c->add(BookPeer::ISBN, '043935806X'); + $c->leftJoin('Book.Review'); + $c->with('Review'); + $c->limit(5); + $books = $c->find(); + } + + public function testFindOneWithOneToMany() + { + BookstoreDataPopulator::populate(); + BookPeer::clearInstancePool(); + AuthorPeer::clearInstancePool(); + ReviewPeer::clearInstancePool(); + $c = new ModelCriteria('bookstore', 'Book'); + $c->add(BookPeer::ISBN, '043935806X'); + $c->leftJoin('Book.Review'); + $c->with('Review'); + $con = Propel::getConnection(BookPeer::DATABASE_NAME); + $books = $c->find($con); + $this->assertEquals(1, count($books), 'with() does not duplicate the main object'); + $book = $books[0]; + $count = $con->getQueryCount(); + $this->assertEquals($book->getTitle(), 'Harry Potter and the Order of the Phoenix', 'Main object is correctly hydrated'); + $reviews = $book->getReviews(); + $this->assertEquals($count, $con->getQueryCount(), 'with() hydrates the related objects to save a query '); + $this->assertEquals(2, count($reviews), 'Related objects are correctly hydrated'); + try { + $book->save(); + } catch (Exception $e) { + $this->fail('with() does not force objects to be new'); + } + } + + public function testFindOneWithOneToManyCustomOrder() + { + $author1 = new Author(); + $author1->setFirstName('AA'); + $author2 = new Author(); + $author2->setFirstName('BB'); + $book1 = new Book(); + $book1->setTitle('Aaa'); + $book1->setAuthor($author1); + $book1->save(); + $book2 = new Book(); + $book2->setTitle('Bbb'); + $book2->setAuthor($author2); + $book2->save(); + $book3 = new Book(); + $book3->setTitle('Ccc'); + $book3->setAuthor($author1); + $book3->save(); + $authors = AuthorQuery::create() + ->leftJoin('Author.Book') + ->orderBy('Book.Title') + ->with('Book') + ->find(); + $this->assertEquals(2, count($authors), 'with() used on a many-to-many doesn\'t change the main object count'); + } + + public function testFindOneWithOneToManyThenManyToOne() + { + BookstoreDataPopulator::populate(); + BookPeer::clearInstancePool(); + AuthorPeer::clearInstancePool(); + ReviewPeer::clearInstancePool(); + $c = new ModelCriteria('bookstore', 'Author'); + $c->add(AuthorPeer::LAST_NAME, 'Rowling'); + $c->leftJoinWith('Author.Book'); + $c->leftJoinWith('Book.Review'); + $con = Propel::getConnection(BookPeer::DATABASE_NAME); + $authors = $c->find($con); + $this->assertEquals(1, count($authors), 'with() does not duplicate the main object'); + $rowling = $authors[0]; + $count = $con->getQueryCount(); + $this->assertEquals($rowling->getFirstName(), 'J.K.', 'Main object is correctly hydrated'); + $books = $rowling->getBooks(); + $this->assertEquals($count, $con->getQueryCount(), 'with() hydrates the related objects to save a query '); + $this->assertEquals(1, count($books), 'Related objects are correctly hydrated'); + $book = $books[0]; + $this->assertEquals($book->getTitle(), 'Harry Potter and the Order of the Phoenix', 'Related object is correctly hydrated'); + $reviews = $book->getReviews(); + $this->assertEquals($count, $con->getQueryCount(), 'with() hydrates the related objects to save a query '); + $this->assertEquals(2, count($reviews), 'Related objects are correctly hydrated'); + } + + public function testFindOneWithOneToManyThenManyToOneUsingJoinRelated() + { + BookstoreDataPopulator::populate(); + BookPeer::clearInstancePool(); + AuthorPeer::clearInstancePool(); + ReviewPeer::clearInstancePool(); + + $con = Propel::getConnection(AuthorPeer::DATABASE_NAME); + $authors = AuthorQuery::create() + ->filterByLastName('Rowling') + ->joinBook('book') + ->with('book') + ->useQuery('book') + ->joinReview('review') + ->with('review') + ->endUse() + ->find($con); + $this->assertEquals(1, count($authors), 'with() does not duplicate the main object'); + $rowling = $authors[0]; + $count = $con->getQueryCount(); + $this->assertEquals($rowling->getFirstName(), 'J.K.', 'Main object is correctly hydrated'); + $books = $rowling->getBooks(); + $this->assertEquals($count, $con->getQueryCount(), 'with() hydrates the related objects to save a query '); + $this->assertEquals(1, count($books), 'Related objects are correctly hydrated'); + $book = $books[0]; + $this->assertEquals($book->getTitle(), 'Harry Potter and the Order of the Phoenix', 'Related object is correctly hydrated'); + $reviews = $book->getReviews(); + $this->assertEquals($count, $con->getQueryCount(), 'with() hydrates the related objects to save a query '); + $this->assertEquals(2, count($reviews), 'Related objects are correctly hydrated'); + } + + public function testFindOneWithOneToManyThenManyToOneUsingAlias() + { + BookstoreDataPopulator::populate(); + BookPeer::clearInstancePool(); + AuthorPeer::clearInstancePool(); + ReviewPeer::clearInstancePool(); + $c = new ModelCriteria('bookstore', 'Author'); + $c->add(AuthorPeer::LAST_NAME, 'Rowling'); + $c->leftJoinWith('Author.Book b'); + $c->leftJoinWith('b.Review r'); + $con = Propel::getConnection(BookPeer::DATABASE_NAME); + $authors = $c->find($con); + $this->assertEquals(1, count($authors), 'with() does not duplicate the main object'); + $rowling = $authors[0]; + $count = $con->getQueryCount(); + $this->assertEquals($rowling->getFirstName(), 'J.K.', 'Main object is correctly hydrated'); + $books = $rowling->getBooks(); + $this->assertEquals($count, $con->getQueryCount(), 'with() hydrates the related objects to save a query '); + $this->assertEquals(1, count($books), 'Related objects are correctly hydrated'); + $book = $books[0]; + $this->assertEquals($book->getTitle(), 'Harry Potter and the Order of the Phoenix', 'Related object is correctly hydrated'); + $reviews = $book->getReviews(); + $this->assertEquals($count, $con->getQueryCount(), 'with() hydrates the related objects to save a query '); + $this->assertEquals(2, count($reviews), 'Related objects are correctly hydrated'); + } + + public function testFindOneWithColumn() + { + BookstoreDataPopulator::populate(); + BookPeer::clearInstancePool(); + AuthorPeer::clearInstancePool(); + ReviewPeer::clearInstancePool(); + $c = new ModelCriteria('bookstore', 'Book'); + $c->filterByTitle('The Tin Drum'); + $c->join('Book.Author'); + $c->withColumn('Author.FirstName', 'AuthorName'); + $c->withColumn('Author.LastName', 'AuthorName2'); + $con = Propel::getConnection(BookPeer::DATABASE_NAME); + $book = $c->findOne($con); + $this->assertTrue($book instanceof Book, 'withColumn() do not change the resulting model class'); + $this->assertEquals('The Tin Drum', $book->getTitle()); + $this->assertEquals('Gunter', $book->getVirtualColumn('AuthorName'), 'PropelObjectFormatter adds withColumns as virtual columns'); + $this->assertEquals('Grass', $book->getVirtualColumn('AuthorName2'), 'PropelObjectFormatter correctly hydrates all virtual columns'); + $this->assertEquals('Gunter', $book->getAuthorName(), 'PropelObjectFormatter adds withColumns as virtual columns'); + } + + public function testFindOneWithClassAndColumn() + { + BookstoreDataPopulator::populate(); + BookPeer::clearInstancePool(); + AuthorPeer::clearInstancePool(); + ReviewPeer::clearInstancePool(); + $c = new ModelCriteria('bookstore', 'Book'); + $c->filterByTitle('The Tin Drum'); + $c->join('Book.Author'); + $c->withColumn('Author.FirstName', 'AuthorName'); + $c->withColumn('Author.LastName', 'AuthorName2'); + $c->with('Author'); + $con = Propel::getConnection(BookPeer::DATABASE_NAME); + $book = $c->findOne($con); + $this->assertTrue($book instanceof Book, 'withColumn() do not change the resulting model class'); + $this->assertEquals('The Tin Drum', $book->getTitle()); + $this->assertTrue($book->getAuthor() instanceof Author, 'PropelObjectFormatter correctly hydrates with class'); + $this->assertEquals('Gunter', $book->getAuthor()->getFirstName(), 'PropelObjectFormatter correctly hydrates with class'); + $this->assertEquals('Gunter', $book->getVirtualColumn('AuthorName'), 'PropelObjectFormatter adds withColumns as virtual columns'); + $this->assertEquals('Grass', $book->getVirtualColumn('AuthorName2'), 'PropelObjectFormatter correctly hydrates all virtual columns'); + } + + public function testFindPkWithOneToMany() + { + BookstoreDataPopulator::populate(); + BookPeer::clearInstancePool(); + AuthorPeer::clearInstancePool(); + ReviewPeer::clearInstancePool(); + $con = Propel::getConnection(BookPeer::DATABASE_NAME); + $book = BookQuery::create() + ->findOneByTitle('Harry Potter and the Order of the Phoenix', $con); + $pk = $book->getPrimaryKey(); + BookPeer::clearInstancePool(); + $book = BookQuery::create() + ->joinWith('Review') + ->findPk($pk, $con); + $count = $con->getQueryCount(); + $reviews = $book->getReviews(); + $this->assertEquals($count, $con->getQueryCount(), 'with() hydrates the related objects to save a query '); + $this->assertEquals(2, count($reviews), 'Related objects are correctly hydrated'); + } +} diff --git a/library/propel/test/testsuite/runtime/formatter/PropelOnDemandFormatterTest.php b/library/propel/test/testsuite/runtime/formatter/PropelOnDemandFormatterTest.php new file mode 100644 index 000000000..a4c29bb38 --- /dev/null +++ b/library/propel/test/testsuite/runtime/formatter/PropelOnDemandFormatterTest.php @@ -0,0 +1,151 @@ +query('SELECT * FROM book'); + $formatter = new PropelOnDemandFormatter(); + try { + $books = $formatter->format($stmt); + $this->fail('PropelOnDemandFormatter::format() trows an exception when called with no valid criteria'); + } catch (PropelException $e) { + $this->assertTrue(true,'PropelOnDemandFormatter::format() trows an exception when called with no valid criteria'); + } + } + + public function testFormatManyResults() + { + $con = Propel::getConnection(BookPeer::DATABASE_NAME); + BookstoreDataPopulator::populate($con); + + $stmt = $con->query('SELECT * FROM book'); + $formatter = new PropelOnDemandFormatter(); + $formatter->init(new ModelCriteria('bookstore', 'Book')); + $books = $formatter->format($stmt); + + $this->assertTrue($books instanceof PropelOnDemandCollection, 'PropelOnDemandFormatter::format() returns a PropelOnDemandCollection'); + $this->assertEquals(4, count($books), 'PropelOnDemandFormatter::format() returns a collection that counts as many rows as the results in the query'); + foreach ($books as $book) { + $this->assertTrue($book instanceof Book, 'PropelOnDemandFormatter::format() returns an traversable collection of Model objects'); + } + } + + /** + * @expectedException PropelException + */ + public function testFormatManyResultsIteratedTwice() + { + $con = Propel::getConnection(BookPeer::DATABASE_NAME); + BookstoreDataPopulator::populate($con); + + $stmt = $con->query('SELECT * FROM book'); + $formatter = new PropelOnDemandFormatter(); + $formatter->init(new ModelCriteria('bookstore', 'Book')); + $books = $formatter->format($stmt); + + foreach ($books as $book) { + // do nothing + } + foreach ($books as $book) { + // this should throw a PropelException since we're iterating a second time over a stream + } + } + + public function testFormatALotOfResults() + { + $nbBooks = 50; + $con = Propel::getConnection(BookPeer::DATABASE_NAME); + Propel::disableInstancePooling(); + $book = new Book(); + for ($i=0; $i < $nbBooks; $i++) { + $book->clear(); + $book->setTitle('BookTest' . $i); + $book->save($con); + } + + $stmt = $con->query('SELECT * FROM book'); + $formatter = new PropelOnDemandFormatter(); + $formatter->init(new ModelCriteria('bookstore', 'Book')); + $books = $formatter->format($stmt); + + $this->assertTrue($books instanceof PropelOnDemandCollection, 'PropelOnDemandFormatter::format() returns a PropelOnDemandCollection'); + $this->assertEquals($nbBooks, count($books), 'PropelOnDemandFormatter::format() returns a collection that counts as many rows as the results in the query'); + $i = 0; + foreach ($books as $book) { + $this->assertTrue($book instanceof Book, 'PropelOnDemandFormatter::format() returns a collection of Model objects'); + $this->assertEquals('BookTest' . $i, $book->getTitle(), 'PropelOnDemandFormatter::format() returns the model objects matching the query'); + $i++; + } + Propel::enableInstancePooling(); + } + + + public function testFormatOneResult() + { + $con = Propel::getConnection(BookPeer::DATABASE_NAME); + BookstoreDataPopulator::populate($con); + + $stmt = $con->query('SELECT * FROM book WHERE book.TITLE = "Quicksilver"'); + $formatter = new PropelOnDemandFormatter(); + $formatter->init(new ModelCriteria('bookstore', 'Book')); + $books = $formatter->format($stmt); + + $this->assertTrue($books instanceof PropelOnDemandCollection, 'PropelOnDemandFormatter::format() returns a PropelOnDemandCollection'); + $this->assertEquals(1, count($books), 'PropelOnDemandFormatter::format() returns a collection that counts as many rows as the results in the query'); + foreach ($books as $book) { + $this->assertTrue($book instanceof Book, 'PropelOnDemandFormatter::format() returns a collection of Model objects'); + $this->assertEquals('Quicksilver', $book->getTitle(), 'PropelOnDemandFormatter::format() returns the model objects matching the query'); + } + } + + public function testFormatNoResult() + { + $con = Propel::getConnection(BookPeer::DATABASE_NAME); + + $stmt = $con->query('SELECT * FROM book WHERE book.TITLE = "foo"'); + $formatter = new PropelOnDemandFormatter(); + $formatter->init(new ModelCriteria('bookstore', 'Book')); + $books = $formatter->format($stmt); + + $this->assertTrue($books instanceof PropelOnDemandCollection, 'PropelOnDemandFormatter::format() returns a PropelCollection'); + $this->assertEquals(0, count($books), 'PropelOnDemandFormatter::format() returns an empty collection when no record match the query'); + foreach ($books as $book) { + $this->fail('PropelOnDemandFormatter returns an empty iterator when no record match the query'); + } + } + + public function testFormatOneManyResults() + { + $con = Propel::getConnection(BookPeer::DATABASE_NAME); + BookstoreDataPopulator::populate($con); + + $stmt = $con->query('SELECT * FROM book'); + $formatter = new PropelOnDemandFormatter(); + $formatter->init(new ModelCriteria('bookstore', 'Book')); + $book = $formatter->formatOne($stmt); + + $this->assertTrue($book instanceof Book, 'PropelOnDemandFormatter::formatOne() returns a model object'); + } + +} diff --git a/library/propel/test/testsuite/runtime/formatter/PropelOnDemandFormatterWithTest.php b/library/propel/test/testsuite/runtime/formatter/PropelOnDemandFormatterWithTest.php new file mode 100755 index 000000000..264d7b25e --- /dev/null +++ b/library/propel/test/testsuite/runtime/formatter/PropelOnDemandFormatterWithTest.php @@ -0,0 +1,277 @@ +limit(1); + $books = $c->find($con); + foreach ($books as $book) { + break; + } + $count = $con->getQueryCount(); + $this->assertEquals($book->getTitle(), 'Don Juan', 'Main object is correctly hydrated ' . $msg); + $author = $book->getAuthor(); + $this->assertEquals($count, $con->getQueryCount(), 'with() hydrates the related objects to save a query ' . $msg); + $this->assertEquals($author->getLastName(), 'Byron', 'Related object is correctly hydrated ' . $msg); + $publisher = $book->getPublisher(); + $this->assertEquals($count, $con->getQueryCount(), 'with() hydrates the related objects to save a query ' . $msg); + $this->assertEquals($publisher->getName(), 'Penguin', 'Related object is correctly hydrated ' . $msg); + } + + public function testFindOneWith() + { + BookstoreDataPopulator::populate(); + BookPeer::clearInstancePool(); + AuthorPeer::clearInstancePool(); + $c = new ModelCriteria('bookstore', 'Book'); + $c->setFormatter(ModelCriteria::FORMAT_ON_DEMAND); + $c->orderBy('Book.Title'); + $c->join('Book.Author'); + $c->with('Author'); + $c->join('Book.Publisher'); + $c->with('Publisher'); + $this->assertCorrectHydration1($c, 'without instance pool'); + } + + public function testFindOneWithAlias() + { + BookstoreDataPopulator::populate(); + BookPeer::clearInstancePool(); + AuthorPeer::clearInstancePool(); + $c = new ModelCriteria('bookstore', 'Book'); + $c->setFormatter(ModelCriteria::FORMAT_ON_DEMAND); + $c->orderBy('Book.Title'); + $c->join('Book.Author a'); + $c->with('a'); + $c->join('Book.Publisher p'); + $c->with('p'); + $this->assertCorrectHydration1($c, 'with alias'); + } + + public function testFindOneWithMainAlias() + { + BookstoreDataPopulator::populate(); + BookPeer::clearInstancePool(); + AuthorPeer::clearInstancePool(); + $c = new ModelCriteria('bookstore', 'Book'); + $c->setFormatter(ModelCriteria::FORMAT_ON_DEMAND); + $c->setModelAlias('b', true); + $c->orderBy('b.Title'); + $c->join('b.Author a'); + $c->with('a'); + $c->join('b.Publisher p'); + $c->with('p'); + $this->assertCorrectHydration1($c, 'with main alias'); + } + + public function testFindOneWithUsingInstancePool() + { + BookstoreDataPopulator::populate(); + // instance pool contains all objects by default, since they were just populated + $c = new ModelCriteria('bookstore', 'Book'); + $c->setFormatter(ModelCriteria::FORMAT_ON_DEMAND); + $c->orderBy('Book.Title'); + $c->join('Book.Author'); + $c->with('Author'); + $c->join('Book.Publisher'); + $c->with('Publisher'); + $this->assertCorrectHydration1($c, 'with instance pool'); + } + + public function testFindOneWithEmptyLeftJoin() + { + // save a book with no author + $b = new Book(); + $b->setTitle('Foo'); + $b->save(); + $c = new ModelCriteria('bookstore', 'Book'); + $c->setFormatter(ModelCriteria::FORMAT_ON_DEMAND); + $c->where('Book.Title = ?', 'Foo'); + $c->leftJoin('Book.Author'); + $c->with('Author'); + $c->limit(1); + $con = Propel::getConnection(BookPeer::DATABASE_NAME); + $books = $c->find($con); + foreach ($books as $book) { + break; + } + $count = $con->getQueryCount(); + $author = $book->getAuthor(); + $this->assertNull($author, 'Related object is not hydrated if empty'); + } + + public function testFindOneWithRelationName() + { + BookstoreDataPopulator::populate(); + BookstoreEmployeePeer::clearInstancePool(); + $c = new ModelCriteria('bookstore', 'BookstoreEmployee'); + $c->join('BookstoreEmployee.Supervisor s'); + $c->with('s'); + $c->where('s.Name = ?', 'John'); + $c->limit(1); + $con = Propel::getConnection(BookPeer::DATABASE_NAME); + $emps = $c->find($con); + foreach ($emps as $emp) { + break; + } + $count = $con->getQueryCount(); + $this->assertEquals($emp->getName(), 'Pieter', 'Main object is correctly hydrated'); + $sup = $emp->getSupervisor(); + $this->assertEquals($count, $con->getQueryCount(), 'with() hydrates the related objects to save a query'); + $this->assertEquals($sup->getName(), 'John', 'Related object is correctly hydrated'); + } + + public function testFindOneWithDuplicateRelation() + { + EssayPeer::doDeleteAll(); + $auth1 = new Author(); + $auth1->setFirstName('John'); + $auth1->save(); + $auth2 = new Author(); + $auth2->setFirstName('Jack'); + $auth2->save(); + $essay = new Essay(); + $essay->setTitle('Foo'); + $essay->setFirstAuthor($auth1->getId()); + $essay->setSecondAuthor($auth2->getId()); + $essay->save(); + AuthorPeer::clearInstancePool(); + EssayPeer::clearInstancePool(); + + $c = new ModelCriteria('bookstore', 'Essay'); + $c->setFormatter(ModelCriteria::FORMAT_ON_DEMAND); + $c->join('Essay.AuthorRelatedByFirstAuthor'); + $c->with('AuthorRelatedByFirstAuthor'); + $c->where('Essay.Title = ?', 'Foo'); + $c->limit(1); + $con = Propel::getConnection(BookPeer::DATABASE_NAME); + $essays = $c->find($con); + foreach ($essays as $essay) { + break; + } + $count = $con->getQueryCount(); + $this->assertEquals($essay->getTitle(), 'Foo', 'Main object is correctly hydrated'); + $firstAuthor = $essay->getAuthorRelatedByFirstAuthor(); + $this->assertEquals($count, $con->getQueryCount(), 'with() hydrates the related objects to save a query'); + $this->assertEquals($firstAuthor->getFirstName(), 'John', 'Related object is correctly hydrated'); + $secondAuthor = $essay->getAuthorRelatedBySecondAuthor(); + $this->assertEquals($count + 1, $con->getQueryCount(), 'with() does not hydrate objects not in with'); + } + + public function testFindOneWithDistantClass() + { + BookstoreDataPopulator::populate(); + BookPeer::clearInstancePool(); + AuthorPeer::clearInstancePool(); + ReviewPeer::clearInstancePool(); + $c = new ModelCriteria('bookstore', 'Review'); + $c->setFormatter(ModelCriteria::FORMAT_ON_DEMAND); + $c->where('Review.Recommended = ?', true); + $c->join('Review.Book'); + $c->with('Book'); + $c->join('Book.Author'); + $c->with('Author'); + $c->limit(1); + $con = Propel::getConnection(BookPeer::DATABASE_NAME); + $reviews = $c->find($con); + foreach ($reviews as $review) { + break; + } + $count = $con->getQueryCount(); + $this->assertEquals($review->getReviewedBy(), 'Washington Post', 'Main object is correctly hydrated'); + $book = $review->getBook(); + $this->assertEquals($count, $con->getQueryCount(), 'with() hydrates the related objects to save a query'); + $this->assertEquals('Harry Potter and the Order of the Phoenix', $book->getTitle(), 'Related object is correctly hydrated'); + $author = $book->getAuthor(); + $this->assertEquals($count, $con->getQueryCount(), 'with() hydrates the related objects to save a query'); + $this->assertEquals('J.K.', $author->getFirstName(), 'Related object is correctly hydrated'); + } + + /** + * @expectedException PropelException + */ + public function testFindOneWithOneToMany() + { + BookstoreDataPopulator::populate(); + BookPeer::clearInstancePool(); + AuthorPeer::clearInstancePool(); + ReviewPeer::clearInstancePool(); + $c = new ModelCriteria('bookstore', 'Book'); + $c->setFormatter(ModelCriteria::FORMAT_ON_DEMAND); + $c->add(BookPeer::ISBN, '043935806X'); + $c->leftJoin('Book.Review'); + $c->with('Review'); + $books = $c->find(); + } + + public function testFindOneWithColumn() + { + BookstoreDataPopulator::populate(); + BookPeer::clearInstancePool(); + AuthorPeer::clearInstancePool(); + ReviewPeer::clearInstancePool(); + $c = new ModelCriteria('bookstore', 'Book'); + $c->setFormatter(ModelCriteria::FORMAT_ON_DEMAND); + $c->filterByTitle('The Tin Drum'); + $c->join('Book.Author'); + $c->withColumn('Author.FirstName', 'AuthorName'); + $c->withColumn('Author.LastName', 'AuthorName2'); + $c->limit(1); + $con = Propel::getConnection(BookPeer::DATABASE_NAME); + $books = $c->find($con); + foreach ($books as $book) { + break; + } + $this->assertTrue($book instanceof Book, 'withColumn() do not change the resulting model class'); + $this->assertEquals('The Tin Drum', $book->getTitle()); + $this->assertEquals('Gunter', $book->getVirtualColumn('AuthorName'), 'PropelObjectFormatter adds withColumns as virtual columns'); + $this->assertEquals('Grass', $book->getVirtualColumn('AuthorName2'), 'PropelObjectFormatter correctly hydrates all virtual columns'); + $this->assertEquals('Gunter', $book->getAuthorName(), 'PropelObjectFormatter adds withColumns as virtual columns'); + } + + public function testFindOneWithClassAndColumn() + { + BookstoreDataPopulator::populate(); + BookPeer::clearInstancePool(); + AuthorPeer::clearInstancePool(); + ReviewPeer::clearInstancePool(); + $c = new ModelCriteria('bookstore', 'Book'); + $c->setFormatter(ModelCriteria::FORMAT_ON_DEMAND); + $c->filterByTitle('The Tin Drum'); + $c->join('Book.Author'); + $c->withColumn('Author.FirstName', 'AuthorName'); + $c->withColumn('Author.LastName', 'AuthorName2'); + $c->with('Author'); + $c->limit(1); + $con = Propel::getConnection(BookPeer::DATABASE_NAME); + $books = $c->find($con); + foreach ($books as $book) { + break; + } + $this->assertTrue($book instanceof Book, 'withColumn() do not change the resulting model class'); + $this->assertEquals('The Tin Drum', $book->getTitle()); + $this->assertTrue($book->getAuthor() instanceof Author, 'PropelObjectFormatter correctly hydrates with class'); + $this->assertEquals('Gunter', $book->getAuthor()->getFirstName(), 'PropelObjectFormatter correctly hydrates with class'); + $this->assertEquals('Gunter', $book->getVirtualColumn('AuthorName'), 'PropelObjectFormatter adds withColumns as virtual columns'); + $this->assertEquals('Grass', $book->getVirtualColumn('AuthorName2'), 'PropelObjectFormatter correctly hydrates all virtual columns'); + } +} diff --git a/library/propel/test/testsuite/runtime/formatter/PropelStatementFormatterTest.php b/library/propel/test/testsuite/runtime/formatter/PropelStatementFormatterTest.php new file mode 100644 index 000000000..c2ea8077e --- /dev/null +++ b/library/propel/test/testsuite/runtime/formatter/PropelStatementFormatterTest.php @@ -0,0 +1,124 @@ +query('SELECT * FROM book'); + $formatter = new PropelStatementFormatter(); + try { + $books = $formatter->format($stmt); + $this->assertTrue(true, 'PropelStatementFormatter::format() does not trow an exception when called with no valid criteria'); + } catch (PropelException $e) { + $this->fail('PropelStatementFormatter::format() does not trow an exception when called with no valid criteria'); + } + } + + public function testFormatManyResults() + { + $con = Propel::getConnection(BookPeer::DATABASE_NAME); + + $stmt = $con->query('SELECT * FROM book'); + $formatter = new PropelStatementFormatter(); + $formatter->init(new ModelCriteria('bookstore', 'Book')); + $books = $formatter->format($stmt); + + $this->assertTrue($books instanceof PDOStatement, 'PropelStatementFormatter::format() returns a PDOStatement'); + $this->assertEquals(4, $books->rowCount(), 'PropelStatementFormatter::format() returns as many rows as the results in the query'); + while ($book = $books->fetch()) { + $this->assertTrue(is_array($book), 'PropelStatementFormatter::format() returns a statement that can be fetched'); + } + } + + public function testFormatOneResult() + { + $con = Propel::getConnection(BookPeer::DATABASE_NAME); + + $stmt = $con->query('SELECT * FROM book WHERE book.TITLE = "Quicksilver"'); + $formatter = new PropelStatementFormatter(); + $formatter->init(new ModelCriteria('bookstore', 'Book')); + $books = $formatter->format($stmt); + + $this->assertTrue($books instanceof PDOStatement, 'PropelStatementFormatter::format() returns a PDOStatement'); + $this->assertEquals(1, $books->rowCount(), 'PropelStatementFormatter::format() returns as many rows as the results in the query'); + $book = $books->fetch(PDO::FETCH_ASSOC); + $this->assertEquals('Quicksilver', $book['title'], 'PropelStatementFormatter::format() returns the rows matching the query'); + } + + public function testFormatNoResult() + { + $con = Propel::getConnection(BookPeer::DATABASE_NAME); + + $stmt = $con->query('SELECT * FROM book WHERE book.TITLE = "foo"'); + $formatter = new PropelStatementFormatter(); + $formatter->init(new ModelCriteria('bookstore', 'Book')); + $books = $formatter->format($stmt); + + $this->assertTrue($books instanceof PDOStatement, 'PropelStatementFormatter::format() returns a PDOStatement'); + $this->assertEquals(0, $books->rowCount(), 'PropelStatementFormatter::format() returns as many rows as the results in the query'); + } + + public function testFormatoneNoCriteria() + { + $con = Propel::getConnection(BookPeer::DATABASE_NAME); + + $stmt = $con->query('SELECT * FROM book'); + $formatter = new PropelStatementFormatter(); + try { + $books = $formatter->formatOne($stmt); + $this->assertTrue(true, 'PropelStatementFormatter::formatOne() does not trow an exception when called with no valid criteria'); + } catch (PropelException $e) { + $this->fail('PropelStatementFormatter::formatOne() does not trow an exception when called with no valid criteria'); + } + } + + public function testFormatOneManyResults() + { + $con = Propel::getConnection(BookPeer::DATABASE_NAME); + + $stmt = $con->query('SELECT * FROM book'); + $formatter = new PropelStatementFormatter(); + $formatter->init(new ModelCriteria('bookstore', 'Book')); + $book = $formatter->formatOne($stmt); + + $this->assertTrue($book instanceof PDOStatement, 'PropelStatementFormatter::formatOne() returns a PDO Statement'); + } + + public function testFormatOneNoResult() + { + $con = Propel::getConnection(BookPeer::DATABASE_NAME); + + $stmt = $con->query('SELECT * FROM book WHERE book.TITLE = "foo"'); + $formatter = new PropelStatementFormatter(); + $formatter->init(new ModelCriteria('bookstore', 'Book')); + $book = $formatter->formatOne($stmt); + + $this->assertNull($book, 'PropelStatementFormatter::formatOne() returns null when no result'); + } + +} diff --git a/library/propel/test/testsuite/runtime/map/ColumnMapTest.php b/library/propel/test/testsuite/runtime/map/ColumnMapTest.php new file mode 100644 index 000000000..f3225848f --- /dev/null +++ b/library/propel/test/testsuite/runtime/map/ColumnMapTest.php @@ -0,0 +1,141 @@ +dmap = new DatabaseMap('foodb'); + $this->tmap = new TableMap('foo', $this->dmap); + $this->columnName = 'bar'; + $this->cmap = new ColumnMap($this->columnName, $this->tmap); + } + + protected function tearDown() + { + // nothing to do for now + parent::tearDown(); + } + + public function testConstructor() + { + $this->assertEquals($this->columnName, $this->cmap->getName(), 'constructor sets the column name'); + $this->assertEquals($this->tmap, $this->cmap->getTable(), 'Constructor sets the table map'); + $this->assertNull($this->cmap->getType(), 'A new column map has no type'); + } + + public function testPhpName() + { + $this->assertNull($this->cmap->getPhpName(), 'phpName is empty until set'); + $this->cmap->setPhpName('FooBar'); + $this->assertEquals('FooBar', $this->cmap->getPhpName(), 'phpName is set by setPhpName()'); + } + + public function testType() + { + $this->assertNull($this->cmap->getType(), 'type is empty until set'); + $this->cmap->setType('FooBar'); + $this->assertEquals('FooBar', $this->cmap->getType(), 'type is set by setType()'); + } + + public function tesSize() + { + $this->assertEquals(0, $this->cmap->getSize(), 'size is empty until set'); + $this->cmap->setSize(123); + $this->assertEquals(123, $this->cmap->getSize(), 'size is set by setSize()'); + } + + public function testPrimaryKey() + { + $this->assertFalse($this->cmap->isPrimaryKey(), 'primaryKey is false by default'); + $this->cmap->setPrimaryKey(true); + $this->assertTrue($this->cmap->isPrimaryKey(), 'primaryKey is set by setPrimaryKey()'); + } + + public function testNotNull() + { + $this->assertFalse($this->cmap->isNotNull(), 'notNull is false by default'); + $this->cmap->setNotNull(true); + $this->assertTrue($this->cmap->isNotNull(), 'notNull is set by setPrimaryKey()'); + } + + public function testDefaultValue() + { + $this->assertNull($this->cmap->getDefaultValue(), 'defaultValue is empty until set'); + $this->cmap->setDefaultValue('FooBar'); + $this->assertEquals('FooBar', $this->cmap->getDefaultValue(), 'defaultValue is set by setDefaultValue()'); + } + + public function testGetForeignKey() + { + $this->assertFalse($this->cmap->isForeignKey(), 'foreignKey is false by default'); + try + { + $this->cmap->getRelatedTable(); + $this->fail('getRelatedTable throws an exception when called on a column with no foreign key'); + } catch(PropelException $e) { + $this->assertTrue(true, 'getRelatedTable throws an exception when called on a column with no foreign key'); + } + try + { + $this->cmap->getRelatedColumn(); + $this->fail('getRelatedColumn throws an exception when called on a column with no foreign key'); + } catch(PropelException $e) { + $this->assertTrue(true, 'getRelatedColumn throws an exception when called on a column with no foreign key'); + } + $relatedTmap = $this->dmap->addTable('foo2'); + // required to let the database map use the foreign TableMap + $relatedCmap = $relatedTmap->addColumn('BAR2', 'Bar2', 'INTEGER'); + $this->cmap->setForeignKey('foo2', 'BAR2'); + $this->assertTrue($this->cmap->isForeignKey(), 'foreignKey is true after setting the foreign key via setForeignKey()'); + $this->assertEquals($relatedTmap, $this->cmap->getRelatedTable(), 'getRelatedTable returns the related TableMap object'); + $this->assertEquals($relatedCmap, $this->cmap->getRelatedColumn(), 'getRelatedColumn returns the related ColumnMap object'); + } + + public function testGetRelation() + { + set_include_path(get_include_path() . PATH_SEPARATOR . "fixtures/bookstore/build/classes"); + Propel::init('fixtures/bookstore/build/conf/bookstore-conf.php'); + $bookTable = BookPeer::getTableMap(); + $titleColumn = $bookTable->getColumn('TITLE'); + $this->assertNull($titleColumn->getRelation(), 'getRelation() returns null for non-foreign key columns'); + $publisherColumn = $bookTable->getColumn('PUBLISHER_ID'); + $this->assertEquals($publisherColumn->getRelation(), $bookTable->getRelation('Publisher'), 'getRelation() returns the RelationMap object for this foreign key'); + $bookstoreTable = BookstoreEmployeePeer::getTableMap(); + $supervisorColumn = $bookstoreTable->getColumn('SUPERVISOR_ID'); + $this->assertEquals($supervisorColumn->getRelation(), $supervisorColumn->getRelation('Supervisor'), 'getRelation() returns the RelationMap object even whit ha specific refPhpName'); + + } + + public function testNormalizeName() + { + $this->assertEquals('', ColumnMap::normalizeName(''), 'normalizeColumnName() returns an empty string when passed an empty string'); + $this->assertEquals('BAR', ColumnMap::normalizeName('bar'), 'normalizeColumnName() uppercases the input'); + $this->assertEquals('BAR_BAZ', ColumnMap::normalizeName('bar_baz'), 'normalizeColumnName() does not mind underscores'); + $this->assertEquals('BAR', ColumnMap::normalizeName('FOO.BAR'), 'normalizeColumnName() removes table prefix'); + $this->assertEquals('BAR', ColumnMap::normalizeName('BAR'), 'normalizeColumnName() leaves normalized column names unchanged'); + $this->assertEquals('BAR_BAZ', ColumnMap::normalizeName('foo.bar_baz'), 'normalizeColumnName() can do all the above at the same time'); + } + +} diff --git a/library/propel/test/testsuite/runtime/map/DatabaseMapTest.php b/library/propel/test/testsuite/runtime/map/DatabaseMapTest.php new file mode 100644 index 000000000..7a078131d --- /dev/null +++ b/library/propel/test/testsuite/runtime/map/DatabaseMapTest.php @@ -0,0 +1,171 @@ +setName('baz'); + $this->setPhpName('Baz'); + } +} + +/** + * Test class for DatabaseMap. + * + * @author François Zaninotto + * @version $Id: DatabaseMapTest.php 1773 2010-05-25 10:25:06Z francois $ + * @package runtime.map + */ +class DatabaseMapTest extends PHPUnit_Framework_TestCase +{ + protected $databaseMap; + + protected function setUp() + { + parent::setUp(); + $this->databaseName = 'foodb'; + $this->databaseMap = TestDatabaseBuilder::getDmap(); + } + + protected function tearDown() + { + // nothing to do for now + parent::tearDown(); + } + + public function testConstructor() + { + $this->assertEquals($this->databaseName, $this->databaseMap->getName(), 'constructor sets the table name'); + } + + public function testAddTable() + { + $this->assertFalse($this->databaseMap->hasTable('foo'), 'tables are empty by default'); + try + { + $this->databaseMap->getTable('foo'); + $this->fail('getTable() throws an exception when called on an inexistent table'); + } catch(PropelException $e) { + $this->assertTrue(true, 'getTable() throws an exception when called on an inexistent table'); + } + $tmap = $this->databaseMap->addTable('foo'); + $this->assertTrue($this->databaseMap->hasTable('foo'), 'hasTable() returns true when the table was added by way of addTable()'); + $this->assertEquals($tmap, $this->databaseMap->getTable('foo'), 'getTable() returns a table by name when the table was added by way of addTable()'); + } + + public function testAddTableObject() + { + $this->assertFalse($this->databaseMap->hasTable('foo2'), 'tables are empty by default'); + try + { + $this->databaseMap->getTable('foo2'); + $this->fail('getTable() throws an exception when called on a table with no builder'); + } catch(PropelException $e) { + $this->assertTrue(true, 'getTable() throws an exception when called on a table with no builder'); + } + $tmap = new TableMap('foo2'); + $this->databaseMap->addTableObject($tmap); + $this->assertTrue($this->databaseMap->hasTable('foo2'), 'hasTable() returns true when the table was added by way of addTableObject()'); + $this->assertEquals($tmap, $this->databaseMap->getTable('foo2'), 'getTable() returns a table by name when the table was added by way of addTableObject()'); + } + + public function testAddTableFromMapClass() + { + $table1 = $this->databaseMap->addTableFromMapClass('BazTableMap'); + try + { + $table2 = $this->databaseMap->getTable('baz'); + $this->assertEquals($table1, $table2, 'addTableFromMapClass() adds a table from a map class'); + } catch(PropelException $e) { + $this->fail('addTableFromMapClass() adds a table from a map class'); + } + } + + public function testGetColumn() + { + try + { + $this->databaseMap->getColumn('foo.BAR'); + $this->fail('getColumn() throws an exception when called on column of an inexistent table'); + } catch(PropelException $e) { + $this->assertTrue(true, 'getColumn() throws an exception when called on column of an inexistent table'); + } + $tmap = $this->databaseMap->addTable('foo'); + try + { + $this->databaseMap->getColumn('foo.BAR'); + $this->fail('getColumn() throws an exception when called on an inexistent column of an existent table'); + } catch(PropelException $e) { + $this->assertTrue(true, 'getColumn() throws an exception when called on an inexistent column of an existent table'); + } + $column = $tmap->addColumn('BAR', 'Bar', 'INTEGER'); + $this->assertEquals($column, $this->databaseMap->getColumn('foo.BAR'), 'getColumn() returns a ColumnMap object based on a fully qualified name'); + } + + public function testGetTableByPhpName() + { + try + { + $this->databaseMap->getTableByPhpName('Foo1'); + $this->fail('getTableByPhpName() throws an exception when called on an inexistent table'); + } catch(PropelException $e) { + $this->assertTrue(true, 'getTableByPhpName() throws an exception when called on an inexistent table'); + } + $tmap = $this->databaseMap->addTable('foo1'); + try + { + $this->databaseMap->getTableByPhpName('Foo1'); + $this->fail('getTableByPhpName() throws an exception when called on a table with no phpName'); + } catch(PropelException $e) { + $this->assertTrue(true, 'getTableByPhpName() throws an exception when called on a table with no phpName'); + } + $tmap2 = new TableMap('foo2'); + $tmap2->setClassname('Foo2'); + $this->databaseMap->addTableObject($tmap2); + $this->assertEquals($tmap2, $this->databaseMap->getTableByPhpName('Foo2'), 'getTableByPhpName() returns tableMap when phpName was set by way of TableMap::setPhpName()'); + } + + public function testGetTableByPhpNameNotLoaded() + { + set_include_path(get_include_path() . PATH_SEPARATOR . "fixtures/bookstore/build/classes"); + require_once 'bookstore/map/BookTableMap.php'; + require_once 'bookstore/om/BaseBookPeer.php'; + require_once 'bookstore/BookPeer.php'; + $this->assertEquals('book', Propel::getDatabaseMap('bookstore')->getTableByPhpName('Book')->getName(), 'getTableByPhpName() can autoload a TableMap when the Peer class is generated and autoloaded'); + } + +} diff --git a/library/propel/test/testsuite/runtime/map/GeneratedRelationMapTest.php b/library/propel/test/testsuite/runtime/map/GeneratedRelationMapTest.php new file mode 100755 index 000000000..62b84a77c --- /dev/null +++ b/library/propel/test/testsuite/runtime/map/GeneratedRelationMapTest.php @@ -0,0 +1,75 @@ +databaseMap = Propel::getDatabaseMap('bookstore'); + } + + public function testGetRightTable() + { + $bookTable = $this->databaseMap->getTableByPhpName('Book'); + $authorTable = $this->databaseMap->getTableByPhpName('Author'); + $this->assertEquals($authorTable, $bookTable->getRelation('Author')->getRightTable(), 'getRightTable() returns correct table when called on a many to one relationship'); + $this->assertEquals($bookTable, $authorTable->getRelation('Book')->getRightTable(), 'getRightTable() returns correct table when called on a one to many relationship'); + $bookEmpTable = $this->databaseMap->getTableByPhpName('BookstoreEmployee'); + $bookEmpAccTable = $this->databaseMap->getTableByPhpName('BookstoreEmployeeAccount'); + $this->assertEquals($bookEmpAccTable, $bookEmpTable->getRelation('BookstoreEmployeeAccount')->getRightTable(), 'getRightTable() returns correct table when called on a one to one relationship'); + $this->assertEquals($bookEmpTable, $bookEmpAccTable->getRelation('BookstoreEmployee')->getRightTable(), 'getRightTable() returns correct table when called on a one to one relationship'); + } + + public function testColumnMappings() + { + $bookTable = $this->databaseMap->getTableByPhpName('Book'); + $this->assertEquals(array('book.AUTHOR_ID' => 'author.ID'), $bookTable->getRelation('Author')->getColumnMappings(), 'getColumnMappings returns local to foreign by default'); + $this->assertEquals(array('book.AUTHOR_ID' => 'author.ID'), $bookTable->getRelation('Author')->getColumnMappings(RelationMap::LEFT_TO_RIGHT), 'getColumnMappings returns local to foreign when asked left to right for a many to one relationship'); + + $authorTable = $this->databaseMap->getTableByPhpName('Author'); + $this->assertEquals(array('book.AUTHOR_ID' => 'author.ID'), $authorTable->getRelation('Book')->getColumnMappings(), 'getColumnMappings returns local to foreign by default'); + $this->assertEquals(array('author.ID' => 'book.AUTHOR_ID'), $authorTable->getRelation('Book')->getColumnMappings(RelationMap::LEFT_TO_RIGHT), 'getColumnMappings returns foreign to local when asked left to right for a one to many relationship'); + + $bookEmpTable = $this->databaseMap->getTableByPhpName('BookstoreEmployee'); + $this->assertEquals(array('bookstore_employee_account.EMPLOYEE_ID' => 'bookstore_employee.ID'), $bookEmpTable->getRelation('BookstoreEmployeeAccount')->getColumnMappings(), 'getColumnMappings returns local to foreign by default'); + $this->assertEquals(array('bookstore_employee.ID' => 'bookstore_employee_account.EMPLOYEE_ID'), $bookEmpTable->getRelation('BookstoreEmployeeAccount')->getColumnMappings(RelationMap::LEFT_TO_RIGHT), 'getColumnMappings returns foreign to local when asked left to right for a one to one relationship'); + } + + public function testCountColumnMappings() + { + $bookTable = $this->databaseMap->getTableByPhpName('Book'); + $this->assertEquals(1, $bookTable->getRelation('Author')->countColumnMappings()); + + $rfTable = $this->databaseMap->getTableByPhpName('ReaderFavorite'); + $this->assertEquals(2, $rfTable->getRelation('BookOpinion')->countColumnMappings()); + } + + public function testIsComposite() + { + $bookTable = $this->databaseMap->getTableByPhpName('Book'); + $this->assertFalse($bookTable->getRelation('Author')->isComposite()); + + $rfTable = $this->databaseMap->getTableByPhpName('ReaderFavorite'); + $this->assertTrue($rfTable->getRelation('BookOpinion')->isComposite()); + } + +} diff --git a/library/propel/test/testsuite/runtime/map/RelatedMapSymmetricalTest.php b/library/propel/test/testsuite/runtime/map/RelatedMapSymmetricalTest.php new file mode 100755 index 000000000..6737aa7df --- /dev/null +++ b/library/propel/test/testsuite/runtime/map/RelatedMapSymmetricalTest.php @@ -0,0 +1,70 @@ +databaseMap = Propel::getDatabaseMap('bookstore'); + } + + public function testOneToMany() + { + $bookTable = $this->databaseMap->getTableByPhpName('Book'); + $bookToAuthor = $bookTable->getRelation('Author'); + $authorTable = $this->databaseMap->getTableByPhpName('Author'); + $authorToBook = $authorTable->getRelation('Book'); + $this->assertEquals($authorToBook, $bookToAuthor->getSymmetricalRelation()); + $this->assertEquals($bookToAuthor, $authorToBook->getSymmetricalRelation()); + } + + public function testOneToOne() + { + $accountTable = $this->databaseMap->getTableByPhpName('BookstoreEmployeeAccount'); + $accountToEmployee = $accountTable->getRelation('BookstoreEmployee'); + $employeeTable = $this->databaseMap->getTableByPhpName('BookstoreEmployee'); + $employeeToAccount = $employeeTable->getRelation('BookstoreEmployeeAccount'); + $this->assertEquals($accountToEmployee, $employeeToAccount->getSymmetricalRelation()); + $this->assertEquals($employeeToAccount, $accountToEmployee->getSymmetricalRelation()); + } + + public function testSeveralRelationsOnSameTable() + { + $authorTable = $this->databaseMap->getTableByPhpName('Author'); + $authorToEssay = $authorTable->getRelation('EssayRelatedByFirstAuthor'); + $essayTable = $this->databaseMap->getTableByPhpName('Essay'); + $essayToAuthor = $essayTable->getRelation('AuthorRelatedByFirstAuthor'); + $this->assertEquals($authorToEssay, $essayToAuthor->getSymmetricalRelation()); + $this->assertEquals($essayToAuthor, $authorToEssay->getSymmetricalRelation()); + } + + public function testCompositeForeignKey() + { + $favoriteTable = $this->databaseMap->getTableByPhpName('ReaderFavorite'); + $favoriteToOpinion = $favoriteTable->getRelation('BookOpinion'); + $opinionTable = $this->databaseMap->getTableByPhpName('BookOpinion'); + $opinionToFavorite = $opinionTable->getRelation('ReaderFavorite'); + $this->assertEquals($favoriteToOpinion, $opinionToFavorite->getSymmetricalRelation()); + $this->assertEquals($opinionToFavorite, $favoriteToOpinion->getSymmetricalRelation()); + } + +} diff --git a/library/propel/test/testsuite/runtime/map/RelationMapTest.php b/library/propel/test/testsuite/runtime/map/RelationMapTest.php new file mode 100644 index 000000000..202755781 --- /dev/null +++ b/library/propel/test/testsuite/runtime/map/RelationMapTest.php @@ -0,0 +1,89 @@ +databaseMap = new DatabaseMap('foodb'); + $this->relationName = 'foo'; + $this->rmap = new RelationMap($this->relationName); + } + + public function testConstructor() + { + $this->assertEquals($this->relationName, $this->rmap->getName(), 'constructor sets the relation name'); + } + + public function testLocalTable() + { + $this->assertNull($this->rmap->getLocalTable(), 'A new relation has no local table'); + $tmap1 = new TableMap('foo', $this->databaseMap); + $this->rmap->setLocalTable($tmap1); + $this->assertEquals($tmap1, $this->rmap->getLocalTable(), 'The local table is set by setLocalTable()'); + } + + public function testForeignTable() + { + $this->assertNull($this->rmap->getForeignTable(), 'A new relation has no foreign table'); + $tmap2 = new TableMap('bar', $this->databaseMap); + $this->rmap->setForeignTable($tmap2); + $this->assertEquals($tmap2, $this->rmap->getForeignTable(), 'The foreign table is set by setForeignTable()'); + } + + public function testProperties() + { + $properties = array('type', 'onUpdate', 'onDelete'); + foreach ($properties as $property) + { + $getter = 'get' . ucfirst($property); + $setter = 'set' . ucfirst($property); + $this->assertNull($this->rmap->$getter(), "A new relation has no $property"); + $this->rmap->$setter('foo_value'); + $this->assertEquals('foo_value', $this->rmap->$getter(), "The $property is set by setType()"); + } + } + + public function testColumns() + { + $this->assertEquals(array(), $this->rmap->getLocalColumns(), 'A new relation has no local columns'); + $this->assertEquals(array(), $this->rmap->getForeignColumns(), 'A new relation has no foreign columns'); + $tmap1 = new TableMap('foo', $this->databaseMap); + $col1 = $tmap1->addColumn('FOO1', 'Foo1PhpName', 'INTEGER'); + $tmap2 = new TableMap('bar', $this->databaseMap); + $col2 = $tmap2->addColumn('BAR1', 'Bar1PhpName', 'INTEGER'); + $this->rmap->addColumnMapping($col1, $col2); + $this->assertEquals(array($col1), $this->rmap->getLocalColumns(), 'addColumnMapping() adds a local table'); + $this->assertEquals(array($col2), $this->rmap->getForeignColumns(), 'addColumnMapping() adds a foreign table'); + $expected = array('foo.FOO1' => 'bar.BAR1'); + $this->assertEquals($expected, $this->rmap->getColumnMappings(), 'getColumnMappings() returns an associative array of column mappings'); + $col3 = $tmap1->addColumn('FOOFOO', 'FooFooPhpName', 'INTEGER'); + $col4 = $tmap2->addColumn('BARBAR', 'BarBarPhpName', 'INTEGER'); + $this->rmap->addColumnMapping($col3, $col4); + $this->assertEquals(array($col1, $col3), $this->rmap->getLocalColumns(), 'addColumnMapping() adds a local table'); + $this->assertEquals(array($col2, $col4), $this->rmap->getForeignColumns(), 'addColumnMapping() adds a foreign table'); + $expected = array('foo.FOO1' => 'bar.BAR1', 'foo.FOOFOO' => 'bar.BARBAR'); + $this->assertEquals($expected, $this->rmap->getColumnMappings(), 'getColumnMappings() returns an associative array of column mappings'); + } +} diff --git a/library/propel/test/testsuite/runtime/map/TableMapTest.php b/library/propel/test/testsuite/runtime/map/TableMapTest.php new file mode 100644 index 000000000..42e7e748b --- /dev/null +++ b/library/propel/test/testsuite/runtime/map/TableMapTest.php @@ -0,0 +1,286 @@ +rmap = $this->addRelation('Bar', 'Bar', RelationMap::MANY_TO_ONE); + } +} + +class BarTableMap extends TableMap +{ + public function initialize() + { + $this->setName('bar'); + $this->setPhpName('Bar'); + } +} + + + +/** + * Test class for TableMap. + * + * @author François Zaninotto + * @version $Id: TableMapTest.php 1773 2010-05-25 10:25:06Z francois $ + * @package runtime.map + */ +class TableMapTest extends PHPUnit_Framework_TestCase +{ + protected $databaseMap; + + protected function setUp() + { + parent::setUp(); + $this->databaseMap = new DatabaseMap('foodb'); + $this->tableName = 'foo'; + $this->tmap = new TableMap($this->tableName, $this->databaseMap); + } + + protected function tearDown() + { + // nothing to do for now + parent::tearDown(); + } + + public function testConstructor() + { + $this->assertEquals(array(), $this->tmap->getColumns(), 'A new table map has no columns'); + $this->assertEquals($this->tableName, $this->tmap->getName(), 'constructor can set the table name'); + $this->assertEquals($this->databaseMap, $this->tmap->getDatabaseMap(), 'Constructor can set the database map'); + try { + $tmap = new TableMap(); + $this->assertTrue(true, 'A table map can be instanciated with no parameters'); + } catch (Exception $e) { + $this->fail('A table map can be instanciated with no parameters'); + } + } + + public function testProperties() + { + $tmap = new TableMap(); + $properties = array('name', 'phpName', 'className', 'package'); + foreach ($properties as $property) + { + $getter = 'get' . ucfirst($property); + $setter = 'set' . ucfirst($property); + $this->assertNull($tmap->$getter(), "A new relation has no $property"); + $tmap->$setter('foo_value'); + $this->assertEquals('foo_value', $tmap->$getter(), "The $property is set by setType()"); + } + } + + public function testHasColumn() + { + $this->assertFalse($this->tmap->hasColumn('BAR'), 'hascolumn() returns false when the column is not in the table map'); + $column = $this->tmap->addColumn('BAR', 'Bar', 'INTEGER'); + $this->assertTrue($this->tmap->hasColumn('BAR'), 'hascolumn() returns true when the column is in the table map'); + $this->assertTrue($this->tmap->hasColumn('foo.bar'), 'hascolumn() accepts a denormalized column name'); + $this->assertFalse($this->tmap->hasColumn('foo.bar', false), 'hascolumn() accepts a $normalize parameter to skip name normalization'); + $this->assertTrue($this->tmap->hasColumn('BAR', false), 'hascolumn() accepts a $normalize parameter to skip name normalization'); + $this->assertTrue($this->tmap->hasColumn($column), 'hascolumn() accepts a ColumnMap object as parameter'); + } + + public function testGetColumn() + { + $column = $this->tmap->addColumn('BAR', 'Bar', 'INTEGER'); + $this->assertEquals($column, $this->tmap->getColumn('BAR'), 'getColumn returns a ColumnMap according to a column name'); + try + { + $this->tmap->getColumn('FOO'); + $this->fail('getColumn throws an exception when called on an inexistent column'); + } catch(PropelException $e) {} + $this->assertEquals($column, $this->tmap->getColumn('foo.bar'), 'getColumn accepts a denormalized column name'); + try + { + $this->tmap->getColumn('foo.bar', false); + $this->fail('getColumn accepts a $normalize parameter to skip name normalization'); + } catch(PropelException $e) {} + } + + public function testGetColumnByPhpName() + { + $column = $this->tmap->addColumn('BAR_BAZ', 'BarBaz', 'INTEGER'); + $this->assertEquals($column, $this->tmap->getColumnByPhpName('BarBaz'), 'getColumnByPhpName() returns a ColumnMap according to a column phpName'); + try + { + $this->tmap->getColumn('Foo'); + $this->fail('getColumnByPhpName() throws an exception when called on an inexistent column'); + } catch(PropelException $e) {} + } + + public function testGetColumns() + { + $this->assertEquals(array(), $this->tmap->getColumns(), 'getColumns returns an empty array when no columns were added'); + $column1 = $this->tmap->addColumn('BAR', 'Bar', 'INTEGER'); + $column2 = $this->tmap->addColumn('BAZ', 'Baz', 'INTEGER'); + $this->assertEquals(array('BAR' => $column1, 'BAZ' => $column2), $this->tmap->getColumns(), 'getColumns returns the columns indexed by name'); + } + + public function testAddPrimaryKey() + { + $column1 = $this->tmap->addPrimaryKey('BAR', 'Bar', 'INTEGER'); + $this->assertTrue($column1->isPrimaryKey(), 'Columns added by way of addPrimaryKey() are primary keys'); + $column2 = $this->tmap->addColumn('BAZ', 'Baz', 'INTEGER'); + $this->assertFalse($column2->isPrimaryKey(), 'Columns added by way of addColumn() are not primary keys by default'); + $column3 = $this->tmap->addColumn('BAZZ', 'Bazz', 'INTEGER', null, null, null, true); + $this->assertTrue($column3->isPrimaryKey(), 'Columns added by way of addColumn() can be defined as primary keys'); + $column4 = $this->tmap->addForeignKey('BAZZZ', 'Bazzz', 'INTEGER', 'Table1', 'column1'); + $this->assertFalse($column4->isPrimaryKey(), 'Columns added by way of addForeignKey() are not primary keys'); + $column5 = $this->tmap->addForeignPrimaryKey('BAZZZZ', 'Bazzzz', 'INTEGER', 'table1', 'column1'); + $this->assertTrue($column5->isPrimaryKey(), 'Columns added by way of addForeignPrimaryKey() are primary keys'); + } + + public function testGetPrimaryKeyColumns() + { + $this->assertEquals(array(), $this->tmap->getPrimaryKeyColumns(), 'getPrimaryKeyColumns() returns an empty array by default'); + $column1 = $this->tmap->addPrimaryKey('BAR', 'Bar', 'INTEGER'); + $column3 = $this->tmap->addColumn('BAZZ', 'Bazz', 'INTEGER', null, null, null, true); + $expected = array($column1, $column3); + $this->assertEquals($expected, $this->tmap->getPrimaryKeyColumns(), 'getPrimaryKeyColumns() returns an array of the table primary keys'); + } + + public function testGetPrimaryKeys() + { + $this->assertEquals(array(), $this->tmap->getPrimaryKeys(), 'getPrimaryKeys() returns an empty array by default'); + $column1 = $this->tmap->addPrimaryKey('BAR', 'Bar', 'INTEGER'); + $column3 = $this->tmap->addColumn('BAZZ', 'Bazz', 'INTEGER', null, null, null, true); + $expected = array('BAR' => $column1, 'BAZZ' => $column3); + $this->assertEquals($expected, $this->tmap->getPrimaryKeys(), 'getPrimaryKeys() returns an array of the table primary keys'); + } + + public function testAddForeignKey() + { + $column1 = $this->tmap->addForeignKey('BAR', 'Bar', 'INTEGER', 'Table1', 'column1'); + $this->assertTrue($column1->isForeignKey(), 'Columns added by way of addForeignKey() are foreign keys'); + $column2 = $this->tmap->addColumn('BAZ', 'Baz', 'INTEGER'); + $this->assertFalse($column2->isForeignKey(), 'Columns added by way of addColumn() are not foreign keys by default'); + $column3 = $this->tmap->addColumn('BAZZ', 'Bazz', 'INTEGER', null, null, null, false, 'Table1', 'column1'); + $this->assertTrue($column3->isForeignKey(), 'Columns added by way of addColumn() can be defined as foreign keys'); + $column4 = $this->tmap->addPrimaryKey('BAZZZ', 'Bazzz', 'INTEGER'); + $this->assertFalse($column4->isForeignKey(), 'Columns added by way of addPrimaryKey() are not foreign keys'); + $column5 = $this->tmap->addForeignPrimaryKey('BAZZZZ', 'Bazzzz', 'INTEGER', 'table1', 'column1'); + $this->assertTrue($column5->isForeignKey(), 'Columns added by way of addForeignPrimaryKey() are foreign keys'); + } + + public function testGetForeignKeys() + { + $this->assertEquals(array(), $this->tmap->getForeignKeys(), 'getForeignKeys() returns an empty array by default'); + $column1 = $this->tmap->addForeignKey('BAR', 'Bar', 'INTEGER', 'Table1', 'column1'); + $column3 = $this->tmap->addColumn('BAZZ', 'Bazz', 'INTEGER', null, null, null, false, 'Table1', 'column1'); + $expected = array('BAR' => $column1, 'BAZZ' => $column3); + $this->assertEquals($expected, $this->tmap->getForeignKeys(), 'getForeignKeys() returns an array of the table foreign keys'); + } + + public function testLazyLoadRelations() + { + try { + $this->tmap->getRelation('Bar'); + $this->fail('getRelation() throws an exception when called on a table with no relations'); + } catch (PropelException $e) { + $this->assertTrue(true, 'getRelation() throws an exception when called on a table with no relations'); + } + $foreigntmap = new BarTableMap(); + $this->databaseMap->addTableObject($foreigntmap); + $localtmap = new FooTableMap(); + $this->databaseMap->addTableObject($localtmap); + $rmap = $localtmap->getRelation('Bar'); + $this->assertEquals($rmap, $localtmap->rmap, 'getRelation() returns the relations lazy loaded by buildRelations()'); + } + + public function testAddRelation() + { + $foreigntmap1 = new TableMap('bar'); + $foreigntmap1->setClassname('Bar'); + $this->databaseMap->addTableObject($foreigntmap1); + $foreigntmap2 = new TableMap('baz'); + $foreigntmap2->setClassname('Baz'); + $this->databaseMap->addTableObject($foreigntmap2); + $this->rmap1 = $this->tmap->addRelation('Bar', 'Bar', RelationMap::MANY_TO_ONE); + $this->rmap2 = $this->tmap->addRelation('Bazz', 'Baz', RelationMap::ONE_TO_MANY); + $this->tmap->getRelations(); + // now on to the test + $this->assertEquals($this->rmap1->getLocalTable(), $this->tmap, 'adding a relation with HAS_ONE sets the local table to the current table'); + $this->assertEquals($this->rmap1->getForeignTable(), $foreigntmap1, 'adding a relation with HAS_ONE sets the foreign table according to the name given'); + $this->assertEquals(RelationMap::MANY_TO_ONE, $this->rmap1->getType(), 'adding a relation with HAS_ONE sets the foreign table type accordingly'); + + $this->assertEquals($this->rmap2->getForeignTable(), $this->tmap, 'adding a relation with HAS_MANY sets the foreign table to the current table'); + $this->assertEquals($this->rmap2->getLocalTable(), $foreigntmap2, 'adding a relation with HAS_MANY sets the local table according to the name given'); + $this->assertEquals(RelationMap::ONE_TO_MANY, $this->rmap2->getType(), 'adding a relation with HAS_MANY sets the foreign table type accordingly'); + + $expectedRelations = array('Bar' => $this->rmap1, 'Bazz' => $this->rmap2); + $this->assertEquals($expectedRelations, $this->tmap->getRelations(), 'getRelations() returns an associative array of all the relations'); + } + + // deprecated method + public function testNormalizeColName() + { + $tmap = new TestableTableMap(); + $this->assertEquals('', $tmap->normalizeColName(''), 'normalizeColName returns an empty string when passed an empty string'); + $this->assertEquals('BAR', $tmap->normalizeColName('bar'), 'normalizeColName uppercases the input'); + $this->assertEquals('BAR_BAZ', $tmap->normalizeColName('bar_baz'), 'normalizeColName does not mind underscores'); + $this->assertEquals('BAR', $tmap->normalizeColName('FOO.BAR'), 'normalizeColName removes table prefix'); + $this->assertEquals('BAR', $tmap->normalizeColName('BAR'), 'normalizeColName leaves normalized column names unchanged'); + $this->assertEquals('BAR_BAZ', $tmap->normalizeColName('foo.bar_baz'), 'normalizeColName can do all the above at the same time'); + } + + // deprecated method + public function testContainsColumn() + { + $this->assertFalse($this->tmap->containsColumn('BAR'), 'containsColumn returns false when the column is not in the table map'); + $column = $this->tmap->addColumn('BAR', 'Bar', 'INTEGER'); + $this->assertTrue($this->tmap->containsColumn('BAR'), 'containsColumn returns true when the column is in the table map'); + $this->assertTrue($this->tmap->containsColumn('foo.bar'), 'containsColumn accepts a denormalized column name'); + $this->assertFalse($this->tmap->containsColumn('foo.bar', false), 'containsColumn accepts a $normalize parameter to skip name normalization'); + $this->assertTrue($this->tmap->containsColumn('BAR', false), 'containsColumn accepts a $normalize parameter to skip name normalization'); + $this->assertTrue($this->tmap->containsColumn($column), 'containsColumn accepts a ColumnMap object as parameter'); + } + + // deprecated methods + public function testPrefix() + { + $tmap = new TestableTableMap(); + $this->assertNull($tmap->getPrefix(), 'prefix is empty until set'); + $this->assertFalse($tmap->hasPrefix('barbaz'), 'hasPrefix returns false when prefix is not set'); + $tmap->setPrefix('bar'); + $this->assertEquals('bar', $tmap->getPrefix(), 'prefix is set by setPrefix()'); + $this->assertTrue($tmap->hasPrefix('barbaz'), 'hasPrefix returns true when prefix is set and found in string'); + $this->assertFalse($tmap->hasPrefix('baz'), 'hasPrefix returns false when prefix is set and not found in string'); + $this->assertFalse($tmap->hasPrefix('bazbar'), 'hasPrefix returns false when prefix is set and not found anywhere in string'); + $this->assertEquals('baz', $tmap->removePrefix('barbaz'), 'removePrefix returns string without prefix if found at the beginning'); + $this->assertEquals('bazbaz', $tmap->removePrefix('bazbaz'), 'removePrefix returns original string when prefix is not found'); + $this->assertEquals('bazbar', $tmap->removePrefix('bazbar'), 'removePrefix returns original string when prefix is not found at the beginning'); + } +} diff --git a/library/propel/test/testsuite/runtime/om/BaseObjectSerializeTest.php b/library/propel/test/testsuite/runtime/om/BaseObjectSerializeTest.php new file mode 100755 index 000000000..16a6fb836 --- /dev/null +++ b/library/propel/test/testsuite/runtime/om/BaseObjectSerializeTest.php @@ -0,0 +1,95 @@ +assertEquals($book, unserialize($sb)); + } + + public function testSerializePopulatedObject() + { + $book = new Book(); + $book->setTitle('Foo1'); + $book->setISBN('1234'); + $sb = serialize($book); + $this->assertEquals($book, unserialize($sb)); + } + + public function testSerializePersistedObject() + { + $book = new Book(); + $book->setTitle('Foo2'); + $book->setISBN('1234'); + $book->save(); + $sb = serialize($book); + $this->assertEquals($book, unserialize($sb)); + } + + public function testSerializeHydratedObject() + { + $book = new Book(); + $book->setTitle('Foo3'); + $book->setISBN('1234'); + $book->save(); + BookPeer::clearInstancePool(); + + $book = BookQuery::create()->findOneByTitle('Foo3'); + $sb = serialize($book); + $this->assertEquals($book, unserialize($sb)); + } + + public function testSerializeObjectWithRelations() + { + $author = new Author(); + $author->setFirstName('John'); + $book = new Book(); + $book->setTitle('Foo4'); + $book->setISBN('1234'); + $book->setAuthor($author); + $book->save(); + $b = clone $book; + $sb = serialize($b); + $book->clearAllReferences(); + $this->assertEquals($book, unserialize($sb)); + } + + public function testSerializeObjectWithCollections() + { + $book1 = new Book(); + $book1->setTitle('Foo5'); + $book1->setISBN('1234'); + $book2 = new Book(); + $book2->setTitle('Foo6'); + $book2->setISBN('1234'); + $author = new Author(); + $author->setFirstName('JAne'); + $author->addBook($book1); + $author->addBook($book2); + $author->save(); + $a = clone $author; + $sa = serialize($a); + $author->clearAllReferences(); + $this->assertEquals($author, unserialize($sa)); + } +} \ No newline at end of file diff --git a/library/propel/test/testsuite/runtime/om/BaseObjectTest.php b/library/propel/test/testsuite/runtime/om/BaseObjectTest.php new file mode 100755 index 000000000..dd3f4ebe2 --- /dev/null +++ b/library/propel/test/testsuite/runtime/om/BaseObjectTest.php @@ -0,0 +1,69 @@ +assertEquals(array(), $b->getVirtualColumns(), 'getVirtualColumns() returns an empty array for new objects'); + $b->virtualColumns = array('foo' => 'bar'); + $this->assertEquals(array('foo' => 'bar'), $b->getVirtualColumns(), 'getVirtualColumns() returns an associative array of virtual columns'); + } + + public function testHasVirtualColumn() + { + $b = new TestableBaseObject(); + $this->assertFalse($b->hasVirtualColumn('foo'), 'hasVirtualColumn() returns false if the virtual column is not set'); + $b->virtualColumns = array('foo' => 'bar'); + $this->assertTrue($b->hasVirtualColumn('foo'), 'hasVirtualColumn() returns true if the virtual column is set'); + } + + /** + * @expectedException PropelException + */ + public function testGetVirtualColumnWrongKey() + { + $b = new TestableBaseObject(); + $b->getVirtualColumn('foo'); + } + + public function testGetVirtualColumn() + { + $b = new TestableBaseObject(); + $b->virtualColumns = array('foo' => 'bar'); + $this->assertEquals('bar', $b->getVirtualColumn('foo'), 'getVirtualColumn() returns a virtual column value based on its key'); + } + + public function testSetVirtualColumn() + { + $b = new TestableBaseObject(); + $b->setVirtualColumn('foo', 'bar'); + $this->assertEquals('bar', $b->getVirtualColumn('foo'), 'setVirtualColumn() sets a virtual column value based on its key'); + $b->setVirtualColumn('foo', 'baz'); + $this->assertEquals('baz', $b->getVirtualColumn('foo'), 'setVirtualColumn() can modify the value of an existing virtual column'); + $this->assertEquals($b, $b->setVirtualColumn('foo', 'bar'), 'setVirtualColumn() returns the current object'); + } +} + +class TestableBaseObject extends BaseObject +{ + public $virtualColumns = array(); +} \ No newline at end of file diff --git a/library/propel/test/testsuite/runtime/query/CriteriaCombineTest.php b/library/propel/test/testsuite/runtime/query/CriteriaCombineTest.php new file mode 100644 index 000000000..d5cec12ea --- /dev/null +++ b/library/propel/test/testsuite/runtime/query/CriteriaCombineTest.php @@ -0,0 +1,385 @@ +c = new Criteria(); + $this->savedAdapter = Propel::getDB(null); + Propel::setDB(null, new DBSQLite()); + } + + protected function tearDown() + { + Propel::setDB(null, $this->savedAdapter); + parent::tearDown(); + } + + /** + * test various properties of Criterion and nested criterion + */ + public function testNestedCriterion() + { + $table2 = "myTable2"; + $column2 = "myColumn2"; + $value2 = "myValue2"; + $key2 = "$table2.$column2"; + + $table3 = "myTable3"; + $column3 = "myColumn3"; + $value3 = "myValue3"; + $key3 = "$table3.$column3"; + + $table4 = "myTable4"; + $column4 = "myColumn4"; + $value4 = "myValue4"; + $key4 = "$table4.$column4"; + + $table5 = "myTable5"; + $column5 = "myColumn5"; + $value5 = "myValue5"; + $key5 = "$table5.$column5"; + + $crit2 = $this->c->getNewCriterion($key2, $value2, Criteria::EQUAL); + $crit3 = $this->c->getNewCriterion($key3, $value3, Criteria::EQUAL); + $crit4 = $this->c->getNewCriterion($key4, $value4, Criteria::EQUAL); + $crit5 = $this->c->getNewCriterion($key5, $value5, Criteria::EQUAL); + + $crit2->addAnd($crit3)->addOr($crit4->addAnd($crit5)); + $expect = "((myTable2.myColumn2=:p1 AND myTable3.myColumn3=:p2) " + . "OR (myTable4.myColumn4=:p3 AND myTable5.myColumn5=:p4))"; + + $sb = ""; + $params = array(); + $crit2->appendPsTo($sb, $params); + + $expect_params = array( + array('table' => 'myTable2', 'column' => 'myColumn2', 'value' => 'myValue2'), + array('table' => 'myTable3', 'column' => 'myColumn3', 'value' => 'myValue3'), + array('table' => 'myTable4', 'column' => 'myColumn4', 'value' => 'myValue4'), + array('table' => 'myTable5', 'column' => 'myColumn5', 'value' => 'myValue5'), + ); + + $this->assertEquals($expect, $sb); + $this->assertEquals($expect_params, $params); + + $crit6 = $this->c->getNewCriterion($key2, $value2, Criteria::EQUAL); + $crit7 = $this->c->getNewCriterion($key3, $value3, Criteria::EQUAL); + $crit8 = $this->c->getNewCriterion($key4, $value4, Criteria::EQUAL); + $crit9 = $this->c->getNewCriterion($key5, $value5, Criteria::EQUAL); + + $crit6->addAnd($crit7)->addOr($crit8)->addAnd($crit9); + $expect = "(((myTable2.myColumn2=:p1 AND myTable3.myColumn3=:p2) " + . "OR myTable4.myColumn4=:p3) AND myTable5.myColumn5=:p4)"; + + $sb = ""; + $params = array(); + $crit6->appendPsTo($sb, $params); + + $expect_params = array( + array('table' => 'myTable2', 'column' => 'myColumn2', 'value' => 'myValue2'), + array('table' => 'myTable3', 'column' => 'myColumn3', 'value' => 'myValue3'), + array('table' => 'myTable4', 'column' => 'myColumn4', 'value' => 'myValue4'), + array('table' => 'myTable5', 'column' => 'myColumn5', 'value' => 'myValue5'), + ); + + $this->assertEquals($expect, $sb); + $this->assertEquals($expect_params, $params); + + // should make sure we have tests for all possibilities + + $crita = $crit2->getAttachedCriterion(); + + $this->assertEquals($crit2, $crita[0]); + $this->assertEquals($crit3, $crita[1]); + $this->assertEquals($crit4, $crita[2]); + $this->assertEquals($crit5, $crita[3]); + + $tables = $crit2->getAllTables(); + + $this->assertEquals($crit2->getTable(), $tables[0]); + $this->assertEquals($crit3->getTable(), $tables[1]); + $this->assertEquals($crit4->getTable(), $tables[2]); + $this->assertEquals($crit5->getTable(), $tables[3]); + + // simple confirmations that equality operations work + $this->assertTrue($crit2->hashCode() === $crit2->hashCode()); + } + + /** + * Tests <= and >=. + */ + public function testBetweenCriterion() + { + $cn1 = $this->c->getNewCriterion("INVOICE.COST", 1000, Criteria::GREATER_EQUAL); + $cn2 = $this->c->getNewCriterion("INVOICE.COST", 5000, Criteria::LESS_EQUAL); + $this->c->add($cn1->addAnd($cn2)); + + $expect = "SELECT FROM INVOICE WHERE (INVOICE.COST>=:p1 AND INVOICE.COST<=:p2)"; + $expect_params = array( + array('table' => 'INVOICE', 'column' => 'COST', 'value' => 1000), + array('table' => 'INVOICE', 'column' => 'COST', 'value' => 5000), + ); + + try { + $params = array(); + $result = BasePeer::createSelectSql($this->c, $params); + } catch (PropelException $e) { + $this->fail("PropelException thrown in BasePeer.createSelectSql(): ".$e->getMessage()); + } + + $this->assertEquals($expect, $result); + $this->assertEquals($expect_params, $params); + } + + /** + * Verify that AND and OR criterion are nested correctly. + */ + public function testPrecedence() + { + $cn1 = $this->c->getNewCriterion("INVOICE.COST", "1000", Criteria::GREATER_EQUAL); + $cn2 = $this->c->getNewCriterion("INVOICE.COST", "2000", Criteria::LESS_EQUAL); + $cn3 = $this->c->getNewCriterion("INVOICE.COST", "8000", Criteria::GREATER_EQUAL); + $cn4 = $this->c->getNewCriterion("INVOICE.COST", "9000", Criteria::LESS_EQUAL); + $this->c->add($cn1->addAnd($cn2)); + $this->c->addOr($cn3->addAnd($cn4)); + + $expect = + "SELECT FROM INVOICE WHERE ((INVOICE.COST>=:p1 AND INVOICE.COST<=:p2) OR (INVOICE.COST>=:p3 AND INVOICE.COST<=:p4))"; + + $expect_params = array( + array('table' => 'INVOICE', 'column' => 'COST', 'value' => '1000'), + array('table' => 'INVOICE', 'column' => 'COST', 'value' => '2000'), + array('table' => 'INVOICE', 'column' => 'COST', 'value' => '8000'), + array('table' => 'INVOICE', 'column' => 'COST', 'value' => '9000'), + ); + + try { + $params=array(); + $result = BasePeer::createSelectSql($this->c, $params); + } catch (PropelException $e) { + $this->fail("PropelException thrown in BasePeer::createSelectSql()"); + } + + $this->assertEquals($expect, $result); + $this->assertEquals($expect_params, $params); + } + + public function testCombineCriterionAndSimple() + { + $this->c->addCond('cond1', "INVOICE.COST", "1000", Criteria::GREATER_EQUAL); + $this->c->addCond('cond2', "INVOICE.COST", "2000", Criteria::LESS_EQUAL); + $this->c->combine(array('cond1', 'cond2'), Criteria::LOGICAL_AND); + + $expect = "SELECT FROM INVOICE WHERE (INVOICE.COST>=:p1 AND INVOICE.COST<=:p2)"; + $expect_params = array( + array('table' => 'INVOICE', 'column' => 'COST', 'value' => '1000'), + array('table' => 'INVOICE', 'column' => 'COST', 'value' => '2000'), + ); + + $params = array(); + $result = BasePeer::createSelectSql($this->c, $params); + + $this->assertEquals($expect, $result); + $this->assertEquals($expect_params, $params); + } + + public function testCombineCriterionAndLessSimple() + { + $this->c->addCond('cond1', "INVOICE.COST1", "1000", Criteria::GREATER_EQUAL); + $this->c->addCond('cond2', "INVOICE.COST2", "2000", Criteria::LESS_EQUAL); + $this->c->add("INVOICE.COST3", "8000", Criteria::GREATER_EQUAL); + $this->c->combine(array('cond1', 'cond2'), Criteria::LOGICAL_AND); + $this->c->add("INVOICE.COST4", "9000", Criteria::LESS_EQUAL); + + $expect = "SELECT FROM INVOICE WHERE INVOICE.COST3>=:p1 AND (INVOICE.COST1>=:p2 AND INVOICE.COST2<=:p3) AND INVOICE.COST4<=:p4"; + $expect_params = array( + array('table' => 'INVOICE', 'column' => 'COST3', 'value' => '8000'), + array('table' => 'INVOICE', 'column' => 'COST1', 'value' => '1000'), + array('table' => 'INVOICE', 'column' => 'COST2', 'value' => '2000'), + array('table' => 'INVOICE', 'column' => 'COST4', 'value' => '9000'), + ); + + $params = array(); + $result = BasePeer::createSelectSql($this->c, $params); + + $this->assertEquals($expect, $result); + $this->assertEquals($expect_params, $params); + } + + public function testCombineCriterionAndMultiple() + { + $this->c->addCond('cond1',"INVOICE.COST1", "1000", Criteria::GREATER_EQUAL); + $this->c->addCond('cond2', "INVOICE.COST2", "2000", Criteria::LESS_EQUAL); + $this->c->addCond('cond3', "INVOICE.COST3", "8000", Criteria::GREATER_EQUAL); + $this->c->addCond('cond4', "INVOICE.COST4", "9000", Criteria::LESS_EQUAL); + $this->c->combine(array('cond1', 'cond2', 'cond3', 'cond4'), Criteria::LOGICAL_AND); + + $expect = "SELECT FROM INVOICE WHERE (((INVOICE.COST1>=:p1 AND INVOICE.COST2<=:p2) AND INVOICE.COST3>=:p3) AND INVOICE.COST4<=:p4)"; + $expect_params = array( + array('table' => 'INVOICE', 'column' => 'COST1', 'value' => '1000'), + array('table' => 'INVOICE', 'column' => 'COST2', 'value' => '2000'), + array('table' => 'INVOICE', 'column' => 'COST3', 'value' => '8000'), + array('table' => 'INVOICE', 'column' => 'COST4', 'value' => '9000'), + ); + + $params = array(); + $result = BasePeer::createSelectSql($this->c, $params); + + $this->assertEquals($expect, $result); + $this->assertEquals($expect_params, $params); + } + + public function testCombineCriterionOrSimple() + { + $this->c->addCond('cond1', "INVOICE.COST", "1000", Criteria::GREATER_EQUAL); + $this->c->addCond('cond2', "INVOICE.COST", "2000", Criteria::LESS_EQUAL); + $this->c->combine(array('cond1', 'cond2'), Criteria::LOGICAL_OR); + + $expect = "SELECT FROM INVOICE WHERE (INVOICE.COST>=:p1 OR INVOICE.COST<=:p2)"; + $expect_params = array( + array('table' => 'INVOICE', 'column' => 'COST', 'value' => '1000'), + array('table' => 'INVOICE', 'column' => 'COST', 'value' => '2000'), + ); + + $params = array(); + $result = BasePeer::createSelectSql($this->c, $params); + + $this->assertEquals($expect, $result); + $this->assertEquals($expect_params, $params); + } + + public function testCombineCriterionOrLessSimple() + { + $this->c->addCond('cond1', "INVOICE.COST1", "1000", Criteria::GREATER_EQUAL); + $this->c->addCond('cond2', "INVOICE.COST2", "2000", Criteria::LESS_EQUAL); + $this->c->add("INVOICE.COST3", "8000", Criteria::GREATER_EQUAL); + $this->c->combine(array('cond1', 'cond2'), Criteria::LOGICAL_OR); + $this->c->addOr("INVOICE.COST4", "9000", Criteria::LESS_EQUAL); + + $expect = "SELECT FROM INVOICE WHERE INVOICE.COST3>=:p1 AND ((INVOICE.COST1>=:p2 OR INVOICE.COST2<=:p3) OR INVOICE.COST4<=:p4)"; + $expect_params = array( + array('table' => 'INVOICE', 'column' => 'COST3', 'value' => '8000'), + array('table' => 'INVOICE', 'column' => 'COST1', 'value' => '1000'), + array('table' => 'INVOICE', 'column' => 'COST2', 'value' => '2000'), + array('table' => 'INVOICE', 'column' => 'COST4', 'value' => '9000'), + ); + + $params = array(); + $result = BasePeer::createSelectSql($this->c, $params); + + $this->assertEquals($expect, $result); + $this->assertEquals($expect_params, $params); + } + + public function testCombineCriterionOrMultiple() + { + $this->c->addCond('cond1',"INVOICE.COST1", "1000", Criteria::GREATER_EQUAL); + $this->c->addCond('cond2', "INVOICE.COST2", "2000", Criteria::LESS_EQUAL); + $this->c->addCond('cond3', "INVOICE.COST3", "8000", Criteria::GREATER_EQUAL); + $this->c->addCond('cond4', "INVOICE.COST4", "9000", Criteria::LESS_EQUAL); + $this->c->combine(array('cond1', 'cond2', 'cond3', 'cond4'), Criteria::LOGICAL_OR); + + $expect = "SELECT FROM INVOICE WHERE (((INVOICE.COST1>=:p1 OR INVOICE.COST2<=:p2) OR INVOICE.COST3>=:p3) OR INVOICE.COST4<=:p4)"; + $expect_params = array( + array('table' => 'INVOICE', 'column' => 'COST1', 'value' => '1000'), + array('table' => 'INVOICE', 'column' => 'COST2', 'value' => '2000'), + array('table' => 'INVOICE', 'column' => 'COST3', 'value' => '8000'), + array('table' => 'INVOICE', 'column' => 'COST4', 'value' => '9000'), + ); + + $params = array(); + $result = BasePeer::createSelectSql($this->c, $params); + + $this->assertEquals($expect, $result); + $this->assertEquals($expect_params, $params); + } + + public function testCombineNamedCriterions() + { + $this->c->addCond('cond1', "INVOICE.COST1", "1000", Criteria::GREATER_EQUAL); + $this->c->addCond('cond2', "INVOICE.COST2", "2000", Criteria::LESS_EQUAL); + $this->c->combine(array('cond1', 'cond2'), Criteria::LOGICAL_AND, 'cond12'); + $this->c->addCond('cond3', "INVOICE.COST3", "8000", Criteria::GREATER_EQUAL); + $this->c->addCond('cond4', "INVOICE.COST4", "9000", Criteria::LESS_EQUAL); + $this->c->combine(array('cond3', 'cond4'), Criteria::LOGICAL_AND, 'cond34'); + $this->c->combine(array('cond12', 'cond34'), Criteria::LOGICAL_OR); + + $expect = "SELECT FROM INVOICE WHERE ((INVOICE.COST1>=:p1 AND INVOICE.COST2<=:p2) OR (INVOICE.COST3>=:p3 AND INVOICE.COST4<=:p4))"; + $expect_params = array( + array('table' => 'INVOICE', 'column' => 'COST1', 'value' => '1000'), + array('table' => 'INVOICE', 'column' => 'COST2', 'value' => '2000'), + array('table' => 'INVOICE', 'column' => 'COST3', 'value' => '8000'), + array('table' => 'INVOICE', 'column' => 'COST4', 'value' => '9000'), + ); + + $params = array(); + $result = BasePeer::createSelectSql($this->c, $params); + + $this->assertEquals($expect, $result); + $this->assertEquals($expect_params, $params); + } + + public function testCombineDirtyOperators() + { + $this->c->addCond('cond1', "INVOICE.COST1", "1000", Criteria::GREATER_EQUAL); + $this->c->addCond('cond2', "INVOICE.COST2", "2000", Criteria::LESS_EQUAL); + $this->c->combine(array('cond1', 'cond2'), 'AnD', 'cond12'); + $this->c->addCond('cond3', "INVOICE.COST3", "8000", Criteria::GREATER_EQUAL); + $this->c->addCond('cond4', "INVOICE.COST4", "9000", Criteria::LESS_EQUAL); + $this->c->combine(array('cond3', 'cond4'), 'aNd', 'cond34'); + $this->c->combine(array('cond12', 'cond34'), 'oR'); + + $expect = "SELECT FROM INVOICE WHERE ((INVOICE.COST1>=:p1 AND INVOICE.COST2<=:p2) OR (INVOICE.COST3>=:p3 AND INVOICE.COST4<=:p4))"; + $expect_params = array( + array('table' => 'INVOICE', 'column' => 'COST1', 'value' => '1000'), + array('table' => 'INVOICE', 'column' => 'COST2', 'value' => '2000'), + array('table' => 'INVOICE', 'column' => 'COST3', 'value' => '8000'), + array('table' => 'INVOICE', 'column' => 'COST4', 'value' => '9000'), + ); + + $params = array(); + $result = BasePeer::createSelectSql($this->c, $params); + + $this->assertEquals($expect, $result); + $this->assertEquals($expect_params, $params); + } + +} diff --git a/library/propel/test/testsuite/runtime/query/CriteriaFluidConditionTest.php b/library/propel/test/testsuite/runtime/query/CriteriaFluidConditionTest.php new file mode 100644 index 000000000..a2f0d2bbc --- /dev/null +++ b/library/propel/test/testsuite/runtime/query/CriteriaFluidConditionTest.php @@ -0,0 +1,181 @@ + + _if(true)-> + test()-> + _endif(); + $this->assertTrue($f->getTest(), '_if() executes the next method if the test is true'); + $f = new TestableCriteria(); + $f-> + _if(false)-> + foo()-> + _endif(); + $this->assertFalse($f->getTest(), '_if() does not check the existence of the next method if the test is false'); + $f = new TestableCriteria(); + $f-> + _if(true)-> + dummy()-> + test()-> + _endif(); + $this->assertTrue($f->getTest(), '_if() executes the next methods until _endif() if the test is true'); + $f = new TestableCriteria(); + $f-> + _if(false)-> + dummy()-> + test()-> + _endif(); + $this->assertFalse($f->getTest(), '_if() does not execute the next methods until _endif() if the test is false'); + } + + /** + * @expectedException PropelException + */ + public function testNestedIf() + { + $f = new TestableCriteria(); + $f-> + _if(false)-> + _if(true)-> + test()-> + _endif(); + } + + public function testElseIf() + { + $f = new TestableCriteria(); + $f-> + _if(true)-> + _elseif(true)-> + test()-> + _endif(); + $this->assertFalse($f->getTest(), '_elseif() does not execute the next method if the main test is true'); + $f = new TestableCriteria(); + $f-> + _if(true)-> + _elseif(false)-> + test()-> + _endif(); + $this->assertFalse($f->getTest(), '_elseif() does not execute the next method if the main test is true'); + $f = new TestableCriteria(); + $f-> + _if(false)-> + _elseif(true)-> + test()-> + _endif(); + $this->assertTrue($f->getTest(), '_elseif() executes the next method if the main test is false and the elseif test is true'); + $f = new TestableCriteria(); + $f-> + _if(false)-> + _elseif(false)-> + test()-> + _endif(); + $this->assertFalse($f->getTest(), '_elseif() does not execute the next method if the main test is false and the elseif test is false'); + } + + public function testElse() + { + $f = new TestableCriteria(); + $f-> + _if(true)-> + _else()-> + test()-> + _endif(); + $this->assertFalse($f->getTest(), '_else() does not execute the next method if the main test is true'); + $f = new TestableCriteria(); + $f-> + _if(false)-> + _else()-> + test()-> + _endif(); + $this->assertTrue($f->getTest(), '_else() executes the next method if the main test is false'); + $f = new TestableCriteria(); + $f-> + _if(false)-> + _elseif(true)-> + _else()-> + test()-> + _endif(); + $this->assertFalse($f->getTest(), '_else() does not execute the next method if the previous test is true'); + $f-> + _if(false)-> + _elseif(false)-> + _else()-> + test()-> + _endif(); + $this->assertTrue($f->getTest(), '_else() executes the next method if all the previous tests are false'); + } + + public function testEndif() + { + $f = new TestableCriteria(); + $res = $f-> + _if(true)-> + test()-> + _endif(); + $this->assertEquals($res, $f, '_endif() returns the main object if the test is true'); + $f = new TestableCriteria(); + $res = $f-> + _if(false)-> + test()-> + _endif(); + $this->assertEquals($res, $f, '_endif() returns the main object if the test is false'); + $f = new TestableCriteria(); + $f-> + _if(true)-> + _endif()-> + test(); + $this->assertTrue($f->getTest(), '_endif() stops the condition check'); + $f = new TestableCriteria(); + $f-> + _if(false)-> + _endif()-> + test(); + $this->assertTrue($f->getTest(), '_endif() stops the condition check'); + } +} + +class TestableCriteria extends Criteria +{ + protected $test = false; + + public function test() + { + $this->test = true; + + return $this; + } + + public function dummy() + { + return $this; + } + + public function getTest() + { + return $this->test; + } +} diff --git a/library/propel/test/testsuite/runtime/query/CriteriaMergeTest.php b/library/propel/test/testsuite/runtime/query/CriteriaMergeTest.php new file mode 100644 index 000000000..e28d8a30b --- /dev/null +++ b/library/propel/test/testsuite/runtime/query/CriteriaMergeTest.php @@ -0,0 +1,399 @@ +Christopher Elkins + * @author Sam Joseph + * @version $Id: CriteriaTest.php 1347 2009-12-03 21:06:36Z francois $ + * @package runtime.query + */ +class CriteriaMergeTest extends BaseTestCase +{ + + protected function assertCriteriaTranslation($criteria, $expectedSql, $message = '') + { + $params = array(); + $result = BasePeer::createSelectSql($criteria, $params); + $this->assertEquals($expectedSql, $result, $message); + } + + public function testMergeWithLimit() + { + $c1 = new Criteria(); + $c1->setLimit(123); + $c2 = new Criteria(); + $c1->mergeWith($c2); + $this->assertEquals(123, $c1->getLimit(), 'mergeWith() does not remove an existing limit'); + $c1 = new Criteria(); + $c2 = new Criteria(); + $c2->setLimit(123); + $c1->mergeWith($c2); + $this->assertEquals(123, $c1->getLimit(), 'mergeWith() merges the limit'); + $c1 = new Criteria(); + $c1->setLimit(456); + $c2 = new Criteria(); + $c2->setLimit(123); + $c1->mergeWith($c2); + $this->assertEquals(456, $c1->getLimit(), 'mergeWith() does not merge the limit in case of conflict'); + } + + public function testMergeWithOffset() + { + $c1 = new Criteria(); + $c1->setOffset(123); + $c2 = new Criteria(); + $c1->mergeWith($c2); + $this->assertEquals(123, $c1->getOffset(), 'mergeWith() does not remove an existing offset'); + $c1 = new Criteria(); + $c2 = new Criteria(); + $c2->setOffset(123); + $c1->mergeWith($c2); + $this->assertEquals(123, $c1->getOffset(), 'mergeWith() merges the offset'); + $c1 = new Criteria(); + $c1->setOffset(456); + $c2 = new Criteria(); + $c2->setOffset(123); + $c1->mergeWith($c2); + $this->assertEquals(456, $c1->getOffset(), 'mergeWith() does not merge the offset in case of conflict'); + } + + public function testMergeWithSelectModifiers() + { + $c1 = new Criteria(); + $c1->setDistinct(); + $c2 = new Criteria(); + $c1->mergeWith($c2); + $this->assertEquals(array(Criteria::DISTINCT), $c1->getSelectModifiers(), 'mergeWith() does not remove an existing select modifier'); + $c1 = new Criteria(); + $c2 = new Criteria(); + $c2->setDistinct(); + $c1->mergeWith($c2); + $this->assertEquals(array(Criteria::DISTINCT), $c1->getSelectModifiers(), 'mergeWith() merges the select modifiers'); + $c1 = new Criteria(); + $c1->setDistinct(); + $c2 = new Criteria(); + $c2->setDistinct(); + $c1->mergeWith($c2); + $this->assertEquals(array(Criteria::DISTINCT), $c1->getSelectModifiers(), 'mergeWith() does not duplicate select modifiers'); + $c1 = new Criteria(); + $c1->setAll(); + $c2 = new Criteria(); + $c2->setDistinct(); + $c1->mergeWith($c2); + $this->assertEquals(array(Criteria::ALL), $c1->getSelectModifiers(), 'mergeWith() does not merge the select modifiers in case of conflict'); + } + + public function testMergeWithSelectColumns() + { + $c1 = new Criteria(); + $c1->addSelectColumn(BookPeer::TITLE); + $c1->addSelectColumn(BookPeer::ID); + $c2 = new Criteria(); + $c1->mergeWith($c2); + $this->assertEquals(array(BookPeer::TITLE, BookPeer::ID), $c1->getSelectColumns(), 'mergeWith() does not remove an existing select columns'); + $c1 = new Criteria(); + $c2 = new Criteria(); + $c2->addSelectColumn(BookPeer::TITLE); + $c2->addSelectColumn(BookPeer::ID); + $c1->mergeWith($c2); + $this->assertEquals(array(BookPeer::TITLE, BookPeer::ID), $c1->getSelectColumns(), 'mergeWith() merges the select columns to an empty select'); + $c1 = new Criteria(); + $c1->addSelectColumn(BookPeer::TITLE); + $c2 = new Criteria(); + $c2->addSelectColumn(BookPeer::ID); + $c1->mergeWith($c2); + $this->assertEquals(array(BookPeer::TITLE, BookPeer::ID), $c1->getSelectColumns(), 'mergeWith() merges the select columns after the existing select columns'); + $c1 = new Criteria(); + $c1->addSelectColumn(BookPeer::TITLE); + $c2 = new Criteria(); + $c2->addSelectColumn(BookPeer::TITLE); + $c1->mergeWith($c2); + $this->assertEquals(array(BookPeer::TITLE, BookPeer::TITLE), $c1->getSelectColumns(), 'mergeWith() merges the select columns to an existing select, even if duplicated'); + } + + public function testMergeWithAsColumns() + { + $c1 = new Criteria(); + $c1->addAsColumn('foo', BookPeer::TITLE); + $c1->addAsColumn('bar', BookPeer::ID); + $c2 = new Criteria(); + $c1->mergeWith($c2); + $this->assertEquals(array('foo' => BookPeer::TITLE, 'bar' => BookPeer::ID), $c1->getAsColumns(), 'mergeWith() does not remove an existing as columns'); + $c1 = new Criteria(); + $c2 = new Criteria(); + $c2->addAsColumn('foo', BookPeer::TITLE); + $c2->addAsColumn('bar', BookPeer::ID); + $c1->mergeWith($c2); + $this->assertEquals(array('foo' => BookPeer::TITLE, 'bar' => BookPeer::ID), $c1->getAsColumns(), 'mergeWith() merges the select columns to an empty as'); + $c1 = new Criteria(); + $c1->addAsColumn('foo', BookPeer::TITLE); + $c2 = new Criteria(); + $c2->addAsColumn('bar', BookPeer::ID); + $c1->mergeWith($c2); + $this->assertEquals(array('foo' => BookPeer::TITLE, 'bar' => BookPeer::ID), $c1->getAsColumns(), 'mergeWith() merges the select columns after the existing as columns'); + } + + /** + * @expectedException PropelException + */ + public function testMergeWithAsColumnsThrowsException() + { + $c1 = new Criteria(); + $c1->addAsColumn('foo', BookPeer::TITLE); + $c2 = new Criteria(); + $c2->addAsColumn('foo', BookPeer::ID); + $c1->mergeWith($c2); + } + + public function testMergeWithOrderByColumns() + { + $c1 = new Criteria(); + $c1->addAscendingOrderByColumn(BookPeer::TITLE); + $c1->addAscendingOrderByColumn(BookPeer::ID); + $c2 = new Criteria(); + $c1->mergeWith($c2); + $this->assertEquals(array(BookPeer::TITLE . ' ASC', BookPeer::ID . ' ASC'), $c1->getOrderByColumns(), 'mergeWith() does not remove an existing orderby columns'); + $c1 = new Criteria(); + $c2 = new Criteria(); + $c2->addAscendingOrderByColumn(BookPeer::TITLE); + $c2->addAscendingOrderByColumn(BookPeer::ID); + $c1->mergeWith($c2); + $this->assertEquals(array(BookPeer::TITLE . ' ASC', BookPeer::ID . ' ASC'), $c1->getOrderByColumns(), 'mergeWith() merges the select columns to an empty order by'); + $c1 = new Criteria(); + $c1->addAscendingOrderByColumn(BookPeer::TITLE); + $c2 = new Criteria(); + $c2->addAscendingOrderByColumn(BookPeer::ID); + $c1->mergeWith($c2); + $this->assertEquals(array(BookPeer::TITLE . ' ASC', BookPeer::ID . ' ASC'), $c1->getOrderByColumns(), 'mergeWith() merges the select columns after the existing orderby columns'); + $c1 = new Criteria(); + $c1->addAscendingOrderByColumn(BookPeer::TITLE); + $c2 = new Criteria(); + $c2->addAscendingOrderByColumn(BookPeer::TITLE); + $c1->mergeWith($c2); + $this->assertEquals(array(BookPeer::TITLE . ' ASC'), $c1->getOrderByColumns(), 'mergeWith() does not merge duplicated orderby columns'); + $c1 = new Criteria(); + $c1->addAscendingOrderByColumn(BookPeer::TITLE); + $c2 = new Criteria(); + $c2->addDescendingOrderByColumn(BookPeer::TITLE); + $c1->mergeWith($c2); + $this->assertEquals(array(BookPeer::TITLE . ' ASC', BookPeer::TITLE . ' DESC'), $c1->getOrderByColumns(), 'mergeWith() merges duplicated orderby columns with inverse direction'); + } + + public function testMergeWithGroupByColumns() + { + $c1 = new Criteria(); + $c1->addGroupByColumn(BookPeer::TITLE); + $c1->addGroupByColumn(BookPeer::ID); + $c2 = new Criteria(); + $c1->mergeWith($c2); + $this->assertEquals(array(BookPeer::TITLE, BookPeer::ID), $c1->getGroupByColumns(), 'mergeWith() does not remove an existing groupby columns'); + $c1 = new Criteria(); + $c2 = new Criteria(); + $c2->addGroupByColumn(BookPeer::TITLE); + $c2->addGroupByColumn(BookPeer::ID); + $c1->mergeWith($c2); + $this->assertEquals(array(BookPeer::TITLE, BookPeer::ID), $c1->getGroupByColumns(), 'mergeWith() merges the select columns to an empty groupby'); + $c1 = new Criteria(); + $c1->addGroupByColumn(BookPeer::TITLE); + $c2 = new Criteria(); + $c2->addGroupByColumn(BookPeer::ID); + $c1->mergeWith($c2); + $this->assertEquals(array(BookPeer::TITLE, BookPeer::ID), $c1->getGroupByColumns(), 'mergeWith() merges the select columns after the existing groupby columns'); + $c1 = new Criteria(); + $c1->addGroupByColumn(BookPeer::TITLE); + $c2 = new Criteria(); + $c2->addGroupByColumn(BookPeer::TITLE); + $c1->mergeWith($c2); + $this->assertEquals(array(BookPeer::TITLE), $c1->getGroupByColumns(), 'mergeWith() does not merge duplicated groupby columns'); + } + + public function testMergeWithWhereConditions() + { + $c1 = new Criteria(); + $c1->add(BookPeer::TITLE, 'foo'); + $c2 = new Criteria(); + $c1->mergeWith($c2); + $sql = 'SELECT FROM `book` WHERE book.TITLE=:p1'; + $this->assertCriteriaTranslation($c1, $sql, 'mergeWith() does not remove an existing where condition'); + $c1 = new Criteria(); + $c2 = new Criteria(); + $c2->add(BookPeer::TITLE, 'foo'); + $c1->mergeWith($c2); + $sql = 'SELECT FROM `book` WHERE book.TITLE=:p1'; + $this->assertCriteriaTranslation($c1, $sql, 'mergeWith() merges where condition to an empty condition'); + $c1 = new Criteria(); + $c1->add(BookPeer::ID, 123); + $c2 = new Criteria(); + $c2->add(BookPeer::TITLE, 'foo'); + $c1->mergeWith($c2); + $sql = 'SELECT FROM `book` WHERE book.ID=:p1 AND book.TITLE=:p2'; + $this->assertCriteriaTranslation($c1, $sql, 'mergeWith() merges where condition to existing conditions'); + $c1 = new Criteria(); + $c1->add(BookPeer::TITLE, 'foo'); + $c2 = new Criteria(); + $c2->add(BookPeer::TITLE, 'bar'); + $c1->mergeWith($c2); + $sql = 'SELECT FROM `book` WHERE (book.TITLE=:p1 AND book.TITLE=:p2)'; + $this->assertCriteriaTranslation($c1, $sql, 'mergeWith() merges where condition to existing conditions on the same column'); + $c1 = new Criteria(); + $c1->add(BookPeer::TITLE, 'foo'); + $c1->addJoin(BookPeer::AUTHOR_ID, AuthorPeer::ID, Criteria::LEFT_JOIN); + $c2 = new Criteria(); + $c2->add(AuthorPeer::FIRST_NAME, 'bar'); + $c1->mergeWith($c2); + $sql = 'SELECT FROM `book` LEFT JOIN author ON (book.AUTHOR_ID=author.ID) WHERE book.TITLE=:p1 AND author.FIRST_NAME=:p2'; + $this->assertCriteriaTranslation($c1, $sql, 'mergeWith() merges where condition to existing conditions on the different tables'); + } + + public function testMergeOrWithWhereConditions() + { + $c1 = new Criteria(); + $c1->add(BookPeer::TITLE, 'foo'); + $c2 = new Criteria(); + $c1->mergeWith($c2, Criteria::LOGICAL_OR); + $sql = 'SELECT FROM `book` WHERE book.TITLE=:p1'; + $this->assertCriteriaTranslation($c1, $sql, 'mergeWith() does not remove an existing where condition'); + $c1 = new Criteria(); + $c2 = new Criteria(); + $c2->add(BookPeer::TITLE, 'foo'); + $c1->mergeWith($c2, Criteria::LOGICAL_OR); + $sql = 'SELECT FROM `book` WHERE book.TITLE=:p1'; + $this->assertCriteriaTranslation($c1, $sql, 'mergeWith() merges where condition to an empty condition'); + $c1 = new Criteria(); + $c1->add(BookPeer::ID, 123); + $c2 = new Criteria(); + $c2->add(BookPeer::TITLE, 'foo'); + $c1->mergeWith($c2, Criteria::LOGICAL_OR); + $sql = 'SELECT FROM `book` WHERE (book.ID=:p1 OR book.TITLE=:p2)'; + $this->assertCriteriaTranslation($c1, $sql, 'mergeWith() merges where condition to existing conditions'); + $c1 = new Criteria(); + $c1->add(BookPeer::TITLE, 'foo'); + $c2 = new Criteria(); + $c2->add(BookPeer::TITLE, 'bar'); + $c1->mergeWith($c2, Criteria::LOGICAL_OR); + $sql = 'SELECT FROM `book` WHERE (book.TITLE=:p1 OR book.TITLE=:p2)'; + $this->assertCriteriaTranslation($c1, $sql, 'mergeWith() merges where condition to existing conditions on the same column'); + $c1 = new Criteria(); + $c1->add(BookPeer::TITLE, 'foo'); + $c1->addJoin(BookPeer::AUTHOR_ID, AuthorPeer::ID, Criteria::LEFT_JOIN); + $c2 = new Criteria(); + $c2->add(AuthorPeer::FIRST_NAME, 'bar'); + $c1->mergeWith($c2, Criteria::LOGICAL_OR); + $sql = 'SELECT FROM `book` LEFT JOIN author ON (book.AUTHOR_ID=author.ID) WHERE (book.TITLE=:p1 OR author.FIRST_NAME=:p2)'; + $this->assertCriteriaTranslation($c1, $sql, 'mergeWith() merges where condition to existing conditions on the different tables'); + } + + public function testMergeWithHavingConditions() + { + $c1 = new Criteria(); + $cton = $c1->getNewCriterion(BookPeer::TITLE, 'foo', Criteria::EQUAL); + $c1->addHaving($cton); + $c2 = new Criteria(); + $c1->mergeWith($c2); + $sql = 'SELECT FROM HAVING book.TITLE=:p1'; + $this->assertCriteriaTranslation($c1, $sql, 'mergeWith() does not remove an existing having condition'); + $c1 = new Criteria(); + $c2 = new Criteria(); + $cton = $c2->getNewCriterion(BookPeer::TITLE, 'foo', Criteria::EQUAL); + $c2->addHaving($cton); + $c1->mergeWith($c2); + $sql = 'SELECT FROM HAVING book.TITLE=:p1'; + $this->assertCriteriaTranslation($c1, $sql, 'mergeWith() merges having condition to an empty having'); + $c1 = new Criteria(); + $cton = $c1->getNewCriterion(BookPeer::TITLE, 'foo', Criteria::EQUAL); + $c1->addHaving($cton); + $c2 = new Criteria(); + $cton = $c2->getNewCriterion(BookPeer::TITLE, 'bar', Criteria::EQUAL); + $c2->addHaving($cton); + $c1->mergeWith($c2); + $sql = 'SELECT FROM HAVING (book.TITLE=:p1 AND book.TITLE=:p2)'; + $this->assertCriteriaTranslation($c1, $sql, 'mergeWith() combines having with AND'); + } + + public function testMergeWithAliases() + { + $c1 = new Criteria(); + $c1->addAlias('b', BookPeer::TABLE_NAME); + $c2 = new Criteria(); + $c1->mergeWith($c2); + $this->assertEquals(array('b' => BookPeer::TABLE_NAME), $c1->getAliases(), 'mergeWith() does not remove an existing alias'); + $c1 = new Criteria(); + $c2 = new Criteria(); + $c2->addAlias('a', AuthorPeer::TABLE_NAME); + $c1->mergeWith($c2); + $this->assertEquals(array('a' => AuthorPeer::TABLE_NAME), $c1->getAliases(), 'mergeWith() merge aliases to an empty alias'); + $c1 = new Criteria(); + $c1->addAlias('b', BookPeer::TABLE_NAME); + $c2 = new Criteria(); + $c2->addAlias('a', AuthorPeer::TABLE_NAME); + $c1->mergeWith($c2); + $this->assertEquals(array('b' => BookPeer::TABLE_NAME, 'a' => AuthorPeer::TABLE_NAME), $c1->getAliases(), 'mergeWith() merge aliases to an existing alias'); + } + + /** + * @expectedException PropelException + */ + public function testMergeWithAliasesThrowsException() + { + $c1 = new Criteria(); + $c1->addAlias('b', BookPeer::TABLE_NAME); + $c2 = new Criteria(); + $c2->addAlias('b', AuthorPeer::TABLE_NAME); + $c1->mergeWith($c2); + } + + public function testMergeWithJoins() + { + $c1 = new Criteria(); + $c1->addJoin(BookPeer::AUTHOR_ID, AuthorPeer::ID, Criteria::LEFT_JOIN); + $c2 = new Criteria(); + $c1->mergeWith($c2); + $joins = $c1->getJoins(); + $this->assertEquals(1, count($joins), 'mergeWith() does not remove an existing join'); + $this->assertEquals('LEFT JOIN : book.AUTHOR_ID=author.ID(ignoreCase not considered)', $joins[0]->toString(), 'mergeWith() does not remove an existing join'); + $c1 = new Criteria(); + $c2 = new Criteria(); + $c2->addJoin(BookPeer::AUTHOR_ID, AuthorPeer::ID, Criteria::LEFT_JOIN); + $c1->mergeWith($c2); + $joins = $c1->getJoins(); + $this->assertEquals(1, count($joins), 'mergeWith() merge joins to an empty join'); + $this->assertEquals('LEFT JOIN : book.AUTHOR_ID=author.ID(ignoreCase not considered)', $joins[0]->toString(), 'mergeWith() merge joins to an empty join'); + $c1 = new Criteria(); + $c1->addJoin(BookPeer::AUTHOR_ID, AuthorPeer::ID, Criteria::LEFT_JOIN); + $c2 = new Criteria(); + $c2->addJoin(BookPeer::PUBLISHER_ID, PublisherPeer::ID, Criteria::INNER_JOIN); + $c1->mergeWith($c2); + $joins = $c1->getJoins(); + $this->assertEquals(2, count($joins), 'mergeWith() merge joins to an existing join'); + $this->assertEquals('LEFT JOIN : book.AUTHOR_ID=author.ID(ignoreCase not considered)', $joins[0]->toString(), 'mergeWith() merge joins to an empty join'); + $this->assertEquals('INNER JOIN : book.PUBLISHER_ID=publisher.ID(ignoreCase not considered)', $joins[1]->toString(), 'mergeWith() merge joins to an empty join'); + } + + public function testMergeWithFurtherModified() + { + $c1 = new Criteria(); + $c2 = new Criteria(); + $c2->setLimit(123); + $c1->mergeWith($c2); + $this->assertEquals(123, $c1->getLimit(), 'mergeWith() makes the merge'); + $c2->setLimit(456); + $this->assertEquals(123, $c1->getLimit(), 'further modifying a merged criteria does not affect the merger'); + } + +} diff --git a/library/propel/test/testsuite/runtime/query/CriteriaTest.php b/library/propel/test/testsuite/runtime/query/CriteriaTest.php new file mode 100644 index 000000000..c05b9b0e8 --- /dev/null +++ b/library/propel/test/testsuite/runtime/query/CriteriaTest.php @@ -0,0 +1,974 @@ +Christopher Elkins + * @author Sam Joseph + * @version $Id: CriteriaTest.php 1773 2010-05-25 10:25:06Z francois $ + * @package runtime.query + */ +class CriteriaTest extends BaseTestCase +{ + + /** + * The criteria to use in the test. + * @var Criteria + */ + private $c; + + /** + * DB adapter saved for later. + * + * @var DBAdapter + */ + private $savedAdapter; + + protected function setUp() + { + parent::setUp(); + $this->c = new Criteria(); + $this->savedAdapter = Propel::getDB(null); + Propel::setDB(null, new DBSQLite()); + } + + protected function tearDown() + { + Propel::setDB(null, $this->savedAdapter); + parent::tearDown(); + } + + /** + * Test basic adding of strings. + */ + public function testAddString() + { + $table = "myTable"; + $column = "myColumn"; + $value = "myValue"; + + // Add the string + $this->c->add($table . '.' . $column, $value); + + // Verify that the key exists + $this->assertTrue($this->c->containsKey($table . '.' . $column)); + + // Verify that what we get out is what we put in + $this->assertTrue($this->c->getValue($table . '.' . $column) === $value); + } + + public function testAddAndSameColumns() + { + $table1 = "myTable1"; + $column1 = "myColumn1"; + $value1 = "myValue1"; + $key1 = "$table1.$column1"; + + $table2 = "myTable1"; + $column2 = "myColumn1"; + $value2 = "myValue2"; + $key2 = "$table2.$column2"; + + $this->c->add($key1, $value1, Criteria::EQUAL); + $this->c->addAnd($key2, $value2, Criteria::EQUAL); + + $expect = "SELECT FROM myTable1 WHERE (myTable1.myColumn1=:p1 AND myTable1.myColumn1=:p2)"; + + $params = array(); + $result = BasePeer::createSelectSql($this->c, $params); + + $expect_params = array( + array('table' => 'myTable1', 'column' => 'myColumn1', 'value' => 'myValue1'), + array('table' => 'myTable1', 'column' => 'myColumn1', 'value' => 'myValue2'), + ); + + $this->assertEquals($expect, $result, 'addAnd() called on an existing column creates a combined criterion'); + $this->assertEquals($expect_params, $params, 'addAnd() called on an existing column creates a combined criterion'); + } + + public function testAddAndSameColumnsPropel14Compatibility() + { + $table1 = "myTable1"; + $column1 = "myColumn1"; + $value1 = "myValue1"; + $key1 = "$table1.$column1"; + + $table2 = "myTable1"; + $column2 = "myColumn1"; + $value2 = "myValue2"; + $key2 = "$table2.$column2"; + + $table3 = "myTable3"; + $column3 = "myColumn3"; + $value3 = "myValue3"; + $key3 = "$table3.$column3"; + + $this->c->add($key1, $value1, Criteria::EQUAL); + $this->c->add($key3, $value3, Criteria::EQUAL); + $this->c->addAnd($key2, $value2, Criteria::EQUAL); + + $expect = "SELECT FROM myTable1, myTable3 WHERE (myTable1.myColumn1=:p1 AND myTable1.myColumn1=:p2) AND myTable3.myColumn3=:p3"; + + $params = array(); + $result = BasePeer::createSelectSql($this->c, $params); + + $expect_params = array( + array('table' => 'myTable1', 'column' => 'myColumn1', 'value' => 'myValue1'), + array('table' => 'myTable1', 'column' => 'myColumn1', 'value' => 'myValue2'), + array('table' => 'myTable3', 'column' => 'myColumn3', 'value' => 'myValue3'), + ); + + $this->assertEquals($expect, $result, 'addAnd() called on an existing column creates a combined criterion'); + $this->assertEquals($expect_params, $params, 'addAnd() called on an existing column creates a combined criterion'); + } + + public function testAddAndDistinctColumns() + { + $table1 = "myTable1"; + $column1 = "myColumn1"; + $value1 = "myValue1"; + $key1 = "$table1.$column1"; + + $table2 = "myTable2"; + $column2 = "myColumn2"; + $value2 = "myValue2"; + $key2 = "$table2.$column2"; + + $this->c->add($key1, $value1, Criteria::EQUAL); + $this->c->addAnd($key2, $value2, Criteria::EQUAL); + + $expect = "SELECT FROM myTable1, myTable2 WHERE myTable1.myColumn1=:p1 AND myTable2.myColumn2=:p2"; + + $params = array(); + $result = BasePeer::createSelectSql($this->c, $params); + + $expect_params = array( + array('table' => 'myTable1', 'column' => 'myColumn1', 'value' => 'myValue1'), + array('table' => 'myTable2', 'column' => 'myColumn2', 'value' => 'myValue2'), + ); + + $this->assertEquals($expect, $result, 'addAnd() called on a distinct column adds a criterion to the criteria'); + $this->assertEquals($expect_params, $params, 'addAnd() called on a distinct column adds a criterion to the criteria'); + } + + public function testAddOrSameColumns() + { + $table1 = "myTable1"; + $column1 = "myColumn1"; + $value1 = "myValue1"; + $key1 = "$table1.$column1"; + + $table2 = "myTable1"; + $column2 = "myColumn1"; + $value2 = "myValue2"; + $key2 = "$table2.$column2"; + + $this->c->add($key1, $value1, Criteria::EQUAL); + $this->c->addOr($key2, $value2, Criteria::EQUAL); + + $expect = "SELECT FROM myTable1 WHERE (myTable1.myColumn1=:p1 OR myTable1.myColumn1=:p2)"; + + $params = array(); + $result = BasePeer::createSelectSql($this->c, $params); + + $expect_params = array( + array('table' => 'myTable1', 'column' => 'myColumn1', 'value' => 'myValue1'), + array('table' => 'myTable1', 'column' => 'myColumn1', 'value' => 'myValue2'), + ); + + $this->assertEquals($expect, $result, 'addOr() called on an existing column creates a combined criterion'); + $this->assertEquals($expect_params, $params, 'addOr() called on an existing column creates a combined criterion'); + } + + public function testAddAndOrColumnsPropel14Compatibility() + { + $table1 = "myTable1"; + $column1 = "myColumn1"; + $value1 = "myValue1"; + $key1 = "$table1.$column1"; + + $table2 = "myTable1"; + $column2 = "myColumn1"; + $value2 = "myValue2"; + $key2 = "$table2.$column2"; + + $table3 = "myTable3"; + $column3 = "myColumn3"; + $value3 = "myValue3"; + $key3 = "$table3.$column3"; + + $this->c->add($key1, $value1, Criteria::EQUAL); + $this->c->add($key3, $value3, Criteria::EQUAL); + $this->c->addOr($key2, $value2, Criteria::EQUAL); + + $expect = "SELECT FROM myTable1, myTable3 WHERE (myTable1.myColumn1=:p1 OR myTable1.myColumn1=:p2) AND myTable3.myColumn3=:p3"; + + $params = array(); + $result = BasePeer::createSelectSql($this->c, $params); + + $expect_params = array( + array('table' => 'myTable1', 'column' => 'myColumn1', 'value' => 'myValue1'), + array('table' => 'myTable1', 'column' => 'myColumn1', 'value' => 'myValue2'), + array('table' => 'myTable3', 'column' => 'myColumn3', 'value' => 'myValue3'), + ); + + $this->assertEquals($expect, $result, 'addOr() called on an existing column creates a combined criterion'); + $this->assertEquals($expect_params, $params, 'addOr() called on an existing column creates a combined criterion'); + } + + public function testAddOrDistinctColumns() + { + $table1 = "myTable1"; + $column1 = "myColumn1"; + $value1 = "myValue1"; + $key1 = "$table1.$column1"; + + $table2 = "myTable2"; + $column2 = "myColumn2"; + $value2 = "myValue2"; + $key2 = "$table2.$column2"; + + $this->c->add($key1, $value1, Criteria::EQUAL); + $this->c->addOr($key2, $value2, Criteria::EQUAL); + + $expect = "SELECT FROM myTable1, myTable2 WHERE (myTable1.myColumn1=:p1 OR myTable2.myColumn2=:p2)"; + + $params = array(); + $result = BasePeer::createSelectSql($this->c, $params); + + $expect_params = array( + array('table' => 'myTable1', 'column' => 'myColumn1', 'value' => 'myValue1'), + array('table' => 'myTable2', 'column' => 'myColumn2', 'value' => 'myValue2'), + ); + + $this->assertEquals($expect, $result, 'addOr() called on a distinct column adds a criterion to the latest criterion'); + $this->assertEquals($expect_params, $params, 'addOr() called on a distinct column adds a criterion to the latest criterion'); + } + + public function testAddOrEmptyCriteria() + { + $table1 = "myTable1"; + $column1 = "myColumn1"; + $value1 = "myValue1"; + $key1 = "$table1.$column1"; + + $this->c->addOr($key1, $value1, Criteria::EQUAL); + + $expect = "SELECT FROM myTable1 WHERE myTable1.myColumn1=:p1"; + + $params = array(); + $result = BasePeer::createSelectSql($this->c, $params); + + $expect_params = array( + array('table' => 'myTable1', 'column' => 'myColumn1', 'value' => 'myValue1'), + ); + + $this->assertEquals($expect, $result, 'addOr() called on an empty Criteria adds a criterion to the criteria'); + $this->assertEquals($expect_params, $params, 'addOr() called on an empty Criteria adds a criterion to the criteria'); + } + + /** + * Test Criterion.setIgnoreCase(). + * As the output is db specific the test just prints the result to + * System.out + */ + public function testCriterionIgnoreCase() + { + $originalDB = Propel::getDB(); + $adapters = array(new DBMySQL(), new DBPostgres()); + $expectedIgnore = array("UPPER(TABLE.COLUMN) LIKE UPPER(:p1)", "TABLE.COLUMN ILIKE :p1"); + + $i =0; + foreach ($adapters as $adapter) { + + Propel::setDB(null, $adapter); + $myCriteria = new Criteria(); + + $myCriterion = $myCriteria->getNewCriterion( + "TABLE.COLUMN", "FoObAr", Criteria::LIKE); + $sb = ""; + $params=array(); + $myCriterion->appendPsTo($sb, $params); + $expected = "TABLE.COLUMN LIKE :p1"; + + $this->assertEquals($expected, $sb); + + $ignoreCriterion = $myCriterion->setIgnoreCase(true); + + $sb = ""; + $params=array(); + $ignoreCriterion->appendPsTo($sb, $params); + // $expected = "UPPER(TABLE.COLUMN) LIKE UPPER(?)"; + $this->assertEquals($expectedIgnore[$i], $sb); + $i++; + } + Propel::setDB(null, $originalDB); + } + + public function testOrderByIgnoreCase() + { + $originalDB = Propel::getDB(); + Propel::setDB(null, new DBMySQL()); + + $criteria = new Criteria(); + $criteria->setIgnoreCase(true); + $criteria->addAscendingOrderByColumn(BookPeer::TITLE); + BookPeer::addSelectColumns($criteria); + $params=array(); + $sql = BasePeer::createSelectSql($criteria, $params); + $expectedSQL = 'SELECT book.ID, book.TITLE, book.ISBN, book.PRICE, book.PUBLISHER_ID, book.AUTHOR_ID, UPPER(book.TITLE) FROM `book` ORDER BY UPPER(book.TITLE) ASC'; + $this->assertEquals($expectedSQL, $sql); + + Propel::setDB(null, $originalDB); + } + + /** + * Test that true is evaluated correctly. + */ + public function testBoolean() + { + $this->c = new Criteria(); + $this->c->add("TABLE.COLUMN", true); + + $expect = "SELECT FROM TABLE WHERE TABLE.COLUMN=:p1"; + $expect_params = array( array('table' => 'TABLE', 'column' => 'COLUMN', 'value' => true), + ); + try { + $params = array(); + $result = BasePeer::createSelectSql($this->c, $params); + } catch (PropelException $e) { + $this->fail("PropelException thrown in BasePeer.createSelectSql(): ". $e->getMessage()); + } + + $this->assertEquals($expect, $result, "Boolean test failed."); + $this->assertEquals($expect_params, $params); + + } + + public function testCurrentDate() + { + $this->c = new Criteria(); + $this->c->add("TABLE.TIME_COLUMN", Criteria::CURRENT_TIME); + $this->c->add("TABLE.DATE_COLUMN", Criteria::CURRENT_DATE); + + $expect = "SELECT FROM TABLE WHERE TABLE.TIME_COLUMN=CURRENT_TIME AND TABLE.DATE_COLUMN=CURRENT_DATE"; + + $result = null; + try { + $params = array(); + $result = BasePeer::createSelectSql($this->c, $params); + } catch (PropelException $e) { + print $e->getTraceAsString(); + $this->fail("PropelException thrown in BasePeer.createSelectSql(): ". $e->getMessage()); + } + + $this->assertEquals($expect, $result, "Current date test failed!"); + + } + + public function testCountAster() + { + $this->c = new Criteria(); + $this->c->addSelectColumn("COUNT(*)"); + $this->c->add("TABLE.TIME_COLUMN", Criteria::CURRENT_TIME); + $this->c->add("TABLE.DATE_COLUMN", Criteria::CURRENT_DATE); + + $expect = "SELECT COUNT(*) FROM TABLE WHERE TABLE.TIME_COLUMN=CURRENT_TIME AND TABLE.DATE_COLUMN=CURRENT_DATE"; + + $result = null; + try { + $params = array(); + $result = BasePeer::createSelectSql($this->c, $params); + } catch (PropelException $e) { + print $e->getTraceAsString(); + $this->fail("PropelException thrown in BasePeer.createSelectSql(): ". $e->getMessage()); + } + + $this->assertEquals($expect, $result); + + } + + public function testIn() + { + $c = new Criteria(); + $c->addSelectColumn("*"); + $c->add("TABLE.SOME_COLUMN", array(), Criteria::IN); + $c->add("TABLE.OTHER_COLUMN", array(1, 2, 3), Criteria::IN); + + $expect = "SELECT * FROM TABLE WHERE 1<>1 AND TABLE.OTHER_COLUMN IN (:p1,:p2,:p3)"; + try { + $params = array(); + $result = BasePeer::createSelectSql($c, $params); + } catch (PropelException $e) { + print $e->getTraceAsString(); + $this->fail("PropelException thrown in BasePeer.createSelectSql(): ". $e->getMessage()); + } + $this->assertEquals($expect, $result); + } + + public function testInEmptyAfterFull() + { + $c = new Criteria(); + $c->addSelectColumn("*"); + $c->add("TABLE.OTHER_COLUMN", array(1, 2, 3), Criteria::IN); + $c->add("TABLE.SOME_COLUMN", array(), Criteria::IN); + + $expect = "SELECT * FROM TABLE WHERE TABLE.OTHER_COLUMN IN (:p1,:p2,:p3) AND 1<>1"; + try { + $params = array(); + $result = BasePeer::createSelectSql($c, $params); + } catch (PropelException $e) { + print $e->getTraceAsString(); + $this->fail("PropelException thrown in BasePeer.createSelectSql(): ". $e->getMessage()); + } + $this->assertEquals($expect, $result); + } + + public function testInNested() + { + // now do a nested logic test, just for sanity (not that this should be any surprise) + + $c = new Criteria(); + $c->addSelectColumn("*"); + $myCriterion = $c->getNewCriterion("TABLE.COLUMN", array(), Criteria::IN); + $myCriterion->addOr($c->getNewCriterion("TABLE.COLUMN2", array(1,2), Criteria::IN)); + $c->add($myCriterion); + + $expect = "SELECT * FROM TABLE WHERE (1<>1 OR TABLE.COLUMN2 IN (:p1,:p2))"; + try { + $params = array(); + $result = BasePeer::createSelectSql($c, $params); + } catch (PropelException $e) { + print $e->getTraceAsString(); + $this->fail("PropelException thrown in BasePeer.createSelectSql(): ". $e->getMessage()); + } + $this->assertEquals($expect, $result); + + } + + public function testJoinObject () + { + $j = new Join('TABLE_A.COL_1', 'TABLE_B.COL_2'); + $this->assertEquals(null, $j->getJoinType()); + $this->assertEquals('TABLE_A.COL_1', $j->getLeftColumn()); + $this->assertEquals('TABLE_A', $j->getLeftTableName()); + $this->assertEquals('COL_1', $j->getLeftColumnName()); + $this->assertEquals('TABLE_B.COL_2', $j->getRightColumn()); + $this->assertEquals('TABLE_B', $j->getRightTableName()); + $this->assertEquals('COL_2', $j->getRightColumnName()); + + $j = new Join('TABLE_A.COL_1', 'TABLE_B.COL_1', Criteria::LEFT_JOIN); + $this->assertEquals('LEFT JOIN', $j->getJoinType()); + $this->assertEquals('TABLE_A.COL_1', $j->getLeftColumn()); + $this->assertEquals('TABLE_B.COL_1', $j->getRightColumn()); + + $j = new Join('TABLE_A.COL_1', 'TABLE_B.COL_1', Criteria::RIGHT_JOIN); + $this->assertEquals('RIGHT JOIN', $j->getJoinType()); + $this->assertEquals('TABLE_A.COL_1', $j->getLeftColumn()); + $this->assertEquals('TABLE_B.COL_1', $j->getRightColumn()); + + $j = new Join('TABLE_A.COL_1', 'TABLE_B.COL_1', Criteria::INNER_JOIN); + $this->assertEquals('INNER JOIN', $j->getJoinType()); + $this->assertEquals('TABLE_A.COL_1', $j->getLeftColumn()); + $this->assertEquals('TABLE_B.COL_1', $j->getRightColumn()); + + $j = new Join(array('TABLE_A.COL_1', 'TABLE_A.COL_2'), array('TABLE_B.COL_1', 'TABLE_B.COL_2'), Criteria::INNER_JOIN); + $this->assertEquals('TABLE_A.COL_1', $j->getLeftColumn(0)); + $this->assertEquals('TABLE_A.COL_2', $j->getLeftColumn(1)); + $this->assertEquals('TABLE_B.COL_1', $j->getRightColumn(0)); + $this->assertEquals('TABLE_B.COL_2', $j->getRightColumn(1)); + } + + public function testAddStraightJoin () + { + $c = new Criteria(); + $c->addSelectColumn("*"); + $c->addJoin('TABLE_A.COL_1', 'TABLE_B.COL_1'); // straight join + + $expect = "SELECT * FROM TABLE_A, TABLE_B WHERE TABLE_A.COL_1=TABLE_B.COL_1"; + try { + $params = array(); + $result = BasePeer::createSelectSql($c, $params); + } catch (PropelException $e) { + print $e->getTraceAsString(); + $this->fail("PropelException thrown in BasePeer.createSelectSql(): ". $e->getMessage()); + } + $this->assertEquals($expect, $result); + } + + public function testAddSeveralJoins () + { + $c = new Criteria(); + $c->addSelectColumn("*"); + $c->addJoin('TABLE_A.COL_1', 'TABLE_B.COL_1'); + $c->addJoin('TABLE_B.COL_X', 'TABLE_D.COL_X'); + + $expect = 'SELECT * FROM TABLE_A, TABLE_B, TABLE_D ' + .'WHERE TABLE_A.COL_1=TABLE_B.COL_1 AND TABLE_B.COL_X=TABLE_D.COL_X'; + try { + $params = array(); + $result = BasePeer::createSelectSql($c, $params); + } catch (PropelException $e) { + print $e->getTraceAsString(); + $this->fail("PropelException thrown in BasePeer.createSelectSql(): ". $e->getMessage()); + } + $this->assertEquals($expect, $result); + } + + public function testAddLeftJoin () + { + $c = new Criteria(); + $c->addSelectColumn("TABLE_A.*"); + $c->addSelectColumn("TABLE_B.*"); + $c->addJoin('TABLE_A.COL_1', 'TABLE_B.COL_2', Criteria::LEFT_JOIN); + + $expect = "SELECT TABLE_A.*, TABLE_B.* FROM TABLE_A LEFT JOIN TABLE_B ON (TABLE_A.COL_1=TABLE_B.COL_2)"; + try { + $params = array(); + $result = BasePeer::createSelectSql($c, $params); + } catch (PropelException $e) { + print $e->getTraceAsString(); + $this->fail("PropelException thrown in BasePeer.createSelectSql(): ". $e->getMessage()); + } + $this->assertEquals($expect, $result); + } + + public function testAddSeveralLeftJoins () + { + // Fails.. Suspect answer in the chunk starting at BasePeer:605 + $c = new Criteria(); + $c->addSelectColumn('*'); + $c->addJoin('TABLE_A.COL_1', 'TABLE_B.COL_1', Criteria::LEFT_JOIN); + $c->addJoin('TABLE_A.COL_2', 'TABLE_C.COL_2', Criteria::LEFT_JOIN); + + $expect = 'SELECT * FROM TABLE_A ' + .'LEFT JOIN TABLE_B ON (TABLE_A.COL_1=TABLE_B.COL_1) ' + .'LEFT JOIN TABLE_C ON (TABLE_A.COL_2=TABLE_C.COL_2)'; + try { + $params = array(); + $result = BasePeer::createSelectSql($c, $params); + } catch (PropelException $e) { + print $e->getTraceAsString(); + $this->fail("PropelException thrown in BasePeer.createSelectSql(): ". $e->getMessage()); + } + $this->assertEquals($expect, $result); + } + + public function testAddRightJoin () + { + $c = new Criteria(); + $c->addSelectColumn("*"); + $c->addJoin('TABLE_A.COL_1', 'TABLE_B.COL_2', Criteria::RIGHT_JOIN); + + $expect = "SELECT * FROM TABLE_A RIGHT JOIN TABLE_B ON (TABLE_A.COL_1=TABLE_B.COL_2)"; + try { + $params = array(); + $result = BasePeer::createSelectSql($c, $params); + } catch (PropelException $e) { + print $e->getTraceAsString(); + $this->fail("PropelException thrown in BasePeer.createSelectSql(): ". $e->getMessage()); + } + $this->assertEquals($expect, $result); + } + + public function testAddSeveralRightJoins () + { + // Fails.. Suspect answer in the chunk starting at BasePeer:605 + $c = new Criteria(); + $c->addSelectColumn('*'); + $c->addJoin('TABLE_A.COL_1', 'TABLE_B.COL_1', Criteria::RIGHT_JOIN); + $c->addJoin('TABLE_A.COL_2', 'TABLE_C.COL_2', Criteria::RIGHT_JOIN); + + $expect = 'SELECT * FROM TABLE_A ' + .'RIGHT JOIN TABLE_B ON (TABLE_A.COL_1=TABLE_B.COL_1) ' + .'RIGHT JOIN TABLE_C ON (TABLE_A.COL_2=TABLE_C.COL_2)'; + try { + $params = array(); + $result = BasePeer::createSelectSql($c, $params); + } catch (PropelException $e) { + print $e->getTraceAsString(); + $this->fail("PropelException thrown in BasePeer.createSelectSql(): ". $e->getMessage()); + } + $this->assertEquals($expect, $result); + } + + public function testAddInnerJoin () + { + $c = new Criteria(); + $c->addSelectColumn("*"); + $c->addJoin('TABLE_A.COL_1', 'TABLE_B.COL_1', Criteria::INNER_JOIN); + + $expect = "SELECT * FROM TABLE_A INNER JOIN TABLE_B ON (TABLE_A.COL_1=TABLE_B.COL_1)"; + try { + $params = array(); + $result = BasePeer::createSelectSql($c, $params); + } catch (PropelException $e) { + print $e->getTraceAsString(); + $this->fail("PropelException thrown in BasePeer.createSelectSql(): ". $e->getMessage()); + } + $this->assertEquals($expect, $result); + } + + public function testAddSeveralInnerJoin () + { + $c = new Criteria(); + $c->addSelectColumn("*"); + $c->addJoin('TABLE_A.COL_1', 'TABLE_B.COL_1', Criteria::INNER_JOIN); + $c->addJoin('TABLE_B.COL_1', 'TABLE_C.COL_1', Criteria::INNER_JOIN); + + $expect = 'SELECT * FROM TABLE_A ' + .'INNER JOIN TABLE_B ON (TABLE_A.COL_1=TABLE_B.COL_1) ' + .'INNER JOIN TABLE_C ON (TABLE_B.COL_1=TABLE_C.COL_1)'; + try { + $params = array(); + $result = BasePeer::createSelectSql($c, $params); + } catch (PropelException $e) { + print $e->getTraceAsString(); + $this->fail("PropelException thrown in BasePeer.createSelectSql(): ". $e->getMessage()); + } + $this->assertEquals($expect, $result); + } + + /** + * @link http://propel.phpdb.org/trac/ticket/451 + */ + public function testSeveralMixedJoinOrders() + { + $c = new Criteria(); + $c->clearSelectColumns()-> + addJoin("TABLE_A.FOO_ID", "TABLE_B.ID", Criteria::LEFT_JOIN)-> + addJoin("TABLE_A.BAR_ID", "TABLE_C.ID")-> + addSelectColumn("TABLE_A.ID"); + + # These are no longer different, see http://propel.phpdb.org/trac/ticket/283#comment:8 + #$db = Propel::getDB(); + # + #if ($db instanceof DBMySQL) { + # $expect = 'SELECT TABLE_A.ID FROM (TABLE_A CROSS JOIN TABLE_C)' + # .' LEFT JOIN TABLE_B ON (TABLE_A.FOO_ID=TABLE_B.ID) WHERE TABLE_A.BAR_ID=TABLE_C.ID'; + #} else { + $expect = 'SELECT TABLE_A.ID FROM TABLE_A CROSS JOIN TABLE_C' + .' LEFT JOIN TABLE_B ON (TABLE_A.FOO_ID=TABLE_B.ID) WHERE TABLE_A.BAR_ID=TABLE_C.ID'; + #} + $params = array(); + $result = BasePeer::createSelectSql($c, $params); + $this->assertEquals($expect, $result); + } + + /** + * @link http://propel.phpdb.org/trac/ticket/606 + */ + public function testAddJoinArray() + { + $c = new Criteria(); + $c->clearSelectColumns()-> + addJoin(array('TABLE_A.FOO_ID'), array('TABLE_B.ID'), Criteria::LEFT_JOIN)-> + addSelectColumn("TABLE_A.ID"); + + $expect = 'SELECT TABLE_A.ID FROM TABLE_A' + .' LEFT JOIN TABLE_B ON (TABLE_A.FOO_ID=TABLE_B.ID)'; + $params = array(); + $result = BasePeer::createSelectSql($c, $params); + $this->assertEquals($expect, $result); + } + + /** + * @link http://propel.phpdb.org/trac/ticket/606 + */ + public function testAddJoinArrayMultiple() + { + $c = new Criteria(); + $c->clearSelectColumns()-> + addJoin( + array('TABLE_A.FOO_ID', 'TABLE_A.BAR'), + array('TABLE_B.ID', 'TABLE_B.BAZ'), + Criteria::LEFT_JOIN)-> + addSelectColumn("TABLE_A.ID"); + + $expect = 'SELECT TABLE_A.ID FROM TABLE_A' + .' LEFT JOIN TABLE_B ON (TABLE_A.FOO_ID=TABLE_B.ID AND TABLE_A.BAR=TABLE_B.BAZ)'; + $params = array(); + $result = BasePeer::createSelectSql($c, $params); + $this->assertEquals($expect, $result); + } + + /** + * Test the Criteria::addJoinMultiple() method with an implicit join + * + * @link http://propel.phpdb.org/trac/ticket/606 + */ + public function testAddJoinMultiple() + { + $c = new Criteria(); + $c-> + clearSelectColumns()-> + addMultipleJoin(array( + array('TABLE_A.FOO_ID', 'TABLE_B.ID'), + array('TABLE_A.BAR', 'TABLE_B.BAZ')))-> + addSelectColumn("TABLE_A.ID"); + + $expect = 'SELECT TABLE_A.ID FROM TABLE_A, TABLE_B ' + . 'WHERE TABLE_A.FOO_ID=TABLE_B.ID AND TABLE_A.BAR=TABLE_B.BAZ'; + $params = array(); + $result = BasePeer::createSelectSql($c, $params); + $this->assertEquals($expect, $result); + } + + /** + * Test the Criteria::addJoinMultiple() method with a value as second argument + * + * @link http://propel.phpdb.org/trac/ticket/606 + */ + public function testAddJoinMultipleValue() + { + $c = new Criteria(); + $c-> + clearSelectColumns()-> + addMultipleJoin(array( + array('TABLE_A.FOO_ID', 'TABLE_B.ID'), + array('TABLE_A.BAR', 3)))-> + addSelectColumn("TABLE_A.ID"); + + $expect = 'SELECT TABLE_A.ID FROM TABLE_A, TABLE_B ' + . 'WHERE TABLE_A.FOO_ID=TABLE_B.ID AND TABLE_A.BAR=3'; + $params = array(); + $result = BasePeer::createSelectSql($c, $params); + $this->assertEquals($expect, $result); + } + + /** + * Test the Criteria::addJoinMultiple() method with a joinType + * + * @link http://propel.phpdb.org/trac/ticket/606 + */ + public function testAddJoinMultipleWithJoinType() + { + $c = new Criteria(); + $c-> + clearSelectColumns()-> + addMultipleJoin(array( + array('TABLE_A.FOO_ID', 'TABLE_B.ID'), + array('TABLE_A.BAR', 'TABLE_B.BAZ')), + Criteria::LEFT_JOIN)-> + addSelectColumn("TABLE_A.ID"); + + $expect = 'SELECT TABLE_A.ID FROM TABLE_A ' + . 'LEFT JOIN TABLE_B ON (TABLE_A.FOO_ID=TABLE_B.ID AND TABLE_A.BAR=TABLE_B.BAZ)'; + $params = array(); + $result = BasePeer::createSelectSql($c, $params); + $this->assertEquals($expect, $result); + } + + /** + * Test the Criteria::addJoinMultiple() method with operator + * + * @link http://propel.phpdb.org/trac/ticket/606 + */ + public function testAddJoinMultipleWithOperator() + { + $c = new Criteria(); + $c-> + clearSelectColumns()-> + addMultipleJoin(array( + array('TABLE_A.FOO_ID', 'TABLE_B.ID', Criteria::GREATER_EQUAL), + array('TABLE_A.BAR', 'TABLE_B.BAZ', Criteria::LESS_THAN)))-> + addSelectColumn("TABLE_A.ID"); + + $expect = 'SELECT TABLE_A.ID FROM TABLE_A, TABLE_B ' + . 'WHERE TABLE_A.FOO_ID>=TABLE_B.ID AND TABLE_A.BARassertEquals($expect, $result); + } + + /** + * Test the Criteria::addJoinMultiple() method with join type and operator + * + * @link http://propel.phpdb.org/trac/ticket/606 + */ + public function testAddJoinMultipleWithJoinTypeAndOperator() + { + $c = new Criteria(); + $c-> + clearSelectColumns()-> + addMultipleJoin(array( + array('TABLE_A.FOO_ID', 'TABLE_B.ID', Criteria::GREATER_EQUAL), + array('TABLE_A.BAR', 'TABLE_B.BAZ', Criteria::LESS_THAN)), + Criteria::LEFT_JOIN)-> + addSelectColumn("TABLE_A.ID"); + + $expect = 'SELECT TABLE_A.ID FROM TABLE_A ' + . 'LEFT JOIN TABLE_B ON (TABLE_A.FOO_ID>=TABLE_B.ID AND TABLE_A.BARassertEquals($expect, $result); + } + + /** + * Test the Criteria::CUSTOM behavior. + */ + public function testCustomOperator() + { + $c = new Criteria(); + $c->addSelectColumn('A.COL'); + $c->add('A.COL', 'date_part(\'YYYY\', A.COL) = \'2007\'', Criteria::CUSTOM); + + $expected = "SELECT A.COL FROM A WHERE date_part('YYYY', A.COL) = '2007'"; + $params = array(); + $result = BasePeer::createSelectSql($c, $params); + $this->assertEquals($expected, $result); + } + + /** + * Tests adding duplicate joins. + * @link http://propel.phpdb.org/trac/ticket/613 + */ + public function testAddJoin_Duplicate() + { + $c = new Criteria(); + + $c->addJoin("tbl.COL1", "tbl.COL2", Criteria::LEFT_JOIN); + $c->addJoin("tbl.COL1", "tbl.COL2", Criteria::LEFT_JOIN); + $this->assertEquals(1, count($c->getJoins()), "Expected not to have duplciate LJOIN added."); + + $c->addJoin("tbl.COL1", "tbl.COL2", Criteria::RIGHT_JOIN); + $c->addJoin("tbl.COL1", "tbl.COL2", Criteria::RIGHT_JOIN); + $this->assertEquals(2, count($c->getJoins()), "Expected 1 new right join to be added."); + + $c->addJoin("tbl.COL1", "tbl.COL2"); + $c->addJoin("tbl.COL1", "tbl.COL2"); + $this->assertEquals(3, count($c->getJoins()), "Expected 1 new implicit join to be added."); + + $c->addJoin("tbl.COL3", "tbl.COL4"); + $this->assertEquals(4, count($c->getJoins()), "Expected new col join to be added."); + + } + + /** + * @link http://propel.phpdb.org/trac/ticket/634 + */ + public function testHasSelectClause() + { + $c = new Criteria(); + $c->addSelectColumn("foo"); + + $this->assertTrue($c->hasSelectClause()); + + $c = new Criteria(); + $c->addAsColumn("foo", "bar"); + + $this->assertTrue($c->hasSelectClause()); + } + + /** + * Tests including aliases in criterion objects. + * @link http://propel.phpdb.org/trac/ticket/636 + */ + public function testAliasInCriterion() + { + $c = new Criteria(); + $c->addAsColumn("column_alias", "tbl.COL1"); + $crit = $c->getNewCriterion("column_alias", "FOO"); + $this->assertNull($crit->getTable()); + $this->assertEquals("column_alias", $crit->getColumn()); + $c->addHaving($crit); // produces invalid SQL referring to '.olumn_alias' + } + + /** + * Test whether GROUP BY is being respected in equals() check. + * @link http://propel.phpdb.org/trac/ticket/674 + */ + public function testEqualsGroupBy() + { + $c1 = new Criteria(); + $c1->addGroupByColumn('GBY1'); + + $c2 = new Criteria(); + $c2->addGroupByColumn('GBY2'); + + $this->assertFalse($c2->equals($c1), "Expected Criteria NOT to be the same with different GROUP BY columns"); + + $c3 = new Criteria(); + $c3->addGroupByColumn('GBY1'); + $c4 = new Criteria(); + $c4->addGroupByColumn('GBY1'); + $this->assertTrue($c4->equals($c3), "Expected Criteria objects to match."); + } + + /** + * Test whether calling setDistinct twice puts in two distinct keywords or not. + * @link http://propel.phpdb.org/trac/ticket/716 + */ + public function testDoubleSelectModifiers() + { + $c = new Criteria(); + $c->setDistinct(); + $this->assertEquals(array(Criteria::DISTINCT), $c->getSelectModifiers(), 'Initial setDistinct works'); + $c->setDistinct(); + $this->assertEquals(array(Criteria::DISTINCT), $c->getSelectModifiers(), 'Calling setDistinct again leaves a single distinct'); + $c->setAll(); + $this->assertEquals(array(Criteria::ALL), $c->getSelectModifiers(), 'All keyword is swaps distinct out'); + $c->setAll(); + $this->assertEquals(array(Criteria::ALL), $c->getSelectModifiers(), 'Calling setAll leaves a single all'); + $c->setDistinct(); + $this->assertEquals(array(Criteria::DISTINCT), $c->getSelectModifiers(), 'All back to distinct works'); + + $c2 = new Criteria(); + $c2->setAll(); + $this->assertEquals(array(Criteria::ALL), $c2->getSelectModifiers(), 'Initial setAll works'); + } + + public function testAddSelectModifier() + { + $c = new Criteria(); + $c->setDistinct(); + $c->addSelectModifier('SQL_CALC_FOUND_ROWS'); + $this->assertEquals(array(Criteria::DISTINCT, 'SQL_CALC_FOUND_ROWS'), $c->getSelectModifiers(), 'addSelectModifier() adds a select modifier to the Criteria'); + $c->addSelectModifier('SQL_CALC_FOUND_ROWS'); + $this->assertEquals(array(Criteria::DISTINCT, 'SQL_CALC_FOUND_ROWS'), $c->getSelectModifiers(), 'addSelectModifier() adds a select modifier only once'); + $params = array(); + $result = BasePeer::createSelectSql($c, $params); + $this->assertEquals('SELECT DISTINCT SQL_CALC_FOUND_ROWS FROM ', $result, 'addSelectModifier() adds a modifier to the final query'); + } + + public function testClone() + { + $c1 = new Criteria(); + $c1->add('tbl.COL1', 'foo', Criteria::EQUAL); + $c2 = clone $c1; + $c2->addAnd('tbl.COL1', 'bar', Criteria::EQUAL); + $nbCrit = 0; + foreach ($c1->keys() as $key) { + foreach ($c1->getCriterion($key)->getAttachedCriterion() as $criterion) { + $nbCrit++; + } + } + $this->assertEquals(1, $nbCrit, 'cloning a Criteria clones its Criterions'); + } + + public function testComment() + { + $c = new Criteria(); + $this->assertNull($c->getComment(), 'Comment is null by default'); + $c2 = $c->setComment('foo'); + $this->assertEquals('foo', $c->getComment(), 'Comment is set by setComment()'); + $this->assertEquals($c, $c2, 'setComment() returns the current Criteria'); + $c->setComment(); + $this->assertNull($c->getComment(), 'Comment is reset by setComment(null)'); + } +} diff --git a/library/propel/test/testsuite/runtime/query/JoinTest.php b/library/propel/test/testsuite/runtime/query/JoinTest.php new file mode 100644 index 000000000..c3a324552 --- /dev/null +++ b/library/propel/test/testsuite/runtime/query/JoinTest.php @@ -0,0 +1,135 @@ +savedAdapter = Propel::getDB(null); + Propel::setDB(null, new DBSQLite()); + } + + protected function tearDown() + { + Propel::setDB(null, $this->savedAdapter); + parent::tearDown(); + } + + public function testEmptyConditions() + { + $j = new Join(); + $this->assertEquals(array(), $j->getConditions()); + } + + public function testAddCondition() + { + $j = new Join(); + $j->addCondition('foo', 'bar'); + $this->assertEquals('=', $j->getOperator()); + $this->assertEquals('foo', $j->getLeftColumn()); + $this->assertEquals('bar', $j->getRightColumn()); + } + + public function testGetConditions() + { + $j = new Join(); + $j->addCondition('foo', 'bar'); + $expect = array(array('left' => 'foo', 'operator' => '=', 'right' => 'bar')); + $this->assertEquals($expect, $j->getConditions()); + } + + public function testAddConditionWithOperator() + { + $j = new Join(); + $j->addCondition('foo', 'bar', '>='); + $expect = array(array('left' => 'foo', 'operator' => '>=', 'right' => 'bar')); + $this->assertEquals($expect, $j->getConditions()); + } + + public function testAddConditions() + { + $j = new Join(); + $j->addCondition('foo', 'bar'); + $j->addCondition('baz', 'bal'); + $expect = array( + array('left' => 'foo', 'operator' => '=', 'right' => 'bar'), + array('left' => 'baz', 'operator' => '=', 'right' => 'bal') + ); + $this->assertEquals(array('=', '='), $j->getOperators()); + $this->assertEquals(array('foo', 'baz'), $j->getLeftColumns()); + $this->assertEquals(array('bar', 'bal'), $j->getRightColumns()); + $this->assertEquals($expect, $j->getConditions()); + } + + public function testEmptyJoinType() + { + $j = new Join(); + $this->assertNull($j->getJoinType()); + } + + public function testSetJoinType() + { + $j = new Join(); + $j->setJoinType('foo'); + $this->assertEquals('foo', $j->getJoinType()); + } + + public function testSimpleConstructor() + { + $j = new Join('foo', 'bar', 'LEFT JOIN'); + $expect = array(array('left' => 'foo', 'operator' => '=', 'right' => 'bar')); + $this->assertEquals($expect, $j->getConditions()); + $this->assertEquals('LEFT JOIN', $j->getJoinType()); + } + + public function testCompositeeConstructor() + { + $j = new Join(array('foo1', 'foo2'), array('bar1', 'bar2'), 'LEFT JOIN'); + $expect = array( + array('left' => 'foo1', 'operator' => '=', 'right' => 'bar1'), + array('left' => 'foo2', 'operator' => '=', 'right' => 'bar2') + ); + $this->assertEquals($expect, $j->getConditions()); + $this->assertEquals('LEFT JOIN', $j->getJoinType()); + } + + public function testCountConditions() + { + $j = new Join(); + $this->assertEquals(0, $j->countConditions()); + $j->addCondition('foo', 'bar'); + $this->assertEquals(1, $j->countConditions()); + $j->addCondition('foo1', 'bar1'); + $this->assertEquals(2, $j->countConditions()); + + } + +} diff --git a/library/propel/test/testsuite/runtime/query/ModelCriteriaHooksTest.php b/library/propel/test/testsuite/runtime/query/ModelCriteriaHooksTest.php new file mode 100755 index 000000000..8c1107a87 --- /dev/null +++ b/library/propel/test/testsuite/runtime/query/ModelCriteriaHooksTest.php @@ -0,0 +1,196 @@ +find(); + $this->assertEquals(1, count($books), 'preSelect() can modify the Criteria before find() fires the query'); + + $c = new ModelCriteriaWithPreSelectHook('bookstore', 'Book'); + $nbBooks = $c->count(); + $this->assertEquals(1, $nbBooks, 'preSelect() can modify the Criteria before count() fires the query'); + } + + public function testPreDelete() + { + $c = new ModelCriteria('bookstore', 'Book'); + $books = $c->find(); + $count = count($books); + $book = $books->shift(); + + $c = new ModelCriteriaWithPreDeleteHook('bookstore', 'Book', 'b'); + $c->where('b.Id = ?', $book->getId()); + $nbBooks = $c->delete(); + $this->assertEquals(12, $nbBooks, 'preDelete() can change the return value of delete()'); + + $c = new ModelCriteria('bookstore', 'Book'); + $nbBooks = $c->count(); + $this->assertEquals($count, $nbBooks, 'preDelete() can bypass the row deletion'); + + $c = new ModelCriteriaWithPreDeleteHook('bookstore', 'Book'); + $nbBooks = $c->deleteAll(); + $this->assertEquals(12, $nbBooks, 'preDelete() can change the return value of deleteAll()'); + + $c = new ModelCriteria('bookstore', 'Book'); + $nbBooks = $c->count(); + $this->assertEquals($count, $nbBooks, 'preDelete() can bypass the row deletion'); + } + + public function testPostDelete() + { + $c = new ModelCriteria('bookstore', 'Book'); + $books = $c->find(); + $count = count($books); + $book = $books->shift(); + + $this->con->lastAffectedRows = 0; + + $c = new ModelCriteriaWithPostDeleteHook('bookstore', 'Book', 'b'); + $c->where('b.Id = ?', $book->getId()); + $nbBooks = $c->delete($this->con); + $this->assertEquals(1, $this->con->lastAffectedRows, 'postDelete() is called after delete()'); + + $this->con->lastAffectedRows = 0; + + $c = new ModelCriteriaWithPostDeleteHook('bookstore', 'Book'); + $nbBooks = $c->deleteAll($this->con); + $this->assertEquals(3, $this->con->lastAffectedRows, 'postDelete() is called after deleteAll()'); + } + + public function testPreAndPostDelete() + { + $c = new ModelCriteria('bookstore', 'Book'); + $books = $c->find(); + $count = count($books); + $book = $books->shift(); + + $this->con->lastAffectedRows = 0; + + $c = new ModelCriteriaWithPreAndPostDeleteHook('bookstore', 'Book', 'b'); + $c->where('b.Id = ?', $book->getId()); + $nbBooks = $c->delete($this->con); + $this->assertEquals(12, $this->con->lastAffectedRows, 'postDelete() is called after delete() even if preDelete() returns not null'); + + $this->con->lastAffectedRows = 0; + + $c = new ModelCriteriaWithPreAndPostDeleteHook('bookstore', 'Book'); + $nbBooks = $c->deleteAll($this->con); + $this->assertEquals(12, $this->con->lastAffectedRows, 'postDelete() is called after deleteAll() even if preDelete() returns not null'); + } + + public function testPreUpdate() + { + $c = new ModelCriteriaWithPreUpdateHook('bookstore', 'Book', 'b'); + $c->where('b.Title = ?', 'Don Juan'); + $nbBooks = $c->update(array('Title' => 'foo')); + + $c = new ModelCriteriaWithPreUpdateHook('bookstore', 'Book', 'b'); + $c->where('b.Title = ?', 'foo'); + $book = $c->findOne(); + + $this->assertEquals('1234', $book->getISBN(), 'preUpdate() can modify the values'); + } + + public function testPostUpdate() + { + $this->con->lastAffectedRows = 0; + + $c = new ModelCriteriaWithPostUpdateHook('bookstore', 'Book', 'b'); + $c->where('b.Title = ?', 'Don Juan'); + $nbBooks = $c->update(array('Title' => 'foo'), $this->con); + $this->assertEquals(1, $this->con->lastAffectedRows, 'postUpdate() is called after update()'); + } + + public function testPreAndPostUpdate() + { + $this->con->lastAffectedRows = 0; + + $c = new ModelCriteriaWithPreAndPostUpdateHook('bookstore', 'Book', 'b'); + $c->where('b.Title = ?', 'Don Juan'); + $nbBooks = $c->update(array('Title' => 'foo'), $this->con); + $this->assertEquals(52, $this->con->lastAffectedRows, 'postUpdate() is called after update() even if preUpdate() returns not null'); + } +} + +class ModelCriteriaWithPreSelectHook extends ModelCriteria +{ + public function preSelect(PropelPDO $con) + { + $this->where($this->getModelAliasOrName() . '.Title = ?', 'Don Juan'); + } +} + +class ModelCriteriaWithPreDeleteHook extends ModelCriteria +{ + public function preDelete(PropelPDO $con) + { + return 12; + } +} + +class ModelCriteriaWithPostDeleteHook extends ModelCriteria +{ + public function postDelete($affectedRows, PropelPDO $con) + { + $con->lastAffectedRows = $affectedRows; + } +} + +class ModelCriteriaWithPreAndPostDeleteHook extends ModelCriteriaWithPostDeleteHook +{ + public function preDelete(PropelPDO $con) + { + return 12; + } +} + +class ModelCriteriaWithPreUpdateHook extends ModelCriteria +{ + public function preUpdate(&$values, PropelPDO $con, $forceIndividualSaves = false) + { + $values['ISBN'] = '1234'; + } +} + +class ModelCriteriaWithPostUpdateHook extends ModelCriteria +{ + public function postUpdate($affectedRows, PropelPDO $con) + { + $con->lastAffectedRows = $affectedRows; + } +} + +class ModelCriteriaWithPreAndPostUpdateHook extends ModelCriteriaWithPostUpdateHook +{ + public function preUpdate(&$values, PropelPDO $con, $forceIndividualSaves = false) + { + return 52; + } +} \ No newline at end of file diff --git a/library/propel/test/testsuite/runtime/query/ModelCriteriaTest.php b/library/propel/test/testsuite/runtime/query/ModelCriteriaTest.php new file mode 100644 index 000000000..fdd8efb69 --- /dev/null +++ b/library/propel/test/testsuite/runtime/query/ModelCriteriaTest.php @@ -0,0 +1,2068 @@ +assertEquals($expectedSql, $result, $message); + $this->assertEquals($expectedParams, $params, $message); + } + + public function testGetModelName() + { + $c = new ModelCriteria('bookstore', 'Book'); + $this->assertEquals('Book', $c->getModelName(), 'getModelName() returns the name of the class associated to the model class'); + } + + public function testGetModelPeerName() + { + $c = new ModelCriteria('bookstore', 'Book'); + $this->assertEquals('BookPeer', $c->getModelPeerName(), 'getModelPeerName() returns the name of the Peer class associated to the model class'); + } + + public function testFormatter() + { + $c = new ModelCriteria('bookstore', 'Book'); + $this->assertTrue($c->getFormatter() instanceof PropelFormatter, 'getFormatter() returns a PropelFormatter instance'); + + $c = new ModelCriteria('bookstore', 'Book'); + $c->setFormatter(ModelCriteria::FORMAT_STATEMENT); + $this->assertTrue($c->getFormatter() instanceof PropelStatementFormatter, 'setFormatter() accepts the name of a PropelFormatter class'); + + try { + $c->setFormatter('Book'); + $this->fail('setFormatter() throws an exception when passed the name of a class not extending PropelFormatter'); + } catch(PropelException $e) { + $this->assertTrue(true, 'setFormatter() throws an exception when passed the name of a class not extending PropelFormatter'); + } + $c = new ModelCriteria('bookstore', 'Book'); + $formatter = new PropelStatementFormatter(); + $c->setFormatter($formatter); + $this->assertTrue($c->getFormatter() instanceof PropelStatementFormatter, 'setFormatter() accepts a PropelFormatter instance'); + + try { + $formatter = new Book(); + $c->setFormatter($formatter); + $this->fail('setFormatter() throws an exception when passed an object not extending PropelFormatter'); + } catch(PropelException $e) { + $this->assertTrue(true, 'setFormatter() throws an exception when passedan object not extending PropelFormatter'); + } + + } + + public static function conditionsForTestReplaceNames() + { + return array( + array('Book.Title = ?', 'Title', 'book.TITLE = ?'), // basic case + array('Book.Title=?', 'Title', 'book.TITLE=?'), // without spaces + array('Book.Id<= ?', 'Id', 'book.ID<= ?'), // with non-equal comparator + array('Book.AuthorId LIKE ?', 'AuthorId', 'book.AUTHOR_ID LIKE ?'), // with SQL keyword separator + array('(Book.AuthorId) LIKE ?', 'AuthorId', '(book.AUTHOR_ID) LIKE ?'), // with parenthesis + array('(Book.Id*1.5)=1', 'Id', '(book.ID*1.5)=1'), // ignore numbers + array('1=1', null, '1=1'), // with no name + array('', null, '') // with empty string + ); + } + + /** + * @dataProvider conditionsForTestReplaceNames + */ + public function testReplaceNames($origClause, $columnPhpName = false, $modifiedClause) + { + $c = new TestableModelCriteria('bookstore', 'Book'); + $c->replaceNames($origClause); + $columns = $c->replacedColumns; + if ($columnPhpName) { + $this->assertEquals(array(BookPeer::getTableMap()->getColumnByPhpName($columnPhpName)), $columns); + } + $this->assertEquals($modifiedClause, $origClause); + } + + public static function conditionsForTestReplaceMultipleNames() + { + return array( + array('(Book.Id+Book.Id)=1', array('Id', 'Id'), '(book.ID+book.ID)=1'), // match multiple names + array('CONCAT(Book.Title,"Book.Id")= ?', array('Title', 'Id'), 'CONCAT(book.TITLE,"Book.Id")= ?'), // ignore names in strings + array('CONCAT(Book.Title," Book.Id ")= ?', array('Title', 'Id'), 'CONCAT(book.TITLE," Book.Id ")= ?'), // ignore names in strings + array('MATCH (Book.Title,Book.ISBN) AGAINST (?)', array('Title', 'ISBN'), 'MATCH (book.TITLE,book.ISBN) AGAINST (?)'), + ); + } + + /** + * @dataProvider conditionsForTestReplaceMultipleNames + */ + public function testReplaceMultipleNames($origClause, $expectedColumns, $modifiedClause) + { + $c = new TestableModelCriteria('bookstore', 'Book'); + $c->replaceNames($origClause); + $foundColumns = $c->replacedColumns; + foreach ($foundColumns as $column) { + $expectedColumn = BookPeer::getTableMap()->getColumnByPhpName(array_shift($expectedColumns)); + $this->assertEquals($expectedColumn, $column); + } + $this->assertEquals($modifiedClause, $origClause); + } + + public function testTableAlias() + { + $c = new ModelCriteria('bookstore', 'Book'); + $c->setModelAlias('b'); + $c->where('b.Title = ?', 'foo'); + + $sql = "SELECT FROM `book` WHERE book.TITLE = :p1"; + $params = array( + array('table' => 'book', 'column' => 'TITLE', 'value' => 'foo'), + ); + $this->assertCriteriaTranslation($c, $sql, $params, 'setModelAlias() allows the definition of the alias after constrution'); + + $c = new ModelCriteria('bookstore', 'Book', 'b'); + $c->where('b.Title = ?', 'foo'); + + $sql = "SELECT FROM `book` WHERE book.TITLE = :p1"; + $params = array( + array('table' => 'book', 'column' => 'TITLE', 'value' => 'foo'), + ); + $this->assertCriteriaTranslation($c, $sql, $params, 'A ModelCriteria accepts a model name with an alias'); + } + + public function testTrueTableAlias() + { + $c = new ModelCriteria('bookstore', 'Book'); + $c->setModelAlias('b', true); + $c->where('b.Title = ?', 'foo'); + $c->join('b.Author a'); + $c->where('a.FirstName = ?', 'john'); + + + $sql = "SELECT FROM `book` `b` INNER JOIN author a ON (b.AUTHOR_ID=a.ID) WHERE b.TITLE = :p1 AND a.FIRST_NAME = :p2"; + $params = array( + array('table' => 'book', 'column' => 'TITLE', 'value' => 'foo'), + array('table' => 'author', 'column' => 'FIRST_NAME', 'value' => 'john'), + ); + $this->assertCriteriaTranslation($c, $sql, $params, 'setModelAlias() allows the definition of a true SQL alias after constrution'); + } + + public function testCondition() + { + $c = new ModelCriteria('bookstore', 'Book'); + $c->condition('cond1', 'Book.Title <> ?', 'foo'); + $c->condition('cond2', 'Book.Title like ?', '%bar%'); + $c->combine(array('cond1', 'cond2'), 'or'); + + $sql = "SELECT FROM `book` WHERE (book.TITLE <> :p1 OR book.TITLE like :p2)"; + $params = array( + array('table' => 'book', 'column' => 'TITLE', 'value' => 'foo'), + array('table' => 'book', 'column' => 'TITLE', 'value' => '%bar%'), + ); + $this->assertCriteriaTranslation($c, $sql, $params, 'condition() can store condition for later combination'); + } + + public static function conditionsForTestWhere() + { + return array( + array('Book.Title = ?', 'foo', 'book.TITLE = :p1', array(array('table' => 'book', 'column' => 'TITLE', 'value' => 'foo'))), + array('Book.AuthorId = ?', 12, 'book.AUTHOR_ID = :p1', array(array('table' => 'book', 'column' => 'AUTHOR_ID', 'value' => 12))), + array('Book.AuthorId IS NULL', null, 'book.AUTHOR_ID IS NULL', array()), + array('Book.Id BETWEEN ? AND ?', array(3, 4), 'book.ID BETWEEN :p1 AND :p2', array(array('table' => 'book', 'column' => 'ID', 'value' => 3), array('table' => 'book', 'column' => 'ID', 'value' => 4))), + array('Book.Id betWEen ? and ?', array(3, 4), 'book.ID betWEen :p1 and :p2', array(array('table' => 'book', 'column' => 'ID', 'value' => 3), array('table' => 'book', 'column' => 'ID', 'value' => 4))), + array('Book.Id IN ?', array(1, 2, 3), 'book.ID IN (:p1,:p2,:p3)', array(array('table' => 'book', 'column' => 'ID', 'value' => 1), array('table' => 'book', 'column' => 'ID', 'value' => 2), array('table' => 'book', 'column' => 'ID', 'value' => 3))), + array('Book.Id in ?', array(1, 2, 3), 'book.ID in (:p1,:p2,:p3)', array(array('table' => 'book', 'column' => 'ID', 'value' => 1), array('table' => 'book', 'column' => 'ID', 'value' => 2), array('table' => 'book', 'column' => 'ID', 'value' => 3))), + array('Book.Id IN ?', array(), '1<>1', array()), + array('Book.Id not in ?', array(), '1=1', array()), + array('UPPER(Book.Title) = ?', 'foo', 'UPPER(book.TITLE) = :p1', array(array('table' => 'book', 'column' => 'TITLE', 'value' => 'foo'))), + array('MATCH (Book.Title,Book.ISBN) AGAINST (?)', 'foo', 'MATCH (book.TITLE,book.ISBN) AGAINST (:p1)', array(array('table' => 'book', 'column' => 'TITLE', 'value' => 'foo'))), + ); + } + + /** + * @dataProvider conditionsForTestWhere + */ + public function testWhere($clause, $value, $sql, $params) + { + $c = new ModelCriteria('bookstore', 'Book'); + $c->where($clause, $value); + $sql = 'SELECT FROM `book` WHERE ' . $sql; + $this->assertCriteriaTranslation($c, $sql, $params, 'where() accepts a string clause'); + } + + public function testWhereTwiceSameColumn() + { + $c = new ModelCriteria('bookstore', 'Book'); + $c->where('Book.Id IN ?', array(1, 2, 3)); + $c->where('Book.Id <> ?', 5); + $params = array( + array('table' => 'book', 'column' => 'ID', 'value' => '1'), + array('table' => 'book', 'column' => 'ID', 'value' => '2'), + array('table' => 'book', 'column' => 'ID', 'value' => '3'), + array('table' => 'book', 'column' => 'ID', 'value' => '5'), + ); + $sql = 'SELECT FROM `book` WHERE (book.ID IN (:p1,:p2,:p3) AND book.ID <> :p4)'; + $this->assertCriteriaTranslation($c, $sql, $params, 'where() adds clauses on the same column correctly'); + } + + public function testWhereConditions() + { + $c = new ModelCriteria('bookstore', 'Book'); + $c->condition('cond1', 'Book.Title <> ?', 'foo'); + $c->condition('cond2', 'Book.Title like ?', '%bar%'); + $c->where(array('cond1', 'cond2')); + + $sql = "SELECT FROM `book` WHERE (book.TITLE <> :p1 AND book.TITLE like :p2)"; + $params = array( + array('table' => 'book', 'column' => 'TITLE', 'value' => 'foo'), + array('table' => 'book', 'column' => 'TITLE', 'value' => '%bar%'), + ); + $this->assertCriteriaTranslation($c, $sql, $params, 'where() accepts an array of named conditions'); + + $c = new ModelCriteria('bookstore', 'Book'); + $c->condition('cond1', 'Book.Title <> ?', 'foo'); + $c->condition('cond2', 'Book.Title like ?', '%bar%'); + $c->where(array('cond1', 'cond2'), Criteria::LOGICAL_OR); + + $sql = "SELECT FROM `book` WHERE (book.TITLE <> :p1 OR book.TITLE like :p2)"; + $this->assertCriteriaTranslation($c, $sql, $params, 'where() accepts an array of named conditions with operator'); + } + + public function testWhereNoReplacement() + { + $c = new ModelCriteria('bookstore', 'Book', 'b'); + $c->where('b.Title = ?', 'foo'); + $c->where('1=1'); + + $sql = "SELECT FROM `book` WHERE book.TITLE = :p1 AND 1=1"; + $params = array( + array('table' => 'book', 'column' => 'TITLE', 'value' => 'foo'), + ); + $this->assertCriteriaTranslation($c, $sql, $params, 'where() results in a Criteria::CUSTOM if no column name is matched'); + + $c = new ModelCriteria('bookstore', 'Book'); + try { + $c->where('b.Title = ?', 'foo'); + $this->fail('where() throws an exception when it finds a ? but cannot determine a column'); + } catch (PropelException $e) { + $this->assertTrue(true, 'where() throws an exception when it finds a ? but cannot determine a column'); + } + } + + public function testWhereFunction() + { + $c = new ModelCriteria('bookstore', 'Book', 'b'); + $c->where('UPPER(b.Title) = ?', 'foo'); + + $sql = "SELECT FROM `book` WHERE UPPER(book.TITLE) = :p1"; + $params = array( + array('table' => 'book', 'column' => 'TITLE', 'value' => 'foo'), + ); + $this->assertCriteriaTranslation($c, $sql, $params, 'where() accepts a complex calculation'); + } + + public function testOrWhere() + { + $c = new ModelCriteria('bookstore', 'Book'); + $c->where('Book.Title <> ?', 'foo'); + $c->orWhere('Book.Title like ?', '%bar%'); + + $sql = "SELECT FROM `book` WHERE (book.TITLE <> :p1 OR book.TITLE like :p2)"; + $params = array( + array('table' => 'book', 'column' => 'TITLE', 'value' => 'foo'), + array('table' => 'book', 'column' => 'TITLE', 'value' => '%bar%'), + ); + $this->assertCriteriaTranslation($c, $sql, $params, 'orWhere() combines the clause with the previous one using OR'); + } + + public function testOrWhereConditions() + { + $c = new ModelCriteria('bookstore', 'Book'); + $c->where('Book.Id = ?', 12); + $c->condition('cond1', 'Book.Title <> ?', 'foo'); + $c->condition('cond2', 'Book.Title like ?', '%bar%'); + $c->orWhere(array('cond1', 'cond2')); + + $sql = "SELECT FROM `book` WHERE (book.ID = :p1 OR (book.TITLE <> :p2 AND book.TITLE like :p3))"; + $params = array( + array('table' => 'book', 'column' => 'ID', 'value' => 12), + array('table' => 'book', 'column' => 'TITLE', 'value' => 'foo'), + array('table' => 'book', 'column' => 'TITLE', 'value' => '%bar%'), + ); + $this->assertCriteriaTranslation($c, $sql, $params, 'orWhere() accepts an array of named conditions'); + + $c = new ModelCriteria('bookstore', 'Book'); + $c->where('Book.Id = ?', 12); + $c->condition('cond1', 'Book.Title <> ?', 'foo'); + $c->condition('cond2', 'Book.Title like ?', '%bar%'); + $c->orWhere(array('cond1', 'cond2'), Criteria::LOGICAL_OR); + + $sql = "SELECT FROM `book` WHERE (book.ID = :p1 OR (book.TITLE <> :p2 OR book.TITLE like :p3))"; + $this->assertCriteriaTranslation($c, $sql, $params, 'orWhere() accepts an array of named conditions with operator'); + } + + public function testMixedCriteria() + { + $c = new ModelCriteria('bookstore', 'Book'); + $c->where('Book.Title = ?', 'foo'); + $c->add(BookPeer::ID, array(1, 2), Criteria::IN); + + $sql = 'SELECT FROM `book` WHERE book.TITLE = :p1 AND book.ID IN (:p2,:p3)'; + $params = array( + array('table' => 'book', 'column' => 'TITLE', 'value' => 'foo'), + array('table' => 'book', 'column' => 'ID', 'value' => 1), + array('table' => 'book', 'column' => 'ID', 'value' => 2) + ); + $this->assertCriteriaTranslation($c, $sql, $params, 'ModelCriteria accepts Criteria operators'); + } + + public function testFilterBy() + { + $c = new ModelCriteria('bookstore', 'Book'); + $c->filterBy('Title', 'foo'); + + $sql = 'SELECT FROM `book` WHERE book.TITLE=:p1'; + $params = array( + array('table' => 'book', 'column' => 'TITLE', 'value' => 'foo'), + ); + $this->assertCriteriaTranslation($c, $sql, $params, 'filterBy() accepts a simple column name'); + + $c = new ModelCriteria('bookstore', 'Book'); + $c->filterBy('Title', 'foo', Criteria::NOT_EQUAL); + + $sql = 'SELECT FROM `book` WHERE book.TITLE<>:p1'; + $params = array( + array('table' => 'book', 'column' => 'TITLE', 'value' => 'foo'), + ); + $this->assertCriteriaTranslation($c, $sql, $params, 'filterBy() accepts a sicustom comparator'); + + $c = new ModelCriteria('bookstore', 'Book', 'b'); + $c->filterBy('Title', 'foo'); + + $sql = 'SELECT FROM `book` WHERE book.TITLE=:p1'; + $params = array( + array('table' => 'book', 'column' => 'TITLE', 'value' => 'foo'), + ); + $this->assertCriteriaTranslation($c, $sql, $params, 'filterBy() accepts a simple column name, even if initialized with an alias'); + } + + public function testHaving() + { + $c = new ModelCriteria('bookstore', 'Book'); + $c->having('Book.Title <> ?', 'foo'); + + $sql = "SELECT FROM HAVING book.TITLE <> :p1"; + $params = array( + array('table' => 'book', 'column' => 'TITLE', 'value' => 'foo'), + ); + $this->assertCriteriaTranslation($c, $sql, $params, 'having() accepts a string clause'); + } + + public function testHavingConditions() + { + $c = new ModelCriteria('bookstore', 'Book'); + $c->condition('cond1', 'Book.Title <> ?', 'foo'); + $c->condition('cond2', 'Book.Title like ?', '%bar%'); + $c->having(array('cond1', 'cond2')); + + $sql = "SELECT FROM HAVING (book.TITLE <> :p1 AND book.TITLE like :p2)"; + $params = array( + array('table' => 'book', 'column' => 'TITLE', 'value' => 'foo'), + array('table' => 'book', 'column' => 'TITLE', 'value' => '%bar%'), + ); + $this->assertCriteriaTranslation($c, $sql, $params, 'having() accepts an array of named conditions'); + + $c = new ModelCriteria('bookstore', 'Book'); + $c->condition('cond1', 'Book.Title <> ?', 'foo'); + $c->condition('cond2', 'Book.Title like ?', '%bar%'); + $c->having(array('cond1', 'cond2'), Criteria::LOGICAL_OR); + + $sql = "SELECT FROM HAVING (book.TITLE <> :p1 OR book.TITLE like :p2)"; + $this->assertCriteriaTranslation($c, $sql, $params, 'having() accepts an array of named conditions with an operator'); + } + + public function testOrderBy() + { + $c = new ModelCriteria('bookstore', 'Book'); + $c->orderBy('Book.Title'); + + $sql = 'SELECT FROM ORDER BY book.TITLE ASC'; + $params = array(); + $this->assertCriteriaTranslation($c, $sql, $params, 'orderBy() accepts a column name and adds an ORDER BY clause'); + + $c = new ModelCriteria('bookstore', 'Book'); + $c->orderBy('Book.Title', 'desc'); + + $sql = 'SELECT FROM ORDER BY book.TITLE DESC'; + $this->assertCriteriaTranslation($c, $sql, $params, 'orderBy() accepts an order parameter'); + + $c = new ModelCriteria('bookstore', 'Book'); + try { + $c->orderBy('Book.Foo'); + $this->fail('orderBy() throws an exception when called with an unkown column name'); + } catch (PropelException $e) { + $this->assertTrue(true, 'orderBy() throws an exception when called with an unkown column name'); + } + $c = new ModelCriteria('bookstore', 'Book'); + try { + $c->orderBy('Book.Title', 'foo'); + $this->fail('orderBy() throws an exception when called with an unkown order'); + } catch (PropelException $e) { + $this->assertTrue(true, 'orderBy() throws an exception when called with an unkown order'); + } + } + + public function testOrderBySimpleColumn() + { + $c = new ModelCriteria('bookstore', 'Book'); + $c->orderBy('Title'); + + $sql = 'SELECT FROM ORDER BY book.TITLE ASC'; + $params = array(); + $this->assertCriteriaTranslation($c, $sql, $params, 'orderBy() accepts a simple column name and adds an ORDER BY clause'); + } + + public function testOrderByAlias() + { + $c = new ModelCriteria('bookstore', 'Book'); + $c->addAsColumn('t', BookPeer::TITLE); + $c->orderBy('t'); + + $sql = 'SELECT book.TITLE AS t FROM ORDER BY t ASC'; + $params = array(); + $this->assertCriteriaTranslation($c, $sql, $params, 'orderBy() accepts a column alias and adds an ORDER BY clause'); + } + + public function testGroupBy() + { + $c = new ModelCriteria('bookstore', 'Book'); + $c->groupBy('Book.AuthorId'); + + $sql = 'SELECT FROM GROUP BY book.AUTHOR_ID'; + $params = array(); + $this->assertCriteriaTranslation($c, $sql, $params, 'groupBy() accepts a column name and adds a GROUP BY clause'); + + $c = new ModelCriteria('bookstore', 'Book'); + try { + $c->groupBy('Book.Foo'); + $this->fail('groupBy() throws an exception when called with an unkown column name'); + } catch (PropelException $e) { + $this->assertTrue(true, 'groupBy() throws an exception when called with an unkown column name'); + } + } + + public function testGroupBySimpleColumn() + { + $c = new ModelCriteria('bookstore', 'Book'); + $c->groupBy('AuthorId'); + + $sql = 'SELECT FROM GROUP BY book.AUTHOR_ID'; + $params = array(); + $this->assertCriteriaTranslation($c, $sql, $params, 'groupBy() accepts a simple column name and adds a GROUP BY clause'); + } + + public function testGroupByAlias() + { + $c = new ModelCriteria('bookstore', 'Book'); + $c->addAsColumn('t', BookPeer::TITLE); + $c->groupBy('t'); + + $sql = 'SELECT book.TITLE AS t FROM GROUP BY t'; + $params = array(); + $this->assertCriteriaTranslation($c, $sql, $params, 'groupBy() accepts a column alias and adds a GROUP BY clause'); + } + + public function testDistinct() + { + $c = new ModelCriteria('bookstore', 'Book'); + $c->distinct(); + $sql = 'SELECT DISTINCT FROM '; + $params = array(); + $this->assertCriteriaTranslation($c, $sql, $params, 'distinct() adds a DISTINCT clause'); + } + + public function testLimit() + { + $c = new ModelCriteria('bookstore', 'Book'); + $c->limit(10); + $sql = 'SELECT FROM LIMIT 10'; + $params = array(); + $this->assertCriteriaTranslation($c, $sql, $params, 'limit() adds a LIMIT clause'); + } + + public function testOffset() + { + $c = new ModelCriteria('bookstore', 'Book'); + $c->limit(50); + $c->offset(10); + $sql = 'SELECT FROM LIMIT 10, 50'; + $params = array(); + $this->assertCriteriaTranslation($c, $sql, $params, 'offset() adds an OFFSET clause'); + } + + public function testJoin() + { + $c = new ModelCriteria('bookstore', 'Book'); + $c->join('Book.Author'); + $sql = 'SELECT FROM `book` INNER JOIN author ON (book.AUTHOR_ID=author.ID)'; + $params = array(); + $this->assertCriteriaTranslation($c, $sql, $params, 'join() uses a relation to guess the columns'); + + $c = new ModelCriteria('bookstore', 'Book'); + try { + $c->join('Book.Foo'); + $this->fail('join() throws an exception when called with a non-existing relation'); + } catch (PropelException $e) { + $this->assertTrue(true, 'join() throws an exception when called with a non-existing relation'); + } + + $c = new ModelCriteria('bookstore', 'Book'); + $c->join('Book.Author'); + $c->where('Author.FirstName = ?', 'Leo'); + $sql = 'SELECT FROM INNER JOIN author ON (book.AUTHOR_ID=author.ID) WHERE author.FIRST_NAME = :p1'; + $params = array( + array('table' => 'author', 'column' => 'FIRST_NAME', 'value' => 'Leo'), + ); + $this->assertCriteriaTranslation($c, $sql, $params, 'join() uses a relation to guess the columns'); + + $c = new ModelCriteria('bookstore', 'Book'); + $c->join('Author'); + $c->where('Author.FirstName = ?', 'Leo'); + $sql = 'SELECT FROM INNER JOIN author ON (book.AUTHOR_ID=author.ID) WHERE author.FIRST_NAME = :p1'; + $params = array( + array('table' => 'author', 'column' => 'FIRST_NAME', 'value' => 'Leo'), + ); + $this->assertCriteriaTranslation($c, $sql, $params, 'join() uses the current model name when given a simple relation name'); + } + + public function testJoinQuery() + { + $con = Propel::getConnection(BookPeer::DATABASE_NAME); + BookstoreDataPopulator::depopulate($con); + BookstoreDataPopulator::populate($con); + + $c = new ModelCriteria('bookstore', 'Book'); + $c->join('Book.Author'); + $c->where('Author.FirstName = ?', 'Neal'); + $books = BookPeer::doSelect($c); + $expectedSQL = "SELECT book.ID, book.TITLE, book.ISBN, book.PRICE, book.PUBLISHER_ID, book.AUTHOR_ID FROM `book` INNER JOIN author ON (book.AUTHOR_ID=author.ID) WHERE author.FIRST_NAME = 'Neal'"; + $this->assertEquals($expectedSQL, $con->getLastExecutedQuery(), 'join() issues a real JOIN query'); + $this->assertEquals(1, count($books), 'join() issues a real JOIN query'); + } + + public function testJoinRelationName() + { + $c = new ModelCriteria('bookstore', 'BookstoreEmployee'); + $c->join('BookstoreEmployee.Supervisor'); + $sql = 'SELECT FROM INNER JOIN bookstore_employee ON (bookstore_employee.SUPERVISOR_ID=bookstore_employee.ID)'; + $params = array(); + $this->assertCriteriaTranslation($c, $sql, $params, 'join() uses relation names as defined in schema.xml'); + } + + public function testJoinComposite() + { + $c = new ModelCriteria('bookstore', 'ReaderFavorite'); + $c->join('ReaderFavorite.BookOpinion'); + $sql = 'SELECT FROM `reader_favorite` INNER JOIN book_opinion ON (reader_favorite.BOOK_ID=book_opinion.BOOK_ID AND reader_favorite.READER_ID=book_opinion.READER_ID)'; + $params = array(); + $this->assertCriteriaTranslation($c, $sql, $params, 'join() knows how to create a JOIN clause for relationships with composite fkeys'); + } + + public function testJoinType() + { + $c = new ModelCriteria('bookstore', 'Book'); + $c->join('Book.Author'); + $sql = 'SELECT FROM `book` INNER JOIN author ON (book.AUTHOR_ID=author.ID)'; + $params = array(); + $this->assertCriteriaTranslation($c, $sql, $params, 'join() adds an INNER JOIN by default'); + + $c = new ModelCriteria('bookstore', 'Book'); + $c->join('Book.Author', Criteria::INNER_JOIN); + $sql = 'SELECT FROM `book` INNER JOIN author ON (book.AUTHOR_ID=author.ID)'; + $params = array(); + $this->assertCriteriaTranslation($c, $sql, $params, 'join() adds an INNER JOIN by default'); + + $c = new ModelCriteria('bookstore', 'Book'); + $c->join('Book.Author', Criteria::LEFT_JOIN); + $sql = 'SELECT FROM `book` LEFT JOIN author ON (book.AUTHOR_ID=author.ID)'; + $params = array(); + $this->assertCriteriaTranslation($c, $sql, $params, 'join() can add a LEFT JOIN'); + + $c = new ModelCriteria('bookstore', 'Book'); + $c->join('Book.Author', Criteria::RIGHT_JOIN); + $sql = 'SELECT FROM `book` RIGHT JOIN author ON (book.AUTHOR_ID=author.ID)'; + $params = array(); + $this->assertCriteriaTranslation($c, $sql, $params, 'join() can add a RIGHT JOIN'); + + $c = new ModelCriteria('bookstore', 'Book'); + $c->join('Book.Author', 'incorrect join'); + $sql = 'SELECT FROM `book` incorrect join author ON (book.AUTHOR_ID=author.ID)'; + $params = array(); + $this->assertCriteriaTranslation($c, $sql, $params, 'join() accepts any join string'); + } + + public function testJoinDirection() + { + $c = new ModelCriteria('bookstore', 'Book'); + $c->join('Book.Author'); + $sql = 'SELECT FROM `book` INNER JOIN author ON (book.AUTHOR_ID=author.ID)'; + $params = array(); + $this->assertCriteriaTranslation($c, $sql, $params, 'join() adds a JOIN clause correctly for many to one relationship'); + + $c = new ModelCriteria('bookstore', 'Author'); + $c->join('Author.Book'); + $sql = 'SELECT FROM `author` INNER JOIN book ON (author.ID=book.AUTHOR_ID)'; + $params = array(); + $this->assertCriteriaTranslation($c, $sql, $params, 'join() adds a JOIN clause correctly for one to many relationship'); + + $c = new ModelCriteria('bookstore', 'BookstoreEmployee'); + $c->join('BookstoreEmployee.BookstoreEmployeeAccount'); + $sql = 'SELECT FROM `bookstore_employee` INNER JOIN bookstore_employee_account ON (bookstore_employee.ID=bookstore_employee_account.EMPLOYEE_ID)'; + $params = array(); + $this->assertCriteriaTranslation($c, $sql, $params, 'join() adds a JOIN clause correctly for one to one relationship'); + + $c = new ModelCriteria('bookstore', 'BookstoreEmployeeAccount'); + $c->join('BookstoreEmployeeAccount.BookstoreEmployee'); + $sql = 'SELECT FROM `bookstore_employee_account` INNER JOIN bookstore_employee ON (bookstore_employee_account.EMPLOYEE_ID=bookstore_employee.ID)'; + $params = array(); + $this->assertCriteriaTranslation($c, $sql, $params, 'join() adds a JOIN clause correctly for one to one relationship'); + } + + public function testJoinSeveral() + { + $c = new ModelCriteria('bookstore', 'Author'); + $c->join('Author.Book'); + $c->join('Book.Publisher'); + $c->where('Publisher.Name = ?', 'foo'); + $sql = 'SELECT FROM INNER JOIN book ON (author.ID=book.AUTHOR_ID) INNER JOIN publisher ON (book.PUBLISHER_ID=publisher.ID) WHERE publisher.NAME = :p1'; + $params = array( + array('table' => 'publisher', 'column' => 'NAME', 'value' => 'foo'), + ); + $this->assertCriteriaTranslation($c, $sql, $params, 'join() can guess relationships from related tables'); + } + + public function testJoinAlias() + { + $c = new ModelCriteria('bookstore', 'Book', 'b'); + $c->join('b.Author'); + $sql = 'SELECT FROM `book` INNER JOIN author ON (book.AUTHOR_ID=author.ID)'; + $params = array(); + $this->assertCriteriaTranslation($c, $sql, $params, 'join() supports relation on main alias'); + + $c = new ModelCriteria('bookstore', 'Book', 'b'); + $c->join('Author'); + $sql = 'SELECT FROM `book` INNER JOIN author ON (book.AUTHOR_ID=author.ID)'; + $params = array(); + $this->assertCriteriaTranslation($c, $sql, $params, 'join() can use a simple relation name when the model has an alias'); + + $c = new ModelCriteria('bookstore', 'Book'); + $c->join('Book.Author a'); + $sql = 'SELECT FROM `book` INNER JOIN author a ON (book.AUTHOR_ID=a.ID)'; + $params = array(); + $this->assertCriteriaTranslation($c, $sql, $params, 'join() supports relation alias'); + + $c = new ModelCriteria('bookstore', 'Book', 'b'); + $c->join('b.Author a'); + $sql = 'SELECT FROM `book` INNER JOIN author a ON (book.AUTHOR_ID=a.ID)'; + $params = array(); + $this->assertCriteriaTranslation($c, $sql, $params, 'join() supports relation alias on main alias'); + + $con = Propel::getConnection(BookPeer::DATABASE_NAME); + $c = new ModelCriteria('bookstore', 'Book', 'b'); + $c->join('b.Author a'); + $c->where('a.FirstName = ?', 'Leo'); + $sql = 'SELECT FROM INNER JOIN author a ON (book.AUTHOR_ID=a.ID) WHERE a.FIRST_NAME = :p1'; + $params = array( + array('table' => 'author', 'column' => 'FIRST_NAME', 'value' => 'Leo'), + ); + $this->assertCriteriaTranslation($c, $sql, $params, 'join() allows the use of relation alias in where()'); + + $c = new ModelCriteria('bookstore', 'Author', 'a'); + $c->join('a.Book b'); + $c->join('b.Publisher p'); + $c->where('p.Name = ?', 'foo'); + $sql = 'SELECT FROM INNER JOIN book b ON (author.ID=b.AUTHOR_ID) INNER JOIN publisher p ON (b.PUBLISHER_ID=p.ID) WHERE p.NAME = :p1'; + $params = array( + array('table' => 'publisher', 'column' => 'NAME', 'value' => 'foo'), + ); + $this->assertCriteriaTranslation($c, $sql, $params, 'join() allows the use of relation alias in further join()'); + } + + public function testJoinTrueTableAlias() + { + $c = new ModelCriteria('bookstore', 'Book'); + $c->setModelAlias('b', true); + $c->join('b.Author'); + $sql = 'SELECT FROM `book` `b` INNER JOIN author ON (b.AUTHOR_ID=author.ID)'; + $params = array(); + $this->assertCriteriaTranslation($c, $sql, $params, 'join() supports relation on true table alias'); + + $c = new ModelCriteria('bookstore', 'Book'); + $c->setModelAlias('b', true); + $c->join('Author'); + $sql = 'SELECT FROM `book` `b` INNER JOIN author ON (b.AUTHOR_ID=author.ID)'; + $params = array(); + $this->assertCriteriaTranslation($c, $sql, $params, 'join() supports relation without alias name on true table alias'); + } + + public function testJoinOnSameTable() + { + $c = new ModelCriteria('bookstore', 'BookstoreEmployee', 'be'); + $c->join('be.Supervisor sup'); + $c->join('sup.Subordinate sub'); + $c->where('sub.Name = ?', 'Foo'); + $sql = 'SELECT FROM INNER JOIN bookstore_employee sup ON (bookstore_employee.SUPERVISOR_ID=sup.ID) INNER JOIN bookstore_employee sub ON (sup.ID=sub.SUPERVISOR_ID) WHERE sub.NAME = :p1'; + $params = array( + array('table' => 'bookstore_employee', 'column' => 'NAME', 'value' => 'Foo'), + ); + $this->assertCriteriaTranslation($c, $sql, $params, 'join() allows two joins on the same table thanks to aliases'); + } + + public function testJoinAliasQuery() + { + $con = Propel::getConnection(BookPeer::DATABASE_NAME); + $c = new ModelCriteria('bookstore', 'Book', 'b'); + $c->join('b.Author a'); + $c->where('a.FirstName = ?', 'Leo'); + $books = BookPeer::doSelect($c, $con); + $expectedSQL = "SELECT book.ID, book.TITLE, book.ISBN, book.PRICE, book.PUBLISHER_ID, book.AUTHOR_ID FROM `book` INNER JOIN author a ON (book.AUTHOR_ID=a.ID) WHERE a.FIRST_NAME = 'Leo'"; + $this->assertEquals($expectedSQL, $con->getLastExecutedQuery(), 'join() allows the use of relation alias in where()'); + + $c = new ModelCriteria('bookstore', 'BookstoreEmployee', 'be'); + $c->join('be.Supervisor sup'); + $c->join('sup.Subordinate sub'); + $c->where('sub.Name = ?', 'Foo'); + $employees = BookstoreEmployeePeer::doSelect($c, $con); + $expectedSQL = "SELECT bookstore_employee.ID, bookstore_employee.CLASS_KEY, bookstore_employee.NAME, bookstore_employee.JOB_TITLE, bookstore_employee.SUPERVISOR_ID FROM `bookstore_employee` INNER JOIN bookstore_employee sup ON (bookstore_employee.SUPERVISOR_ID=sup.ID) INNER JOIN bookstore_employee sub ON (sup.ID=sub.SUPERVISOR_ID) WHERE sub.NAME = 'Foo'"; + $this->assertEquals($expectedSQL, $con->getLastExecutedQuery(), 'join() allows the use of relation alias in further joins()'); + } + + public function testGetJoin() + { + $c = new ModelCriteria('bookstore', 'Book'); + $c->join('Book.Author'); + + $joins = $c->getJoins(); + $this->assertEquals($joins['Author'], $c->getJoin('Author'), "getJoin() returns a specific Join from the ModelCriteria"); + } + + public function testWith() + { + $c = new TestableModelCriteria('bookstore', 'Book'); + $c->join('Book.Author'); + $c->with('Author'); + $withs = $c->getWith(); + $this->assertTrue(array_key_exists('Author', $withs), 'with() adds an entry to the internal list of Withs'); + $this->assertTrue($withs['Author'] instanceof ModelJoin, 'with() references the ModelJoin object'); + } + + /** + * @expectedException PropelException + */ + public function testWithThrowsExceptionWhenJoinLacks() + { + $c = new ModelCriteria('bookstore', 'Book'); + $c->with('Author'); + } + + public function testWithAlias() + { + $c = new TestableModelCriteria('bookstore', 'Book'); + $c->join('Book.Author a'); + $c->with('a'); + $withs = $c->getWith(); + $this->assertTrue(array_key_exists('a', $withs), 'with() uses the alias for the index of the internal list of Withs'); + } + + /** + * @expectedException PropelException + */ + public function testWithThrowsExceptionWhenNotUsingAlias() + { + $c = new ModelCriteria('bookstore', 'Book'); + $c->join('Book.Author a'); + $c->with('Author'); + } + + public function testWithAddsSelectColumns() + { + $c = new TestableModelCriteria('bookstore', 'Book'); + BookPeer::addSelectColumns($c); + $c->join('Book.Author'); + $c->with('Author'); + $expectedColumns = array( + BookPeer::ID, + BookPeer::TITLE, + BookPeer::ISBN, + BookPeer::PRICE, + BookPeer::PUBLISHER_ID, + BookPeer::AUTHOR_ID, + AuthorPeer::ID, + AuthorPeer::FIRST_NAME, + AuthorPeer::LAST_NAME, + AuthorPeer::EMAIL, + AuthorPeer::AGE + ); + $this->assertEquals($expectedColumns, $c->getSelectColumns(), 'with() adds the columns of the related table'); + } + + public function testWithAliasAddsSelectColumns() + { + $c = new TestableModelCriteria('bookstore', 'Book'); + BookPeer::addSelectColumns($c); + $c->join('Book.Author a'); + $c->with('a'); + $expectedColumns = array( + BookPeer::ID, + BookPeer::TITLE, + BookPeer::ISBN, + BookPeer::PRICE, + BookPeer::PUBLISHER_ID, + BookPeer::AUTHOR_ID, + 'a.ID', + 'a.FIRST_NAME', + 'a.LAST_NAME', + 'a.EMAIL', + 'a.AGE' + ); + $this->assertEquals($expectedColumns, $c->getSelectColumns(), 'with() adds the columns of the related table'); + } + + public function testWithAddsSelectColumnsOfMainTable() + { + $c = new TestableModelCriteria('bookstore', 'Book'); + $c->join('Book.Author'); + $c->with('Author'); + $expectedColumns = array( + BookPeer::ID, + BookPeer::TITLE, + BookPeer::ISBN, + BookPeer::PRICE, + BookPeer::PUBLISHER_ID, + BookPeer::AUTHOR_ID, + AuthorPeer::ID, + AuthorPeer::FIRST_NAME, + AuthorPeer::LAST_NAME, + AuthorPeer::EMAIL, + AuthorPeer::AGE + ); + $this->assertEquals($expectedColumns, $c->getSelectColumns(), 'with() adds the columns of the main table if required'); + } + + public function testWithAliasAddsSelectColumnsOfMainTable() + { + $c = new TestableModelCriteria('bookstore', 'Book'); + $c->setModelAlias('b', true); + $c->join('b.Author a'); + $c->with('a'); + $expectedColumns = array( + 'b.ID', + 'b.TITLE', + 'b.ISBN', + 'b.PRICE', + 'b.PUBLISHER_ID', + 'b.AUTHOR_ID', + 'a.ID', + 'a.FIRST_NAME', + 'a.LAST_NAME', + 'a.EMAIL', + 'a.AGE' + ); + $this->assertEquals($expectedColumns, $c->getSelectColumns(), 'with() adds the columns of the main table with an alias if required'); + } + + public function testWithOneToManyAddsSelectColumns() + { + $c = new TestableModelCriteria('bookstore', 'Author'); + AuthorPeer::addSelectColumns($c); + $c->leftJoin('Author.Book'); + $c->with('Book'); + $expectedColumns = array( + AuthorPeer::ID, + AuthorPeer::FIRST_NAME, + AuthorPeer::LAST_NAME, + AuthorPeer::EMAIL, + AuthorPeer::AGE, + BookPeer::ID, + BookPeer::TITLE, + BookPeer::ISBN, + BookPeer::PRICE, + BookPeer::PUBLISHER_ID, + BookPeer::AUTHOR_ID, + ); + $this->assertEquals($expectedColumns, $c->getSelectColumns(), 'with() adds the columns of the related table even in a one-to-many relationship'); + } + + public function testJoinWith() + { + $c = new TestableModelCriteria('bookstore', 'Book'); + $c->joinWith('Book.Author'); + $expectedColumns = array( + BookPeer::ID, + BookPeer::TITLE, + BookPeer::ISBN, + BookPeer::PRICE, + BookPeer::PUBLISHER_ID, + BookPeer::AUTHOR_ID, + AuthorPeer::ID, + AuthorPeer::FIRST_NAME, + AuthorPeer::LAST_NAME, + AuthorPeer::EMAIL, + AuthorPeer::AGE + ); + $this->assertEquals($expectedColumns, $c->getSelectColumns(), 'joinWith() adds the join'); + $joins = $c->getJoins(); + $join = $joins['Author']; + $this->assertEquals(Criteria::INNER_JOIN, $join->getJoinType(), 'joinWith() adds an INNER JOIN by default'); + } + + public function testJoinWithType() + { + $c = new TestableModelCriteria('bookstore', 'Book'); + $c->joinWith('Book.Author', Criteria::LEFT_JOIN); + $joins = $c->getJoins(); + $join = $joins['Author']; + $this->assertEquals(Criteria::LEFT_JOIN, $join->getJoinType(), 'joinWith() accepts a join type as second parameter'); + } + + public function testJoinWithAlias() + { + $c = new TestableModelCriteria('bookstore', 'Book'); + $c->joinWith('Book.Author a'); + $expectedColumns = array( + BookPeer::ID, + BookPeer::TITLE, + BookPeer::ISBN, + BookPeer::PRICE, + BookPeer::PUBLISHER_ID, + BookPeer::AUTHOR_ID, + 'a.ID', + 'a.FIRST_NAME', + 'a.LAST_NAME', + 'a.EMAIL', + 'a.AGE' + ); + $this->assertEquals($expectedColumns, $c->getSelectColumns(), 'joinWith() adds the join with the alias'); + } + + public function testJoinWithSeveral() + { + $c = new TestableModelCriteria('bookstore', 'Review'); + $c->joinWith('Review.Book'); + $c->joinWith('Book.Author'); + $c->joinWith('Book.Publisher'); + $expectedColumns = array( + ReviewPeer::ID, + ReviewPeer::REVIEWED_BY, + ReviewPeer::REVIEW_DATE, + ReviewPeer::RECOMMENDED, + ReviewPeer::STATUS, + ReviewPeer::BOOK_ID, + BookPeer::ID, + BookPeer::TITLE, + BookPeer::ISBN, + BookPeer::PRICE, + BookPeer::PUBLISHER_ID, + BookPeer::AUTHOR_ID, + AuthorPeer::ID, + AuthorPeer::FIRST_NAME, + AuthorPeer::LAST_NAME, + AuthorPeer::EMAIL, + AuthorPeer::AGE, + PublisherPeer::ID, + PublisherPeer::NAME + ); + $this->assertEquals($expectedColumns, $c->getSelectColumns(), 'joinWith() adds the with'); + $joins = $c->getJoins(); + $expectedJoinKeys = array('Book', 'Author', 'Publisher'); + $this->assertEquals($expectedJoinKeys, array_keys($joins), 'joinWith() adds the join'); + } + + public function testJoinWithTwice() + { + $c = new TestableModelCriteria('bookstore', 'Book'); + $c->join('Book.Review'); + $c->joinWith('Book.Author'); + $c->joinWith('Book.Review'); + $expectedColumns = array( + BookPeer::ID, + BookPeer::TITLE, + BookPeer::ISBN, + BookPeer::PRICE, + BookPeer::PUBLISHER_ID, + BookPeer::AUTHOR_ID, + AuthorPeer::ID, + AuthorPeer::FIRST_NAME, + AuthorPeer::LAST_NAME, + AuthorPeer::EMAIL, + AuthorPeer::AGE, + ReviewPeer::ID, + ReviewPeer::REVIEWED_BY, + ReviewPeer::REVIEW_DATE, + ReviewPeer::RECOMMENDED, + ReviewPeer::STATUS, + ReviewPeer::BOOK_ID, + ); + $this->assertEquals($expectedColumns, $c->getSelectColumns(), 'joinWith() adds the with'); + $joins = $c->getJoins(); + $expectedJoinKeys = array('Review', 'Author'); + $this->assertEquals($expectedJoinKeys, array_keys($joins), 'joinWith() adds the join'); + } + + public static function conditionsForTestWithColumn() + { + return array( + array('Book.Title', 'BookTitle', 'book.TITLE AS BookTitle'), + array('Book.Title', null, 'book.TITLE AS BookTitle'), + array('UPPER(Book.Title)', null, 'UPPER(book.TITLE) AS UPPERBookTitle'), + array('CONCAT(Book.Title, Book.ISBN)', 'foo', 'CONCAT(book.TITLE, book.ISBN) AS foo'), + ); + } + + /** + * @dataProvider conditionsForTestWithColumn + */ + public function testWithColumn($clause, $alias, $selectTranslation) + { + $c = new ModelCriteria('bookstore', 'Book'); + $c->withColumn($clause, $alias); + $sql = 'SELECT book.ID, book.TITLE, book.ISBN, book.PRICE, book.PUBLISHER_ID, book.AUTHOR_ID, ' . $selectTranslation . ' FROM `book`'; + $params = array(); + $this->assertCriteriaTranslation($c, $sql, $params, 'withColumn() adds a calculated column to the select clause'); + } + + public function testWithColumnAndSelectColumns() + { + $c = new ModelCriteria('bookstore', 'Book'); + $c->withColumn('UPPER(Book.Title)', 'foo'); + $sql = 'SELECT book.ID, book.TITLE, book.ISBN, book.PRICE, book.PUBLISHER_ID, book.AUTHOR_ID, UPPER(book.TITLE) AS foo FROM `book`'; + $params = array(); + $this->assertCriteriaTranslation($c, $sql, $params, 'withColumn() adds the object columns if the criteria has no select columns'); + + $c = new ModelCriteria('bookstore', 'Book'); + $c->addSelectColumn('book.ID'); + $c->withColumn('UPPER(Book.Title)', 'foo'); + $sql = 'SELECT book.ID, UPPER(book.TITLE) AS foo FROM `book`'; + $params = array(); + $this->assertCriteriaTranslation($c, $sql, $params, 'withColumn() does not add the object columns if the criteria already has select columns'); + + $c = new ModelCriteria('bookstore', 'Book'); + $c->addSelectColumn('book.ID'); + $c->withColumn('UPPER(Book.Title)', 'foo'); + $c->addSelectColumn('book.TITLE'); + $sql = 'SELECT book.ID, book.TITLE, UPPER(book.TITLE) AS foo FROM `book`'; + $params = array(); + $this->assertCriteriaTranslation($c, $sql, $params, 'withColumn() does adds as column after the select columns even though the withColumn() method was called first'); + + $c = new ModelCriteria('bookstore', 'Book'); + $c->addSelectColumn('book.ID'); + $c->withColumn('UPPER(Book.Title)', 'foo'); + $c->withColumn('UPPER(Book.ISBN)', 'isbn'); + $sql = 'SELECT book.ID, UPPER(book.TITLE) AS foo, UPPER(book.ISBN) AS isbn FROM `book`'; + $params = array(); + $this->assertCriteriaTranslation($c, $sql, $params, 'withColumn() called repeatedly adds several as colums'); + } + + public function testKeepQuery() + { + $c = BookQuery::create(); + $this->assertFalse($c->isKeepQuery(), 'keepQuery is disabled by default'); + $c->keepQuery(); + $this->assertTrue($c->isKeepQuery(), 'keepQuery() enables the keepQuery property'); + $c->keepQuery(false); + $this->assertFalse($c->isKeepQuery(), 'keepQuery(false) disables the keepQuery property'); + } + + public function testKeepQueryFind() + { + $c = BookQuery::create(); + $c->filterByTitle('foo'); + $c->find(); + $expected = array('book.ID', 'book.TITLE', 'book.ISBN', 'book.PRICE', 'book.PUBLISHER_ID', 'book.AUTHOR_ID'); + $this->assertEquals($expected, $c->getSelectColumns(), 'find() modifies the query by default'); + + $c = BookQuery::create(); + $c->filterByTitle('foo'); + $c->keepQuery(); + $c->find(); + $this->assertEquals(array(), $c->getSelectColumns(), 'keepQuery() forces find() to use a clone and keep the original query unmodified'); + } + + public function testKeepQueryFindOne() + { + $c = BookQuery::create(); + $c->filterByTitle('foo'); + $c->findOne(); + $this->assertEquals(1, $c->getLimit(), 'findOne() modifies the query by default'); + + $c = BookQuery::create(); + $c->filterByTitle('foo'); + $c->keepQuery(); + $c->findOne(); + $this->assertEquals(0, $c->getLimit(), 'keepQuery() forces findOne() to use a clone and keep the original query unmodified'); + } + + public function testKeepQueryFindPk() + { + $c = BookQuery::create(); + $c->findPk(1); + $expected = array('book.ID', 'book.TITLE', 'book.ISBN', 'book.PRICE', 'book.PUBLISHER_ID', 'book.AUTHOR_ID'); + $this->assertEquals($expected, $c->getSelectColumns(), 'findPk() modifies the query by default'); + + $c = BookQuery::create(); + $c->keepQuery(); + $c->findPk(1); + $this->assertEquals(array(), $c->getSelectColumns(), 'keepQuery() forces findPk() to use a clone and keep the original query unmodified'); + } + + public function testKeepQueryCount() + { + $c = BookQuery::create(); + $c->orderByTitle(); + $c->count(); + $this->assertEquals(array(), $c->getOrderByColumns(), 'count() modifies the query by default'); + + $c = BookQuery::create(); + $c->orderByTitle(); + $c->keepQuery(); + $c->count(); + $this->assertEquals(array('book.TITLE ASC'), $c->getOrderByColumns(), 'keepQuery() forces count() to use a clone and keep the original query unmodified'); + } + + public function testFind() + { + $c = new ModelCriteria('bookstore', 'Book', 'b'); + $c->where('b.Title = ?', 'foo'); + $books = $c->find(); + $this->assertTrue($books instanceof PropelCollection, 'find() returns a collection by default'); + $this->assertEquals(0, count($books), 'find() returns an empty array when the query returns no result'); + + $c = new ModelCriteria('bookstore', 'Book', 'b'); + $c->join('b.Author a'); + $c->where('a.FirstName = ?', 'Neal'); + $books = $c->find(); + $this->assertTrue($books instanceof PropelCollection, 'find() returns a collection by default'); + $this->assertEquals(1, count($books), 'find() returns as many rows as the results in the query'); + $book = $books->shift(); + $this->assertTrue($book instanceof Book, 'find() returns an array of Model objects by default'); + $this->assertEquals('Quicksilver', $book->getTitle(), 'find() returns the model objects matching the query'); + } + + public function testFindAddsSelectColumns() + { + $con = Propel::getConnection(BookPeer::DATABASE_NAME); + $c = new ModelCriteria('bookstore', 'Book'); + $books = $c->find($con); + $sql = "SELECT book.ID, book.TITLE, book.ISBN, book.PRICE, book.PUBLISHER_ID, book.AUTHOR_ID FROM `book`"; + $this->assertEquals($sql, $con->getLastExecutedQuery(), 'find() adds the select columns of the current model'); + } + + public function testFindTrueAliasAddsSelectColumns() + { + $con = Propel::getConnection(BookPeer::DATABASE_NAME); + $c = new ModelCriteria('bookstore', 'Book'); + $c->setModelAlias('b', true); + $books = $c->find($con); + $sql = "SELECT b.ID, b.TITLE, b.ISBN, b.PRICE, b.PUBLISHER_ID, b.AUTHOR_ID FROM `book` `b`"; + $this->assertEquals($sql, $con->getLastExecutedQuery(), 'find() uses the true model alias if available'); + } + + public function testFindOne() + { + $c = new ModelCriteria('bookstore', 'Book', 'b'); + $c->where('b.Title = ?', 'foo'); + $book = $c->findOne(); + $this->assertNull($book, 'findOne() returns null when the query returns no result'); + + $c = new ModelCriteria('bookstore', 'Book', 'b'); + $c->orderBy('b.Title'); + $book = $c->findOne(); + $this->assertTrue($book instanceof Book, 'findOne() returns a Model object by default'); + $this->assertEquals('Don Juan', $book->getTitle(), 'find() returns the model objects matching the query'); + } + + public function testFindOneOrCreateNotExists() + { + BookQuery::create()->deleteAll(); + $book = BookQuery::create('b') + ->where('b.Title = ?', 'foo') + ->filterByPrice(125) + ->findOneOrCreate(); + $this->assertTrue($book instanceof Book, 'findOneOrCreate() returns an instance of the model when the request has no result'); + $this->assertTrue($book->isNew(), 'findOneOrCreate() returns a new instance of the model when the request has no result'); + $this->assertEquals('foo', $book->getTitle(), 'findOneOrCreate() returns a populated objects based on the conditions'); + $this->assertEquals(125, $book->getPrice(), 'findOneOrCreate() returns a populated objects based on the conditions'); + } + + public function testFindOneOrCreateNotExistsFormatter() + { + BookQuery::create()->deleteAll(); + $book = BookQuery::create('b') + ->where('b.Title = ?', 'foo') + ->filterByPrice(125) + ->setFormatter(ModelCriteria::FORMAT_ARRAY) + ->findOneOrCreate(); + $this->assertTrue(is_array($book), 'findOneOrCreate() uses the query formatter even when the request has no result'); + $this->assertEquals('foo', $book['Title'], 'findOneOrCreate() returns a populated array based on the conditions'); + $this->assertEquals(125, $book['Price'], 'findOneOrCreate() returns a populated array based on the conditions'); + } + + public function testFindOneOrCreateExists() + { + BookQuery::create()->deleteAll(); + $book = new Book(); + $book->setTitle('foo'); + $book->setPrice(125); + $book->save(); + $book = BookQuery::create('b') + ->where('b.Title = ?', 'foo') + ->filterByPrice(125) + ->findOneOrCreate(); + $this->assertTrue($book instanceof Book, 'findOneOrCreate() returns an instance of the model when the request has one result'); + $this->assertFalse($book->isNew(), 'findOneOrCreate() returns an existing instance of the model when the request has one result'); + $this->assertEquals('foo', $book->getTitle(), 'findOneOrCreate() returns a populated objects based on the conditions'); + $this->assertEquals(125, $book->getPrice(), 'findOneOrCreate() returns a populated objects based on the conditions'); + } + + public function testFindPkSimpleKey() + { + BookstoreDataPopulator::depopulate(); + + $c = new ModelCriteria('bookstore', 'Book'); + $book = $c->findPk(765432); + $this->assertNull($book, 'findPk() returns null when the primary key is not found'); + + BookstoreDataPopulator::populate(); + + // retrieve the test data + $c = new ModelCriteria('bookstore', 'Book'); + $testBook = $c->findOne(); + + $c = new ModelCriteria('bookstore', 'Book'); + $book = $c->findPk($testBook->getId()); + $this->assertEquals($testBook, $book, 'findPk() returns a model object corresponding to the pk'); + } + + public function testFindPksSimpleKey() + { + BookstoreDataPopulator::depopulate(); + + $c = new ModelCriteria('bookstore', 'Book'); + $books = $c->findPks(array(765432, 434535)); + $this->assertEquals($books instanceof PropelCollection, 'findPks() returns a PropelCollection'); + $this->assertEquals(0, count($books), 'findPks() returns an empty collection when the primary keys are not found'); + + BookstoreDataPopulator::populate(); + + // retrieve the test data + $c = new ModelCriteria('bookstore', 'Book'); + $testBooks = $c->find(); + $testBook1 = $testBooks->pop(); + $testBook2 = $testBooks->pop(); + + $c = new ModelCriteria('bookstore', 'Book'); + $books = $c->findPks(array($testBook1->getId(), $testBook2->getId())); + $this->assertEquals(array($testBook2, $testBook1), $books->getData(), 'findPks() returns an array of model objects corresponding to the pks'); + } + + public function testFindPkCompositeKey() + { + BookstoreDataPopulator::depopulate(); + + $c = new ModelCriteria('bookstore', 'BookListRel'); + $bookListRel = $c->findPk(array(1, 2)); + $this->assertNull($bookListRel, 'findPk() returns null when the composite primary key is not found'); + + Propel::enableInstancePooling(); + BookstoreDataPopulator::populate(); + + // save all books to make sure related objects are also saved - BookstoreDataPopulator keeps some unsaved + $c = new ModelCriteria('bookstore', 'Book'); + $books = $c->find(); + foreach ($books as $book) { + $book->save(); + } + + // retrieve the test data + $c = new ModelCriteria('bookstore', 'BookListRel'); + $bookListRelTest = $c->findOne(); + $pk = $bookListRelTest->getPrimaryKey(); + + $c = new ModelCriteria('bookstore', 'BookListRel'); + $bookListRel = $c->findPk($pk); + $this->assertEquals($bookListRelTest, $bookListRel, 'findPk() can find objects with composite primary keys'); + } + + /** + * @expectedException PropelException + */ + public function testFindPksCompositeKey() + { + $c = new ModelCriteria('bookstore', 'BookListRel'); + $bookListRel = $c->findPks(array(array(1, 2))); + + } + + public function testFindBy() + { + try { + $c = new ModelCriteria('bookstore', 'Book'); + $books = $c->findBy('Foo', 'Bar'); + $this->fail('findBy() throws an exception when called on an unknown column name'); + } catch (PropelException $e) { + $this->assertTrue(true, 'findBy() throws an exception when called on an unknown column name'); + } + + $con = Propel::getConnection(BookPeer::DATABASE_NAME); + $c = new ModelCriteria('bookstore', 'Book'); + $books = $c->findBy('Title', 'Don Juan', $con); + $expectedSQL = "SELECT book.ID, book.TITLE, book.ISBN, book.PRICE, book.PUBLISHER_ID, book.AUTHOR_ID FROM `book` WHERE book.TITLE='Don Juan'"; + $this->assertEquals($expectedSQL, $con->getLastExecutedQuery(), 'findBy() adds simple column conditions'); + $this->assertTrue($books instanceof PropelCollection, 'findBy() issues a find()'); + $this->assertEquals(1, count($books), 'findBy() adds simple column conditions'); + $book = $books->shift(); + $this->assertTrue($book instanceof Book, 'findBy() returns an array of Model objects by default'); + $this->assertEquals('Don Juan', $book->getTitle(), 'findBy() returns the model objects matching the query'); + } + + public function testFindByArray() + { + $con = Propel::getConnection(BookPeer::DATABASE_NAME); + $c = new ModelCriteria('bookstore', 'Book'); + $books = $c->findByArray(array('Title' => 'Don Juan', 'ISBN' => 12345), $con); + $expectedSQL = "SELECT book.ID, book.TITLE, book.ISBN, book.PRICE, book.PUBLISHER_ID, book.AUTHOR_ID FROM `book` WHERE book.TITLE='Don Juan' AND book.ISBN=12345"; + $this->assertEquals($expectedSQL, $con->getLastExecutedQuery(), 'findByArray() adds multiple column conditions'); + } + + public function testFindOneBy() + { + try { + $c = new ModelCriteria('bookstore', 'Book'); + $book = $c->findOneBy('Foo', 'Bar'); + $this->fail('findOneBy() throws an exception when called on an unknown column name'); + } catch (PropelException $e) { + $this->assertTrue(true, 'findOneBy() throws an exception when called on an unknown column name'); + } + + $con = Propel::getConnection(BookPeer::DATABASE_NAME); + $c = new ModelCriteria('bookstore', 'Book'); + $book = $c->findOneBy('Title', 'Don Juan', $con); + $expectedSQL = "SELECT book.ID, book.TITLE, book.ISBN, book.PRICE, book.PUBLISHER_ID, book.AUTHOR_ID FROM `book` WHERE book.TITLE='Don Juan' LIMIT 1"; + $this->assertEquals($expectedSQL, $con->getLastExecutedQuery(), 'findOneBy() adds simple column conditions'); + $this->assertTrue($book instanceof Book, 'findOneBy() returns a Model object by default'); + $this->assertEquals('Don Juan', $book->getTitle(), 'findOneBy() returns the model object matching the query'); + } + + public function testFindOneByArray() + { + $con = Propel::getConnection(BookPeer::DATABASE_NAME); + $c = new ModelCriteria('bookstore', 'Book'); + $book = $c->findOneByArray(array('Title' => 'Don Juan', 'ISBN' => 12345), $con); + $expectedSQL = "SELECT book.ID, book.TITLE, book.ISBN, book.PRICE, book.PUBLISHER_ID, book.AUTHOR_ID FROM `book` WHERE book.TITLE='Don Juan' AND book.ISBN=12345 LIMIT 1"; + $this->assertEquals($expectedSQL, $con->getLastExecutedQuery(), 'findOneBy() adds multiple column conditions'); + } + + public function testCount() + { + $c = new ModelCriteria('bookstore', 'Book', 'b'); + $c->where('b.Title = ?', 'foo'); + $nbBooks = $c->count(); + $this->assertTrue(is_int($nbBooks), 'count() returns an integer'); + $this->assertEquals(0, $nbBooks, 'count() returns 0 when the query returns no result'); + + $c = new ModelCriteria('bookstore', 'Book', 'b'); + $c->join('b.Author a'); + $c->where('a.FirstName = ?', 'Neal'); + $nbBooks = $c->count(); + $this->assertTrue(is_int($nbBooks), 'count() returns an integer'); + $this->assertEquals(1, $nbBooks, 'count() returns the number of results in the query'); + } + + public function testPaginate() + { + $c = new ModelCriteria('bookstore', 'Book', 'b'); + $c->join('b.Author a'); + $c->where('a.FirstName = ?', 'Neal'); + $books = $c->paginate(1, 5); + $this->assertTrue($books instanceof PropelModelPager, 'paginate() returns a PropelModelPager'); + $this->assertEquals(1, count($books), 'paginate() returns a countable pager with the correct count'); + foreach ($books as $book) { + $this->assertEquals('Neal', $book->getAuthor()->getFirstName(), 'paginate() returns an iterable pager'); + } + } + + public function testDelete() + { + BookstoreDataPopulator::depopulate(); + BookstoreDataPopulator::populate(); + + $c = new ModelCriteria('bookstore', 'Book'); + try { + $nbBooks = $c->delete(); + $this->fail('delete() throws an exception when called on an empty Criteria'); + } catch (PropelException $e) { + $this->assertTrue(true, 'delete() throws an exception when called on an empty Criteria'); + } + + $c = new ModelCriteria('bookstore', 'Book', 'b'); + $c->where('b.Title = ?', 'foo'); + $nbBooks = $c->delete(); + $this->assertTrue(is_int($nbBooks), 'delete() returns an integer'); + $this->assertEquals(0, $nbBooks, 'delete() returns 0 when the query deleted no rows'); + + $c = new ModelCriteria('bookstore', 'Book', 'b'); + $c->where('b.Title = ?', 'Don Juan'); + $nbBooks = $c->delete(); + $this->assertTrue(is_int($nbBooks), 'delete() returns an integer'); + $this->assertEquals(1, $nbBooks, 'delete() returns the number of the deleted rows'); + + $c = new ModelCriteria('bookstore', 'Book'); + $nbBooks = $c->count(); + $this->assertEquals(3, $nbBooks, 'delete() deletes rows in the database'); + } + + public function testDeleteUsingTableAlias() + { + $con = Propel::getConnection(BookPeer::DATABASE_NAME); + + $c = new ModelCriteria('bookstore', 'Book'); + $c->setModelAlias('b', false); + $c->where('b.Title = ?', 'foo'); + $c->delete(); + $expectedSQL = "DELETE FROM `book` WHERE book.TITLE = 'foo'"; + $this->assertEquals($expectedSQL, $con->getLastExecutedQuery(), 'delete() also works on tables with table alias'); + + $c = new ModelCriteria('bookstore', 'Book'); + $c->setModelAlias('b', true); + $c->where('b.Title = ?', 'foo'); + $c->delete(); + $expectedSQL = "DELETE b FROM `book` AS b WHERE b.TITLE = 'foo'"; + $this->assertEquals($expectedSQL, $con->getLastExecutedQuery(), 'delete() also works on tables with true table alias'); + } + + public function testDeleteAll() + { + BookstoreDataPopulator::depopulate(); + BookstoreDataPopulator::populate(); + + $c = new ModelCriteria('bookstore', 'Book'); + $nbBooks = $c->deleteAll(); + $this->assertTrue(is_int($nbBooks), 'deleteAll() returns an integer'); + $this->assertEquals(4, $nbBooks, 'deleteAll() returns the number of deleted rows'); + + BookstoreDataPopulator::depopulate(); + BookstoreDataPopulator::populate(); + + $c = new ModelCriteria('bookstore', 'Book', 'b'); + $c->where('b.Title = ?', 'Don Juan'); + $nbBooks = $c->deleteAll(); + $this->assertEquals(4, $nbBooks, 'deleteAll() ignores conditions on the criteria'); + } + + public function testUpdate() + { + $con = Propel::getConnection(BookPeer::DATABASE_NAME); + BookstoreDataPopulator::depopulate($con); + BookstoreDataPopulator::populate($con); + + $count = $con->getQueryCount(); + $c = new ModelCriteria('bookstore', 'Book'); + $nbBooks = $c->update(array('Title' => 'foo'), $con); + $this->assertEquals(4, $nbBooks, 'update() returns the number of updated rows'); + $this->assertEquals($count + 1, $con->getQueryCount(), 'update() updates all the objects in one query by default'); + + $c = new ModelCriteria('bookstore', 'Book', 'b'); + $c->where('b.Title = ?', 'foo'); + $nbBooks = $c->count(); + $this->assertEquals(4, $nbBooks, 'update() updates all records by default'); + + BookstoreDataPopulator::depopulate($con); + BookstoreDataPopulator::populate($con); + + $count = $con->getQueryCount(); + $c = new ModelCriteria('bookstore', 'Book', 'b'); + $c->where('b.Title = ?', 'Don Juan'); + $nbBooks = $c->update(array('ISBN' => '3456'), $con); + $this->assertEquals(1, $nbBooks, 'update() updates only the records matching the criteria'); + $this->assertEquals($count + 1, $con->getQueryCount(), 'update() updates all the objects in one query by default'); + + $c = new ModelCriteria('bookstore', 'Book', 'b'); + $c->where('b.Title = ?', 'Don Juan'); + $book = $c->findOne(); + $this->assertEquals('3456', $book->getISBN(), 'update() updates only the records matching the criteria'); + } + + public function testUpdateUsingTableAlias() + { + $con = Propel::getConnection(BookPeer::DATABASE_NAME); + + $c = new ModelCriteria('bookstore', 'Book'); + $c->setModelAlias('b', false); + $c->where('b.Title = ?', 'foo'); + $c->update(array('Title' => 'foo2'), $con); + $expectedSQL = "UPDATE `book` SET `TITLE`='foo2' WHERE book.TITLE = 'foo'"; + $this->assertEquals($expectedSQL, $con->getLastExecutedQuery(), 'update() also works on tables with table alias'); + + $c = new ModelCriteria('bookstore', 'Book'); + $c->setModelAlias('b', true); + $c->where('b.Title = ?', 'foo'); + $c->update(array('Title' => 'foo2'), $con); + $expectedSQL = "UPDATE `book` `b` SET `TITLE`='foo2' WHERE b.TITLE = 'foo'"; + $this->assertEquals($expectedSQL, $con->getLastExecutedQuery(), 'update() also works on tables with true table alias'); + } + + public function testUpdateOneByOne() + { + $con = Propel::getConnection(BookPeer::DATABASE_NAME); + BookstoreDataPopulator::depopulate($con); + BookstoreDataPopulator::populate($con); + + // save all books to make sure related objects are also saved - BookstoreDataPopulator keeps some unsaved + $c = new ModelCriteria('bookstore', 'Book'); + $books = $c->find(); + foreach ($books as $book) { + $book->save(); + } + + $count = $con->getQueryCount(); + $c = new ModelCriteria('bookstore', 'Book'); + $nbBooks = $c->update(array('Title' => 'foo'), $con, true); + $this->assertEquals(4, $nbBooks, 'update() returns the number of updated rows'); + $this->assertEquals($count + 1 + 4, $con->getQueryCount(), 'update() updates the objects one by one when called with true as last parameter'); + + $c = new ModelCriteria('bookstore', 'Book', 'b'); + $c->where('b.Title = ?', 'foo'); + $nbBooks = $c->count(); + $this->assertEquals(4, $nbBooks, 'update() updates all records by default'); + + BookstoreDataPopulator::depopulate($con); + BookstoreDataPopulator::populate($con); + + // save all books to make sure related objects are also saved - BookstoreDataPopulator keeps some unsaved + $c = new ModelCriteria('bookstore', 'Book'); + $books = $c->find(); + foreach ($books as $book) { + $book->save(); + } + + $count = $con->getQueryCount(); + $c = new ModelCriteria('bookstore', 'Book', 'b'); + $c->where('b.Title = ?', 'Don Juan'); + $nbBooks = $c->update(array('ISBN' => '3456'), $con, true); + $this->assertEquals(1, $nbBooks, 'update() updates only the records matching the criteria'); + $this->assertEquals($count + 1 + 1, $con->getQueryCount(), 'update() updates the objects one by one when called with true as last parameter'); + + $c = new ModelCriteria('bookstore', 'Book', 'b'); + $c->where('b.Title = ?', 'Don Juan'); + $book = $c->findOne(); + $this->assertEquals('3456', $book->getISBN(), 'update() updates only the records matching the criteria'); + } + + public static function conditionsForTestGetRelationName() + { + return array( + array('Author', 'Author'), + array('Book.Author', 'Author'), + array('Author.Book', 'Book'), + array('Book.Author a', 'a'), + ); + } + + /** + * @dataProvider conditionsForTestGetRelationName + */ + public function testGetRelationName($relation, $relationName) + { + $this->assertEquals($relationName, ModelCriteria::getrelationName($relation)); + } + + public function testMagicJoin() + { + $con = Propel::getConnection(BookPeer::DATABASE_NAME); + + $c = new ModelCriteria('bookstore', 'Book', 'b'); + $c->leftJoin('b.Author a'); + $c->where('a.FirstName = ?', 'Leo'); + $books = $c->findOne($con); + $expectedSQL = "SELECT book.ID, book.TITLE, book.ISBN, book.PRICE, book.PUBLISHER_ID, book.AUTHOR_ID FROM `book` LEFT JOIN author a ON (book.AUTHOR_ID=a.ID) WHERE a.FIRST_NAME = 'Leo' LIMIT 1"; + $this->assertEquals($expectedSQL, $con->getLastExecutedQuery(), 'leftJoin($x) is turned into join($x, Criteria::LEFT_JOIN)'); + + $books = BookQuery::create() + ->leftJoinAuthor('a') + ->where('a.FirstName = ?', 'Leo') + ->findOne($con); + $expectedSQL = "SELECT book.ID, book.TITLE, book.ISBN, book.PRICE, book.PUBLISHER_ID, book.AUTHOR_ID FROM `book` LEFT JOIN author a ON (book.AUTHOR_ID=a.ID) WHERE a.FIRST_NAME = 'Leo' LIMIT 1"; + $this->assertEquals($expectedSQL, $con->getLastExecutedQuery(), 'leftJoinX() is turned into join($x, Criteria::LEFT_JOIN)'); + + $c = new ModelCriteria('bookstore', 'Book', 'b'); + $c->innerJoin('b.Author a'); + $c->where('a.FirstName = ?', 'Leo'); + $books = $c->findOne($con); + $expectedSQL = "SELECT book.ID, book.TITLE, book.ISBN, book.PRICE, book.PUBLISHER_ID, book.AUTHOR_ID FROM `book` INNER JOIN author a ON (book.AUTHOR_ID=a.ID) WHERE a.FIRST_NAME = 'Leo' LIMIT 1"; + $this->assertEquals($expectedSQL, $con->getLastExecutedQuery(), 'innerJoin($x) is turned into join($x, Criteria::INNER_JOIN)'); + + $books = BookQuery::create() + ->innerJoinAuthor('a') + ->where('a.FirstName = ?', 'Leo') + ->findOne($con); + $expectedSQL = "SELECT book.ID, book.TITLE, book.ISBN, book.PRICE, book.PUBLISHER_ID, book.AUTHOR_ID FROM `book` INNER JOIN author a ON (book.AUTHOR_ID=a.ID) WHERE a.FIRST_NAME = 'Leo' LIMIT 1"; + $this->assertEquals($expectedSQL, $con->getLastExecutedQuery(), 'innerJoinX() is turned into join($x, Criteria::INNER_JOIN)'); + + $c = new ModelCriteria('bookstore', 'Book', 'b'); + $c->rightJoin('b.Author a'); + $c->where('a.FirstName = ?', 'Leo'); + $books = $c->findOne($con); + $expectedSQL = "SELECT book.ID, book.TITLE, book.ISBN, book.PRICE, book.PUBLISHER_ID, book.AUTHOR_ID FROM `book` RIGHT JOIN author a ON (book.AUTHOR_ID=a.ID) WHERE a.FIRST_NAME = 'Leo' LIMIT 1"; + $this->assertEquals($expectedSQL, $con->getLastExecutedQuery(), 'rightJoin($x) is turned into join($x, Criteria::RIGHT_JOIN)'); + + $books = BookQuery::create() + ->rightJoinAuthor('a') + ->where('a.FirstName = ?', 'Leo') + ->findOne($con); + $expectedSQL = "SELECT book.ID, book.TITLE, book.ISBN, book.PRICE, book.PUBLISHER_ID, book.AUTHOR_ID FROM `book` RIGHT JOIN author a ON (book.AUTHOR_ID=a.ID) WHERE a.FIRST_NAME = 'Leo' LIMIT 1"; + $this->assertEquals($expectedSQL, $con->getLastExecutedQuery(), 'rightJoinX() is turned into join($x, Criteria::RIGHT_JOIN)'); + + $books = BookQuery::create() + ->leftJoinAuthor() + ->where('Author.FirstName = ?', 'Leo') + ->findOne($con); + $expectedSQL = "SELECT book.ID, book.TITLE, book.ISBN, book.PRICE, book.PUBLISHER_ID, book.AUTHOR_ID FROM `book` LEFT JOIN author ON (book.AUTHOR_ID=author.ID) WHERE author.FIRST_NAME = 'Leo' LIMIT 1"; + $this->assertEquals($expectedSQL, $con->getLastExecutedQuery(), 'leftJoinX() is turned into join($x, Criteria::LEFT_JOIN)'); + } + + public function testMagicJoinWith() + { + $c = new TestableModelCriteria('bookstore', 'Book'); + $c->leftJoinWith('Book.Author a'); + $expectedColumns = array( + BookPeer::ID, + BookPeer::TITLE, + BookPeer::ISBN, + BookPeer::PRICE, + BookPeer::PUBLISHER_ID, + BookPeer::AUTHOR_ID, + 'a.ID', + 'a.FIRST_NAME', + 'a.LAST_NAME', + 'a.EMAIL', + 'a.AGE' + ); + $this->assertEquals($expectedColumns, $c->getSelectColumns(), 'leftJoinWith() adds the join with the alias'); + $joins = $c->getJoins(); + $join = $joins['a']; + $this->assertEquals(Criteria::LEFT_JOIN, $join->getJoinType(), 'leftJoinWith() adds a LEFT JOIN'); + } + + public function testMagicJoinWithRelation() + { + $c = new TestableModelCriteria('bookstore', 'Book'); + $c->joinWithAuthor(); + $expectedColumns = array( + BookPeer::ID, + BookPeer::TITLE, + BookPeer::ISBN, + BookPeer::PRICE, + BookPeer::PUBLISHER_ID, + BookPeer::AUTHOR_ID, + AuthorPeer::ID, + AuthorPeer::FIRST_NAME, + AuthorPeer::LAST_NAME, + AuthorPeer::EMAIL, + AuthorPeer::AGE + ); + $this->assertEquals($expectedColumns, $c->getSelectColumns(), 'joinWithXXX() adds the join with the XXX relation'); + $joins = $c->getJoins(); + $join = $joins['Author']; + $this->assertEquals(Criteria::INNER_JOIN, $join->getJoinType(), 'joinWithXXX() adds an INNER JOIN'); + } + + public function testMagicJoinWithTypeAndRelation() + { + $c = new TestableModelCriteria('bookstore', 'Book'); + $c->leftJoinWithAuthor(); + $expectedColumns = array( + BookPeer::ID, + BookPeer::TITLE, + BookPeer::ISBN, + BookPeer::PRICE, + BookPeer::PUBLISHER_ID, + BookPeer::AUTHOR_ID, + AuthorPeer::ID, + AuthorPeer::FIRST_NAME, + AuthorPeer::LAST_NAME, + AuthorPeer::EMAIL, + AuthorPeer::AGE + ); + $this->assertEquals($expectedColumns, $c->getSelectColumns(), 'leftJoinWithXXX() adds the join with the XXX relation'); + $joins = $c->getJoins(); + $join = $joins['Author']; + $this->assertEquals(Criteria::LEFT_JOIN, $join->getJoinType(), 'leftJoinWithXXX() adds an INNER JOIN'); + } + + public function testMagicFind() + { + $con = Propel::getConnection(BookPeer::DATABASE_NAME); + + $c = new ModelCriteria('bookstore', 'Book'); + $books = $c->findByTitle('Don Juan'); + $expectedSQL = "SELECT book.ID, book.TITLE, book.ISBN, book.PRICE, book.PUBLISHER_ID, book.AUTHOR_ID FROM `book` WHERE book.TITLE='Don Juan'"; + $this->assertEquals($expectedSQL, $con->getLastExecutedQuery(), 'findByXXX($value) is turned into findBy(XXX, $value)'); + + $c = new ModelCriteria('bookstore', 'Book'); + $books = $c->findByTitleAndISBN('Don Juan', 1234); + $expectedSQL = "SELECT book.ID, book.TITLE, book.ISBN, book.PRICE, book.PUBLISHER_ID, book.AUTHOR_ID FROM `book` WHERE book.TITLE='Don Juan' AND book.ISBN=1234"; + $this->assertEquals($expectedSQL, $con->getLastExecutedQuery(), 'findByXXXAndYYY($value) is turned into findBy(array(XXX,YYY), $value)'); + + $c = new ModelCriteria('bookstore', 'Book'); + $book = $c->findOneByTitle('Don Juan'); + $expectedSQL = "SELECT book.ID, book.TITLE, book.ISBN, book.PRICE, book.PUBLISHER_ID, book.AUTHOR_ID FROM `book` WHERE book.TITLE='Don Juan' LIMIT 1"; + $this->assertEquals($expectedSQL, $con->getLastExecutedQuery(), 'findOneByXXX($value) is turned into findOneBy(XXX, $value)'); + + $c = new ModelCriteria('bookstore', 'Book'); + $book = $c->findOneByTitleAndISBN('Don Juan', 1234); + $expectedSQL = "SELECT book.ID, book.TITLE, book.ISBN, book.PRICE, book.PUBLISHER_ID, book.AUTHOR_ID FROM `book` WHERE book.TITLE='Don Juan' AND book.ISBN=1234 LIMIT 1"; + $this->assertEquals($expectedSQL, $con->getLastExecutedQuery(), 'findOneByXXX($value) is turned into findOneBy(XXX, $value)'); + } + + public function testMagicFilterBy() + { + $con = Propel::getConnection(BookPeer::DATABASE_NAME); + + $c = new ModelCriteria('bookstore', 'Book'); + $books = $c->filterByTitle('Don Juan')->find($con); + $expectedSQL = "SELECT book.ID, book.TITLE, book.ISBN, book.PRICE, book.PUBLISHER_ID, book.AUTHOR_ID FROM `book` WHERE book.TITLE='Don Juan'"; + $this->assertEquals($expectedSQL, $con->getLastExecutedQuery(), 'filterByXXX($value) is turned into filterBy(XXX, $value)'); + } + + public function testMagicOrderBy() + { + $con = Propel::getConnection(BookPeer::DATABASE_NAME); + + $c = new ModelCriteria('bookstore', 'Book'); + $books = $c->orderByTitle()->find($con); + $expectedSQL = "SELECT book.ID, book.TITLE, book.ISBN, book.PRICE, book.PUBLISHER_ID, book.AUTHOR_ID FROM `book` ORDER BY book.TITLE ASC"; + $this->assertEquals($expectedSQL, $con->getLastExecutedQuery(), 'orderByXXX() is turned into orderBy(XXX)'); + + $c = new ModelCriteria('bookstore', 'Book'); + $books = $c->orderByTitle(Criteria::DESC)->find($con); + $expectedSQL = "SELECT book.ID, book.TITLE, book.ISBN, book.PRICE, book.PUBLISHER_ID, book.AUTHOR_ID FROM `book` ORDER BY book.TITLE DESC"; + $this->assertEquals($expectedSQL, $con->getLastExecutedQuery(), 'orderByXXX($direction) is turned into orderBy(XXX, $direction)'); + } + + public function testMagicGroupBy() + { + $con = Propel::getConnection(BookPeer::DATABASE_NAME); + + $c = new ModelCriteria('bookstore', 'Book'); + $books = $c->groupByTitle()->find($con); + $expectedSQL = "SELECT book.ID, book.TITLE, book.ISBN, book.PRICE, book.PUBLISHER_ID, book.AUTHOR_ID FROM `book` GROUP BY book.TITLE"; + $this->assertEquals($expectedSQL, $con->getLastExecutedQuery(), 'groupByXXX() is turned into groupBy(XXX)'); + } + + public function testUseQuery() + { + $c = new ModelCriteria('bookstore', 'Book', 'b'); + $c->thisIsMe = true; + $c->where('b.Title = ?', 'foo'); + $c->setOffset(10); + $c->leftJoin('b.Author'); + + $c2 = $c->useQuery('Author'); + $this->assertTrue($c2 instanceof AuthorQuery, 'useQuery() returns a secondary Criteria'); + $this->assertEquals($c, $c2->getPrimaryCriteria(), 'useQuery() sets the primary Criteria os the secondary Criteria'); + $c2->where('Author.FirstName = ?', 'john'); + $c2->limit(5); + + $c = $c2->endUse(); + $this->assertTrue($c->thisIsMe, 'endUse() returns the Primary Criteria'); + $this->assertEquals('Book', $c->getModelName(), 'endUse() returns the Primary Criteria'); + + $con = Propel::getConnection(BookPeer::DATABASE_NAME); + $c->find($con); + $expectedSQL = "SELECT book.ID, book.TITLE, book.ISBN, book.PRICE, book.PUBLISHER_ID, book.AUTHOR_ID FROM `book` LEFT JOIN author ON (book.AUTHOR_ID=author.ID) WHERE book.TITLE = 'foo' AND author.FIRST_NAME = 'john' LIMIT 10, 5"; + $this->assertEquals($expectedSQL, $con->getLastExecutedQuery(), 'useQuery() and endUse() allow to merge a secondary criteria'); + } + + public function testUseQueryAlias() + { + $c = new ModelCriteria('bookstore', 'Book', 'b'); + $c->thisIsMe = true; + $c->where('b.Title = ?', 'foo'); + $c->setOffset(10); + $c->leftJoin('b.Author a'); + + $c2 = $c->useQuery('a'); + $this->assertTrue($c2 instanceof AuthorQuery, 'useQuery() returns a secondary Criteria'); + $this->assertEquals($c, $c2->getPrimaryCriteria(), 'useQuery() sets the primary Criteria os the secondary Criteria'); + $this->assertEquals(array('a' => 'author'), $c2->getAliases(), 'useQuery() sets the secondary Criteria alias correctly'); + $c2->where('a.FirstName = ?', 'john'); + $c2->limit(5); + + $c = $c2->endUse(); + $this->assertTrue($c->thisIsMe, 'endUse() returns the Primary Criteria'); + $this->assertEquals('Book', $c->getModelName(), 'endUse() returns the Primary Criteria'); + + $con = Propel::getConnection(BookPeer::DATABASE_NAME); + $c->find($con); + $expectedSQL = "SELECT book.ID, book.TITLE, book.ISBN, book.PRICE, book.PUBLISHER_ID, book.AUTHOR_ID FROM `book` LEFT JOIN author a ON (book.AUTHOR_ID=a.ID) WHERE book.TITLE = 'foo' AND a.FIRST_NAME = 'john' LIMIT 10, 5"; + $this->assertEquals($expectedSQL, $con->getLastExecutedQuery(), 'useQuery() and endUse() allow to merge a secondary criteria'); + } + + public function testUseQueryCustomClass() + { + $c = new ModelCriteria('bookstore', 'Book', 'b'); + $c->thisIsMe = true; + $c->where('b.Title = ?', 'foo'); + $c->setLimit(10); + $c->leftJoin('b.Author a'); + + $c2 = $c->useQuery('a', 'ModelCriteriaForUseQuery'); + $this->assertTrue($c2 instanceof ModelCriteriaForUseQuery, 'useQuery() returns a secondary Criteria with the custom class'); + $c2->withNoName(); + $c = $c2->endUse(); + + $con = Propel::getConnection(BookPeer::DATABASE_NAME); + $c->find($con); + $expectedSQL = "SELECT book.ID, book.TITLE, book.ISBN, book.PRICE, book.PUBLISHER_ID, book.AUTHOR_ID FROM `book` LEFT JOIN author a ON (book.AUTHOR_ID=a.ID) WHERE book.TITLE = 'foo' AND a.FIRST_NAME IS NOT NULL AND a.LAST_NAME IS NOT NULL LIMIT 10"; + $this->assertEquals($expectedSQL, $con->getLastExecutedQuery(), 'useQuery() and endUse() allow to merge a custom secondary criteria'); + } + + public function testUseQueryJoinWithFind() + { + $c = new ModelCriteria('bookstore', 'Review'); + $c->joinWith('Book'); + + $c2 = $c->useQuery('Book'); + + $joins = $c->getJoins(); + $this->assertEquals($c->getPreviousJoin(), null, 'The default value for previousJoin remains null'); + $this->assertEquals($c2->getPreviousJoin(), $joins['Book'], 'useQuery() sets the previousJoin'); + + // join Book with Author, which is possible since previousJoin is set, which makes resolving of relations possible during hydration + $c2->joinWith('Author'); + + $c = $c2->endUse(); + + $con = Propel::getConnection(BookPeer::DATABASE_NAME); + $c->find($con); + $expectedSQL = "SELECT review.ID, review.REVIEWED_BY, review.REVIEW_DATE, review.RECOMMENDED, review.STATUS, review.BOOK_ID, book.ID, book.TITLE, book.ISBN, book.PRICE, book.PUBLISHER_ID, book.AUTHOR_ID, author.ID, author.FIRST_NAME, author.LAST_NAME, author.EMAIL, author.AGE FROM `review` INNER JOIN book ON (review.BOOK_ID=book.ID) INNER JOIN author ON (book.AUTHOR_ID=author.ID)"; + $this->assertEquals($expectedSQL, $con->getLastExecutedQuery(), 'useQuery() and joinWith() can be used together and form a correct query'); + } + + public function testUseQueryCustomRelationPhpName() + { + $c = new ModelCriteria('bookstore', 'BookstoreContest'); + $c->leftJoin('BookstoreContest.Work'); + $c2 = $c->useQuery('Work'); + $this->assertTrue($c2 instanceof BookQuery, 'useQuery() returns a secondary Criteria'); + $this->assertEquals($c, $c2->getPrimaryCriteria(), 'useQuery() sets the primary Criteria os the secondary Criteria'); + //$this->assertEquals(array('a' => 'author'), $c2->getAliases(), 'useQuery() sets the secondary Criteria alias correctly'); + $c2->where('Work.Title = ?', 'War And Peace'); + + $c = $c2->endUse(); + $this->assertEquals('BookstoreContest', $c->getModelName(), 'endUse() returns the Primary Criteria'); + + $con = Propel::getConnection(BookPeer::DATABASE_NAME); + $c->find($con); + $expectedSQL = "SELECT bookstore_contest.BOOKSTORE_ID, bookstore_contest.CONTEST_ID, bookstore_contest.PRIZE_BOOK_ID FROM `bookstore_contest` LEFT JOIN book ON (bookstore_contest.PRIZE_BOOK_ID=book.ID) WHERE book.TITLE = 'War And Peace'"; + $this->assertEquals($expectedSQL, $con->getLastExecutedQuery(), 'useQuery() and endUse() allow to merge a secondary criteria'); + } + + public function testUseQueryCustomRelationPhpNameAndAlias() + { + $c = new ModelCriteria('bookstore', 'BookstoreContest'); + $c->leftJoin('BookstoreContest.Work w'); + $c2 = $c->useQuery('w'); + $this->assertTrue($c2 instanceof BookQuery, 'useQuery() returns a secondary Criteria'); + $this->assertEquals($c, $c2->getPrimaryCriteria(), 'useQuery() sets the primary Criteria os the secondary Criteria'); + //$this->assertEquals(array('a' => 'author'), $c2->getAliases(), 'useQuery() sets the secondary Criteria alias correctly'); + $c2->where('w.Title = ?', 'War And Peace'); + + $c = $c2->endUse(); + $this->assertEquals('BookstoreContest', $c->getModelName(), 'endUse() returns the Primary Criteria'); + + $con = Propel::getConnection(BookPeer::DATABASE_NAME); + $c->find($con); + $expectedSQL = "SELECT bookstore_contest.BOOKSTORE_ID, bookstore_contest.CONTEST_ID, bookstore_contest.PRIZE_BOOK_ID FROM `bookstore_contest` LEFT JOIN book w ON (bookstore_contest.PRIZE_BOOK_ID=w.ID) WHERE w.TITLE = 'War And Peace'"; + $this->assertEquals($expectedSQL, $con->getLastExecutedQuery(), 'useQuery() and endUse() allow to merge a secondary criteria'); + } + + public function testMergeWithJoins() + { + $c1 = new ModelCriteria('bookstore', 'Book', 'b'); + $c1->leftJoin('b.Author a'); + $c2 = new ModelCriteria('bookstore', 'Author'); + $c1->mergeWith($c2); + $joins = $c1->getJoins(); + $this->assertEquals(1, count($joins), 'mergeWith() does not remove an existing join'); + $this->assertEquals('LEFT JOIN : book.AUTHOR_ID=a.ID(ignoreCase not considered)', $joins['a']->toString(), 'mergeWith() does not remove an existing join'); + $c1 = new ModelCriteria('bookstore', 'Book', 'b'); + $c2 = new ModelCriteria('bookstore', 'Book', 'b'); + $c2->leftJoin('b.Author a'); + $c1->mergeWith($c2); + $joins = $c1->getJoins(); + $this->assertEquals(1, count($joins), 'mergeWith() merge joins to an empty join'); + $this->assertEquals('LEFT JOIN : book.AUTHOR_ID=a.ID(ignoreCase not considered)', $joins['a']->toString(), 'mergeWith() merge joins to an empty join'); + + $c1 = new ModelCriteria('bookstore', 'Book', 'b'); + $c1->leftJoin('b.Author a'); + $c2 = new ModelCriteria('bookstore', 'Book', 'b'); + $c2->innerJoin('b.Publisher p'); + $c1->mergeWith($c2); + $joins = $c1->getJoins(); + $this->assertEquals(2, count($joins), 'mergeWith() merge joins to an existing join'); + $this->assertEquals('LEFT JOIN : book.AUTHOR_ID=a.ID(ignoreCase not considered)', $joins['a']->toString(), 'mergeWith() merge joins to an empty join'); + $this->assertEquals('INNER JOIN : book.PUBLISHER_ID=p.ID(ignoreCase not considered)', $joins['p']->toString(), 'mergeWith() merge joins to an empty join'); + } + + public function testMergeWithWiths() + { + $c1 = new ModelCriteria('bookstore', 'Book', 'b'); + $c1->leftJoinWith('b.Author a'); + $c2 = new ModelCriteria('bookstore', 'Author'); + $c1->mergeWith($c2); + $with = $c1->getWith(); + $this->assertEquals(1, count($with), 'mergeWith() does not remove an existing join'); + $this->assertEquals('LEFT JOIN : book.AUTHOR_ID=a.ID(ignoreCase not considered) tableMap: AuthorTableMap relationMap: Author previousJoin: null relationAlias: a', $with['a']->__toString(), 'mergeWith() does not remove an existing join'); + + $c1 = new ModelCriteria('bookstore', 'Book', 'b'); + $c2 = new ModelCriteria('bookstore', 'Book', 'b'); + $c2->leftJoinWith('b.Author a'); + $c1->mergeWith($c2); + $with = $c1->getWith(); + $this->assertEquals(1, count($with), 'mergeWith() merge joins to an empty join'); + $this->assertEquals('LEFT JOIN : book.AUTHOR_ID=a.ID(ignoreCase not considered) tableMap: AuthorTableMap relationMap: Author previousJoin: null relationAlias: a', $with['a']->__toString(), 'mergeWith() merge joins to an empty join'); + + $c1 = new ModelCriteria('bookstore', 'Book', 'b'); + $c1->leftJoinWith('b.Author a'); + $c2 = new ModelCriteria('bookstore', 'Book', 'b'); + $c2->innerJoinWith('b.Publisher p'); + $c1->mergeWith($c2); + $with = $c1->getWith(); + $this->assertEquals(2, count($with), 'mergeWith() merge joins to an existing join'); + $this->assertEquals('LEFT JOIN : book.AUTHOR_ID=a.ID(ignoreCase not considered) tableMap: AuthorTableMap relationMap: Author previousJoin: null relationAlias: a', $with['a']->__toString(), 'mergeWith() merge joins to an empty join'); + $this->assertEquals('INNER JOIN : book.PUBLISHER_ID=p.ID(ignoreCase not considered) tableMap: PublisherTableMap relationMap: Publisher previousJoin: null relationAlias: p', $with['p']->__toString(), 'mergeWith() merge joins to an empty join'); + + } + + public function testGetAliasedColName() + { + $c = new ModelCriteria('bookstore', 'Book'); + $this->assertEquals(BookPeer::TITLE, $c->getAliasedColName(BookPeer::TITLE), 'getAliasedColName() returns the input when the table has no alias'); + + $c = new ModelCriteria('bookstore', 'Book'); + $c->setModelAlias('foo'); + $this->assertEquals(BookPeer::TITLE, $c->getAliasedColName(BookPeer::TITLE), 'getAliasedColName() returns the input when the table has a query alias'); + + $c = new ModelCriteria('bookstore', 'Book'); + $c->setModelAlias('foo', true); + $this->assertEquals('foo.TITLE', $c->getAliasedColName(BookPeer::TITLE), 'getAliasedColName() returns the column name with table alias when the table has a true alias'); + } + + public function testAddUsingAliasNoAlias() + { + $c1 = new ModelCriteria('bookstore', 'Book'); + $c1->addUsingAlias(BookPeer::TITLE, 'foo'); + $c2 = new ModelCriteria('bookstore', 'Book'); + $c2->add(BookPeer::TITLE, 'foo'); + $this->assertEquals($c2, $c1, 'addUsingalias() translates to add() when the table has no alias'); + } + + public function testAddUsingAliasQueryAlias() + { + $c1 = new ModelCriteria('bookstore', 'Book', 'b'); + $c1->addUsingAlias(BookPeer::TITLE, 'foo'); + $c2 = new ModelCriteria('bookstore', 'Book', 'b'); + $c2->add(BookPeer::TITLE, 'foo'); + $this->assertEquals($c2, $c1, 'addUsingalias() translates the colname using the table alias before calling add() when the table has a true alias'); + } + + public function testAddUsingAliasTrueAlias() + { + $c1 = new ModelCriteria('bookstore', 'Book'); + $c1->setModelAlias('b', true); + $c1->addUsingAlias(BookPeer::TITLE, 'foo'); + $c2 = new ModelCriteria('bookstore', 'Book'); + $c2->setModelAlias('b', true); + $c2->add('b.TITLE', 'foo'); + $this->assertEquals($c2, $c1, 'addUsingalias() translates to add() when the table has a true alias'); + } + + public function testAddUsingAliasTwice() + { + $c1 = new ModelCriteria('bookstore', 'Book'); + $c1->addUsingAlias(BookPeer::TITLE, 'foo'); + $c1->addUsingAlias(BookPeer::TITLE, 'bar'); + $c2 = new ModelCriteria('bookstore', 'Book'); + $c2->add(BookPeer::TITLE, 'foo'); + $c2->addAnd(BookPeer::TITLE, 'bar'); + $this->assertEquals($c2, $c1, 'addUsingalias() translates to addAnd() when the table already has a condition on the column'); + } + + public function testAddUsingAliasTrueAliasTwice() + { + $c1 = new ModelCriteria('bookstore', 'Book'); + $c1->setModelAlias('b', true); + $c1->addUsingAlias(BookPeer::TITLE, 'foo'); + $c1->addUsingAlias(BookPeer::TITLE, 'bar'); + $c2 = new ModelCriteria('bookstore', 'Book'); + $c2->setModelAlias('b', true); + $c2->add('b.TITLE', 'foo'); + $c2->addAnd('b.TITLE', 'bar'); + $this->assertEquals($c2, $c1, 'addUsingalias() translates to addAnd() when the table already has a condition on the column'); + } + + public function testClone() + { + $bookQuery1 = BookQuery::create() + ->filterByPrice(1); + $bookQuery2 = clone $bookQuery1; + $bookQuery2 + ->filterByPrice(2); + $params = array(); + $sql = BasePeer::createSelectSql($bookQuery1, $params); + $this->assertEquals('SELECT FROM `book` WHERE book.PRICE=:p1', $sql, 'conditions applied on a cloned query don\'t get applied on the original query'); + } +} + +class TestableModelCriteria extends ModelCriteria +{ + public $joins = array(); + + public function replaceNames(&$clause) + { + return parent::replaceNames($clause); + } + +} + +class ModelCriteriaForUseQuery extends ModelCriteria +{ + public function __construct($dbName = 'bookstore', $modelName = 'Author', $modelAlias = null) + { + parent::__construct($dbName, $modelName, $modelAlias); + } + + public function withNoName() + { + return $this + ->filterBy('FirstName', null, Criteria::ISNOTNULL) + ->where($this->getModelAliasOrName() . '.LastName IS NOT NULL'); + } +} \ No newline at end of file diff --git a/library/propel/test/testsuite/runtime/query/ModelJoinTest.php b/library/propel/test/testsuite/runtime/query/ModelJoinTest.php new file mode 100644 index 000000000..afc89ff6d --- /dev/null +++ b/library/propel/test/testsuite/runtime/query/ModelJoinTest.php @@ -0,0 +1,85 @@ +assertNull($join->getTableMap(), 'getTableMap() returns null as long as no table map is set'); + + $tmap = new TableMap(); + $tmap->foo = 'bar'; + + $join->setTableMap($tmap); + $this->assertEquals($tmap, $join->getTableMap(), 'getTableMap() returns the TableMap previously set by setTableMap()'); + } + + public function testSetRelationMap() + { + $join = new ModelJoin(); + $this->assertNull($join->getRelationMap(), 'getRelationMap() returns null as long as no relation map is set'); + $bookTable = BookPeer::getTableMap(); + $relationMap = $bookTable->getRelation('Author'); + $join->setRelationMap($relationMap); + $this->assertEquals($relationMap, $join->getRelationMap(), 'getRelationMap() returns the RelationMap previously set by setRelationMap()'); + } + + public function testSetRelationMapDefinesJoinColumns() + { + $bookTable = BookPeer::getTableMap(); + $join = new ModelJoin(); + $join->setTableMap($bookTable); + $join->setRelationMap($bookTable->getRelation('Author')); + $this->assertEquals(array(BookPeer::AUTHOR_ID), $join->getLeftColumns(), 'setRelationMap() automatically sets the left columns'); + $this->assertEquals(array(AuthorPeer::ID), $join->getRightColumns(), 'setRelationMap() automatically sets the right columns'); + } + + public function testSetRelationMapLeftAlias() + { + $bookTable = BookPeer::getTableMap(); + $join = new ModelJoin(); + $join->setTableMap($bookTable); + $join->setRelationMap($bookTable->getRelation('Author'), 'b'); + $this->assertEquals(array('b.AUTHOR_ID'), $join->getLeftColumns(), 'setRelationMap() automatically sets the left columns using the left table alias'); + $this->assertEquals(array(AuthorPeer::ID), $join->getRightColumns(), 'setRelationMap() automatically sets the right columns'); + } + + public function testSetRelationMapRightAlias() + { + $bookTable = BookPeer::getTableMap(); + $join = new ModelJoin(); + $join->setTableMap($bookTable); + $join->setRelationMap($bookTable->getRelation('Author'), null, 'a'); + $this->assertEquals(array(BookPeer::AUTHOR_ID), $join->getLeftColumns(), 'setRelationMap() automatically sets the left columns'); + $this->assertEquals(array('a.ID'), $join->getRightColumns(), 'setRelationMap() automatically sets the right columns using the right table alias'); + } + + public function testSetRelationMapComposite() + { + $table = ReaderFavoritePeer::getTableMap(); + $join = new ModelJoin(); + $join->setTableMap($table); + $join->setRelationMap($table->getRelation('BookOpinion')); + $this->assertEquals(array(ReaderFavoritePeer::BOOK_ID, ReaderFavoritePeer::READER_ID), $join->getLeftColumns(), 'setRelationMap() automatically sets the left columns for composite relationships'); + $this->assertEquals(array(BookOpinionPeer::BOOK_ID, BookOpinionPeer::READER_ID), $join->getRightColumns(), 'setRelationMap() automatically sets the right columns for composite relationships'); + } + +} diff --git a/library/propel/test/testsuite/runtime/query/ModelWithTest.php b/library/propel/test/testsuite/runtime/query/ModelWithTest.php new file mode 100755 index 000000000..ffe5e8fe6 --- /dev/null +++ b/library/propel/test/testsuite/runtime/query/ModelWithTest.php @@ -0,0 +1,183 @@ +joinAuthor(); + $joins = $q->getJoins(); + $join = $joins['Author']; + $with = new ModelWith($join); + $this->assertEquals($with->getModelName(), 'Author', 'A ModelWith computes the model name from the join'); + $this->assertEquals($with->getModelPeerName(), 'AuthorPeer', 'A ModelWith computes the model peer name from the join'); + } + + public function testModelNameOneToMany() + { + $q = AuthorQuery::create() + ->joinBook(); + $joins = $q->getJoins(); + $join = $joins['Book']; + $with = new ModelWith($join); + $this->assertEquals($with->getModelName(), 'Book', 'A ModelWith computes the model peer name from the join'); + $this->assertEquals($with->getModelPeerName(), 'BookPeer', 'A ModelWith computes the model peer name from the join'); + } + + public function testModelNameAlias() + { + $q = BookQuery::create() + ->joinAuthor('a'); + $joins = $q->getJoins(); + $join = $joins['a']; + $with = new ModelWith($join); + $this->assertEquals($with->getModelName(), 'Author', 'A ModelWith computes the model peer name from the join'); + $this->assertEquals($with->getModelPeerName(), 'AuthorPeer', 'A ModelWith computes the model peer name from the join'); + } + + public function testRelationManyToOne() + { + $q = BookQuery::create() + ->joinAuthor(); + $joins = $q->getJoins(); + $join = $joins['Author']; + $with = new ModelWith($join); + $this->assertEquals($with->getRelationMethod(), 'setAuthor', 'A ModelWith computes the relation method from the join'); + $this->assertEquals($with->getRelationName(), 'Author', 'A ModelWith computes the relation name from the join'); + $this->assertFalse($with->isAdd(), 'A ModelWith computes the relation cardinality from the join'); + } + + public function testRelationOneToMany() + { + $q = AuthorQuery::create() + ->joinBook(); + $joins = $q->getJoins(); + $join = $joins['Book']; + $with = new ModelWith($join); + $this->assertEquals($with->getRelationMethod(), 'addBook', 'A ModelWith computes the relation method from the join'); + $this->assertEquals($with->getRelationName(), 'Books', 'A ModelWith computes the relation name from the join'); + $this->assertTrue($with->isAdd(), 'A ModelWith computes the relation cardinality from the join'); + } + + public function testRelationOneToOne() + { + $q = BookstoreEmployeeQuery::create() + ->joinBookstoreEmployeeAccount(); + $joins = $q->getJoins(); + $join = $joins['BookstoreEmployeeAccount']; + $with = new ModelWith($join); + $this->assertEquals($with->getRelationMethod(), 'setBookstoreEmployeeAccount', 'A ModelWith computes the relation method from the join'); + $this->assertEquals($with->getRelationName(), 'BookstoreEmployeeAccount', 'A ModelWith computes the relation name from the join'); + $this->assertFalse($with->isAdd(), 'A ModelWith computes the relation cardinality from the join'); + } + + public function testIsPrimary() + { + $q = AuthorQuery::create() + ->joinBook(); + $joins = $q->getJoins(); + $join = $joins['Book']; + $with = new ModelWith($join); + $this->assertTrue($with->isPrimary(), 'A ModelWith initialized from a primary join is primary'); + + $q = BookQuery::create() + ->joinAuthor() + ->joinReview(); + $joins = $q->getJoins(); + $join = $joins['Review']; + $with = new ModelWith($join); + $this->assertTrue($with->isPrimary(), 'A ModelWith initialized from a primary join is primary'); + + $q = AuthorQuery::create() + ->join('Author.Book') + ->join('Book.Publisher'); + $joins = $q->getJoins(); + $join = $joins['Publisher']; + $with = new ModelWith($join); + $this->assertFalse($with->isPrimary(), 'A ModelWith initialized from a non-primary join is not primary'); + } + + public function testGetRelatedClass() + { + $q = AuthorQuery::create() + ->joinBook(); + $joins = $q->getJoins(); + $join = $joins['Book']; + $with = new ModelWith($join); + $this->assertNull($with->getRelatedClass(), 'A ModelWith initialized from a primary join has a null related class'); + + $q = AuthorQuery::create('a') + ->joinBook(); + $joins = $q->getJoins(); + $join = $joins['Book']; + $with = new ModelWith($join); + $this->assertNull($with->getRelatedClass(), 'A ModelWith initialized from a primary join with alias has a null related class'); + + $q = AuthorQuery::create() + ->joinBook('b'); + $joins = $q->getJoins(); + $join = $joins['b']; + $with = new ModelWith($join); + $this->assertNull($with->getRelatedClass(), 'A ModelWith initialized from a primary join with alias has a null related class'); + + $q = AuthorQuery::create() + ->join('Author.Book') + ->join('Book.Publisher'); + $joins = $q->getJoins(); + $join = $joins['Publisher']; + $with = new ModelWith($join); + $this->assertEquals($with->getRelatedClass(), 'Book', 'A ModelWith uses the previous join relation name as related class'); + + $q = ReviewQuery::create() + ->join('Review.Book') + ->join('Book.Author') + ->join('Book.Publisher'); + $joins = $q->getJoins(); + $join = $joins['Publisher']; + $with = new ModelWith($join); + $this->assertEquals($with->getRelatedClass(), 'Book', 'A ModelWith uses the previous join relation name as related class'); + + $q = ReviewQuery::create() + ->join('Review.Book') + ->join('Book.BookOpinion') + ->join('BookOpinion.BookReader'); + $joins = $q->getJoins(); + $join = $joins['BookOpinion']; + $with = new ModelWith($join); + $this->assertEquals($with->getRelatedClass(), 'Book', 'A ModelWith uses the previous join relation name as related class'); + $join = $joins['BookReader']; + $with = new ModelWith($join); + $this->assertEquals($with->getRelatedClass(), 'BookOpinion', 'A ModelWith uses the previous join relation name as related class'); + + $q = BookReaderQuery::create() + ->join('BookReader.BookOpinion') + ->join('BookOpinion.Book') + ->join('Book.Author'); + $joins = $q->getJoins(); + $join = $joins['Book']; + $with = new ModelWith($join); + $this->assertEquals($with->getRelatedClass(), 'BookOpinion', 'A ModelWith uses the previous join relation name as related class'); + $join = $joins['Author']; + $with = new ModelWith($join); + $this->assertEquals($with->getRelatedClass(), 'Book', 'A ModelWith uses the previous join relation name as related class'); + } +} diff --git a/library/propel/test/testsuite/runtime/query/PropelQueryTest.php b/library/propel/test/testsuite/runtime/query/PropelQueryTest.php new file mode 100644 index 000000000..5ef619da1 --- /dev/null +++ b/library/propel/test/testsuite/runtime/query/PropelQueryTest.php @@ -0,0 +1,63 @@ +assertEquals($expected, $q, 'from() returns a Model query instance based on the model name'); + + $q = PropelQuery::from('Book b'); + $expected = new BookQuery(); + $expected->setModelAlias('b'); + $this->assertEquals($expected, $q, 'from() sets the model alias if found after the blank'); + + $q = PropelQuery::from('myBook'); + $expected = new myBookQuery(); + $this->assertEquals($expected, $q, 'from() can find custom query classes'); + + try { + $q = PropelQuery::from('Foo'); + $this->fail('PropelQuery::from() throws an exception when called on a non-existing query class'); + } catch (PropelException $e) { + $this->assertTrue(true, 'PropelQuery::from() throws an exception when called on a non-existing query class'); + } + } + + public function testQuery() + { + BookstoreDataPopulator::depopulate(); + BookstoreDataPopulator::populate(); + + $book = PropelQuery::from('Book b') + ->where('b.Title like ?', 'Don%') + ->orderBy('b.ISBN', 'desc') + ->findOne(); + $this->assertTrue($book instanceof Book); + $this->assertEquals('Don Juan', $book->getTitle()); + + } +} + +class myBookQuery extends BookQuery +{ +} \ No newline at end of file diff --git a/library/propel/test/testsuite/runtime/util/BasePeerExceptionsTest.php b/library/propel/test/testsuite/runtime/util/BasePeerExceptionsTest.php new file mode 100755 index 000000000..e3d56f2a6 --- /dev/null +++ b/library/propel/test/testsuite/runtime/util/BasePeerExceptionsTest.php @@ -0,0 +1,94 @@ +add(BookPeer::ID, 12, ' BAD SQL'); + BookPeer::addSelectColumns($c); + BasePeer::doSelect($c); + } catch (PropelException $e) { + $this->assertContains('[SELECT book.ID, book.TITLE, book.ISBN, book.PRICE, book.PUBLISHER_ID, book.AUTHOR_ID FROM `book` WHERE book.ID BAD SQL:p1]', $e->getMessage(), 'SQL query is written in the exception message'); + } + } + + public function testDoCount() + { + try { + $c = new Criteria(); + $c->add(BookPeer::ID, 12, ' BAD SQL'); + BookPeer::addSelectColumns($c); + BasePeer::doCount($c); + } catch (PropelException $e) { + $this->assertContains('[SELECT COUNT(*) FROM `book` WHERE book.ID BAD SQL:p1]', $e->getMessage(), 'SQL query is written in the exception message'); + } + } + + public function testDoDelete() + { + try { + $c = new Criteria(); + $c->setPrimaryTableName(BookPeer::TABLE_NAME); + $c->add(BookPeer::ID, 12, ' BAD SQL'); + BasePeer::doDelete($c, Propel::getConnection()); + } catch (PropelException $e) { + $this->assertContains('[DELETE FROM `book` WHERE book.ID BAD SQL:p1]', $e->getMessage(), 'SQL query is written in the exception message'); + } + } + + public function testDoDeleteAll() + { + try { + BasePeer::doDeleteAll('BAD TABLE', Propel::getConnection()); + } catch (PropelException $e) { + $this->assertContains('[DELETE FROM `BAD` `TABLE`]', $e->getMessage(), 'SQL query is written in the exception message'); + } + } + + public function testDoUpdate() + { + try { + $c1 = new Criteria(); + $c1->setPrimaryTableName(BookPeer::TABLE_NAME); + $c1->add(BookPeer::ID, 12, ' BAD SQL'); + $c2 = new Criteria(); + $c2->add(BookPeer::TITLE, 'Foo'); + BasePeer::doUpdate($c1, $c2, Propel::getConnection()); + } catch (PropelException $e) { + $this->assertContains('[UPDATE `book` SET `TITLE`=:p1 WHERE book.ID BAD SQL:p2]', $e->getMessage(), 'SQL query is written in the exception message'); + } + } + + public function testDoInsert() + { + try { + $c = new Criteria(); + $c->setPrimaryTableName(BookPeer::TABLE_NAME); + $c->add(BookPeer::AUTHOR_ID, 'lkhlkhj'); + BasePeer::doInsert($c, Propel::getConnection()); + } catch (PropelException $e) { + $this->assertContains('[INSERT INTO `book` (`AUTHOR_ID`) VALUES (:p1)]', $e->getMessage(), 'SQL query is written in the exception message'); + } + } + +} diff --git a/library/propel/test/testsuite/runtime/util/BasePeerTest.php b/library/propel/test/testsuite/runtime/util/BasePeerTest.php new file mode 100644 index 000000000..cb8772ee5 --- /dev/null +++ b/library/propel/test/testsuite/runtime/util/BasePeerTest.php @@ -0,0 +1,413 @@ + + * @package runtime.util + */ +class BasePeerTest extends BookstoreTestBase +{ + + /** + * @link http://propel.phpdb.org/trac/ticket/425 + */ + public function testMultipleFunctionInCriteria() + { + $db = Propel::getDB(BookPeer::DATABASE_NAME); + try { + $c = new Criteria(); + $c->setDistinct(); + if ($db instanceof DBPostgres) { + $c->addSelectColumn("substring(".BookPeer::TITLE." from position('Potter' in ".BookPeer::TITLE.")) AS col"); + } else { + $this->markTestSkipped(); + } + $stmt = BookPeer::doSelectStmt( $c ); + } catch (PropelException $x) { + $this->fail("Paring of nested functions failed: " . $x->getMessage()); + } + } + + public function testNeedsSelectAliases() + { + $c = new Criteria(); + $this->assertFalse(BasePeer::needsSelectAliases($c), 'Empty Criterias dont need aliases'); + + $c = new Criteria(); + $c->addSelectColumn(BookPeer::ID); + $c->addSelectColumn(BookPeer::TITLE); + $this->assertFalse(BasePeer::needsSelectAliases($c), 'Criterias with distinct column names dont need aliases'); + + $c = new Criteria(); + BookPeer::addSelectColumns($c); + $this->assertFalse(BasePeer::needsSelectAliases($c), 'Criterias with only the columns of a model dont need aliases'); + + $c = new Criteria(); + $c->addSelectColumn(BookPeer::ID); + $c->addSelectColumn(AuthorPeer::ID); + $this->assertTrue(BasePeer::needsSelectAliases($c), 'Criterias with common column names do need aliases'); + } + + public function testTurnSelectColumnsToAliases() + { + $c1 = new Criteria(); + $c1->addSelectColumn(BookPeer::ID); + BasePeer::turnSelectColumnsToAliases($c1); + + $c2 = new Criteria(); + $c2->addAsColumn('book_ID', BookPeer::ID); + $this->assertTrue($c1->equals($c2)); + } + + public function testTurnSelectColumnsToAliasesPreservesAliases() + { + $c1 = new Criteria(); + $c1->addSelectColumn(BookPeer::ID); + $c1->addAsColumn('foo', BookPeer::TITLE); + BasePeer::turnSelectColumnsToAliases($c1); + + $c2 = new Criteria(); + $c2->addAsColumn('book_ID', BookPeer::ID); + $c2->addAsColumn('foo', BookPeer::TITLE); + $this->assertTrue($c1->equals($c2)); + } + + public function testTurnSelectColumnsToAliasesExisting() + { + $c1 = new Criteria(); + $c1->addSelectColumn(BookPeer::ID); + $c1->addAsColumn('book_ID', BookPeer::ID); + BasePeer::turnSelectColumnsToAliases($c1); + + $c2 = new Criteria(); + $c2->addAsColumn('book_ID_1', BookPeer::ID); + $c2->addAsColumn('book_ID', BookPeer::ID); + $this->assertTrue($c1->equals($c2)); + } + + public function testTurnSelectColumnsToAliasesDuplicate() + { + $c1 = new Criteria(); + $c1->addSelectColumn(BookPeer::ID); + $c1->addSelectColumn(BookPeer::ID); + BasePeer::turnSelectColumnsToAliases($c1); + + $c2 = new Criteria(); + $c2->addAsColumn('book_ID', BookPeer::ID); + $c2->addAsColumn('book_ID_1', BookPeer::ID); + $this->assertTrue($c1->equals($c2)); + } + + public function testDoCountDuplicateColumnName() + { + $con = Propel::getConnection(); + $c = new Criteria(); + $c->addSelectColumn(BookPeer::ID); + $c->addJoin(BookPeer::AUTHOR_ID, AuthorPeer::ID); + $c->addSelectColumn(AuthorPeer::ID); + $c->setLimit(3); + try { + $count = BasePeer::doCount($c, $con); + } catch (Exception $e) { + $this->fail('doCount() cannot deal with a criteria selecting duplicate column names '); + } + } + + public function testCreateSelectSqlPart() + { + $c = new Criteria(); + $c->addSelectColumn(BookPeer::ID); + $c->addAsColumn('book_ID', BookPeer::ID); + $fromClause = array(); + $selectSql = BasePeer::createSelectSqlPart($c, $fromClause); + $this->assertEquals('SELECT book.ID, book.ID AS book_ID', $selectSql, 'createSelectSqlPart() returns a SQL SELECT clause with both select and as columns'); + $this->assertEquals(array('book'), $fromClause, 'createSelectSqlPart() adds the tables from the select columns to the from clause'); + } + + public function testCreateSelectSqlPartSelectModifier() + { + $c = new Criteria(); + $c->addSelectColumn(BookPeer::ID); + $c->addAsColumn('book_ID', BookPeer::ID); + $c->setDistinct(); + $fromClause = array(); + $selectSql = BasePeer::createSelectSqlPart($c, $fromClause); + $this->assertEquals('SELECT DISTINCT book.ID, book.ID AS book_ID', $selectSql, 'createSelectSqlPart() includes the select modifiers in the SELECT clause'); + $this->assertEquals(array('book'), $fromClause, 'createSelectSqlPart() adds the tables from the select columns to the from clause'); + } + + public function testCreateSelectSqlPartAliasAll() + { + $c = new Criteria(); + $c->addSelectColumn(BookPeer::ID); + $c->addAsColumn('book_ID', BookPeer::ID); + $fromClause = array(); + $selectSql = BasePeer::createSelectSqlPart($c, $fromClause, true); + $this->assertEquals('SELECT book.ID AS book_ID_1, book.ID AS book_ID', $selectSql, 'createSelectSqlPart() aliases all columns if passed true as last parameter'); + $this->assertEquals(array(), $fromClause, 'createSelectSqlPart() does not add the tables from an all-aliased list of select columns'); + } + + public function testBigIntIgnoreCaseOrderBy() + { + BookstorePeer::doDeleteAll(); + + // Some sample data + $b = new Bookstore(); + $b->setStoreName("SortTest1")->setPopulationServed(2000)->save(); + + $b = new Bookstore(); + $b->setStoreName("SortTest2")->setPopulationServed(201)->save(); + + $b = new Bookstore(); + $b->setStoreName("SortTest3")->setPopulationServed(302)->save(); + + $b = new Bookstore(); + $b->setStoreName("SortTest4")->setPopulationServed(10000000)->save(); + + $c = new Criteria(); + $c->setIgnoreCase(true); + $c->add(BookstorePeer::STORE_NAME, 'SortTest%', Criteria::LIKE); + $c->addAscendingOrderByColumn(BookstorePeer::POPULATION_SERVED); + + $rows = BookstorePeer::doSelect($c); + $this->assertEquals('SortTest2', $rows[0]->getStoreName()); + $this->assertEquals('SortTest3', $rows[1]->getStoreName()); + $this->assertEquals('SortTest1', $rows[2]->getStoreName()); + $this->assertEquals('SortTest4', $rows[3]->getStoreName()); + } + + /** + * + */ + public function testMixedJoinOrder() + { + $this->markTestSkipped('Famous cross join problem, to be solved one day'); + $c = new Criteria(BookPeer::DATABASE_NAME); + $c->addSelectColumn(BookPeer::ID); + $c->addSelectColumn(BookPeer::TITLE); + + $c->addJoin(BookPeer::PUBLISHER_ID, PublisherPeer::ID, Criteria::LEFT_JOIN); + $c->addJoin(BookPeer::AUTHOR_ID, AuthorPeer::ID); + + $params = array(); + $sql = BasePeer::createSelectSql($c, $params); + + $expectedSql = "SELECT book.ID, book.TITLE FROM book LEFT JOIN publisher ON (book.PUBLISHER_ID=publisher.ID), author WHERE book.AUTHOR_ID=author.ID"; + $this->assertEquals($expectedSql, $sql); + } + + public function testMssqlApplyLimitNoOffset() + { + $db = Propel::getDB(BookPeer::DATABASE_NAME); + if(! ($db instanceof DBMSSQL)) + { + $this->markTestSkipped(); + } + + $c = new Criteria(BookPeer::DATABASE_NAME); + $c->addSelectColumn(BookPeer::ID); + $c->addSelectColumn(BookPeer::TITLE); + $c->addSelectColumn(PublisherPeer::NAME); + $c->addAsColumn('PublisherName','(SELECT MAX(publisher.NAME) FROM publisher WHERE publisher.ID = book.PUBLISHER_ID)'); + + $c->addJoin(BookPeer::PUBLISHER_ID, PublisherPeer::ID, Criteria::LEFT_JOIN); + + $c->setOffset(0); + $c->setLimit(20); + + $params = array(); + $sql = BasePeer::createSelectSql($c, $params); + + $expectedSql = "SELECT TOP 20 book.ID, book.TITLE, publisher.NAME, (SELECT MAX(publisher.NAME) FROM publisher WHERE publisher.ID = book.PUBLISHER_ID) AS PublisherName FROM book LEFT JOIN publisher ON (book.PUBLISHER_ID=publisher.ID)"; + $this->assertEquals($expectedSql, $sql); + } + + public function testMssqlApplyLimitWithOffset() + { + $db = Propel::getDB(BookPeer::DATABASE_NAME); + if(! ($db instanceof DBMSSQL)) + { + $this->markTestSkipped(); + } + + $c = new Criteria(BookPeer::DATABASE_NAME); + $c->addSelectColumn(BookPeer::ID); + $c->addSelectColumn(BookPeer::TITLE); + $c->addSelectColumn(PublisherPeer::NAME); + $c->addAsColumn('PublisherName','(SELECT MAX(publisher.NAME) FROM publisher WHERE publisher.ID = book.PUBLISHER_ID)'); + $c->addJoin(BookPeer::PUBLISHER_ID, PublisherPeer::ID, Criteria::LEFT_JOIN); + $c->setOffset(20); + $c->setLimit(20); + + $params = array(); + + $expectedSql = "SELECT [book.ID], [book.TITLE], [publisher.NAME], [PublisherName] FROM (SELECT ROW_NUMBER() OVER(ORDER BY book.ID) AS RowNumber, book.ID AS [book.ID], book.TITLE AS [book.TITLE], publisher.NAME AS [publisher.NAME], (SELECT MAX(publisher.NAME) FROM publisher WHERE publisher.ID = book.PUBLISHER_ID) AS [PublisherName] FROM book LEFT JOIN publisher ON (book.PUBLISHER_ID=publisher.ID)) AS derivedb WHERE RowNumber BETWEEN 21 AND 40"; + $sql = BasePeer::createSelectSql($c, $params); + $this->assertEquals($expectedSql, $sql); + } + + public function testMssqlApplyLimitWithOffsetOrderByAggregate() + { + $db = Propel::getDB(BookPeer::DATABASE_NAME); + if(! ($db instanceof DBMSSQL)) + { + $this->markTestSkipped(); + } + + $c = new Criteria(BookPeer::DATABASE_NAME); + $c->addSelectColumn(BookPeer::ID); + $c->addSelectColumn(BookPeer::TITLE); + $c->addSelectColumn(PublisherPeer::NAME); + $c->addAsColumn('PublisherName','(SELECT MAX(publisher.NAME) FROM publisher WHERE publisher.ID = book.PUBLISHER_ID)'); + $c->addJoin(BookPeer::PUBLISHER_ID, PublisherPeer::ID, Criteria::LEFT_JOIN); + $c->addDescendingOrderByColumn('PublisherName'); + $c->setOffset(20); + $c->setLimit(20); + + $params = array(); + + $expectedSql = "SELECT [book.ID], [book.TITLE], [publisher.NAME], [PublisherName] FROM (SELECT ROW_NUMBER() OVER(ORDER BY (SELECT MAX(publisher.NAME) FROM publisher WHERE publisher.ID = book.PUBLISHER_ID) DESC) AS RowNumber, book.ID AS [book.ID], book.TITLE AS [book.TITLE], publisher.NAME AS [publisher.NAME], (SELECT MAX(publisher.NAME) FROM publisher WHERE publisher.ID = book.PUBLISHER_ID) AS [PublisherName] FROM book LEFT JOIN publisher ON (book.PUBLISHER_ID=publisher.ID)) AS derivedb WHERE RowNumber BETWEEN 21 AND 40"; + $sql = BasePeer::createSelectSql($c, $params); + $this->assertEquals($expectedSql, $sql); + } + + public function testMssqlApplyLimitWithOffsetMultipleOrderBy() + { + $db = Propel::getDB(BookPeer::DATABASE_NAME); + if(! ($db instanceof DBMSSQL)) + { + $this->markTestSkipped(); + } + + $c = new Criteria(BookPeer::DATABASE_NAME); + $c->addSelectColumn(BookPeer::ID); + $c->addSelectColumn(BookPeer::TITLE); + $c->addSelectColumn(PublisherPeer::NAME); + $c->addAsColumn('PublisherName','(SELECT MAX(publisher.NAME) FROM publisher WHERE publisher.ID = book.PUBLISHER_ID)'); + $c->addJoin(BookPeer::PUBLISHER_ID, PublisherPeer::ID, Criteria::LEFT_JOIN); + $c->addDescendingOrderByColumn('PublisherName'); + $c->addAscendingOrderByColumn(BookPeer::TITLE); + $c->setOffset(20); + $c->setLimit(20); + + $params = array(); + + $expectedSql = "SELECT [book.ID], [book.TITLE], [publisher.NAME], [PublisherName] FROM (SELECT ROW_NUMBER() OVER(ORDER BY (SELECT MAX(publisher.NAME) FROM publisher WHERE publisher.ID = book.PUBLISHER_ID) DESC, book.TITLE ASC) AS RowNumber, book.ID AS [book.ID], book.TITLE AS [book.TITLE], publisher.NAME AS [publisher.NAME], (SELECT MAX(publisher.NAME) FROM publisher WHERE publisher.ID = book.PUBLISHER_ID) AS [PublisherName] FROM book LEFT JOIN publisher ON (book.PUBLISHER_ID=publisher.ID)) AS derivedb WHERE RowNumber BETWEEN 21 AND 40"; + $sql = BasePeer::createSelectSql($c, $params); + $this->assertEquals($expectedSql, $sql); + } + + /** + * @expectedException PropelException + */ + public function testDoDeleteNoCondition() + { + $con = Propel::getConnection(); + $c = new Criteria(BookPeer::DATABASE_NAME); + BasePeer::doDelete($c, $con); + } + + public function testDoDeleteSimpleCondition() + { + $con = Propel::getConnection(); + $c = new Criteria(BookPeer::DATABASE_NAME); + $c->add(BookPeer::TITLE, 'War And Peace'); + BasePeer::doDelete($c, $con); + $expectedSQL = "DELETE FROM `book` WHERE book.TITLE='War And Peace'"; + $this->assertEquals($expectedSQL, $con->getLastExecutedQuery(), 'doDelete() translates a contition into a WHERE'); + } + + public function testDoDeleteSeveralConditions() + { + $con = Propel::getConnection(); + $c = new Criteria(BookPeer::DATABASE_NAME); + $c->add(BookPeer::TITLE, 'War And Peace'); + $c->add(BookPeer::ID, 12); + BasePeer::doDelete($c, $con); + $expectedSQL = "DELETE FROM `book` WHERE book.TITLE='War And Peace' AND book.ID=12"; + $this->assertEquals($expectedSQL, $con->getLastExecutedQuery(), 'doDelete() combines conditions in WHERE whith an AND'); + } + + public function testDoDeleteTableAlias() + { + $con = Propel::getConnection(); + $c = new Criteria(BookPeer::DATABASE_NAME); + $c->addAlias('b', BookPeer::TABLE_NAME); + $c->add('b.TITLE', 'War And Peace'); + BasePeer::doDelete($c, $con); + $expectedSQL = "DELETE b FROM `book` AS b WHERE b.TITLE='War And Peace'"; + $this->assertEquals($expectedSQL, $con->getLastExecutedQuery(), 'doDelete() accepts a Criteria with a table alias'); + } + + /** + * Not documented anywhere, and probably wrong + * @see http://www.propelorm.org/ticket/952 + */ + public function testDoDeleteSeveralTables() + { + $con = Propel::getConnection(); + $count = $con->getQueryCount(); + $c = new Criteria(BookPeer::DATABASE_NAME); + $c->add(BookPeer::TITLE, 'War And Peace'); + $c->add(AuthorPeer::FIRST_NAME, 'Leo'); + BasePeer::doDelete($c, $con); + $expectedSQL = "DELETE FROM `author` WHERE author.FIRST_NAME='Leo'"; + $this->assertEquals($expectedSQL, $con->getLastExecutedQuery(), 'doDelete() issues two DELETE queries when passed conditions on two tables'); + $this->assertEquals($count + 2, $con->getQueryCount(), 'doDelete() issues two DELETE queries when passed conditions on two tables'); + + $c = new Criteria(BookPeer::DATABASE_NAME); + $c->add(AuthorPeer::FIRST_NAME, 'Leo'); + $c->add(BookPeer::TITLE, 'War And Peace'); + BasePeer::doDelete($c, $con); + $expectedSQL = "DELETE FROM `book` WHERE book.TITLE='War And Peace'"; + $this->assertEquals($expectedSQL, $con->getLastExecutedQuery(), 'doDelete() issues two DELETE queries when passed conditions on two tables'); + $this->assertEquals($count + 4, $con->getQueryCount(), 'doDelete() issues two DELETE queries when passed conditions on two tables'); + } + + public function testCommentDoSelect() + { + $c = new Criteria(); + $c->setComment('Foo'); + $c->addSelectColumn(BookPeer::ID); + $expected = 'SELECT /* Foo */ book.ID FROM `book`'; + $params = array(); + $this->assertEquals($expected, BasePeer::createSelectSQL($c, $params), 'Criteria::setComment() adds a comment to select queries'); + } + + public function testCommentDoUpdate() + { + $c1 = new Criteria(); + $c1->setPrimaryTableName(BookPeer::TABLE_NAME); + $c1->setComment('Foo'); + $c2 = new Criteria(); + $c2->add(BookPeer::TITLE, 'Updated Title'); + $con = Propel::getConnection(BookPeer::DATABASE_NAME); + BasePeer::doUpdate($c1, $c2, $con); + $expected = 'UPDATE /* Foo */ `book` SET `TITLE`=\'Updated Title\''; + $this->assertEquals($expected, $con->getLastExecutedQuery(), 'Criteria::setComment() adds a comment to update queries'); + } + + public function testCommentDoDelete() + { + $c = new Criteria(); + $c->setComment('Foo'); + $c->add(BookPeer::TITLE, 'War And Peace'); + $con = Propel::getConnection(BookPeer::DATABASE_NAME); + BasePeer::doDelete($c, $con); + $expected = 'DELETE /* Foo */ FROM `book` WHERE book.TITLE=\'War And Peace\''; + $this->assertEquals($expected, $con->getLastExecutedQuery(), 'Criteria::setComment() adds a comment to delete queries'); + } + +} diff --git a/library/propel/test/testsuite/runtime/util/PropelConfigurationTest.php b/library/propel/test/testsuite/runtime/util/PropelConfigurationTest.php new file mode 100644 index 000000000..d653f3318 --- /dev/null +++ b/library/propel/test/testsuite/runtime/util/PropelConfigurationTest.php @@ -0,0 +1,69 @@ + array('fooo' => 'bar', 'fi' => array('fooooo' => 'bara')), 'baz' => 'bar2'); + + public function testConstruct() + { + $conf = new PropelConfiguration($this->testArray); + $this->assertEquals($this->testArray, $conf->getParameters(), 'constructor sets values from an associative array'); + } + + public function testGetParameters() + { + $conf = new PropelConfiguration($this->testArray); + $expected = array('foo.fooo' => 'bar', 'foo.fi.fooooo' => 'bara', 'baz' => 'bar2'); + $this->assertEquals($expected, $conf->getParameters(PropelConfiguration::TYPE_ARRAY_FLAT), 'getParameters can return a flat array'); + } + + public function testGetParameter() + { + $conf = new PropelConfiguration($this->testArray); + $this->assertEquals('bar', $conf->getParameter('foo.fooo'), 'getParameter accepts a flat key'); + $this->assertEquals('bara', $conf->getParameter('foo.fi.fooooo'), 'getParameter accepts a flat key'); + $this->assertEquals('bar2', $conf->getParameter('baz'), 'getParameter accepts a flat key'); + } + + public function testGetParameterDefault() + { + $conf = new PropelConfiguration($this->testArray); + $this->assertEquals('bar', $conf->getParameter('foo.fooo'), 'getParameter accepts a flat key'); + $this->assertEquals('', $conf->getParameter('foo.fooo2'), 'getParameter returns null for nonexistent keys'); + $this->assertEquals('babar', $conf->getParameter('foo.fooo3', 'babar'), 'getParameter accepts a default value'); + } + + public function testSetParameter() + { + $conf = new PropelConfiguration(array()); + $conf->setParameter('foo.fooo', 'bar'); + $conf->setParameter('foo.fi.fooooo', 'bara'); + $conf->setParameter('baz', 'bar2'); + $this->assertEquals($this->testArray, $conf->getParameters(), 'setParameter accepts a flat array'); + } + + public function testArrayAccess() + { + $conf = new PropelConfiguration($this->testArray); + $expected = array('fooo' => 'bar', 'fi' => array('fooooo' => 'bara')); + $this->assertEquals($expected, $conf['foo'], 'PropelConfiguration implements ArrayAccess for OffsetGet'); + $this->assertEquals('bar', $conf['foo']['fooo'], 'Array access allows deep access'); + } +} diff --git a/library/propel/test/testsuite/runtime/util/PropelDateTimeTest.php b/library/propel/test/testsuite/runtime/util/PropelDateTimeTest.php new file mode 100644 index 000000000..af490e0e4 --- /dev/null +++ b/library/propel/test/testsuite/runtime/util/PropelDateTimeTest.php @@ -0,0 +1,139 @@ +assertEquals($dt1->format('Y-m-d H:i:s'), $dt1->format('Y-m-d H:i:s'), sprintf($msg, "Dates w/ no timezone resolution were not the same.")); + $this->assertEquals($dt1->getTimeZone()->getName(), $dt2->getTimeZone()->getName(), sprintf($msg, "timezones were not the same.")); + + + // We do this last, because a PHP bug will make this true while the dates + // may not truly be equal. + // See: http://bugs.php.net/bug.php?id=40743 + $this->assertTrue($dt1 == $dt2, sprintf($msg, "dates did not pass equality check (==).")); + } + + /** + * Assert that two dates are equal. + */ + protected function assertDatesEqual(DateTime $dt1, DateTime $dt2, $msg = "Expected DateTime1 == DateTime2: %s") + { + if ($dt1 != $dt2) { + if ($dt1->getTimeZone()->getName() != $dt2->getTimeZone()->getName()) { + $this->fail(sprintf($msg, "Timezones were not the same.")); + } else { + $this->fail(sprintf($msg, "Timezones were the same, but date values were different.")); + } + } + } + + /** + * Assert that two dates are not equal. + */ + protected function assertDatesNotEqual(DateTime $dt1, DateTime $dt2, $msg = "Expected DateTime1 != DateTime2: %s") + { + $this->assertTrue($dt1 != $dt2, $msg); + } + + /** + * Ensure that our constructor matches DateTime constructor signature. + */ + public function testConstruct() + { + + // Because of a PHP bug () + // we cannot use a timestamp format that includes a timezone. It gets weird. :) + $now = date('Y-m-d H:i:s'); + + $dt = new DateTime($now); + $pdt = new PropelDateTime($now); + $this->assertDatesEqual($dt, $pdt, "Expected DateTime == PropelDateTime: %s"); + + $dt = new DateTime($now, new DateTimeZone('UTC')); + $pdt = new PropelDateTime($now, new DateTimeZone('America/New_York')); + $this->assertDatesNotEqual($dt, $pdt, "Expected DateTime != PropelDateTime: %s"); + + } + + /** + * Tests the ability to serialize() a PropelDateTime object. + */ + public function testSerialize_NoTZ() + { + $now = date('Y-m-d H:i:s'); + $dt = new DateTime($now); + $pdt = new PropelDateTime($now); + + $this->assertDatesIdentical($dt, $pdt); + + // We expect these to be the same -- there's no time zone info + $ser = serialize($pdt); + unset($pdt); + + $pdt = unserialize($ser); + $this->assertDatesIdentical($dt, $pdt); + } + + /** + * Tests the ability to serialize() a PropelDateTime object. + */ + public function testSerialize_SameTZ() + { + $now = date('Y-m-d H:i:s'); + $dt = new DateTime($now, new DateTimeZone('America/New_York')); + $pdt = new PropelDateTime($now, new DateTimeZone('America/New_York')); + + $this->assertDatesIdentical($dt, $pdt); + + // We expect these to be the same -- there's no time zone info + $ser = serialize($pdt); + unset($pdt); + + $pdt = unserialize($ser); + $this->assertDatesIdentical($dt, $pdt); + } + + /** + * Tests the ability to serialize() a PropelDateTime object. + */ + public function testSerialize_DiffTZ() + { + $now = date('Y-m-d H:i:s'); + $dt = new DateTime($now, new DateTimeZone('UTC')); + $pdt = new PropelDateTime($now, new DateTimeZone('America/New_York')); + + $this->assertDatesNotEqual($dt, $pdt); + + // We expect these to be the same -- there's no time zone info + $ser = serialize($pdt); + unset($pdt); + + $pdt = unserialize($ser); + $this->assertDatesNotEqual($dt, $pdt); + } + + +} diff --git a/library/propel/test/testsuite/runtime/util/PropelModelPagerTest.php b/library/propel/test/testsuite/runtime/util/PropelModelPagerTest.php new file mode 100644 index 000000000..b622663df --- /dev/null +++ b/library/propel/test/testsuite/runtime/util/PropelModelPagerTest.php @@ -0,0 +1,149 @@ +deleteAll($con); + $books = new PropelObjectCollection(); + $books->setModel('Book'); + for ($i=0; $i < $nb; $i++) { + $b = new Book(); + $b->setTitle('Book' . $i); + $books[]= $b; + } + $books->save($con); + } + + protected function getPager($maxPerPage, $page = 1) + { + $pager = new PropelModelPager(BookQuery::create(), $maxPerPage); + $pager->setPage($page); + $pager->init(); + return $pager; + } + + public function testHaveToPaginate() + { + BookQuery::create()->deleteAll(); + $this->assertEquals(false, $this->getPager(0)->haveToPaginate(), 'haveToPaginate() returns false when there is no result'); + $this->createBooks(5); + $this->assertEquals(false, $this->getPager(0)->haveToPaginate(), 'haveToPaginate() returns false when the maxPerPage is null'); + $this->assertEquals(true, $this->getPager(2)->haveToPaginate(), 'haveToPaginate() returns true when the maxPerPage is less than the number of results'); + $this->assertEquals(false, $this->getPager(6)->haveToPaginate(), 'haveToPaginate() returns false when the maxPerPage is greater than the number of results'); + $this->assertEquals(false, $this->getPager(5)->haveToPaginate(), 'haveToPaginate() returns false when the maxPerPage is equal to the number of results'); + } + + public function testGetNbResults() + { + BookQuery::create()->deleteAll(); + $pager = $this->getPager(4, 1); + $this->assertEquals(0, $pager->getNbResults(), 'getNbResults() returns 0 when there are no results'); + $this->createBooks(5); + $pager = $this->getPager(4, 1); + $this->assertEquals(5, $pager->getNbResults(), 'getNbResults() returns the total number of results'); + $pager = $this->getPager(2, 1); + $this->assertEquals(5, $pager->getNbResults(), 'getNbResults() returns the total number of results'); + $pager = $this->getPager(2, 2); + $this->assertEquals(5, $pager->getNbResults(), 'getNbResults() returns the total number of results'); + $pager = $this->getPager(7, 6); + $this->assertEquals(5, $pager->getNbResults(), 'getNbResults() returns the total number of results'); + $pager = $this->getPager(0, 0); + $this->assertEquals(5, $pager->getNbResults(), 'getNbResults() returns the total number of results'); + } + + public function testGetResults() + { + $this->createBooks(5); + $pager = $this->getPager(4, 1); + $this->assertTrue($pager->getResults() instanceof PropelObjectCollection, 'getResults() returns a PropelObjectCollection'); + $this->assertEquals(4, count($pager->getResults()), 'getResults() returns at most $maxPerPage results'); + $pager = $this->getPager(4, 2); + $this->assertEquals(1, count($pager->getResults()), 'getResults() returns the remaining results when in the last page'); + $pager = $this->getPager(4, 3); + $this->assertEquals(1, count($pager->getResults()), 'getResults() returns the results of the last page when called on nonexistent pages'); + } + + public function testGetIterator() + { + $this->createBooks(5); + + $pager = $this->getPager(4, 1); + $i = 0; + foreach ($pager as $book) { + $this->assertEquals('Book' . $i, $book->getTitle(), 'getIterator() returns an iterator'); + $i++; + } + $this->assertEquals(4, $i, 'getIterator() uses the results collection'); + } + + public function testIterateTwice() + { + $this->createBooks(5); + $pager = $this->getPager(4, 1); + + $i = 0; + foreach ($pager as $book) { + $this->assertEquals('Book' . $i, $book->getTitle(), 'getIterator() returns an iterator'); + $i++; + } + $this->assertEquals(4, $i, 'getIterator() uses the results collection'); + + $i = 0; + foreach ($pager as $book) { + $this->assertEquals('Book' . $i, $book->getTitle()); + $i++; + } + $this->assertEquals(4, $i, 'getIterator() can be called several times'); + } + + public function testSetPage() + { + $this->createBooks(5); + $pager = $this->getPager(2, 2); + $i = 2; + foreach ($pager as $book) { + $this->assertEquals('Book' . $i, $book->getTitle(), 'setPage() sets the list to start on a given page'); + $i++; + } + $this->assertEquals(4, $i, 'setPage() doesn\'t change the page count'); + } + + public function testIsFirstPage() + { + $this->createBooks(5); + $pager = $this->getPager(4, 1); + $this->assertTrue($pager->isFirstPage(), 'isFirstPage() returns true on the first page'); + $pager = $this->getPager(4, 2); + $this->assertFalse($pager->isFirstPage(), 'isFirstPage() returns false when not on the first page'); + } + + public function testIsLastPage() + { + $this->createBooks(5); + $pager = $this->getPager(4, 1); + $this->assertFalse($pager->isLastPage(), 'isLastPage() returns false when not on the last page'); + $pager = $this->getPager(4, 2); + $this->assertTrue($pager->isLastPage(), 'isLastPage() returns true on the last page'); + } +} diff --git a/library/propel/test/testsuite/runtime/util/PropelPagerTest.php b/library/propel/test/testsuite/runtime/util/PropelPagerTest.php new file mode 100644 index 000000000..473f56c09 --- /dev/null +++ b/library/propel/test/testsuite/runtime/util/PropelPagerTest.php @@ -0,0 +1,162 @@ + + * @version $Id: PropelPagerTest.php + * @package runtime.util + */ +class PropelPagerTest extends BookstoreEmptyTestBase +{ + private $authorId; + private $books; + + protected function setUp() + { + parent::setUp(); + BookstoreDataPopulator::populate(); + + $cr = new Criteria(); + $cr->add(AuthorPeer::LAST_NAME, "Rowling"); + $cr->add(AuthorPeer::FIRST_NAME, "J.K."); + $rowling = AuthorPeer::doSelectOne($cr); + $this->authorId = $rowling->getId(); + + $book = new Book(); + $book->setTitle("Harry Potter and the Philosopher's Stone"); + $book->setISBN("1234"); + $book->setAuthor($rowling); + $book->save(); + $this->books[] = $book->getId(); + + $book = new Book(); + $book->setTitle("Harry Potter and the Chamber of Secrets"); + $book->setISBN("1234"); + $book->setAuthor($rowling); + $book->save(); + $this->books[] = $book->getId(); + + $book = new Book(); + $book->setTitle("Harry Potter and the Prisoner of Azkaban"); + $book->setISBN("1234"); + $book->setAuthor($rowling); + $book->save(); + $this->books[] = $book->getId(); + + $book = new Book(); + $book->setTitle("Harry Potter and the Goblet of Fire"); + $book->setISBN("1234"); + $book->setAuthor($rowling); + $book->save(); + $this->books[] = $book->getId(); + + $book = new Book(); + $book->setTitle("Harry Potter and the Half-Blood Prince"); + $book->setISBN("1234"); + $book->setAuthor($rowling); + $book->save(); + $this->books[] = $book->getId(); + + $book = new Book(); + $book->setTitle("Harry Potter and the Deathly Hallows"); + $book->setISBN("1234"); + $book->setAuthor($rowling); + $book->save(); + $this->books[] = $book->getId(); + } + + protected function tearDown() + { + parent::tearDown(); + $cr = new Criteria(); + $cr->add(BookPeer::ID, $this->books, Criteria::IN); + BookPeer::doDelete($cr); + } + + public function testCountNoPageNoLimit() + { + $cr = new Criteria(); + $cr->add(BookPeer::AUTHOR_ID, $this->authorId); + $pager = new PropelPager($cr, "BookPeer", "doSelect"); + $this->assertEquals(7, count($pager)); + } + + public function testCountFirstPageWithLimits() + { + $cr = new Criteria(); + $cr->add(BookPeer::AUTHOR_ID, $this->authorId); + $pager = new PropelPager($cr, "BookPeer", "doSelect", 1, 5); + $this->assertEquals(5, count($pager)); + } + + public function testCountLastPageWithLimits() + { + $cr = new Criteria(); + $cr->add(BookPeer::AUTHOR_ID, $this->authorId); + $pager = new PropelPager($cr, "BookPeer", "doSelect", 2, 5); + $this->assertEquals(2, count($pager)); + } + + public function testIterateAll() + { + $cr = new Criteria(); + $cr->add(BookPeer::AUTHOR_ID, $this->authorId); + $pager = new PropelPager($cr, "BookPeer", "doSelect"); + $i = 0; + foreach ($pager as $key => $book) { + $i++; + } + $this->assertEquals(7, $i); + } + + public function testIterateWithLimits() + { + $cr = new Criteria(); + $cr->add(BookPeer::AUTHOR_ID, $this->authorId); + $pager = new PropelPager($cr, "BookPeer", "doSelect", 2, 5); + $i = 0; + foreach ($pager as $key => $book) { + $i++; + } + $this->assertEquals(2, $i); + } + + public function testIterateCheckSecond() + { + $cr = new Criteria(); + $cr->add(BookPeer::AUTHOR_ID, $this->authorId); + $cr->addAscendingOrderByColumn(BookPeer::TITLE); + $pager = new PropelPager($cr, "BookPeer", "doSelect"); + $books = array(); + foreach($pager as $book) { + $books[] = $book; + } + $this->assertEquals("Harry Potter and the Goblet of Fire", $books[2]->getTitle()); + } + + public function testIterateTwice() + { + $cr = new Criteria(); + $cr->add(BookPeer::AUTHOR_ID, $this->authorId); + $cr->addAscendingOrderByColumn(BookPeer::TITLE); + $pager = new PropelPager($cr, "BookPeer", "doSelect"); + $i = 0; + foreach($pager as $book) { + $i++; + } + foreach($pager as $book) { + $i++; + } + $this->assertEquals(14, $i); + } +} diff --git a/library/propel/test/testsuite/runtime/validator/ValidatorTest.php b/library/propel/test/testsuite/runtime/validator/ValidatorTest.php new file mode 100644 index 000000000..323c58629 --- /dev/null +++ b/library/propel/test/testsuite/runtime/validator/ValidatorTest.php @@ -0,0 +1,228 @@ + + * @package runtime.validator + */ +class ValidatorTest extends BookstoreEmptyTestBase +{ + + protected function setUp() + { + parent::setUp(); + BookstoreDataPopulator::populate(); + require_once 'tools/helpers/bookstore/validator/ISBNValidator.php'; + } + + /** + * Test minLength validator. + * This also tests the ${value} substitution. + */ + public function testDoValidate_MinLength() + { + $book = new Book(); + $book->setTitle("12345"); // min length is 10 + + $res = $book->validate(); + $this->assertFalse($res, "Expected validation to fail."); + + $failures = $book->getValidationFailures(); + $this->assertSingleValidation($failures, "Book title must be more than 10 characters long."); + } + + /** + * Test unique validator. + */ + public function testDoValidate_Unique() + { + $book = new Book(); + $book->setTitle("Don Juan"); + + $ret = $book->validate(); + $failures = $book->getValidationFailures(); + $this->assertSingleValidation($failures, "Book title already in database."); + } + + /** + * Test recursive validaton. + */ + public function testDoValidate_Complex() + { + $book = new Book(); + $book->setTitle("12345"); // min length is 10 + + $author = new Author(); + $author->setFirstName("Hans"); // last name required, valid email format, age > 0 + + $review = new Review(); + $review->setReviewDate("08/09/2001"); // reviewed_by column required, invalid status (new, reviewed, archived) + + $book->setAuthor($author); + $book->addReview($review); + + $res = $book->validate(); + + $this->assertFalse($res, "Expected validation to fail."); + + $failures = $book->getValidationFailures(); + + /* Make sure 3 validation messages were returned; NOT 6, because the others were NULL */ + $this->assertEquals(3, count($failures), ""); + + /* Make sure correct columns failed */ + $expectedCols = array( + AuthorPeer::LAST_NAME, + BookPeer::TITLE, + ReviewPeer::REVIEWED_BY + ); + $returnedCols = array_keys($failures); + + /* implode for readability */ + $this->assertEquals(implode(',', $expectedCols), implode(',', $returnedCols)); + } + + /** + * Test recursive validaton with specified columns. + */ + public function testDoValidate_ComplexSpecifiedCols() + { + $book = new Book(); + $book->setTitle("12345"); // min length is 10 + + $author = new Author(); + $author->setFirstName("Hans"); // last name required, valid email format, age > 0 + + $review = new Review(); + $review->setReviewDate("08/09/2001"); // reviewed_by column required, invalid status (new, reviewed, archived) + + $book->setAuthor($author); + $book->addReview($review); + + $cols = array(AuthorPeer::LAST_NAME, ReviewPeer::REVIEWED_BY); + + $res = $book->validate($cols); + + $this->assertFalse($res, "Expected validation to fail."); + + $failures = $book->getValidationFailures(); + + /* Make sure 3 validation messages were returned; NOT 6, because the others were NULL */ + $this->assertEquals(2, count($failures), ""); + + /* Make sure correct columns failed */ + $expectedCols = array( + AuthorPeer::LAST_NAME, + ReviewPeer::REVIEWED_BY + ); + + $returnedCols = array_keys($failures); + + /* implode for readability */ + $this->assertEquals(implode(',', $expectedCols), implode(',', $returnedCols)); + } + + /** + * Test the fact that validators should not complain NULL values for non-required columns. + */ + public function testDoValidate_Nulls() + { + $author = new Author(); + $author->setFirstName("Malcolm"); // last name required, valid email format, age > 0 + $author->setLastName("X"); + + $author->setEmail(null); // just to be explicit, of course these are the defaults anyway + $author->setAge(null); + + $res = $author->validate(); + + + $this->assertTrue($res, "Expected validation to pass with NULL columns"); + + $author->setEmail('malcolm@'); // fail + $res = $author->validate(); + + $this->assertFalse($res, "Expected validation to fail."); + + $failures = $author->getValidationFailures(); + $this->assertEquals(1, count($failures), "Expected 1 column to fail validation."); + $this->assertEquals(array(AuthorPeer::EMAIL), array_keys($failures), "Expected EMAIL to fail validation."); + + } + + public function testDoValidate_BasicValidatorObj() + { + $author = new Author(); + $author->setFirstName("Malcolm"); // last name required, valid email format, age > 0 + $author->setLastName("X"); + $author->setEmail('malcolm@'); // fail + + $res = $author->validate(); + + $this->assertFalse($res, "Expected validation to fail."); + + $failures = $author->getValidationFailures(); + + $this->assertEquals(1, count($failures), "Expected 1 column to fail validation."); + $this->assertEquals(array(AuthorPeer::EMAIL), array_keys($failures), "Expected EMAIL to fail validation."); + + $validator = $failures[AuthorPeer::EMAIL]->getValidator(); + $this->assertTrue($validator instanceof MatchValidator, "Expected validator that failed to be MatchValidator"); + + } + + public function testDoValidate_CustomValidator() + { + $book = new Book(); + $book->setTitle("testDoValidate_CustomValidator"); // (valid) + $book->setISBN("Foo.Bar.Baz"); // (invalid) + + $res = $book->validate(); + + $this->assertFalse($res, "Expected validation to fail."); + + $failures = $book->getValidationFailures(); + + $this->assertEquals(1, count($failures), "Expected 1 column to fail validation."); + $this->assertEquals(array(BookPeer::ISBN), array_keys($failures), "Expected EMAIL to fail validation."); + + $validator = $failures[BookPeer::ISBN]->getValidator(); + $this->assertType('ISBNValidator', $validator, "Expected validator that failed to be ISBNValidator"); + } + + protected function assertSingleValidation($ret, $expectedMsg) + { + /* Make sure validation failed */ + $this->assertTrue($ret !== true, "Expected validation to fail !"); + + /* Make sure 1 validation message was returned */ + $count = count($ret); + $this->assertTrue($count === 1, "Expected that exactly one validation failed ($count) !"); + + /* Make sure expected validation message was returned */ + $el = array_shift($ret); + $this->assertEquals($el->getMessage(), $expectedMsg, "Got unexpected validation failed message: " . $el->getMessage()); + } + +} diff --git a/library/propel/test/tools/helpers/BaseTestCase.php b/library/propel/test/tools/helpers/BaseTestCase.php new file mode 100644 index 000000000..f3827c24b --- /dev/null +++ b/library/propel/test/tools/helpers/BaseTestCase.php @@ -0,0 +1,30 @@ + (Propel) + * @author Daniel Rall (Torque) + * @author Christopher Elkins (Torque) + * @version $Revision: 1773 $ + */ +abstract class BaseTestCase extends PHPUnit_Framework_TestCase { + + /** + * Conditional compilation flag. + */ + const DEBUG = false; + +} diff --git a/library/propel/test/tools/helpers/bookstore/BookstoreDataPopulator.php b/library/propel/test/tools/helpers/bookstore/BookstoreDataPopulator.php new file mode 100644 index 000000000..5dc3902be --- /dev/null +++ b/library/propel/test/tools/helpers/bookstore/BookstoreDataPopulator.php @@ -0,0 +1,247 @@ + + */ +class BookstoreDataPopulator +{ + + public static function populate($con = null) + { + if($con === null) { + $con = Propel::getConnection(BookPeer::DATABASE_NAME); + } + $con->beginTransaction(); + + // Add publisher records + // --------------------- + + $scholastic = new Publisher(); + $scholastic->setName("Scholastic"); + // do not save, will do later to test cascade + + $morrow = new Publisher(); + $morrow->setName("William Morrow"); + $morrow->save($con); + $morrow_id = $morrow->getId(); + + $penguin = new Publisher(); + $penguin->setName("Penguin"); + $penguin->save(); + $penguin_id = $penguin->getId(); + + $vintage = new Publisher(); + $vintage->setName("Vintage"); + $vintage->save($con); + $vintage_id = $vintage->getId(); + + $rowling = new Author(); + $rowling->setFirstName("J.K."); + $rowling->setLastName("Rowling"); + // no save() + + $stephenson = new Author(); + $stephenson->setFirstName("Neal"); + $stephenson->setLastName("Stephenson"); + $stephenson->save($con); + $stephenson_id = $stephenson->getId(); + + $byron = new Author(); + $byron->setFirstName("George"); + $byron->setLastName("Byron"); + $byron->save($con); + $byron_id = $byron->getId(); + + $grass = new Author(); + $grass->setFirstName("Gunter"); + $grass->setLastName("Grass"); + $grass->save($con); + $grass_id = $grass->getId(); + + $phoenix = new Book(); + $phoenix->setTitle("Harry Potter and the Order of the Phoenix"); + $phoenix->setISBN("043935806X"); + $phoenix->setAuthor($rowling); + $phoenix->setPublisher($scholastic); + $phoenix->setPrice(10.99); + $phoenix->save($con); + $phoenix_id = $phoenix->getId(); + + $qs = new Book(); + $qs->setISBN("0380977427"); + $qs->setTitle("Quicksilver"); + $qs->setPrice(11.99); + $qs->setAuthor($stephenson); + $qs->setPublisher($morrow); + $qs->save($con); + $qs_id = $qs->getId(); + + $dj = new Book(); + $dj->setISBN("0140422161"); + $dj->setTitle("Don Juan"); + $dj->setPrice(12.99); + $dj->setAuthor($byron); + $dj->setPublisher($penguin); + $dj->save($con); + $dj_id = $dj->getId(); + + $td = new Book(); + $td->setISBN("067972575X"); + $td->setTitle("The Tin Drum"); + $td->setPrice(13.99); + $td->setAuthor($grass); + $td->setPublisher($vintage); + $td->save($con); + $td_id = $td->getId(); + + $r1 = new Review(); + $r1->setBook($phoenix); + $r1->setReviewedBy("Washington Post"); + $r1->setRecommended(true); + $r1->setReviewDate(time()); + $r1->save($con); + $r1_id = $r1->getId(); + + $r2 = new Review(); + $r2->setBook($phoenix); + $r2->setReviewedBy("New York Times"); + $r2->setRecommended(false); + $r2->setReviewDate(time()); + $r2->save($con); + $r2_id = $r2->getId(); + + $blob_path = _LOB_SAMPLE_FILE_PATH . '/tin_drum.gif'; + $clob_path = _LOB_SAMPLE_FILE_PATH . '/tin_drum.txt'; + + $m1 = new Media(); + $m1->setBook($td); + $m1->setCoverImage(file_get_contents($blob_path)); + // CLOB is broken in PDO OCI, see http://pecl.php.net/bugs/bug.php?id=7943 + if (get_class(Propel::getDB()) != "DBOracle") { + $m1->setExcerpt(file_get_contents($clob_path)); + } + $m1->save($con); + + // Add book list records + // --------------------- + // (this is for many-to-many tests) + + $blc1 = new BookClubList(); + $blc1->setGroupLeader("Crazyleggs"); + $blc1->setTheme("Happiness"); + + $brel1 = new BookListRel(); + $brel1->setBook($phoenix); + + $brel2 = new BookListRel(); + $brel2->setBook($dj); + + $blc1->addBookListRel($brel1); + $blc1->addBookListRel($brel2); + + $blc1->save(); + + $bemp1 = new BookstoreEmployee(); + $bemp1->setName("John"); + $bemp1->setJobTitle("Manager"); + + $bemp2 = new BookstoreEmployee(); + $bemp2->setName("Pieter"); + $bemp2->setJobTitle("Clerk"); + $bemp2->setSupervisor($bemp1); + $bemp2->save($con); + + $role = new AcctAccessRole(); + $role->setName("Admin"); + + $bempacct = new BookstoreEmployeeAccount(); + $bempacct->setBookstoreEmployee($bemp1); + $bempacct->setAcctAccessRole($role); + $bempacct->setLogin("john"); + $bempacct->setPassword("johnp4ss"); + $bempacct->save($con); + + // Add bookstores + + $store = new Bookstore(); + $store->setStoreName("Amazon"); + $store->setPopulationServed(5000000000); // world population + $store->setTotalBooks(300); + $store->save($con); + + $store = new Bookstore(); + $store->setStoreName("Local Store"); + $store->setPopulationServed(20); + $store->setTotalBooks(500000); + $store->save($con); + + $con->commit(); + } + + public static function populateOpinionFavorite($con = null) + { + if($con === null) { + $con = Propel::getConnection(BookPeer::DATABASE_NAME); + } + $con->beginTransaction(); + + $book1 = BookPeer::doSelectOne(new Criteria(), $con); + $reader1 = new BookReader(); + $reader1->save($con); + + $bo = new BookOpinion(); + $bo->setBook($book1); + $bo->setBookReader($reader1); + $bo->save($con); + + $rf = new ReaderFavorite(); + $rf->setBookOpinion($bo); + $rf->save($con); + + $con->commit(); + } + + public static function depopulate($con = null) + { + if($con === null) { + $con = Propel::getConnection(BookPeer::DATABASE_NAME); + } + $con->beginTransaction(); + AuthorPeer::doDeleteAll($con); + BookstorePeer::doDeleteAll($con); + BookstoreContestPeer::doDeleteAll($con); + BookstoreContestEntryPeer::doDeleteAll($con); + BookstoreEmployeePeer::doDeleteAll($con); + BookstoreEmployeeAccountPeer::doDeleteAll($con); + BookstoreSalePeer::doDeleteAll($con); + BookClubListPeer::doDeleteAll($con); + BookOpinionPeer::doDeleteAll($con); + BookReaderPeer::doDeleteAll($con); + BookListRelPeer::doDeleteAll($con); + BookPeer::doDeleteAll($con); + ContestPeer::doDeleteAll($con); + CustomerPeer::doDeleteAll($con); + MediaPeer::doDeleteAll($con); + PublisherPeer::doDeleteAll($con); + ReaderFavoritePeer::doDeleteAll($con); + ReviewPeer::doDeleteAll($con); + $con->commit(); + } + +} diff --git a/library/propel/test/tools/helpers/bookstore/BookstoreEmptyTestBase.php b/library/propel/test/tools/helpers/bookstore/BookstoreEmptyTestBase.php new file mode 100644 index 000000000..08efab6a8 --- /dev/null +++ b/library/propel/test/tools/helpers/bookstore/BookstoreEmptyTestBase.php @@ -0,0 +1,38 @@ + +con); + } + + /** + * This is run after each unit test. It empties the database. + */ + protected function tearDown() + { + BookstoreDataPopulator::depopulate($this->con); + parent::tearDown(); + } + +} diff --git a/library/propel/test/tools/helpers/bookstore/BookstoreTestBase.php b/library/propel/test/tools/helpers/bookstore/BookstoreTestBase.php new file mode 100644 index 000000000..4ea139e6d --- /dev/null +++ b/library/propel/test/tools/helpers/bookstore/BookstoreTestBase.php @@ -0,0 +1,47 @@ +con = Propel::getConnection(BookPeer::DATABASE_NAME); + $this->con->beginTransaction(); + } + + /** + * This is run after each unit test. It empties the database. + */ + protected function tearDown() + { + parent::tearDown(); + // Only commit if the transaction hasn't failed. + // This is because tearDown() is also executed on a failed tests, + // and we don't want to call PropelPDO::commit() in that case + // since it will trigger an exception on its own + // ('Cannot commit because a nested transaction was rolled back') + if ($this->con->isCommitable()) { + $this->con->commit(); + } + } +} diff --git a/library/propel/test/tools/helpers/bookstore/behavior/BookstoreNestedSetTestBase.php b/library/propel/test/tools/helpers/bookstore/behavior/BookstoreNestedSetTestBase.php new file mode 100644 index 000000000..222c0eb50 --- /dev/null +++ b/library/propel/test/tools/helpers/bookstore/behavior/BookstoreNestedSetTestBase.php @@ -0,0 +1,146 @@ +getTitle()] = array($node->getLeftValue(), $node->getRightValue(), $node->getLevel()); + } + return $tree; + } + + /** + * Tree used for tests + * t1 + * | \ + * t2 t3 + * | \ + * t4 t5 + * | \ + * t6 t7 + */ + protected function initTree() + { + Table9Peer::doDeleteAll(); + $ret = array(); + // shuffling the results so the db order is not the natural one + $fixtures = array( + 't2' => array(2, 3, 1), + 't5' => array(7, 12, 2), + 't4' => array(5, 6, 2), + 't7' => array(10, 11, 3), + 't1' => array(1, 14, 0), + 't6' => array(8, 9, 3), + 't3' => array(4, 13, 1), + ); + /* in correct order, this is: + 't1' => array(1, 14, 0), + 't2' => array(2, 3, 1), + 't3' => array(4, 13, 1), + 't4' => array(5, 6, 2), + 't5' => array(7, 12, 2), + 't6' => array(8, 9, 3), + 't7' => array(10, 11, 3), + */ + foreach ($fixtures as $key => $data) { + $t = new PublicTable9(); + $t->setTitle($key); + $t->setLeftValue($data[0]); + $t->setRightValue($data[1]); + $t->setLevel($data[2]); + $t->save(); + $ret[$key]= $t; + } + // reordering the results in the fixtures + ksort($ret); + return array_values($ret); + } + + protected function dumpTree() + { + $c = new Criteria(); + $c->addAscendingOrderBycolumn(Table9Peer::TITLE); + return $this->dumpNodes(Table9Peer::doSelect($c)); + } + + /** + * Tree used for tests + * Scope 1 + * t1 + * | \ + * t2 t3 + * | \ + * t4 t5 + * | \ + * t6 t7 + * Scope 2 + * t8 + * | \ + * t9 t10 + */ + protected function initTreeWithScope() + { + Table10Peer::doDeleteAll(); + $ret = array(); + $fixtures = array( + 't1' => array(1, 14, 0, 1), + 't2' => array(2, 3, 1, 1), + 't3' => array(4, 13, 1, 1), + 't4' => array(5, 6, 2, 1), + 't5' => array(7, 12, 2, 1), + 't6' => array(8, 9, 3, 1), + 't7' => array(10, 11, 3, 1), + 't8' => array(1, 6, 0, 2), + 't9' => array(2, 3, 1, 2), + 't10' => array(4, 5, 1, 2), + ); + foreach ($fixtures as $key => $data) { + $t = new PublicTable10(); + $t->setTitle($key); + $t->setLeftValue($data[0]); + $t->setRightValue($data[1]); + $t->setLevel($data[2]); + $t->setScopeValue($data[3]); + $t->save(); + $ret []= $t; + } + return $ret; + } + + protected function dumpTreeWithScope($scope) + { + $c = new Criteria(); + $c->add(Table10Peer::SCOPE_COL, $scope); + $c->addAscendingOrderBycolumn(Table10Peer::TITLE); + return $this->dumpNodes(Table10Peer::doSelect($c)); + } +} + +// we need this class to test protected methods +class PublicTable9 extends Table9 +{ + public $hasParentNode = null; + public $parentNode = null; + public $hasPrevSibling = null; + public $prevSibling = null; + public $hasNextSibling = null; + public $nextSibling = null; +} + +class PublicTable10 extends Table10 +{ + public $hasParentNode = null; + public $parentNode = null; +} \ No newline at end of file diff --git a/library/propel/test/tools/helpers/bookstore/behavior/BookstoreSortableTestBase.php b/library/propel/test/tools/helpers/bookstore/behavior/BookstoreSortableTestBase.php new file mode 100644 index 000000000..2af39debe --- /dev/null +++ b/library/propel/test/tools/helpers/bookstore/behavior/BookstoreSortableTestBase.php @@ -0,0 +1,104 @@ +setRank(1); + $t1->setTitle('row1'); + $t1->save(); + $t2 = new Table11(); + $t2->setRank(4); + $t2->setTitle('row4'); + $t2->save(); + $t3 = new Table11(); + $t3->setRank(2); + $t3->setTitle('row2'); + $t3->save(); + $t4 = new Table11(); + $t4->setRank(3); + $t4->setTitle('row3'); + $t4->save(); + } + + protected function populateTable12() + { + /* List used for tests + scope=1 scope=2 + row1 row5 + row2 row6 + row3 + row4 + */ + Table12Peer::doDeleteAll(); + $t1 = new Table12(); + $t1->setRank(1); + $t1->setScopeValue(1); + $t1->setTitle('row1'); + $t1->save(); + $t2 = new Table12(); + $t2->setRank(4); + $t2->setScopeValue(1); + $t2->setTitle('row4'); + $t2->save(); + $t3 = new Table12(); + $t3->setRank(2); + $t3->setScopeValue(1); + $t3->setTitle('row2'); + $t3->save(); + $t4 = new Table12(); + $t4->setRank(1); + $t4->setScopeValue(2); + $t4->setTitle('row5'); + $t4->save(); + $t5 = new Table12(); + $t5->setRank(3); + $t5->setScopeValue(1); + $t5->setTitle('row3'); + $t5->save(); + $t6 = new Table12(); + $t6->setRank(2); + $t6->setScopeValue(2); + $t6->setTitle('row6'); + $t6->save(); + } + + protected function getFixturesArray() + { + $c = new Criteria(); + $c->addAscendingOrderByColumn(Table11Peer::RANK_COL); + $ts = Table11Peer::doSelect($c); + $ret = array(); + foreach ($ts as $t) { + $ret[$t->getRank()] = $t->getTitle(); + } + return $ret; + } + + protected function getFixturesArrayWithScope($scope = null) + { + $c = new Criteria(); + if ($scope !== null) { + $c->add(Table12Peer::SCOPE_COL, $scope); + } + $c->addAscendingOrderByColumn(Table12Peer::RANK_COL); + $ts = Table12Peer::doSelect($c); + $ret = array(); + foreach ($ts as $t) { + $ret[$t->getRank()] = $t->getTitle(); + } + return $ret; + } +} \ No newline at end of file diff --git a/library/propel/test/tools/helpers/bookstore/behavior/DonothingBehavior.php b/library/propel/test/tools/helpers/bookstore/behavior/DonothingBehavior.php new file mode 100644 index 000000000..2b6f2fb97 --- /dev/null +++ b/library/propel/test/tools/helpers/bookstore/behavior/DonothingBehavior.php @@ -0,0 +1,13 @@ +setFirstName('PreInsertedFirstname'); + return true; + } + + public function postInsert(PropelPDO $con = null) + { + parent::postInsert($con); + $this->setLastName('PostInsertedLastName'); + } + + public function preUpdate(PropelPDO $con = null) + { + parent::preUpdate($con); + $this->setFirstName('PreUpdatedFirstname'); + return true; + } + + public function postUpdate(PropelPDO $con = null) + { + parent::postUpdate($con); + $this->setLastName('PostUpdatedLastName'); + } + + public function preSave(PropelPDO $con = null) + { + parent::preSave($con); + $this->setEmail("pre@save.com"); + return true; + } + + public function postSave(PropelPDO $con = null) + { + parent::postSave($con); + $this->setAge(115); + } + + public function preDelete(PropelPDO $con = null) + { + parent::preDelete($con); + $this->setFirstName("Pre-Deleted"); + return true; + } + + public function postDelete(PropelPDO $con = null) + { + parent::postDelete($con); + $this->setLastName("Post-Deleted"); + } +} + +class TestAuthorDeleteFalse extends TestAuthor +{ + public function preDelete(PropelPDO $con = null) + { + parent::preDelete($con); + $this->setFirstName("Pre-Deleted"); + return false; + } +} +class TestAuthorSaveFalse extends TestAuthor +{ + public function preSave(PropelPDO $con = null) + { + parent::preSave($con); + $this->setEmail("pre@save.com"); + return false; + } + +} \ No newline at end of file diff --git a/library/propel/test/tools/helpers/bookstore/behavior/Testallhooksbehavior.php b/library/propel/test/tools/helpers/bookstore/behavior/Testallhooksbehavior.php new file mode 100644 index 000000000..4a44c2323 --- /dev/null +++ b/library/propel/test/tools/helpers/bookstore/behavior/Testallhooksbehavior.php @@ -0,0 +1,183 @@ +tableModifier)) + { + $this->tableModifier = new TestAllHooksTableModifier($this); + } + return $this->tableModifier; + } + + public function getObjectBuilderModifier() + { + if (is_null($this->objectBuilderModifier)) + { + $this->objectBuilderModifier = new TestAllHooksObjectBuilderModifier($this); + } + return $this->objectBuilderModifier; + } + + public function getPeerBuilderModifier() + { + if (is_null($this->peerBuilderModifier)) + { + $this->peerBuilderModifier = new TestAllHooksPeerBuilderModifier($this); + } + return $this->peerBuilderModifier; + } + + public function getQueryBuilderModifier() + { + if (is_null($this->queryBuilderModifier)) + { + $this->queryBuilderModifier = new TestAllHooksQueryBuilderModifier($this); + } + return $this->queryBuilderModifier; + } +} + +class TestAllHooksTableModifier +{ + protected $behavior, $table; + + public function __construct($behavior) + { + $this->behavior = $behavior; + $this->table = $behavior->getTable(); + } + + public function modifyTable() + { + $this->table->addColumn(array( + 'name' => 'test', + 'type' => 'TIMESTAMP' + )); + } +} + +class TestAllHooksObjectBuilderModifier +{ + public function objectAttributes($builder) + { + return 'public $customAttribute = 1;'; + } + + public function preSave($builder) + { + return '$this->preSave = 1;$this->preSaveIsAfterSave = isset($affectedRows);$this->preSaveBuilder="' . get_class($builder) . '";'; + } + + public function postSave($builder) + { + return '$this->postSave = 1;$this->postSaveIsAfterSave = isset($affectedRows);$this->postSaveBuilder="' . get_class($builder) . '";'; + } + + public function preInsert($builder) + { + return '$this->preInsert = 1;$this->preInsertIsAfterSave = isset($affectedRows);$this->preInsertBuilder="' . get_class($builder) . '";'; + } + + public function postInsert($builder) + { + return '$this->postInsert = 1;$this->postInsertIsAfterSave = isset($affectedRows);$this->postInsertBuilder="' . get_class($builder) . '";'; + } + + public function preUpdate($builder) + { + return '$this->preUpdate = 1;$this->preUpdateIsAfterSave = isset($affectedRows);$this->preUpdateBuilder="' . get_class($builder) . '";'; + } + + public function postUpdate($builder) + { + return '$this->postUpdate = 1;$this->postUpdateIsAfterSave = isset($affectedRows);$this->postUpdateBuilder="' . get_class($builder) . '";'; + } + + public function preDelete($builder) + { + return '$this->preDelete = 1;$this->preDeleteIsBeforeDelete = isset(Table3Peer::$instances[$this->id]);$this->preDeleteBuilder="' . get_class($builder) . '";'; + } + + public function postDelete($builder) + { + return '$this->postDelete = 1;$this->postDeleteIsBeforeDelete = isset(Table3Peer::$instances[$this->id]);$this->postDeleteBuilder="' . get_class($builder) . '";'; + } + + public function objectMethods($builder) + { + return 'public function hello() { return "' . get_class($builder) .'"; }'; + } + + public function objectCall($builder) + { + return 'if ($name == "foo") return "bar";'; + } + + public function objectFilter(&$string, $builder) + { + $string .= 'class testObjectFilter { const FOO = "' . get_class($builder) . '"; }'; + } +} + +class TestAllHooksPeerBuilderModifier +{ + public function staticAttributes($builder) + { + return 'public static $customStaticAttribute = 1;public static $staticAttributeBuilder = "' . get_class($builder) . '";'; + } + + public function staticMethods($builder) + { + return 'public static function hello() { return "' . get_class($builder) . '"; }'; + } + + public function preSelect($builder) + { + return '$con->preSelect = "' . get_class($builder) . '";'; + } + + public function peerFilter(&$string, $builder) + { + $string .= 'class testPeerFilter { const FOO = "' . get_class($builder) . '"; }'; + } +} + +class TestAllHooksQueryBuilderModifier +{ + public function preSelectQuery($builder) + { + return '// foo'; + } + + public function preDeleteQuery($builder) + { + return '// foo'; + } + + public function postDeleteQuery($builder) + { + return '// foo'; + } + + public function preUpdateQuery($builder) + { + return '// foo'; + } + + public function postUpdateQuery($builder) + { + return '// foo'; + } +} \ No newline at end of file diff --git a/library/propel/test/tools/helpers/bookstore/validator/ISBNValidator.php b/library/propel/test/tools/helpers/bookstore/validator/ISBNValidator.php new file mode 100644 index 000000000..690c81a40 --- /dev/null +++ b/library/propel/test/tools/helpers/bookstore/validator/ISBNValidator.php @@ -0,0 +1,29 @@ + + * @version $Revision: 1612 $ + * @package propel.validator + */ +class ISBNValidator implements BasicValidator +{ + const NOT_ISBN_REGEXP = '/[^0-9A-Z]/'; + + /** + * Whether the passed string matches regular expression. + */ + public function isValid (ValidatorMap $map, $str) + { + return !(preg_match(self::NOT_ISBN_REGEXP, $str)); + } +} diff --git a/library/propel/test/tools/helpers/cms/CmsDataPopulator.php b/library/propel/test/tools/helpers/cms/CmsDataPopulator.php new file mode 100644 index 000000000..2a05f3ff3 --- /dev/null +++ b/library/propel/test/tools/helpers/cms/CmsDataPopulator.php @@ -0,0 +1,143 @@ + + */ +class CmsDataPopulator { + + public static function populate($con = null) + { + if($con === null) + { + $con = Propel::getConnection(PagePeer::DATABASE_NAME); + } + $con->beginTransaction(); + $con->exec("INSERT INTO Page (ScopeId, LeftChild, RightChild, Title) VALUES (1, 1,194,'home')"); + $con->exec("INSERT INTO Page (ScopeId, LeftChild, RightChild, Title) VALUES (1, 2,5,'school')"); + $con->exec("INSERT INTO Page (ScopeId, LeftChild, RightChild, Title) VALUES (1, 6,43,'education')"); + $con->exec("INSERT INTO Page (ScopeId, LeftChild, RightChild, Title) VALUES (1, 44,45,'simulator')"); + $con->exec("INSERT INTO Page (ScopeId, LeftChild, RightChild, Title) VALUES (1, 46,47,'ac')"); + $con->exec("INSERT INTO Page (ScopeId, LeftChild, RightChild, Title) VALUES (1, 3,4,'history')"); + $con->exec("INSERT INTO Page (ScopeId, LeftChild, RightChild, Title) VALUES (1, 7,14,'master-mariner')"); + $con->exec("INSERT INTO Page (ScopeId, LeftChild, RightChild, Title) VALUES (1, 8,9,'education')"); + $con->exec("INSERT INTO Page (ScopeId, LeftChild, RightChild, Title) VALUES (1, 48,85,'courses')"); + $con->exec("INSERT INTO Page (ScopeId, LeftChild, RightChild, Title) VALUES (1, 98,101,'contact')"); + $con->exec("INSERT INTO Page (ScopeId, LeftChild, RightChild, Title) VALUES (1, 10,11,'entrance')"); + $con->exec("INSERT INTO Page (ScopeId, LeftChild, RightChild, Title) VALUES (1, 104,191,'intra')"); + $con->exec("INSERT INTO Page (ScopeId, LeftChild, RightChild, Title) VALUES (1, 102,103,'services')"); + $con->exec("INSERT INTO Page (ScopeId, LeftChild, RightChild, Title) VALUES (1, 12,13,'competency')"); + $con->exec("INSERT INTO Page (ScopeId, LeftChild, RightChild, Title) VALUES (1, 15,22,'watchkeeping-officer')"); + $con->exec("INSERT INTO Page (ScopeId, LeftChild, RightChild, Title) VALUES (1, 16,17,'education')"); + $con->exec("INSERT INTO Page (ScopeId, LeftChild, RightChild, Title) VALUES (1, 18,19,'entrance')"); + $con->exec("INSERT INTO Page (ScopeId, LeftChild, RightChild, Title) VALUES (1, 20,21,'competency')"); + $con->exec("INSERT INTO Page (ScopeId, LeftChild, RightChild, Title) VALUES (1, 31,38,'watchkeeping-engineer')"); + $con->exec("INSERT INTO Page (ScopeId, LeftChild, RightChild, Title) VALUES (1, 32,33,'education')"); + $con->exec("INSERT INTO Page (ScopeId, LeftChild, RightChild, Title) VALUES (1, 34,35,'entrance')"); + $con->exec("INSERT INTO Page (ScopeId, LeftChild, RightChild, Title) VALUES (1, 36,37,'competency')"); + $con->exec("INSERT INTO Page (ScopeId, LeftChild, RightChild, Title) VALUES (1, 39,40,'practice')"); + $con->exec("INSERT INTO Page (ScopeId, LeftChild, RightChild, Title) VALUES (1, 86,97,'news')"); + $con->exec("INSERT INTO Page (ScopeId, LeftChild, RightChild, Title) VALUES (1, 95,96,'2007-02')"); + $con->exec("INSERT INTO Page (ScopeId, LeftChild, RightChild, Title) VALUES (1, 99,100,'personnel')"); + $con->exec("INSERT INTO Page (ScopeId, LeftChild, RightChild, Title) VALUES (1, 87,88,'2007-06')"); + $con->exec("INSERT INTO Page (ScopeId, LeftChild, RightChild, Title) VALUES (1, 49,50,'nautical')"); + $con->exec("INSERT INTO Page (ScopeId, LeftChild, RightChild, Title) VALUES (1, 51,52,'radiotechnical')"); + $con->exec("INSERT INTO Page (ScopeId, LeftChild, RightChild, Title) VALUES (1, 53,54,'resourcemgmt')"); + $con->exec("INSERT INTO Page (ScopeId, LeftChild, RightChild, Title) VALUES (1, 57,58,'safety')"); + $con->exec("INSERT INTO Page (ScopeId, LeftChild, RightChild, Title) VALUES (1, 59,60,'firstaid')"); + $con->exec("INSERT INTO Page (ScopeId, LeftChild, RightChild, Title) VALUES (1, 61,62,'sar')"); + $con->exec("INSERT INTO Page (ScopeId, LeftChild, RightChild, Title) VALUES (1, 67,84,'upcoming')"); + $con->exec("INSERT INTO Page (ScopeId, LeftChild, RightChild, Title) VALUES (1, 65,66,'languages')"); + $con->exec("INSERT INTO Page (ScopeId, LeftChild, RightChild, Title) VALUES (1, 55,56,'cargomgmt')"); + $con->exec("INSERT INTO Page (ScopeId, LeftChild, RightChild, Title) VALUES (1, 119,120,'timetable')"); + $con->exec("INSERT INTO Page (ScopeId, LeftChild, RightChild, Title) VALUES (1, 63,64,'boaters')"); + $con->exec("INSERT INTO Page (ScopeId, LeftChild, RightChild, Title) VALUES (1, 105,118,'bulletinboard')"); + $con->exec("INSERT INTO Page (ScopeId, LeftChild, RightChild, Title) VALUES (1, 106,107,'sdf')"); + $con->exec("INSERT INTO Page (ScopeId, LeftChild, RightChild, Title) VALUES (1, 41,42,'fristaende')"); + $con->exec("INSERT INTO Page (ScopeId, LeftChild, RightChild, Title) VALUES (1, 23,30,'ingenj')"); + $con->exec("INSERT INTO Page (ScopeId, LeftChild, RightChild, Title) VALUES (1, 24,25,'utbildn')"); + $con->exec("INSERT INTO Page (ScopeId, LeftChild, RightChild, Title) VALUES (1, 26,27,'ansokn')"); + $con->exec("INSERT INTO Page (ScopeId, LeftChild, RightChild, Title) VALUES (1, 93,94,'utexaminerade')"); + $con->exec("INSERT INTO Page (ScopeId, LeftChild, RightChild, Title) VALUES (1, 89,92,'Massan')"); + $con->exec("INSERT INTO Page (ScopeId, LeftChild, RightChild, Title) VALUES (1, 192,193,'lankar')"); + $con->exec("INSERT INTO Page (ScopeId, LeftChild, RightChild, Title) VALUES (1, 68,69,'FRB')"); + $con->exec("INSERT INTO Page (ScopeId, LeftChild, RightChild, Title) VALUES (1, 70,71,'pelastautumis')"); + $con->exec("INSERT INTO Page (ScopeId, LeftChild, RightChild, Title) VALUES (1, 72,73,'CCM')"); + $con->exec("INSERT INTO Page (ScopeId, LeftChild, RightChild, Title) VALUES (1, 74,75,'sjukvard')"); + $con->exec("INSERT INTO Page (ScopeId, LeftChild, RightChild, Title) VALUES (1, 121,188,'Veckoscheman')"); + $con->exec("INSERT INTO Page (ScopeId, LeftChild, RightChild, Title) VALUES (1, 134,135,'VS3VSVsjukv')"); + $con->exec("INSERT INTO Page (ScopeId, LeftChild, RightChild, Title) VALUES (1, 122,123,'sjoarb')"); + $con->exec("INSERT INTO Page (ScopeId, LeftChild, RightChild, Title) VALUES (1, 130,131,'fysik1')"); + $con->exec("INSERT INTO Page (ScopeId, LeftChild, RightChild, Title) VALUES (1, 140,141,'kemi')"); + $con->exec("INSERT INTO Page (ScopeId, LeftChild, RightChild, Title) VALUES (1, 76,77,'inr')"); + $con->exec("INSERT INTO Page (ScopeId, LeftChild, RightChild, Title) VALUES (1, 78,79,'forare')"); + $con->exec("INSERT INTO Page (ScopeId, LeftChild, RightChild, Title) VALUES (1, 144,145,'AlexandraYH2')"); + $con->exec("INSERT INTO Page (ScopeId, LeftChild, RightChild, Title) VALUES (1, 132,133,'AlexandraVS2')"); + $con->exec("INSERT INTO Page (ScopeId, LeftChild, RightChild, Title) VALUES (1, 80,81,'Maskin')"); + $con->exec("INSERT INTO Page (ScopeId, LeftChild, RightChild, Title) VALUES (1, 126,127,'forstahjalp')"); + $con->exec("INSERT INTO Page (ScopeId, LeftChild, RightChild, Title) VALUES (1, 136,137,'Juridik')"); + $con->exec("INSERT INTO Page (ScopeId, LeftChild, RightChild, Title) VALUES (1, 142,143,'mate')"); + $con->exec("INSERT INTO Page (ScopeId, LeftChild, RightChild, Title) VALUES (1, 82,83,'basic')"); + $con->exec("INSERT INTO Page (ScopeId, LeftChild, RightChild, Title) VALUES (1, 124,125,'mask')"); + $con->exec("INSERT INTO Page (ScopeId, LeftChild, RightChild, Title) VALUES (1, 108,109,'magnus')"); + $con->exec("INSERT INTO Page (ScopeId, LeftChild, RightChild, Title) VALUES (1, 138,139,'sjosakerhet')"); + $con->exec("INSERT INTO Page (ScopeId, LeftChild, RightChild, Title) VALUES (1, 28,29,'pate')"); + $con->exec("INSERT INTO Page (ScopeId, LeftChild, RightChild, Title) VALUES (1, 148,149,'eng')"); + $con->exec("INSERT INTO Page (ScopeId, LeftChild, RightChild, Title) VALUES (1, 146,147,'forstahjalpYH1')"); + $con->exec("INSERT INTO Page (ScopeId, LeftChild, RightChild, Title) VALUES (1, 110,111,'kortoverlevnadskurs')"); + $con->exec("INSERT INTO Page (ScopeId, LeftChild, RightChild, Title) VALUES (1, 158,159,'kortoverlevnadskurs')"); + $con->exec("INSERT INTO Page (ScopeId, LeftChild, RightChild, Title) VALUES (1, 128,129,'metall')"); + $con->exec("INSERT INTO Page (ScopeId, LeftChild, RightChild, Title) VALUES (1, 152,153,'fysik')"); + $con->exec("INSERT INTO Page (ScopeId, LeftChild, RightChild, Title) VALUES (1, 156,157,'fardplan')"); + $con->exec("INSERT INTO Page (ScopeId, LeftChild, RightChild, Title) VALUES (1, 154,155,'astro')"); + $con->exec("INSERT INTO Page (ScopeId, LeftChild, RightChild, Title) VALUES (1, 90,91,'utstallare')"); + $con->exec("INSERT INTO Page (ScopeId, LeftChild, RightChild, Title) VALUES (1, 150,151,'eng')"); + $con->exec("INSERT INTO Page (ScopeId, LeftChild, RightChild, Title) VALUES (1, 160,161,'ent')"); + $con->exec("INSERT INTO Page (ScopeId, LeftChild, RightChild, Title) VALUES (1, 162,163,'juridik')"); + $con->exec("INSERT INTO Page (ScopeId, LeftChild, RightChild, Title) VALUES (1, 168,169,'svenska')"); + $con->exec("INSERT INTO Page (ScopeId, LeftChild, RightChild, Title) VALUES (1, 164,165,'matemat')"); + $con->exec("INSERT INTO Page (ScopeId, LeftChild, RightChild, Title) VALUES (1, 166,167,'operativa')"); + $con->exec("INSERT INTO Page (ScopeId, LeftChild, RightChild, Title) VALUES (1, 170,171,'plan')"); + $con->exec("INSERT INTO Page (ScopeId, LeftChild, RightChild, Title) VALUES (1, 172,173,'src')"); + $con->exec("INSERT INTO Page (ScopeId, LeftChild, RightChild, Title) VALUES (1, 112,113,'sjukv')"); + $con->exec("INSERT INTO Page (ScopeId, LeftChild, RightChild, Title) VALUES (1, 174,175,'matemati')"); + $con->exec("INSERT INTO Page (ScopeId, LeftChild, RightChild, Title) VALUES (1, 176,177,'fysiikka')"); + $con->exec("INSERT INTO Page (ScopeId, LeftChild, RightChild, Title) VALUES (1, 114,115,'hantv')"); + $con->exec("INSERT INTO Page (ScopeId, LeftChild, RightChild, Title) VALUES (1, 116,117,'CCM')"); + $con->exec("INSERT INTO Page (ScopeId, LeftChild, RightChild, Title) VALUES (1, 178,179,'haveri')"); + $con->exec("INSERT INTO Page (ScopeId, LeftChild, RightChild, Title) VALUES (1, 180,181,'FRB')"); + $con->exec("INSERT INTO Page (ScopeId, LeftChild, RightChild, Title) VALUES (1, 182,183,'kemia')"); + $con->exec("INSERT INTO Page (ScopeId, LeftChild, RightChild, Title) VALUES (1, 184,185,'vaktrutiner')"); + $con->exec("INSERT INTO Page (ScopeId, LeftChild, RightChild, Title) VALUES (1, 189,190,'laroplan')"); + $con->exec("INSERT INTO Page (ScopeId, LeftChild, RightChild, Title) VALUES (1, 186,187,'SSOkurs')"); + + $con->exec("INSERT INTO Category (LeftChild, RightChild, Title) VALUES (1, 8, 'Cat_1')"); + $con->exec("INSERT INTO Category (LeftChild, RightChild, Title) VALUES (2, 7, 'Cat_1_1')"); + $con->exec("INSERT INTO Category (LeftChild, RightChild, Title) VALUES (3, 6, 'Cat_1_1_1')"); + $con->exec("INSERT INTO Category (LeftChild, RightChild, Title) VALUES (4, 5, 'Cat_1_1_1_1')"); + + $con->commit(); + } + + public static function depopulate($con = null) + { + if($con === null) + { + $con = Propel::getConnection(PagePeer::DATABASE_NAME); + } + $con->beginTransaction(); + $con->exec("DELETE FROM Page"); + $con->exec("DELETE FROM Category"); + + $con->commit(); + } +} diff --git a/library/propel/test/tools/helpers/cms/CmsTestBase.php b/library/propel/test/tools/helpers/cms/CmsTestBase.php new file mode 100644 index 000000000..0d9c73791 --- /dev/null +++ b/library/propel/test/tools/helpers/cms/CmsTestBase.php @@ -0,0 +1,45 @@ +con = Propel::getConnection(PagePeer::DATABASE_NAME); + $this->con->beginTransaction(); + CmsDataPopulator::depopulate($this->con); + CmsDataPopulator::populate($this->con); + } + + /** + * This is run after each unit test. It empties the database. + */ + protected function tearDown() + { + CmsDataPopulator::depopulate($this->con); + $this->con->commit(); + parent::tearDown(); + } + +} diff --git a/library/propel/test/tools/phing/DefineTask.php b/library/propel/test/tools/phing/DefineTask.php new file mode 100644 index 000000000..ef6f208be --- /dev/null +++ b/library/propel/test/tools/phing/DefineTask.php @@ -0,0 +1,58 @@ +name = $v; + } + + /** + * Sets the value for the constant. + * @param string $v + */ + public function setValue($v) { + $this->value = $v; + } + + public function main() { + if (!isset($this->name) || !isset($this->value)) { + throw new BuildException("Both name and value params are required.", $this->getLocation()); + } + $const = strtoupper($this->name); + if (defined($const)) { + $this->log("The constant $const has already been defined!", Project::MSG_ERR); + } else { + define($const, $this->value); + $this->log("Defined $const with value " . var_export($this->value, true), Project::MSG_INFO); + } + } +} diff --git a/library/propel/test/tree-test.php b/library/propel/test/tree-test.php new file mode 100644 index 000000000..c807f32e9 --- /dev/null +++ b/library/propel/test/tree-test.php @@ -0,0 +1,426 @@ +"; + +error_reporting(E_ALL); + +$conf_path = realpath(dirname(__FILE__) . '/fixtures/treetest/build/conf/treetest-conf.php'); +if (!file_exists($conf_path)) { + echo "Make sure that you specify properties in conf/treetest.properties and " + ."build propel before running this script."; + exit; +} + +// Add PHP_CLASSPATH, if set +if (getenv("PHP_CLASSPATH")) { + set_include_path(getenv("PHP_CLASSPATH") . PATH_SEPARATOR . get_include_path()); +} + + // Add build/classes/ and classes/ to path +set_include_path( + realpath(dirname(__FILE__) . '/fixtures/treetest/build/classes') . PATH_SEPARATOR . + dirname(__FILE__) . '/../runtime/classes' . PATH_SEPARATOR . + get_include_path() +); + + +// Require classes. +require_once 'propel/Propel.php'; +require_once 'treetest/TestNodePeer.php'; + +function dumpTree($node, $querydb = false, $con = null) +{ + $opts = array('querydb' => $querydb, + 'con' => $con); + + $node->setIteratorOptions('pre', $opts); + + $indent = 0; + $lastLevel = $node->getNodeLevel(); + + foreach ($node as $n) + { + $nodeLevel = $n->getNodeLevel(); + $indent += $nodeLevel - $lastLevel; + echo str_repeat(' ', $indent); + echo $n->getNodePath() . " -> " . $n->getLabel(); + echo "\n"; + $lastLevel = $nodeLevel; + } +} + +try { + // Initialize Propel + Propel::init($conf_path); +} catch (Exception $e) { + die("Error initializing propel: ". $e->__toString()); +} + +try { + + $nodeKeySep = TestNodePeer::NPATH_SEP; + + echo "\nCreating initial tree:\n"; + echo "-------------------------------------\n"; + + $a = new Test(); + $a->setLabel("a"); + $a = TestNodePeer::createNewRootNode($a); + echo "Created 'a' as new root\n"; + + $b = new TestNode(); + $b->setLabel('b'); + $a->addChildNode($b); + echo "Added 'b' as first child of 'a'\n"; + + $c = new TestNode(); + $c->setLabel('c'); + $a->addChildNode($c); + echo "Added 'c' as second child of 'a'\n"; + + $f = new TestNode(); + $f->setLabel('f'); + $b->addChildNode($f); + echo "Added 'f' as first child of 'b'\n"; + + $d = new TestNode(); + $d->setLabel('d'); + $b->addChildNode($d, $f); + echo "Added 'd' as first child of 'b' before 'f' (insert before first child test - f is now second child)\n"; + + $e = new TestNode(); + $e->setLabel('e'); + $b->addChildNode($e, $f); + echo "Added 'e' as second child of 'b' before 'f' (insert before last child test - f is now third child)\n"; + + $g = new TestNode(); + $g->setLabel('g'); + $c->addChildNode($g); + echo "Added 'g' as first child of 'c'\n"; + + $h = new TestNode(); + $h->setLabel('h'); + $c->addChildNode($h); + echo "Added 'h' as second child of 'c'\n"; + + $i = new TestNode(); + $i->setLabel('i'); + $d->addChildNode($i); + echo "Added 'i' as first child of 'd'\n"; + + $j = new TestNode(); + $j->setLabel('j'); + $f->addChildNode($j); + echo "Added 'j' as first child of 'f'\n"; + + $k = new TestNode(); + $k->setLabel('k'); + $j->addChildNode($k); + echo "Added 'k' as first child of 'j'\n"; + + $l = new TestNode(); + $l->setLabel('l'); + $j->addChildNode($l); + echo "Added 'l' as second child of 'j'\n"; + + dumpTree($a); + + + echo "\n\nDeleting 'd' node sub-tree:\n"; + echo "-------------------------------------\n"; + + $d->delete(); + + dumpTree($a); + + + echo "\n\nMove node tests:\n"; + echo "-------------------------------------\n"; + + echo "Move 'j' sub-tree to 'b' before 'e' (move tree/insert before first child test):\n"; + $b->addChildNode($j, $e); + dumpTree($a); + + echo "\nMove 'j' sub-tree to 'c' (move tree after last child test):\n"; + $c->addChildNode($j); + dumpTree($a); + + echo "\nMove 'j' sub-tree to 'g' (move tree to first child test):\n"; + $g->addChildNode($j); + dumpTree($a); + + + echo "\n\nCreating new (in-memory) sub-tree:\n"; + echo "-------------------------------------\n"; + + $m = new TestNode(); + $m->setLabel('m'); + echo "Created 'm' as root of new sub-tree\n"; + + $n = new TestNode(); + $n->setLabel('n'); + $m->addChildNode($n); + echo "Added 'n' as first child of 'm'\n"; + + $o = new TestNode(); + $o->setLabel('o'); + $m->addChildNode($o); + echo "Added 'o' as second child of 'm'\n"; + + $r = new TestNode(); + $r->setLabel('r'); + $n->addChildNode($r); + echo "Added 'r' as first child of 'n'\n"; + + $p = new TestNode(); + $p->setLabel('p'); + $n->addChildNode($p, $r); + echo "Added 'p' as first child of 'n' before 'r' (insert before first child test - r is now second child)\n"; + + $q = new TestNode(); + $q->setLabel('q'); + $n->addChildNode($q, $r); + echo "Added 'q' as second child of 'n' before 'r' (insert before last child test - r is now third child)\n"; + + $s = new TestNode(); + $s->setLabel('s'); + $o->addChildNode($s); + echo "Added 's' as first child of 'o'\n"; + + $t = new TestNode(); + $t->setLabel('t'); + $o->addChildNode($t); + echo "Added 't' as second child of 'o'\n"; + + $u = new TestNode(); + $u->setLabel('u'); + $p->addChildNode($u); + echo "Added 'u' as first child of 'p'\n"; + + $v = new TestNode(); + $v->setLabel('v'); + $r->addChildNode($v); + echo "Added 'v' as first child of 'r'\n"; + + $w = new TestNode(); + $w->setLabel('w'); + $v->addChildNode($w); + echo "Added 'w' as first child of 'v'\n"; + + $x = new TestNode(); + $x->setLabel('x'); + $v->addChildNode($x); + echo "Added 'x' as second child of 'v'\n"; + + dumpTree($m); + + + echo "\n\nDeleting in-memory 'p' node sub-tree:\n"; + echo "-------------------------------------\n"; + + $p->delete(); + + dumpTree($m); + + + echo "\n\nMove in-memory node tests:\n"; + echo "-------------------------------------\n"; + + echo "Move 'v' sub-tree to 'n' before 'q' (move tree/insert before first child test):\n"; + $n->addChildNode($v, $q); + dumpTree($m); + + echo "\nMove 'v' sub-tree to 'o' (move tree after last child test):\n"; + $o->addChildNode($v); + dumpTree($m); + + echo "\nMove 'v' sub-tree to 's' (move tree to first child test):\n"; + $s->addChildNode($v); + dumpTree($m); + + + echo "\n\nAdd in-memory 'm' sub-tree to 'a':\n"; + echo "-------------------------------------\n"; + + $a->addChildNode($m); + + dumpTree($a); + + + echo "\n\nInsert new root node 'z' and retrieve descendants on demand (via querydb param in iterator):\n"; + echo "-------------------------------------\n"; + $z = new Test(); + $z->setLabel("z"); + $z = TestNodePeer::insertNewRootNode($z); + + dumpTree($z, true); + +} catch (Exception $e) { + die("Error creating initial tree: " . $e->__toString()); +} + +try { + + echo "\n\nTest retrieveRootNode() (without descendants)\n"; + echo "-------------------------------------\n"; + $root = TestNodePeer::retrieveRootNode(false); + dumpTree($root); + + + echo "\n\nTest retrieveRootNode() (with descendants)\n"; + echo "-------------------------------------\n"; + $root = TestNodePeer::retrieveRootNode(true); + dumpTree($root); + + $m_addr = array(1,1,3); + + echo "\n\nTest retrieveNodeByNP() for 'm' (without descendants)\n"; + echo "-------------------------------------\n"; + $node = TestNodePeer::retrieveNodeByNP(implode($nodeKeySep, $m_addr), false, false); + dumpTree($node); + + + echo "\n\nTest retrieveNodeByNP() for 'm' (with descendants)\n"; + echo "-------------------------------------\n"; + $node = TestNodePeer::retrieveNodeByNP(implode($nodeKeySep, $m_addr), false, true); + dumpTree($node); + + + echo "\n\nTest getAncestors() for 'x' in one query:\n"; + echo "-------------------------------------\n"; + + $criteria = new Criteria(); + $criteria->add(TestPeer::LABEL, 'x', Criteria::EQUAL); + + $nodes = TestNodePeer::retrieveNodes($criteria, true, false); + $ancestors = $nodes[0]->getAncestors(false); + + foreach ($ancestors as $ancestor) + echo $ancestor->getNodePath() . " -> " . $ancestor->getLabel() . "\n"; + + + echo "\n\nTest retrieveNodeByNP() for 'o' (with ancestors and descendants in one query):\n"; + echo "-------------------------------------\n"; + + $o_addr = array(1,1,3,2); + + $node = TestNodePeer::retrieveNodeByNP(implode($nodeKeySep, $o_addr), true, true); + + echo "ancestors:\n"; + foreach ($node->getAncestors(false) as $ancestor) + echo $ancestor->getNodePath() . " -> " . $ancestor->getLabel() . "\n"; + + echo "\ndescendants:\n"; + dumpTree($node); + + + echo "\n\nTest retrieveNodes() between 'b' and 'g' (without descendants)\n"; + echo "-------------------------------------\n"; + + $criteria = new Criteria(); + $criteria->add(TestPeer::LABEL, 'b', Criteria::GREATER_EQUAL); + $criteria->addAnd(TestPeer::LABEL, 'g', Criteria::LESS_EQUAL); + $criteria->addAscendingOrderByColumn(TestPeer::LABEL); + + $nodes = TestNodePeer::retrieveNodes($criteria, false, false); + + foreach ($nodes as $node) + echo $node->getNodePath() . " -> " . $node->getLabel() . "\n"; + + + echo "\n\nTest retrieveNodes() between 'b' and 'g' (with descendants)\n"; + echo "-------------------------------------\n"; + + $criteria = new Criteria(); + $criteria->add(TestPeer::LABEL, 'b', Criteria::GREATER_EQUAL); + $criteria->addAnd(TestPeer::LABEL, 'g', Criteria::LESS_EQUAL); + $criteria->addAscendingOrderByColumn(TestPeer::LABEL); + + $nodes = TestNodePeer::retrieveNodes($criteria, false, true); + + foreach ($nodes as $node) + { + dumpTree($node); + echo "\n"; + } + + +} catch (Exception $e) { + die("Error retrieving nodes: " . $e->__toString()); +} + +try { + + echo "\nCreating new tree:\n"; + echo "-------------------------------------\n"; + + $a = new Test(); + $a->setLabel("a"); + $a = TestNodePeer::createNewRootNode($a); + echo "Created 'a' as new root\n"; + + echo "\nAdding 10 child nodes:\n"; + echo "-------------------------------------\n"; + + $b = new TestNode(); + $b->setLabel('b'); + $a->addChildNode($b); + + $c = new TestNode(); + $c->setLabel('c'); + $a->addChildNode($c); + + $d = new TestNode(); + $d->setLabel('d'); + $a->addChildNode($d); + + $e = new TestNode(); + $e->setLabel('e'); + $a->addChildNode($e); + + $f = new TestNode(); + $f->setLabel('f'); + $a->addChildNode($f); + + $g = new TestNode(); + $g->setLabel('g'); + $a->addChildNode($g); + + $h = new TestNode(); + $h->setLabel('h'); + $a->addChildNode($h); + + $i = new TestNode(); + $i->setLabel('i'); + $a->addChildNode($i); + + $j = new TestNode(); + $j->setLabel('j'); + $a->addChildNode($j); + + $k = new TestNode(); + $k->setLabel('k'); + $a->addChildNode($k); + + echo "\ndescendants:\n"; + dumpTree($a); + + echo "\nRetrieving last node:\n"; + echo "-------------------------------------\n"; + + $last = $a->getLastChildNode(true); + echo "Last child node is '" . $last->getLabel() . "' (" . $last->getNodePath() . ")\n"; + +} catch (Exception $e) { + die("Error creating tree with > 10 nodes: " . $e->__toString()); +} + +if (!isset($argc)) echo ""; diff --git a/public/.htaccess b/public/.htaccess new file mode 100644 index 000000000..7fb009bfe --- /dev/null +++ b/public/.htaccess @@ -0,0 +1,7 @@ + +RewriteEngine On +RewriteCond %{REQUEST_FILENAME} -s [OR] +RewriteCond %{REQUEST_FILENAME} -l [OR] +RewriteCond %{REQUEST_FILENAME} -d +RewriteRule ^.*$ - [NC,L] +RewriteRule ^.*$ index.php [NC,L] diff --git a/public/css/excite-bike/excite_bike.css b/public/css/excite-bike/excite_bike.css new file mode 100644 index 000000000..a159a33b0 --- /dev/null +++ b/public/css/excite-bike/excite_bike.css @@ -0,0 +1,572 @@ +/* + * jQuery UI CSS Framework @VERSION + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Theming/API + */ + +/* Layout helpers +----------------------------------*/ +.ui-helper-hidden { display: none; } +.ui-helper-hidden-accessible { position: absolute; left: -99999999px; } +.ui-helper-reset { margin: 0; padding: 0; border: 0; outline: 0; line-height: 1.3; text-decoration: none; font-size: 100%; list-style: none; } +.ui-helper-clearfix:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; } +.ui-helper-clearfix { display: inline-block; } +/* required comment for clearfix to work in Opera \*/ +* html .ui-helper-clearfix { height:1%; } +.ui-helper-clearfix { display:block; } +/* end clearfix */ +.ui-helper-zfix { width: 100%; height: 100%; top: 0; left: 0; position: absolute; opacity: 0; filter:Alpha(Opacity=0); } + + +/* Interaction Cues +----------------------------------*/ +.ui-state-disabled { cursor: default !important; } + + +/* Icons +----------------------------------*/ + +/* states and images */ +.ui-icon { display: block; text-indent: -99999px; overflow: hidden; background-repeat: no-repeat; } + + +/* Misc visuals +----------------------------------*/ + +/* Overlays */ +.ui-widget-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; } + + +/* + * jQuery UI CSS Framework @VERSION + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Theming/API + * + * To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=segoe%20ui,%20Arial,%20sans-serif&fwDefault=bold&fsDefault=1.1em&cornerRadius=3px&bgColorHeader=f9f9f9&bgTextureHeader=03_highlight_soft.png&bgImgOpacityHeader=100&borderColorHeader=cccccc&fcHeader=e69700&iconColorHeader=5fa5e3&bgColorContent=eeeeee&bgTextureContent=06_inset_hard.png&bgImgOpacityContent=100&borderColorContent=aaaaaa&fcContent=222222&iconColorContent=0a82eb&bgColorDefault=1484e6&bgTextureDefault=08_diagonals_thick.png&bgImgOpacityDefault=22&borderColorDefault=ffffff&fcDefault=ffffff&iconColorDefault=fcdd4a&bgColorHover=2293f7&bgTextureHover=08_diagonals_thick.png&bgImgOpacityHover=26&borderColorHover=2293f7&fcHover=ffffff&iconColorHover=ffffff&bgColorActive=e69700&bgTextureActive=08_diagonals_thick.png&bgImgOpacityActive=20&borderColorActive=e69700&fcActive=ffffff&iconColorActive=ffffff&bgColorHighlight=c5ddfc&bgTextureHighlight=07_diagonals_small.png&bgImgOpacityHighlight=25&borderColorHighlight=ffffff&fcHighlight=333333&iconColorHighlight=0b54d5&bgColorError=e69700&bgTextureError=08_diagonals_thick.png&bgImgOpacityError=20&borderColorError=e69700&fcError=ffffff&iconColorError=ffffff&bgColorOverlay=e6b900&bgTextureOverlay=01_flat.png&bgImgOpacityOverlay=0&opacityOverlay=30&bgColorShadow=e69700&bgTextureShadow=01_flat.png&bgImgOpacityShadow=0&opacityShadow=20&thicknessShadow=0px&offsetTopShadow=6px&offsetLeftShadow=6px&cornerRadiusShadow=3px + */ + + +/* Component containers +----------------------------------*/ +.ui-widget { font-family: segoe ui, Arial, sans-serif; font-size: 1.1em; } +.ui-widget .ui-widget { font-size: 1em; } +.ui-widget input, .ui-widget select, .ui-widget textarea, .ui-widget button { font-family: segoe ui, Arial, sans-serif; font-size: 1em; } +.ui-widget-content { border: 1px solid #aaaaaa; background: #eeeeee url(images/ui-bg_inset-hard_100_eeeeee_1x100.png) 50% bottom repeat-x; color: #222222; } +.ui-widget-content a { color: #222222; } +.ui-widget-header { border: 1px solid #cccccc; background: #f9f9f9 url(images/ui-bg_highlight-soft_100_f9f9f9_1x100.png) 50% 50% repeat-x; color: #e69700; font-weight: bold; } +.ui-widget-header a { color: #e69700; } + +/* Interaction states +----------------------------------*/ +.ui-state-default, .ui-widget-content .ui-state-default, .ui-widget-header .ui-state-default { border: 1px solid #ffffff; background: #1484e6 url(images/ui-bg_diagonals-thick_22_1484e6_40x40.png) 50% 50% repeat; font-weight: bold; color: #ffffff; } +.ui-state-default a, .ui-state-default a:link, .ui-state-default a:visited { color: #ffffff; text-decoration: none; } +.ui-state-hover, .ui-widget-content .ui-state-hover, .ui-widget-header .ui-state-hover, .ui-state-focus, .ui-widget-content .ui-state-focus, .ui-widget-header .ui-state-focus { border: 1px solid #2293f7; background: #2293f7 url(images/ui-bg_diagonals-thick_26_2293f7_40x40.png) 50% 50% repeat; font-weight: bold; color: #ffffff; } +.ui-state-hover a, .ui-state-hover a:hover { color: #ffffff; text-decoration: none; } +.ui-state-active, .ui-widget-content .ui-state-active, .ui-widget-header .ui-state-active { border: 1px solid #e69700; background: #e69700 url(images/ui-bg_diagonals-thick_20_e69700_40x40.png) 50% 50% repeat; font-weight: bold; color: #ffffff; } +.ui-state-active a, .ui-state-active a:link, .ui-state-active a:visited { color: #ffffff; text-decoration: none; } +.ui-widget :active { outline: none; } + +/* Interaction Cues +----------------------------------*/ +.ui-state-highlight, .ui-widget-content .ui-state-highlight, .ui-widget-header .ui-state-highlight {border: 1px solid #ffffff; background: #c5ddfc url(images/ui-bg_diagonals-small_25_c5ddfc_40x40.png) 50% 50% repeat; color: #333333; } +.ui-state-highlight a, .ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a { color: #333333; } +.ui-state-error, .ui-widget-content .ui-state-error, .ui-widget-header .ui-state-error {border: 1px solid #e69700; background: #e69700 url(images/ui-bg_diagonals-thick_20_e69700_40x40.png) 50% 50% repeat; color: #ffffff; } +.ui-state-error a, .ui-widget-content .ui-state-error a, .ui-widget-header .ui-state-error a { color: #ffffff; } +.ui-state-error-text, .ui-widget-content .ui-state-error-text, .ui-widget-header .ui-state-error-text { color: #ffffff; } +.ui-priority-primary, .ui-widget-content .ui-priority-primary, .ui-widget-header .ui-priority-primary { font-weight: bold; } +.ui-priority-secondary, .ui-widget-content .ui-priority-secondary, .ui-widget-header .ui-priority-secondary { opacity: .7; filter:Alpha(Opacity=70); font-weight: normal; } +.ui-state-disabled, .ui-widget-content .ui-state-disabled, .ui-widget-header .ui-state-disabled { opacity: .35; filter:Alpha(Opacity=35); background-image: none; } + +/* Icons +----------------------------------*/ + +/* states and images */ +.ui-icon { width: 16px; height: 16px; background-image: url(images/ui-icons_0a82eb_256x240.png); } +.ui-widget-content .ui-icon {background-image: url(images/ui-icons_0a82eb_256x240.png); } +.ui-widget-header .ui-icon {background-image: url(images/ui-icons_5fa5e3_256x240.png); } +.ui-state-default .ui-icon { background-image: url(images/ui-icons_fcdd4a_256x240.png); } +.ui-state-hover .ui-icon, .ui-state-focus .ui-icon {background-image: url(images/ui-icons_ffffff_256x240.png); } +.ui-state-active .ui-icon {background-image: url(images/ui-icons_ffffff_256x240.png); } +.ui-state-highlight .ui-icon {background-image: url(images/ui-icons_0b54d5_256x240.png); } +.ui-state-error .ui-icon, .ui-state-error-text .ui-icon {background-image: url(images/ui-icons_ffffff_256x240.png); } + +/* positioning */ +.ui-icon-carat-1-n { background-position: 0 0; } +.ui-icon-carat-1-ne { background-position: -16px 0; } +.ui-icon-carat-1-e { background-position: -32px 0; } +.ui-icon-carat-1-se { background-position: -48px 0; } +.ui-icon-carat-1-s { background-position: -64px 0; } +.ui-icon-carat-1-sw { background-position: -80px 0; } +.ui-icon-carat-1-w { background-position: -96px 0; } +.ui-icon-carat-1-nw { background-position: -112px 0; } +.ui-icon-carat-2-n-s { background-position: -128px 0; } +.ui-icon-carat-2-e-w { background-position: -144px 0; } +.ui-icon-triangle-1-n { background-position: 0 -16px; } +.ui-icon-triangle-1-ne { background-position: -16px -16px; } +.ui-icon-triangle-1-e { background-position: -32px -16px; } +.ui-icon-triangle-1-se { background-position: -48px -16px; } +.ui-icon-triangle-1-s { background-position: -64px -16px; } +.ui-icon-triangle-1-sw { background-position: -80px -16px; } +.ui-icon-triangle-1-w { background-position: -96px -16px; } +.ui-icon-triangle-1-nw { background-position: -112px -16px; } +.ui-icon-triangle-2-n-s { background-position: -128px -16px; } +.ui-icon-triangle-2-e-w { background-position: -144px -16px; } +.ui-icon-arrow-1-n { background-position: 0 -32px; } +.ui-icon-arrow-1-ne { background-position: -16px -32px; } +.ui-icon-arrow-1-e { background-position: -32px -32px; } +.ui-icon-arrow-1-se { background-position: -48px -32px; } +.ui-icon-arrow-1-s { background-position: -64px -32px; } +.ui-icon-arrow-1-sw { background-position: -80px -32px; } +.ui-icon-arrow-1-w { background-position: -96px -32px; } +.ui-icon-arrow-1-nw { background-position: -112px -32px; } +.ui-icon-arrow-2-n-s { background-position: -128px -32px; } +.ui-icon-arrow-2-ne-sw { background-position: -144px -32px; } +.ui-icon-arrow-2-e-w { background-position: -160px -32px; } +.ui-icon-arrow-2-se-nw { background-position: -176px -32px; } +.ui-icon-arrowstop-1-n { background-position: -192px -32px; } +.ui-icon-arrowstop-1-e { background-position: -208px -32px; } +.ui-icon-arrowstop-1-s { background-position: -224px -32px; } +.ui-icon-arrowstop-1-w { background-position: -240px -32px; } +.ui-icon-arrowthick-1-n { background-position: 0 -48px; } +.ui-icon-arrowthick-1-ne { background-position: -16px -48px; } +.ui-icon-arrowthick-1-e { background-position: -32px -48px; } +.ui-icon-arrowthick-1-se { background-position: -48px -48px; } +.ui-icon-arrowthick-1-s { background-position: -64px -48px; } +.ui-icon-arrowthick-1-sw { background-position: -80px -48px; } +.ui-icon-arrowthick-1-w { background-position: -96px -48px; } +.ui-icon-arrowthick-1-nw { background-position: -112px -48px; } +.ui-icon-arrowthick-2-n-s { background-position: -128px -48px; } +.ui-icon-arrowthick-2-ne-sw { background-position: -144px -48px; } +.ui-icon-arrowthick-2-e-w { background-position: -160px -48px; } +.ui-icon-arrowthick-2-se-nw { background-position: -176px -48px; } +.ui-icon-arrowthickstop-1-n { background-position: -192px -48px; } +.ui-icon-arrowthickstop-1-e { background-position: -208px -48px; } +.ui-icon-arrowthickstop-1-s { background-position: -224px -48px; } +.ui-icon-arrowthickstop-1-w { background-position: -240px -48px; } +.ui-icon-arrowreturnthick-1-w { background-position: 0 -64px; } +.ui-icon-arrowreturnthick-1-n { background-position: -16px -64px; } +.ui-icon-arrowreturnthick-1-e { background-position: -32px -64px; } +.ui-icon-arrowreturnthick-1-s { background-position: -48px -64px; } +.ui-icon-arrowreturn-1-w { background-position: -64px -64px; } +.ui-icon-arrowreturn-1-n { background-position: -80px -64px; } +.ui-icon-arrowreturn-1-e { background-position: -96px -64px; } +.ui-icon-arrowreturn-1-s { background-position: -112px -64px; } +.ui-icon-arrowrefresh-1-w { background-position: -128px -64px; } +.ui-icon-arrowrefresh-1-n { background-position: -144px -64px; } +.ui-icon-arrowrefresh-1-e { background-position: -160px -64px; } +.ui-icon-arrowrefresh-1-s { background-position: -176px -64px; } +.ui-icon-arrow-4 { background-position: 0 -80px; } +.ui-icon-arrow-4-diag { background-position: -16px -80px; } +.ui-icon-extlink { background-position: -32px -80px; } +.ui-icon-newwin { background-position: -48px -80px; } +.ui-icon-refresh { background-position: -64px -80px; } +.ui-icon-shuffle { background-position: -80px -80px; } +.ui-icon-transfer-e-w { background-position: -96px -80px; } +.ui-icon-transferthick-e-w { background-position: -112px -80px; } +.ui-icon-folder-collapsed { background-position: 0 -96px; } +.ui-icon-folder-open { background-position: -16px -96px; } +.ui-icon-document { background-position: -32px -96px; } +.ui-icon-document-b { background-position: -48px -96px; } +.ui-icon-note { background-position: -64px -96px; } +.ui-icon-mail-closed { background-position: -80px -96px; } +.ui-icon-mail-open { background-position: -96px -96px; } +.ui-icon-suitcase { background-position: -112px -96px; } +.ui-icon-comment { background-position: -128px -96px; } +.ui-icon-person { background-position: -144px -96px; } +.ui-icon-print { background-position: -160px -96px; } +.ui-icon-trash { background-position: -176px -96px; } +.ui-icon-locked { background-position: -192px -96px; } +.ui-icon-unlocked { background-position: -208px -96px; } +.ui-icon-bookmark { background-position: -224px -96px; } +.ui-icon-tag { background-position: -240px -96px; } +.ui-icon-home { background-position: 0 -112px; } +.ui-icon-flag { background-position: -16px -112px; } +.ui-icon-calendar { background-position: -32px -112px; } +.ui-icon-cart { background-position: -48px -112px; } +.ui-icon-pencil { background-position: -64px -112px; } +.ui-icon-clock { background-position: -80px -112px; } +.ui-icon-disk { background-position: -96px -112px; } +.ui-icon-calculator { background-position: -112px -112px; } +.ui-icon-zoomin { background-position: -128px -112px; } +.ui-icon-zoomout { background-position: -144px -112px; } +.ui-icon-search { background-position: -160px -112px; } +.ui-icon-wrench { background-position: -176px -112px; } +.ui-icon-gear { background-position: -192px -112px; } +.ui-icon-heart { background-position: -208px -112px; } +.ui-icon-star { background-position: -224px -112px; } +.ui-icon-link { background-position: -240px -112px; } +.ui-icon-cancel { background-position: 0 -128px; } +.ui-icon-plus { background-position: -16px -128px; } +.ui-icon-plusthick { background-position: -32px -128px; } +.ui-icon-minus { background-position: -48px -128px; } +.ui-icon-minusthick { background-position: -64px -128px; } +.ui-icon-close { background-position: -80px -128px; } +.ui-icon-closethick { background-position: -96px -128px; } +.ui-icon-key { background-position: -112px -128px; } +.ui-icon-lightbulb { background-position: -128px -128px; } +.ui-icon-scissors { background-position: -144px -128px; } +.ui-icon-clipboard { background-position: -160px -128px; } +.ui-icon-copy { background-position: -176px -128px; } +.ui-icon-contact { background-position: -192px -128px; } +.ui-icon-image { background-position: -208px -128px; } +.ui-icon-video { background-position: -224px -128px; } +.ui-icon-script { background-position: -240px -128px; } +.ui-icon-alert { background-position: 0 -144px; } +.ui-icon-info { background-position: -16px -144px; } +.ui-icon-notice { background-position: -32px -144px; } +.ui-icon-help { background-position: -48px -144px; } +.ui-icon-check { background-position: -64px -144px; } +.ui-icon-bullet { background-position: -80px -144px; } +.ui-icon-radio-off { background-position: -96px -144px; } +.ui-icon-radio-on { background-position: -112px -144px; } +.ui-icon-pin-w { background-position: -128px -144px; } +.ui-icon-pin-s { background-position: -144px -144px; } +.ui-icon-play { background-position: 0 -160px; } +.ui-icon-pause { background-position: -16px -160px; } +.ui-icon-seek-next { background-position: -32px -160px; } +.ui-icon-seek-prev { background-position: -48px -160px; } +.ui-icon-seek-end { background-position: -64px -160px; } +.ui-icon-seek-start { background-position: -80px -160px; } +/* ui-icon-seek-first is deprecated, use ui-icon-seek-start instead */ +.ui-icon-seek-first { background-position: -80px -160px; } +.ui-icon-stop { background-position: -96px -160px; } +.ui-icon-eject { background-position: -112px -160px; } +.ui-icon-volume-off { background-position: -128px -160px; } +.ui-icon-volume-on { background-position: -144px -160px; } +.ui-icon-power { background-position: 0 -176px; } +.ui-icon-signal-diag { background-position: -16px -176px; } +.ui-icon-signal { background-position: -32px -176px; } +.ui-icon-battery-0 { background-position: -48px -176px; } +.ui-icon-battery-1 { background-position: -64px -176px; } +.ui-icon-battery-2 { background-position: -80px -176px; } +.ui-icon-battery-3 { background-position: -96px -176px; } +.ui-icon-circle-plus { background-position: 0 -192px; } +.ui-icon-circle-minus { background-position: -16px -192px; } +.ui-icon-circle-close { background-position: -32px -192px; } +.ui-icon-circle-triangle-e { background-position: -48px -192px; } +.ui-icon-circle-triangle-s { background-position: -64px -192px; } +.ui-icon-circle-triangle-w { background-position: -80px -192px; } +.ui-icon-circle-triangle-n { background-position: -96px -192px; } +.ui-icon-circle-arrow-e { background-position: -112px -192px; } +.ui-icon-circle-arrow-s { background-position: -128px -192px; } +.ui-icon-circle-arrow-w { background-position: -144px -192px; } +.ui-icon-circle-arrow-n { background-position: -160px -192px; } +.ui-icon-circle-zoomin { background-position: -176px -192px; } +.ui-icon-circle-zoomout { background-position: -192px -192px; } +.ui-icon-circle-check { background-position: -208px -192px; } +.ui-icon-circlesmall-plus { background-position: 0 -208px; } +.ui-icon-circlesmall-minus { background-position: -16px -208px; } +.ui-icon-circlesmall-close { background-position: -32px -208px; } +.ui-icon-squaresmall-plus { background-position: -48px -208px; } +.ui-icon-squaresmall-minus { background-position: -64px -208px; } +.ui-icon-squaresmall-close { background-position: -80px -208px; } +.ui-icon-grip-dotted-vertical { background-position: 0 -224px; } +.ui-icon-grip-dotted-horizontal { background-position: -16px -224px; } +.ui-icon-grip-solid-vertical { background-position: -32px -224px; } +.ui-icon-grip-solid-horizontal { background-position: -48px -224px; } +.ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; } +.ui-icon-grip-diagonal-se { background-position: -80px -224px; } + + +/* Misc visuals +----------------------------------*/ + +/* Corner radius */ +.ui-corner-tl { -moz-border-radius-topleft: 3px; -webkit-border-top-left-radius: 3px; border-top-left-radius: 3px; } +.ui-corner-tr { -moz-border-radius-topright: 3px; -webkit-border-top-right-radius: 3px; border-top-right-radius: 3px; } +.ui-corner-bl { -moz-border-radius-bottomleft: 3px; -webkit-border-bottom-left-radius: 3px; border-bottom-left-radius: 3px; } +.ui-corner-br { -moz-border-radius-bottomright: 3px; -webkit-border-bottom-right-radius: 3px; border-bottom-right-radius: 3px; } +.ui-corner-top { -moz-border-radius-topleft: 3px; -webkit-border-top-left-radius: 3px; border-top-left-radius: 3px; -moz-border-radius-topright: 3px; -webkit-border-top-right-radius: 3px; border-top-right-radius: 3px; } +.ui-corner-bottom { -moz-border-radius-bottomleft: 3px; -webkit-border-bottom-left-radius: 3px; border-bottom-left-radius: 3px; -moz-border-radius-bottomright: 3px; -webkit-border-bottom-right-radius: 3px; border-bottom-right-radius: 3px; } +.ui-corner-right { -moz-border-radius-topright: 3px; -webkit-border-top-right-radius: 3px; border-top-right-radius: 3px; -moz-border-radius-bottomright: 3px; -webkit-border-bottom-right-radius: 3px; border-bottom-right-radius: 3px; } +.ui-corner-left { -moz-border-radius-topleft: 3px; -webkit-border-top-left-radius: 3px; border-top-left-radius: 3px; -moz-border-radius-bottomleft: 3px; -webkit-border-bottom-left-radius: 3px; border-bottom-left-radius: 3px; } +.ui-corner-all { -moz-border-radius: 3px; -webkit-border-radius: 3px; border-radius: 3px; } + +/* Overlays */ +.ui-widget-overlay { background: #e6b900 url(images/ui-bg_flat_0_e6b900_40x100.png) 50% 50% repeat-x; opacity: .30;filter:Alpha(Opacity=30); } +.ui-widget-shadow { margin: 6px 0 0 6px; padding: 0px; background: #e69700 url(images/ui-bg_flat_0_e69700_40x100.png) 50% 50% repeat-x; opacity: .20;filter:Alpha(Opacity=20); -moz-border-radius: 3px; -webkit-border-radius: 3px; border-radius: 3px; }/* + * jQuery UI Resizable @VERSION + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Resizable#theming + */ +.ui-resizable { position: relative;} +.ui-resizable-handle { position: absolute;font-size: 0.1px;z-index: 99999; display: block;} +.ui-resizable-disabled .ui-resizable-handle, .ui-resizable-autohide .ui-resizable-handle { display: none; } +.ui-resizable-n { cursor: n-resize; height: 7px; width: 100%; top: -5px; left: 0; } +.ui-resizable-s { cursor: s-resize; height: 7px; width: 100%; bottom: -5px; left: 0; } +.ui-resizable-e { cursor: e-resize; width: 7px; right: -5px; top: 0; height: 100%; } +.ui-resizable-w { cursor: w-resize; width: 7px; left: -5px; top: 0; height: 100%; } +.ui-resizable-se { cursor: se-resize; width: 12px; height: 12px; right: 1px; bottom: 1px; } +.ui-resizable-sw { cursor: sw-resize; width: 9px; height: 9px; left: -5px; bottom: -5px; } +.ui-resizable-nw { cursor: nw-resize; width: 9px; height: 9px; left: -5px; top: -5px; } +.ui-resizable-ne { cursor: ne-resize; width: 9px; height: 9px; right: -5px; top: -5px;}/* + * jQuery UI Selectable @VERSION + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Selectable#theming + */ +.ui-selectable-helper { position: absolute; z-index: 100; border:1px dotted black; } +/* + * jQuery UI Accordion @VERSION + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Accordion#theming + */ +/* IE/Win - Fix animation bug - #4615 */ +.ui-accordion { width: 100%; } +.ui-accordion .ui-accordion-header { cursor: pointer; position: relative; margin-top: 1px; zoom: 1; } +.ui-accordion .ui-accordion-li-fix { display: inline; } +.ui-accordion .ui-accordion-header-active { border-bottom: 0 !important; } +.ui-accordion .ui-accordion-header a { display: block; font-size: 1em; padding: .5em .5em .5em .7em; } +.ui-accordion-icons .ui-accordion-header a { padding-left: 2.2em; } +.ui-accordion .ui-accordion-header .ui-icon { position: absolute; left: .5em; top: 50%; margin-top: -8px; } +.ui-accordion .ui-accordion-content { padding: 1em 2.2em; border-top: 0; margin-top: -2px; position: relative; top: 1px; margin-bottom: 2px; overflow: auto; display: none; zoom: 1; } +.ui-accordion .ui-accordion-content-active { display: block; }/* + * jQuery UI Autocomplete @VERSION + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Autocomplete#theming + */ +.ui-autocomplete { position: absolute; cursor: default; } + +/* workarounds */ +* html .ui-autocomplete { width:1px; } /* without this, the menu expands to 100% in IE6 */ + +/* + * jQuery UI Menu @VERSION + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Menu#theming + */ +.ui-menu { + list-style:none; + padding: 2px; + margin: 0; + display:block; + float: left; +} +.ui-menu .ui-menu { + margin-top: -3px; +} +.ui-menu .ui-menu-item { + margin:0; + padding: 0; + zoom: 1; + float: left; + clear: left; + width: 100%; +} +.ui-menu .ui-menu-item a { + text-decoration:none; + display:block; + padding:.2em .4em; + line-height:1.5; + zoom:1; +} +.ui-menu .ui-menu-item a.ui-state-hover, +.ui-menu .ui-menu-item a.ui-state-active { + font-weight: normal; + margin: -1px; +} +/* + * jQuery UI Button @VERSION + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Button#theming + */ +.ui-button { display: inline-block; position: relative; padding: 0; margin-right: .1em; text-decoration: none !important; cursor: pointer; text-align: center; zoom: 1; overflow: visible; } /* the overflow property removes extra width in IE */ +.ui-button-icon-only { width: 2.2em; } /* to make room for the icon, a width needs to be set here */ +button.ui-button-icon-only { width: 2.4em; } /* button elements seem to need a little more width */ +.ui-button-icons-only { width: 3.4em; } +button.ui-button-icons-only { width: 3.7em; } + +/*button text element */ +.ui-button .ui-button-text { display: block; line-height: 1.4; } +.ui-button-text-only .ui-button-text { padding: .4em 1em; } +.ui-button-icon-only .ui-button-text, .ui-button-icons-only .ui-button-text { padding: .4em; text-indent: -9999999px; } +.ui-button-text-icon-primary .ui-button-text, .ui-button-text-icons .ui-button-text { padding: .4em 1em .4em 2.1em; } +.ui-button-text-icon-secondary .ui-button-text, .ui-button-text-icons .ui-button-text { padding: .4em 2.1em .4em 1em; } +.ui-button-text-icons .ui-button-text { padding-left: 2.1em; padding-right: 2.1em; } +/* no icon support for input elements, provide padding by default */ +input.ui-button { padding: .4em 1em; } + +/*button icon element(s) */ +.ui-button-icon-only .ui-icon, .ui-button-text-icon-primary .ui-icon, .ui-button-text-icon-secondary .ui-icon, .ui-button-text-icons .ui-icon, .ui-button-icons-only .ui-icon { position: absolute; top: 50%; margin-top: -8px; } +.ui-button-icon-only .ui-icon { left: 50%; margin-left: -8px; } +.ui-button-text-icon-primary .ui-button-icon-primary, .ui-button-text-icons .ui-button-icon-primary, .ui-button-icons-only .ui-button-icon-primary { left: .5em; } +.ui-button-text-icon-secondary .ui-button-icon-secondary, .ui-button-text-icons .ui-button-icon-secondary, .ui-button-icons-only .ui-button-icon-secondary { right: .5em; } +.ui-button-text-icons .ui-button-icon-secondary, .ui-button-icons-only .ui-button-icon-secondary { right: .5em; } + +/*button sets*/ +.ui-buttonset { margin-right: 7px; } +.ui-buttonset .ui-button { margin-left: 0; margin-right: -.3em; } + +/* workarounds */ +button.ui-button::-moz-focus-inner { border: 0; padding: 0; } /* reset extra padding in Firefox */ +/* + * jQuery UI Dialog @VERSION + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Dialog#theming + */ +.ui-dialog { position: absolute; padding: .2em; width: 300px; overflow: hidden; } +.ui-dialog .ui-dialog-titlebar { padding: .5em 1em .3em; position: relative; } +.ui-dialog .ui-dialog-title { float: left; margin: .1em 16px .2em 0; } +.ui-dialog .ui-dialog-titlebar-close { position: absolute; right: .3em; top: 50%; width: 19px; margin: -10px 0 0 0; padding: 1px; height: 18px; } +.ui-dialog .ui-dialog-titlebar-close span { display: block; margin: 1px; } +.ui-dialog .ui-dialog-titlebar-close:hover, .ui-dialog .ui-dialog-titlebar-close:focus { padding: 0; } +.ui-dialog .ui-dialog-content { position: relative; border: 0; padding: .5em 1em; background: none; overflow: auto; zoom: 1; } +.ui-dialog .ui-dialog-buttonpane { text-align: left; border-width: 1px 0 0 0; background-image: none; margin: .5em 0 0 0; padding: .3em 1em .5em .4em; } +.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset { float: right; } +.ui-dialog .ui-dialog-buttonpane button { margin: .5em .4em .5em 0; cursor: pointer; } +.ui-dialog .ui-resizable-se { width: 14px; height: 14px; right: 3px; bottom: 3px; } +.ui-draggable .ui-dialog-titlebar { cursor: move; } +/* + * jQuery UI Slider @VERSION + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Slider#theming + */ +.ui-slider { position: relative; text-align: left; } +.ui-slider .ui-slider-handle { position: absolute; z-index: 2; width: 1.2em; height: 1.2em; cursor: default; } +.ui-slider .ui-slider-range { position: absolute; z-index: 1; font-size: .7em; display: block; border: 0; background-position: 0 0; } + +.ui-slider-horizontal { height: .8em; } +.ui-slider-horizontal .ui-slider-handle { top: -.3em; margin-left: -.6em; } +.ui-slider-horizontal .ui-slider-range { top: 0; height: 100%; } +.ui-slider-horizontal .ui-slider-range-min { left: 0; } +.ui-slider-horizontal .ui-slider-range-max { right: 0; } + +.ui-slider-vertical { width: .8em; height: 100px; } +.ui-slider-vertical .ui-slider-handle { left: -.3em; margin-left: 0; margin-bottom: -.6em; } +.ui-slider-vertical .ui-slider-range { left: 0; width: 100%; } +.ui-slider-vertical .ui-slider-range-min { bottom: 0; } +.ui-slider-vertical .ui-slider-range-max { top: 0; }/* + * jQuery UI Tabs @VERSION + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Tabs#theming + */ +.ui-tabs { position: relative; padding: .2em; zoom: 1; } /* position: relative prevents IE scroll bug (element with position: relative inside container with overflow: auto appear as "fixed") */ +.ui-tabs .ui-tabs-nav { margin: 0; padding: .2em .2em 0; } +.ui-tabs .ui-tabs-nav li { list-style: none; float: left; position: relative; top: 1px; margin: 0 .2em 1px 0; border-bottom: 0 !important; padding: 0; white-space: nowrap; } +.ui-tabs .ui-tabs-nav li a { float: left; padding: .5em 1em; text-decoration: none; } +.ui-tabs .ui-tabs-nav li.ui-tabs-selected { margin-bottom: 0; padding-bottom: 1px; } +.ui-tabs .ui-tabs-nav li.ui-tabs-selected a, .ui-tabs .ui-tabs-nav li.ui-state-disabled a, .ui-tabs .ui-tabs-nav li.ui-state-processing a { cursor: text; } +.ui-tabs .ui-tabs-nav li a, .ui-tabs.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-selected a { cursor: pointer; } /* first selector in group seems obsolete, but required to overcome bug in Opera applying cursor: text overall if defined elsewhere... */ +.ui-tabs .ui-tabs-panel { display: block; border-width: 0; padding: 1em 1.4em; background: none; } +.ui-tabs .ui-tabs-hide { display: none !important; } +/* + * jQuery UI Datepicker @VERSION + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Datepicker#theming + */ +.ui-datepicker { width: 17em; padding: .2em .2em 0; } +.ui-datepicker .ui-datepicker-header { position:relative; padding:.2em 0; } +.ui-datepicker .ui-datepicker-prev, .ui-datepicker .ui-datepicker-next { position:absolute; top: 2px; width: 1.8em; height: 1.8em; } +.ui-datepicker .ui-datepicker-prev-hover, .ui-datepicker .ui-datepicker-next-hover { top: 1px; } +.ui-datepicker .ui-datepicker-prev { left:2px; } +.ui-datepicker .ui-datepicker-next { right:2px; } +.ui-datepicker .ui-datepicker-prev-hover { left:1px; } +.ui-datepicker .ui-datepicker-next-hover { right:1px; } +.ui-datepicker .ui-datepicker-prev span, .ui-datepicker .ui-datepicker-next span { display: block; position: absolute; left: 50%; margin-left: -8px; top: 50%; margin-top: -8px; } +.ui-datepicker .ui-datepicker-title { margin: 0 2.3em; line-height: 1.8em; text-align: center; } +.ui-datepicker .ui-datepicker-title select { font-size:1em; margin:1px 0; } +.ui-datepicker select.ui-datepicker-month-year {width: 100%;} +.ui-datepicker select.ui-datepicker-month, +.ui-datepicker select.ui-datepicker-year { width: 49%;} +.ui-datepicker table {width: 100%; font-size: .9em; border-collapse: collapse; margin:0 0 .4em; } +.ui-datepicker th { padding: .7em .3em; text-align: center; font-weight: bold; border: 0; } +.ui-datepicker td { border: 0; padding: 1px; } +.ui-datepicker td span, .ui-datepicker td a { display: block; padding: .2em; text-align: right; text-decoration: none; } +.ui-datepicker .ui-datepicker-buttonpane { background-image: none; margin: .7em 0 0 0; padding:0 .2em; border-left: 0; border-right: 0; border-bottom: 0; } +.ui-datepicker .ui-datepicker-buttonpane button { float: right; margin: .5em .2em .4em; cursor: pointer; padding: .2em .6em .3em .6em; width:auto; overflow:visible; } +.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current { float:left; } + +/* with multiple calendars */ +.ui-datepicker.ui-datepicker-multi { width:auto; } +.ui-datepicker-multi .ui-datepicker-group { float:left; } +.ui-datepicker-multi .ui-datepicker-group table { width:95%; margin:0 auto .4em; } +.ui-datepicker-multi-2 .ui-datepicker-group { width:50%; } +.ui-datepicker-multi-3 .ui-datepicker-group { width:33.3%; } +.ui-datepicker-multi-4 .ui-datepicker-group { width:25%; } +.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header { border-left-width:0; } +.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header { border-left-width:0; } +.ui-datepicker-multi .ui-datepicker-buttonpane { clear:left; } +.ui-datepicker-row-break { clear:both; width:100%; } + +/* RTL support */ +.ui-datepicker-rtl { direction: rtl; } +.ui-datepicker-rtl .ui-datepicker-prev { right: 2px; left: auto; } +.ui-datepicker-rtl .ui-datepicker-next { left: 2px; right: auto; } +.ui-datepicker-rtl .ui-datepicker-prev:hover { right: 1px; left: auto; } +.ui-datepicker-rtl .ui-datepicker-next:hover { left: 1px; right: auto; } +.ui-datepicker-rtl .ui-datepicker-buttonpane { clear:right; } +.ui-datepicker-rtl .ui-datepicker-buttonpane button { float: left; } +.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current { float:right; } +.ui-datepicker-rtl .ui-datepicker-group { float:right; } +.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header { border-right-width:0; border-left-width:1px; } +.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header { border-right-width:0; border-left-width:1px; } + +/* IE6 IFRAME FIX (taken from datepicker 1.5.3 */ +.ui-datepicker-cover { + display: none; /*sorry for IE5*/ + display/**/: block; /*sorry for IE5*/ + position: absolute; /*must have*/ + z-index: -1; /*must have*/ + filter: mask(); /*must have*/ + top: -4px; /*must have*/ + left: -4px; /*must have*/ + width: 200px; /*must have*/ + height: 200px; /*must have*/ +}/* + * jQuery UI Progressbar @VERSION + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Progressbar#theming + */ +.ui-progressbar { height:2em; text-align: left; } +.ui-progressbar .ui-progressbar-value {margin: -1px; height:100%; } diff --git a/public/css/excite-bike/images/ui-bg_diagonals-small_25_c5ddfc_40x40.png b/public/css/excite-bike/images/ui-bg_diagonals-small_25_c5ddfc_40x40.png new file mode 100644 index 000000000..82524abb3 Binary files /dev/null and b/public/css/excite-bike/images/ui-bg_diagonals-small_25_c5ddfc_40x40.png differ diff --git a/public/css/excite-bike/images/ui-bg_diagonals-thick_20_e69700_40x40.png b/public/css/excite-bike/images/ui-bg_diagonals-thick_20_e69700_40x40.png new file mode 100644 index 000000000..695471443 Binary files /dev/null and b/public/css/excite-bike/images/ui-bg_diagonals-thick_20_e69700_40x40.png differ diff --git a/public/css/excite-bike/images/ui-bg_diagonals-thick_22_1484e6_40x40.png b/public/css/excite-bike/images/ui-bg_diagonals-thick_22_1484e6_40x40.png new file mode 100644 index 000000000..43ba34e34 Binary files /dev/null and b/public/css/excite-bike/images/ui-bg_diagonals-thick_22_1484e6_40x40.png differ diff --git a/public/css/excite-bike/images/ui-bg_diagonals-thick_26_2293f7_40x40.png b/public/css/excite-bike/images/ui-bg_diagonals-thick_26_2293f7_40x40.png new file mode 100644 index 000000000..68306d1c6 Binary files /dev/null and b/public/css/excite-bike/images/ui-bg_diagonals-thick_26_2293f7_40x40.png differ diff --git a/public/css/excite-bike/images/ui-bg_flat_0_e69700_40x100.png b/public/css/excite-bike/images/ui-bg_flat_0_e69700_40x100.png new file mode 100644 index 000000000..01dd29068 Binary files /dev/null and b/public/css/excite-bike/images/ui-bg_flat_0_e69700_40x100.png differ diff --git a/public/css/excite-bike/images/ui-bg_flat_0_e6b900_40x100.png b/public/css/excite-bike/images/ui-bg_flat_0_e6b900_40x100.png new file mode 100644 index 000000000..5c5494fc3 Binary files /dev/null and b/public/css/excite-bike/images/ui-bg_flat_0_e6b900_40x100.png differ diff --git a/public/css/excite-bike/images/ui-bg_highlight-soft_100_f9f9f9_1x100.png b/public/css/excite-bike/images/ui-bg_highlight-soft_100_f9f9f9_1x100.png new file mode 100644 index 000000000..9a46d1959 Binary files /dev/null and b/public/css/excite-bike/images/ui-bg_highlight-soft_100_f9f9f9_1x100.png differ diff --git a/public/css/excite-bike/images/ui-bg_inset-hard_100_eeeeee_1x100.png b/public/css/excite-bike/images/ui-bg_inset-hard_100_eeeeee_1x100.png new file mode 100644 index 000000000..f811f3090 Binary files /dev/null and b/public/css/excite-bike/images/ui-bg_inset-hard_100_eeeeee_1x100.png differ diff --git a/public/css/excite-bike/images/ui-icons_0a82eb_256x240.png b/public/css/excite-bike/images/ui-icons_0a82eb_256x240.png new file mode 100644 index 000000000..755fe99f5 Binary files /dev/null and b/public/css/excite-bike/images/ui-icons_0a82eb_256x240.png differ diff --git a/public/css/excite-bike/images/ui-icons_0b54d5_256x240.png b/public/css/excite-bike/images/ui-icons_0b54d5_256x240.png new file mode 100644 index 000000000..98705f93f Binary files /dev/null and b/public/css/excite-bike/images/ui-icons_0b54d5_256x240.png differ diff --git a/public/css/excite-bike/images/ui-icons_5fa5e3_256x240.png b/public/css/excite-bike/images/ui-icons_5fa5e3_256x240.png new file mode 100644 index 000000000..21790782c Binary files /dev/null and b/public/css/excite-bike/images/ui-icons_5fa5e3_256x240.png differ diff --git a/public/css/excite-bike/images/ui-icons_fcdd4a_256x240.png b/public/css/excite-bike/images/ui-icons_fcdd4a_256x240.png new file mode 100644 index 000000000..de76ce218 Binary files /dev/null and b/public/css/excite-bike/images/ui-icons_fcdd4a_256x240.png differ diff --git a/public/css/excite-bike/images/ui-icons_ffffff_256x240.png b/public/css/excite-bike/images/ui-icons_ffffff_256x240.png new file mode 100644 index 000000000..42f8f992c Binary files /dev/null and b/public/css/excite-bike/images/ui-icons_ffffff_256x240.png differ diff --git a/public/css/fullcalendar.css b/public/css/fullcalendar.css new file mode 100644 index 000000000..c1870fa8b --- /dev/null +++ b/public/css/fullcalendar.css @@ -0,0 +1,586 @@ +/* + * FullCalendar v1.4.9 Stylesheet + * + * Feel free to edit this file to customize the look of FullCalendar. + * When upgrading to newer versions, please upgrade this file as well, + * porting over any customizations afterwards. + * + * Date: Fri Nov 19 22:45:44 2010 -0800 + * + */ + + +/* TODO: make font sizes look the same in all doctypes */ + + +.fc, +.fc .fc-header, +.fc .fc-content { + font-size: 1em; + } + +.fc { + direction: ltr; + text-align: left; + } + +.fc table { + border-collapse: collapse; + border-spacing: 0; + } + +.fc td, .fc th { + padding: 0; + vertical-align: top; + } + + + +/* Header +------------------------------------------------------------------------*/ + +table.fc-header { + width: 100%; + } + +.fc-header-left { + width: 25%; + } + +.fc-header-left table { + float: left; + } + +.fc-header-center { + width: 50%; + text-align: center; + } + +.fc-header-center table { + margin: 0 auto; + } + +.fc-header-right { + width: 25%; + } + +.fc-header-right table { + float: right; + } + +.fc-header-title { + margin-top: 0; + white-space: nowrap; + } + +.fc-header-space { + padding-left: 10px; + } + +/* right-to-left */ + +.fc-rtl .fc-header-title { + direction: rtl; + } + + + +/* Buttons +------------------------------------------------------------------------*/ + +.fc-header .fc-state-default, +.fc-header .ui-state-default { + margin-bottom: 1em; + cursor: pointer; + } + +.fc-header .fc-state-default { + border-width: 1px 0; + padding: 0 1px; + } + +.fc-header .fc-state-default, +.fc-header .fc-state-default a { + border-style: solid; + } + +.fc-header .fc-state-default a { + display: block; + border-width: 0 1px; + margin: 0 -1px; + width: 100%; + text-decoration: none; + } + +.fc-header .fc-state-default span { + display: block; + border-style: solid; + border-width: 1px 0 1px 1px; + padding: 3px 5px; + } + +.fc-header .ui-state-default { + padding: 4px 6px; + } + +.fc-header .fc-state-default span, +.fc-header .ui-state-default span { + white-space: nowrap; + } + +/* for adjacent buttons */ + +.fc-header .fc-no-right { + padding-right: 0; + } + +.fc-header .fc-no-right a { + margin-right: 0; + border-right: 0; + } + +.fc-header .ui-no-right { + border-right: 0; + } + +/* for fake rounded corners */ + +.fc-header .fc-corner-left { + margin-left: 1px; + padding-left: 0; + } + +.fc-header .fc-corner-right { + margin-right: 1px; + padding-right: 0; + } + +/* DEFAULT button COLORS */ + +.fc-header .fc-state-default, +.fc-header .fc-state-default a { + border-color: #777; /* outer border */ + color: #333; + } + +.fc-header .fc-state-default span { + border-color: #fff #fff #d1d1d1; /* inner border */ + background: #e8e8e8; + } + +/* PRESSED button COLORS (down and active) */ + +.fc-header .fc-state-active a { + color: #fff; + } + +.fc-header .fc-state-down span, +.fc-header .fc-state-active span { + background: #888; + border-color: #808080 #808080 #909090; /* inner border */ + } + +/* DISABLED button COLORS */ + +.fc-header .fc-state-disabled a { + color: #999; + } + +.fc-header .fc-state-disabled, +.fc-header .fc-state-disabled a { + border-color: #ccc; /* outer border */ + } + +.fc-header .fc-state-disabled span { + border-color: #fff #fff #f0f0f0; /* inner border */ + background: #f0f0f0; + } + + + +/* Content Area & Global Cell Styles +------------------------------------------------------------------------*/ + +.fc-widget-content { + border: 1px solid #ccc; /* outer border color */ + } + +.fc-content { + clear: both; + } + +.fc-content .fc-state-default { + border-style: solid; + border-color: #ccc; /* inner border color */ + } + +.fc-content .fc-state-highlight { /* today */ + background: #ffc; + } + +.fc-content .fc-not-today { /* override jq-ui highlight (TODO: ui-widget-content) */ + background: none; + } + +.fc-cell-overlay { /* semi-transparent rectangle while dragging */ + background: #9cf; + opacity: .2; + filter: alpha(opacity=20); /* for IE */ + } + +.fc-view { /* prevents dragging outside of widget */ + width: 100%; + overflow: hidden; + } + + + + + +/* Global Event Styles +------------------------------------------------------------------------*/ + +.fc-event, +.fc-agenda .fc-event-time, +.fc-event a { + border-style: solid; + border-color: #36c; /* default BORDER color (probably the same as background-color) */ + background-color: #36c; /* default BACKGROUND color */ + color: #fff; /* default TEXT color */ + } + + /* Use the 'className' CalEvent property and the following + * example CSS to change event color on a per-event basis: + * + * .myclass, + * .fc-agenda .myclass .fc-event-time, + * .myclass a { + * background-color: black; + * border-color: black; + * color: red; + * } + */ + +.fc-event { + text-align: left; + } + +.fc-event a { + overflow: hidden; + font-size: .85em; + text-decoration: none; + cursor: pointer; + } + +.fc-event-editable { + cursor: pointer; + } + +.fc-event-time, +.fc-event-title { + padding: 0 1px; + } + +/* for fake rounded corners */ + +.fc-event a { + display: block; + position: relative; + width: 100%; + height: 100%; + } + +/* right-to-left */ + +.fc-rtl .fc-event a { + text-align: right; + } + +/* resizable */ + +.fc .ui-resizable-handle { /*** TODO: don't use ui-resizable anoymore, change class ***/ + display: block; + position: absolute; + z-index: 99999; + border: 0 !important; /* important overrides pre jquery ui 1.7 styles */ + background: url(data:image/gif;base64,AAAA) !important; /* hover fix for IE */ + } + + + +/* Horizontal Events +------------------------------------------------------------------------*/ + +.fc-event-hori { + border-width: 1px 0; + margin-bottom: 1px; + } + +.fc-event-hori a { + border-width: 0; + } + +/* for fake rounded corners */ + +.fc-content .fc-corner-left { + margin-left: 1px; + } + +.fc-content .fc-corner-left a { + margin-left: -1px; + border-left-width: 1px; + } + +.fc-content .fc-corner-right { + margin-right: 1px; + } + +.fc-content .fc-corner-right a { + margin-right: -1px; + border-right-width: 1px; + } + +/* resizable */ + +.fc-event-hori .ui-resizable-e { + top: 0 !important; /* importants override pre jquery ui 1.7 styles */ + right: -3px !important; + width: 7px !important; + height: 100% !important; + cursor: e-resize; + } + +.fc-event-hori .ui-resizable-w { + top: 0 !important; + left: -3px !important; + width: 7px !important; + height: 100% !important; + cursor: w-resize; + } + +.fc-event-hori .ui-resizable-handle { + _padding-bottom: 14px; /* IE6 had 0 height */ + } + + + + +/* Month View, Basic Week View, Basic Day View +------------------------------------------------------------------------*/ + +.fc-grid table { + width: 100%; + } + +.fc .fc-grid th { + border-width: 0 0 0 1px; + text-align: center; + } + +.fc .fc-grid td { + border-width: 1px 0 0 1px; + } + +.fc-grid th.fc-leftmost, +.fc-grid td.fc-leftmost { + border-left: 0; + } + +.fc-grid .fc-day-number { + float: right; + padding: 0 2px; + } + +.fc-grid .fc-other-month .fc-day-number { + opacity: 0.3; + filter: alpha(opacity=30); /* for IE */ + /* opacity with small font can sometimes look too faded + might want to set the 'color' property instead + making day-numbers bold also fixes the problem */ + } + +.fc-grid .fc-day-content { + clear: both; + padding: 2px 2px 0; /* distance between events and day edges */ + } + +/* event styles */ + +.fc-grid .fc-event-time { + font-weight: bold; + } + +/* right-to-left */ + +.fc-rtl .fc-grid { + direction: rtl; + } + +.fc-rtl .fc-grid .fc-day-number { + float: left; + } + +.fc-rtl .fc-grid .fc-event-time { + float: right; + } + +/* Agenda Week View, Agenda Day View +------------------------------------------------------------------------*/ + +.fc .fc-agenda th, +.fc .fc-agenda td { + border-width: 1px 0 0 1px; + } + +.fc .fc-agenda .fc-leftmost { + border-left: 0; + } + +.fc-agenda tr.fc-first th, +.fc-agenda tr.fc-first td { + border-top: 0; + } + +.fc-agenda-head tr.fc-last th { + border-bottom-width: 1px; + } + +.fc .fc-agenda-head td, +.fc .fc-agenda-body td { + background: none; + } + +.fc-agenda-head th { + text-align: center; + } + +/* the time axis running down the left side */ + +.fc-agenda .fc-axis { + width: 50px; + padding: 0 4px; + vertical-align: middle; + white-space: nowrap; + text-align: right; + font-weight: normal; + } + +/* all-day event cells at top */ + +.fc-agenda-head tr.fc-all-day th { + height: 35px; + } + +.fc-agenda-head td { + padding-bottom: 10px; + } + +.fc .fc-divider div { + font-size: 1px; /* for IE6/7 */ + height: 2px; + } + +.fc .fc-divider .fc-state-default { + background: #eee; /* color for divider between all-day and time-slot events */ + } + +/* body styles */ + +.fc .fc-agenda-body td div { + height: 20px; /* slot height */ + } + +.fc .fc-agenda-body tr.fc-minor th, +.fc .fc-agenda-body tr.fc-minor td { + border-top-style: dotted; + } + +.fc-agenda .fc-day-content { + padding: 2px 2px 0; /* distance between events and day edges */ + } + +/* vertical background columns */ + +.fc .fc-agenda-bg .ui-state-highlight { + background-image: none; /* tall column, don't want repeating background image */ + } + + + +/* Vertical Events +------------------------------------------------------------------------*/ + +.fc-event-vert { + border-width: 0 1px; + } + +.fc-event-vert a { + border-width: 0; + } + +/* for fake rounded corners */ + +.fc-content .fc-corner-top { + margin-top: 1px; + } + +.fc-content .fc-corner-top a { + margin-top: -1px; + border-top-width: 1px; + } + +.fc-content .fc-corner-bottom { + margin-bottom: 1px; + } + +.fc-content .fc-corner-bottom a { + margin-bottom: -1px; + border-bottom-width: 1px; + } + +/* event content */ + +.fc-event-vert span { + display: block; + position: relative; + z-index: 2; + } + +.fc-event-vert span.fc-event-time { + white-space: nowrap; + _white-space: normal; + overflow: hidden; + border: 0; + font-size: 10px; + } + +.fc-event-vert span.fc-event-title { + line-height: 13px; + } + +.fc-event-vert span.fc-event-bg { /* makes the event lighter w/ a semi-transparent overlay */ + position: absolute; + z-index: 1; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: #fff; + opacity: .3; + filter: alpha(opacity=30); /* for IE */ + } + +/* resizable */ + +.fc-event-vert .ui-resizable-s { + bottom: 0 !important; /* importants override pre jquery ui 1.7 styles */ + width: 100% !important; + height: 8px !important; + line-height: 8px !important; + font-size: 11px !important; + font-family: monospace; + text-align: center; + cursor: s-resize; + } + + diff --git a/public/css/img/backgrounds.gif b/public/css/img/backgrounds.gif new file mode 100644 index 000000000..39e33ebc0 Binary files /dev/null and b/public/css/img/backgrounds.gif differ diff --git a/public/css/img/buttons-disabled.png b/public/css/img/buttons-disabled.png new file mode 100644 index 000000000..afa11af9b Binary files /dev/null and b/public/css/img/buttons-disabled.png differ diff --git a/public/css/img/buttons.png b/public/css/img/buttons.png new file mode 100644 index 000000000..153e73885 Binary files /dev/null and b/public/css/img/buttons.png differ diff --git a/public/css/img/delete.gif b/public/css/img/delete.gif new file mode 100644 index 000000000..78ca8b3b4 Binary files /dev/null and b/public/css/img/delete.gif differ diff --git a/public/css/img/done.gif b/public/css/img/done.gif new file mode 100644 index 000000000..29f3ed7c9 Binary files /dev/null and b/public/css/img/done.gif differ diff --git a/public/css/img/error.gif b/public/css/img/error.gif new file mode 100644 index 000000000..4682b6300 Binary files /dev/null and b/public/css/img/error.gif differ diff --git a/public/css/img/throbber.gif b/public/css/img/throbber.gif new file mode 100644 index 000000000..4ae8b16a5 Binary files /dev/null and b/public/css/img/throbber.gif differ diff --git a/public/css/img/transp50.png b/public/css/img/transp50.png new file mode 100644 index 000000000..eb0efe104 Binary files /dev/null and b/public/css/img/transp50.png differ diff --git a/public/css/jquery.contextMenu.css b/public/css/jquery.contextMenu.css new file mode 100644 index 000000000..243c4b08d --- /dev/null +++ b/public/css/jquery.contextMenu.css @@ -0,0 +1,62 @@ +/* Generic context menu styles */ +.contextMenu { + position: absolute; + width: 200px; + z-index: 99999; + border: solid 1px #CCC; + background: #EEE; + padding: 0px; + margin: 0px; + display: none; +} + +.contextMenu LI { + list-style: none; + padding: 0px; + margin: 0px; +} + +.contextMenu A { + color: #333; + text-decoration: none; + display: block; + line-height: 20px; + height: 20px; + background-position: 6px center; + background-repeat: no-repeat; + outline: none; + padding: 1px 5px; + padding-left: 28px; +} + +.contextMenu LI.hover A { + color: #FFF; + background-color: #3399FF; +} + +.contextMenu LI.disabled A { + color: #AAA; + cursor: default; +} + +.contextMenu LI.hover.disabled A { + background-color: transparent; +} + +.contextMenu LI.separator { + border-top: solid 1px #CCC; +} + +/* + Adding Icons + + You can add icons to the context menu by adding + classes to the respective LI element(s) +*/ + +.contextMenu LI.edit A { background-image: url(images/page_white_edit.png); } +.contextMenu LI.cut A { background-image: url(images/cut.png); } +.contextMenu LI.copy A { background-image: url(images/page_white_copy.png); } +.contextMenu LI.paste A { background-image: url(images/page_white_paste.png); } +.contextMenu LI.delete A { background-image: url(images/page_white_delete.png); } +.contextMenu LI.quit A { background-image: url(images/door.png); } diff --git a/public/css/library_search.css b/public/css/library_search.css new file mode 100644 index 000000000..a50a5d435 --- /dev/null +++ b/public/css/library_search.css @@ -0,0 +1,3 @@ +fieldset dl dt, dd { + display: inline-block; +} diff --git a/public/css/plupload.queue.css b/public/css/plupload.queue.css new file mode 100644 index 000000000..75489a419 --- /dev/null +++ b/public/css/plupload.queue.css @@ -0,0 +1,177 @@ +/* + Plupload +------------------------------------------------------------------- */ + +.plupload_button { + display: -moz-inline-box; /* FF < 3*/ + display: inline-block; + font: normal 12px sans-serif; + text-decoration: none; + color: #42454a; + border: 1px solid #bababa; + padding: 2px 8px 3px 20px; + margin-right: 4px; + background: #f3f3f3 url('img/buttons.png') no-repeat 0 center; + outline: 0; + + /* Optional rounded corners for browsers that support it */ + -moz-border-radius: 3px; + -khtml-border-radius: 3px; + -webkit-border-radius: 3px; + border-radius: 3px; +} + +.plupload_button:hover { + color: #000; + text-decoration: none; +} + +.plupload_disabled, a.plupload_disabled:hover { + color: #737373; + border-color: #c5c5c5; + background: #ededed url('img/buttons-disabled.png') no-repeat 0 center; + cursor: default; +} + +.plupload_add { + background-position: -181px center; +} + +.plupload_wrapper { + font: normal 11px Verdana,sans-serif; + width: 100%; +} + +.plupload_container { + padding: 8px; + background: url('img/transp50.png'); + /*-moz-border-radius: 5px;*/ +} + +.plupload_container input { + border: 1px solid #DDD; + font: normal 11px Verdana,sans-serif; + width: 98%; +} + +.plupload_header {background: #2A2C2E url('img/backgrounds.gif') repeat-x;} +.plupload_header_content { + background: url('img/backgrounds.gif') no-repeat 0 -317px; + min-height: 56px; + padding-left: 60px; + color: #FFF; +} +.plupload_header_title { + font: normal 18px sans-serif; + padding: 6px 0 3px; +} +.plupload_header_text { + font: normal 12px sans-serif; +} + +.plupload_filelist { + margin: 0; + padding: 0; + list-style: none; +} + +.plupload_scroll .plupload_filelist { + height: 185px; + background: #F5F5F5; + overflow-y: scroll; +} + +.plupload_filelist li { + padding: 10px 8px; + background: #F5F5F5 url('img/backgrounds.gif') repeat-x 0 -156px; + border-bottom: 1px solid #DDD; +} + +.plupload_filelist_header, .plupload_filelist_footer { + background: #DFDFDF; + padding: 8px 8px; + color: #42454A; +} +.plupload_filelist_header { + border-top: 1px solid #EEE; + border-bottom: 1px solid #CDCDCD; +} + +.plupload_filelist_footer {border-top: 1px solid #FFF; height: 22px; line-height: 20px; vertical-align: middle;} +.plupload_file_name {float: left; overflow: hidden} +.plupload_file_status {color: #777;} +.plupload_file_status span {color: #42454A;} +.plupload_file_size, .plupload_file_status, .plupload_progress { + float: right; + width: 80px; +} +.plupload_file_size, .plupload_file_status, .plupload_file_action {text-align: right;} + +.plupload_filelist .plupload_file_name {width: 205px} + +.plupload_file_action { + float: right; + width: 16px; + height: 16px; + margin-left: 15px; +} + +.plupload_file_action * { + display: none; + width: 16px; + height: 16px; +} + +li.plupload_uploading {background: #ECF3DC url('img/backgrounds.gif') repeat-x 0 -238px;} +li.plupload_done {color:#AAA} + +li.plupload_delete a { + background: url('img/delete.gif'); +} + +li.plupload_failed a { + background: url('img/error.gif'); + cursor: default; +} + +li.plupload_done a { + background: url('img/done.gif'); + cursor: default; +} + +.plupload_progress, .plupload_upload_status { + display: none; +} + +.plupload_progress_container { + margin-top: 3px; + border: 1px solid #CCC; + background: #FFF; + padding: 1px; +} +.plupload_progress_bar { + width: 0px; + height: 7px; + background: #CDEB8B; +} + +.plupload_scroll .plupload_filelist_header .plupload_file_action, .plupload_scroll .plupload_filelist_footer .plupload_file_action { + margin-right: 17px; +} + +/* Floats */ + +.plupload_clear,.plupload_clearer {clear: both;} +.plupload_clearer, .plupload_progress_bar { + display: block; + font-size: 0; + line-height: 0; +} + +li.plupload_droptext { + background: transparent; + text-align: center; + vertical-align: middle; + border: 0; + line-height: 165px; +} diff --git a/public/css/schedule.css b/public/css/schedule.css new file mode 100644 index 000000000..aee64109e --- /dev/null +++ b/public/css/schedule.css @@ -0,0 +1,7 @@ +#content { + width: 75%; +} + +div.ui-datepicker { + font-size: 75%; +} diff --git a/public/index.php b/public/index.php new file mode 100644 index 000000000..8e689eb9b --- /dev/null +++ b/public/index.php @@ -0,0 +1,35 @@ +bootstrap() + ->run(); diff --git a/public/js/campcaster/library/advancedsearch.js b/public/js/campcaster/library/advancedsearch.js new file mode 100644 index 000000000..831e6cd11 --- /dev/null +++ b/public/js/campcaster/library/advancedsearch.js @@ -0,0 +1,42 @@ +function addRemove(el) { + var id, span; + + id = $(el).attr("id").split("_").pop(); + + span = $('Remove').click(function(){ + $(this).parent().parent().remove(); + }); + + $(el).find("dl input").after(span); +} + +function ajaxAddField() { + + var id = $("#search_next_id").val(); + + var url = '/Search/newfield'; + url = url + '/format/html'; + url = url + '/id/' + id; + + $.post(url, function(newElement) { + + var el = $(newElement); + addRemove(el); + + $(".zend_form").append(el); + $("#search_next_id").val(++id); + }); +} + +$(document).ready(function() { + + $("#search_add").click(ajaxAddField); + $("#search_submit").click(function(){ + $("form").submit(); + }); + + $('[id^="fieldset-row_"]').each(function(i, el){ + addRemove(el); + }); + +}); diff --git a/public/js/campcaster/library/library.js b/public/js/campcaster/library/library.js new file mode 100644 index 000000000..41ea59630 --- /dev/null +++ b/public/js/campcaster/library/library.js @@ -0,0 +1,81 @@ +function contextMenu(action, el, pos) { + var method = action.split('/').pop(), + url; + + if (method === 'delete') { + url = action + '/format/json'; + url = url + '/id/' + $(el).attr('id'); + $.post(url, deleteItem); + } + else if (method === 'add-item') { + url = action + '/format/json'; + url = url + '/id/' + $(el).attr('id'); + $.post(url, addToPlaylist); + } +} + +function deleteItem(json){ + var j = jQuery.parseJSON(json), + id; + + if(j.error !== undefined) { + alert(j.error.message); + return; + } + + id = this.url.split('/').pop(); + $("#library_display tr#" +id).remove(); +} + +function addToPlaylist(json){ + var j = jQuery.parseJSON(json); + + if(j.error !== undefined) { + alert(j.error.message); + return; + } +} + +function setLibraryContents(data){ + $("#library_display tr:not(:first-child)").remove(); + $("#library_display").append(data); + + $("#library_display tr:not(:first-child)").contextMenu( + {menu: 'myMenu'}, contextMenu + ); +} + +$(document).ready(function() { + + $("#library_display tr:first-child span.title").data({'ob': 'dc:title', 'order' : 'asc'}); + $("#library_display tr:first-child span.artist").data({'ob': 'dc:creator', 'order' : 'desc'}); + $("#library_display tr:first-child span.album").data({'ob': 'dc:source', 'order' : 'asc'}); + $("#library_display tr:first-child span.track").data({'ob': 'ls:track_num', 'order' : 'asc'}); + $("#library_display tr:first-child span.length").data({'ob': 'dcterms:extent', 'order' : 'asc'}); + + $("#library_display tr:first-child span").click(function(){ + var url = "/Library/contents/format/html", + ob = $(this).data('ob'), + order = $(this).data('order'); + + //append orderby category to url. + url = url + "/ob/" + ob; + //append asc or desc order. + url = url + "/order/" + order; + + //toggle order for next click. + if(order === 'asc') { + $(this).data('order', 'desc'); + } + else { + $(this).data('order', 'asc'); + } + + $.post(url, setLibraryContents); + }); + + $("#library_display tr:not(:first-child)").contextMenu( + {menu: 'myMenu'}, contextMenu + ); + +}); diff --git a/public/js/campcaster/library/plupload.js b/public/js/campcaster/library/plupload.js new file mode 100644 index 000000000..b8c07a3d6 --- /dev/null +++ b/public/js/campcaster/library/plupload.js @@ -0,0 +1,26 @@ +$(document).ready(function() { + + $("#plupload_files").pluploadQueue({ + // General settings + runtimes : 'html5', + url : '/Plupload/upload/format/json', + filters : [ + {title: "Audio Files", extensions: "ogg,mp3"} + ] + }); + + var uploader = $("#plupload_files").pluploadQueue(); + uploader.bind('FileUploaded', function(up, file, json) { + var j = jQuery.parseJSON(json.response); + + if(j.error !== undefined) { + + var row = $("") + .append('' + file.name +'') + .append('' + j.error.message + ''); + + $("#plupload_error").find("table").append(row); + } + }); + +}); diff --git a/public/js/campcaster/playlist/playlist.js b/public/js/campcaster/playlist/playlist.js new file mode 100644 index 000000000..113e400c0 --- /dev/null +++ b/public/js/campcaster/playlist/playlist.js @@ -0,0 +1,248 @@ +function removeFadeInput(){ + var span = $(this).parent(); + var pos = span.parent().parent().attr('id').split("_").pop(); + + var fadeIn, fadeOut, url; + + var regExpr = new RegExp("^\\d{2}[:]\\d{2}[:]\\d{2}([.]\\d{1,6})?$"); + var oldValue = $("#pl_tmp_time").val(); + var newValue = $(this).val().trim(); + + if(newValue === "") + newValue = '00:00:00'; + + if(span.parent().hasClass('pl_fade_in')){ + fadeIn = newValue; + } + else if(span.parent().hasClass('pl_fade_out')){ + fadeOut = newValue; + } + + //test that input is a time. + if (!regExpr.test(newValue)) { + span.empty(); + span.append(oldValue); + span.click(addTextInput); + alert("please put in a time '00:00:00 (.0000)'"); + return; + } + + url = '/Playlist/set-fade'; + url = url + '/format/json'; + url = url + '/pos/' + pos; + + if (fadeIn !== undefined) + url = url + '/fadeIn/' + fadeIn; + if (fadeOut !== undefined) + url = url + '/fadeOut/' + fadeOut; + + $.post( + + url, + + function(json){ + var li, span, data; + data = jQuery.parseJSON(json), + + li = $("#pl_"+pos); + if(data.error){ + var hidden = $("#pl_tmp_time"); + var time = hidden.val(); + + span = hidden.parent(); + span.empty(); + span.append(time); + span.click(addTextInput); + alert(data.error); + } + if(data.fadeIn){ + span = li.find(".pl_fade_in").find(".pl_time"); + span.empty(); + span.append(data.fadeIn); + span.click(addTextInput); + } + if(data.fadeOut){ + span = li.find(".pl_fade_out").find(".pl_time"); + span.empty(); + span.append(data.fadeOut); + span.click(addTextInput); + } + } + ); +} + +function removeCueInput(){ + var span = $(this).parent(); + var pos = span.parent().attr('id').split("_").pop(); + + var cueIn, cueOut, url; + + var regExpr = new RegExp("^\\d{2}[:]\\d{2}[:]\\d{2}([.]\\d{1,6})?$"); + var oldValue = $("#pl_tmp_time").val(); + var newValue = $(this).val().trim(); + + if(span.hasClass('pl_cue_in')){ + if(newValue === "") + newValue = '00:00:00'; + cueIn = newValue; + } + else if(span.hasClass('pl_cue_out')){ + cueOut = newValue; + } + + //test that input is a time. + if (newValue!=="" && !regExpr.test(newValue)) { + span.empty(); + span.append(oldValue); + span.click(addTextInput); + alert("please put in a time '00:00:00 (.0000)'"); + return; + } + + url = '/Playlist/set-cue'; + url = url + '/format/json'; + url = url + '/pos/' + pos; + + if (cueIn !== undefined) + url = url + '/cueIn/' + cueIn; + if (cueOut !== undefined) + url = url + '/cueOut/' + cueOut; + + $.post( + url, + + function(json){ + var li, span, data; + data = jQuery.parseJSON(json), + + li = $("#pl_"+pos); + if(data.error){ + var hidden = $("#pl_tmp_time"); + var time = hidden.val(); + + span = hidden.parent(); + span.empty(); + span.append(time); + span.click(addTextInput); + alert(data.error); + return; + } + span = li.find(".pl_playlength"); + span.empty(); + span.append(data.cliplength); + + span = $(".pl_duration"); + span.empty(); + span.append(data.length); + + if(data.cueIn){ + span = li.find(".pl_cue_in"); + span.empty(); + span.append(data.cueIn); + span.click(addTextInput); + } + if(data.cueOut){ + span = li.find(".pl_cue_out"); + span.empty(); + span.append(data.cueOut); + span.click(addTextInput); + } + + span = li.find(".pl_fade_in").find(".pl_time"); + span.empty(); + span.append(data.fadeIn); + + span = li.find(".pl_fade_out").find(".pl_time"); + span.empty(); + span.append(data.fadeOut); + } + ); +} + +function addTextInput(){ + var time = $(this).text().trim(); + var input = $(""); + + //Firefox seems to have problems losing focus otherwise, Chrome is fine. + $(":input").blur(); + $(this).empty(); + + $(this).append(input); + input.focus(); + + if($(this).hasClass('pl_cue_in') || $(this).hasClass('pl_cue_out')) { + input.blur(removeCueInput); + } + else if($(this).parent().hasClass('pl_fade_in') || $(this).parent().hasClass('pl_fade_out')){ + input.blur(removeFadeInput); + } + + input.keypress(function(ev){ + //don't want enter to submit. + if (ev.keyCode === 13) { + ev.preventDefault(); + $(this).blur(); + } + }); + + input = $(""); + $(this).append(input); + + $(this).unbind('click'); +} + + +function setPLContent(html) { + var ul = $("#pl_sortable"); + + ul.empty(); + ul.append(html); + + $(".pl_time").click(addTextInput); +} + +function deletePLItem(){ + + var url, pos; + + url = '/Playlist/delete-item/format/html'; + + pos = $('form[name="PL"]').find(':checked').not('input[name="all"]').map(function() { + return "/pos/" + $(this).attr('name'); + }).get().join(""); + + url = url + pos; + + $.post(url, setPLContent); +} + +function movePLItem(event, ui) { + var li, newPos, oldPos, url; + + li = ui.item; + + newPos = li.index(); + oldPos = li.attr('id').split("_").pop(); + + url = '/Playlist/move-item' + url = url + '/format/html'; + url = url + '/oldPos/' + oldPos; + url = url + '/newPos/' + newPos; + + $.post(url, setPLContent); +} + +$(document).ready(function() { + + $("#pl_sortable").sortable(); + $("#pl_sortable" ).bind( "sortstop", movePLItem); + + $(".pl_time").click(addTextInput); + + $("#pl_remove_selected").click(deletePLItem); + + $('input[name="all"]').click(function(){ + $('form[name="PL"]').find('input').attr("checked", $(this).attr("checked")); + }); + +}); diff --git a/public/js/campcaster/schedule/schedule.js b/public/js/campcaster/schedule/schedule.js new file mode 100644 index 000000000..afde26526 --- /dev/null +++ b/public/js/campcaster/schedule/schedule.js @@ -0,0 +1,282 @@ +/** +* +* Schedule Dialog creation methods. +* +*/ + +//dateText mm-dd-yy + +function checkDayOfWeek(date) { + var day; + + day = date.getDay(); + $("#schedule_dialog_day_check").find('input[value="'+day+'"]').attr("checked", "true"); +} + +function startDpSelect(dateText, inst) { + var time, date; + + time = dateText.split("-"); + date = new Date(time[0], time[1] - 1, time[2]); + + //checkDayOfWeek(date); + + $("#schedule_add_event_dialog") + .find("input#schedule_dialog_end_date_input") + .datepicker("option", "minDate", date); +} + +function endDpSelect(dateText, inst) { + var time, date; + + time = dateText.split("-"); + date = new Date(time[0], time[1] - 1, time[2]); + + $("#schedule_add_event_dialog") + .find("input#schedule_dialog_start_date_input") + .datepicker( "option", "maxDate", date); +} + +function createDateInput(name, label) { + var d_input, t_input, dp, div, dl, label, format, newDate; + + label = $(''); + d_input = $('') + .datepicker({ + minDate: new Date(), + onSelect: window[name+"DpSelect"], + dateFormat: 'yy-mm-dd' + }); + + //format = $.datepicker.regional[''].dateFormat; + newDate = $.datepicker.formatDate("yy-mm-dd", new Date()); + d_input.val(newDate); + + div = $('
    ') + .append(label) + .append(d_input); + + return div; +} + +function submitShow() { + var name, description, hosts, allDay, repeats, + start_time, duration, start_date, end_date, dofw; + + name = $("#schedule_dialog_name").val(); + description = $("#schedule_dialog_description").val(); + hosts = $("#schedule_dialog_hosts").val(); + allDay = $("#schedule_dialog_all_day").attr("checked"); + repeats = $("#schedule_dialog_repeats").attr("checked"); + start_time = $("#schedule_dialog_start_time").val(); + duration = $("#schedule_dialog_duration").val(); + start_date = $("#schedule_dialog_start_date_input").val(); + end_date = $("#schedule_dialog_end_date_input").val(); + dofw = $("#schedule_dialog_day_check").find(":checked").map(function(){ + return $(this).val(); + }).get(); + + $(this).remove(); +} + +function closeDialog(event, ui) { + $(this).remove(); +} + +function makeShowDialog(json) { + + var dialog, div, dl, time_div, host_div, + label, input, textarea, repeats, all_day, day_checkbox, host_select; + + //main jqueryUI dialog + dialog = $('
    '); + + div_left = $('
    ') + .width(300); + div_middle = $('
    ') + .width(250); + div_right = $('
    ') + .width(350); + + dialog.append(div_left); + dialog.append(div_middle); + dialog.append(div_right); + + dialog.find("div") + .css("float", "left"); + + dl = $('
    '); + + label = $('Name: '); + input = $(''); + + dl.append(label); + dl.append(input); + + label = $('Description: '); + textarea = $('